/** * Creates a URL path from a path string or as empty. * * If a source path string is provided, it should start with "/". Just like in any valid URL, the path string is * not expected to contain characters that cannot be represented literally and percent-encoding is expected to be * used for any such characters. No trailing "/" are stripped off the path string, so if the path is e.g. * "/comp0/comp1/", it produces an empty string as the last component. * * @param string $path **OPTIONAL. Default is** *create an empty URL path*. A string with the source path. */ public function __construct($path = null) { assert('!isset($path) || is_cstring($path)', vs(isset($this), get_defined_vars())); assert('!isset($path) || CString::startsWith($path, "/")', vs(isset($this), get_defined_vars())); if (isset($path)) { $path = CString::stripStart($path, "/"); $this->m_components = CString::split($path, "/"); $len = CArray::length($this->m_components); for ($i = 0; $i < $len; $i++) { $this->m_components[$i] = CUrl::leaveTdNew($this->m_components[$i]); } } else { $this->m_components = CArray::make(); } }
/** * Normalizes a path by removing any trailing slashes, any redundant slashes, any references to the current * directory, and any references to the parent directory where possible, and returns the new path. * * For example, "/path//./dir-a/.././to//../dir-b/" is normalized to "/path/dir-b". * * @param string $path The path to be normalized (can be absolute or relative). * @param bool $targetIsExecutable **OPTIONAL. Default is** `false`. Tells whether the path's target should be * treated as an executable so that, if the path starts with ".", the resulting path will start with "." too and * the "." will not be removed as a reference to the current directory. * * @return CUStringObject The normalized path. */ public static function normalize($path, $targetIsExecutable = false) { assert('is_cstring($path) && is_bool($targetIsExecutable)', vs(isset($this), get_defined_vars())); assert('!CString::isEmpty($path)', vs(isset($this), get_defined_vars())); $path = CRegex::replace($path, "/\\/{2,}/", "/"); // normalize consecutive slashes $path = CString::stripEnd($path, "/"); // remove the trailing slash, if any if (CString::isEmpty($path)) { return "/"; } $path = CRegex::remove($path, "/\\/\\.(?=\\/|\\z)/"); // remove any "/." followed by a slash or at the end if (CString::isEmpty($path)) { return "/"; } if (!$targetIsExecutable) { $path = CString::stripStart($path, "./"); } $pathIsAbsolute; if (!CString::startsWith($path, "/")) { $pathIsAbsolute = false; } else { $pathIsAbsolute = true; $path = CString::substr($path, 1); } if (!CString::find($path, "/")) { if ($pathIsAbsolute) { if (!CString::equals($path, "..")) { $path = "/{$path}"; } else { $path = "/"; } } return $path; } // Recompose the path. $components = CString::split($path, "/"); $newComponents = CArray::make(); $len = CArray::length($components); for ($i = 0; $i < $len; $i++) { $comp = $components[$i]; $lastAddedComp = ""; $noCompsAddedYet = CArray::isEmpty($newComponents); if (!$noCompsAddedYet) { $lastAddedComp = CArray::last($newComponents); } if (CString::equals($comp, "..")) { if ($noCompsAddedYet || CString::equals($lastAddedComp, "..") || CString::equals($lastAddedComp, ".")) { if (!($noCompsAddedYet && $pathIsAbsolute)) { CArray::push($newComponents, $comp); } } else { CArray::pop($newComponents); } } else { CArray::push($newComponents, $comp); } } $path = CArray::join($newComponents, "/"); if ($pathIsAbsolute) { $path = "/{$path}"; } else { if (CString::isEmpty($path)) { $path = "."; } } return $path; }
protected static function timeToComponentsInTimeZone(CTime $time, $timeZone, &$year, &$month, &$day, &$hour, &$minute, &$second, &$millisecond, &$dayOfWeek) { $FTime = $time->FTime(); $UTime = $time->UTime(); $MTime = $time->MTime(); $negMsCase = false; if ($FTime < 0.0 && $MTime != 0) { $negMsCase = true; $UTime--; } $time = self::timeToStringInTimeZone(new self($UTime), "Y,m,d,H,i,s,w", $timeZone); $components = CString::split($time, ","); $year = CString::toInt($components[0]); $month = CString::toInt($components[1]); $day = CString::toInt($components[2]); $hour = CString::toInt($components[3]); $minute = CString::toInt($components[4]); $second = CString::toInt($components[5]); $dayOfWeek = CString::toInt($components[6]); if (!$negMsCase) { $millisecond = $MTime; } else { $millisecond = $MTime + 1000; } }
protected function addHeaderWithoutOverriding($headers, $headerName, $value) { $headerLine; $headerName = CString::trim($headerName); $value = CString::trim($value); $foundHeaderPos; $alreadyExists = CArray::find($headers, $headerName, function ($element0, $element1) { return CRegex::find($element0, "/^\\h*" . CRegex::enterTd($element1) . "\\h*:/i"); }, $foundHeaderPos); if (!$alreadyExists) { $headerLine = "{$headerName}: {$value}"; } else { // The header already exists. Combine the header values, removing duplicates based on case-insensitive // equality. $currValue = CRegex::remove($headers[$foundHeaderPos], "/^.*?:\\h*/"); CArray::remove($headers, $foundHeaderPos); $values = CString::split("{$currValue}, {$value}", ","); $len = CArray::length($values); for ($i = 0; $i < $len; $i++) { $values[$i] = CString::trim($values[$i]); } $values = CArray::filter($values, function ($element) { return !CString::isEmpty($element); }); $values = CArray::unique($values, function ($element0, $element1) { return CString::equalsCi($element0, $element1); }); $combinedValue = CArray::join($values, ", "); $headerLine = "{$headerName}: {$combinedValue}"; } CArray::push($headers, $headerLine); }
/** * Composes a URL query into a query string ready to be used as a part of a URL and returns it. * * Any characters that cannot be represented literally in a valid query string come out percent-encoded. The * resulting query string never starts with "?". * * Because the characters in field named and field values are stored in their literal representations, the * resulting query string is always normalized, with only those characters appearing percent-encoded that really * require it for the query string to be valid and with the hexadecimal letters in percent-encoded characters * appearing uppercased. Also, no duplicate fields are produced in the resulting query string (even if the object * was constructed from a query string with duplicate fields in it) and "=" is added after any field name that goes * without a value and is not followed by "=". * * @param bool $sortFields **OPTIONAL. Default is** `false`. Tells whether the fields in the query string should * appear sorted in the ascending order, case-insensitively, and with natural order comparison used for sorting. * * @return CUStringObject The query string. */ public function queryString($sortFields = false) { assert('is_bool($sortFields)', vs(isset($this), get_defined_vars())); if (!CMap::isEmpty($this->m_query)) { $useQuery = CMap::makeCopy($this->m_query); // Recursively convert any CArray into a CMap for `http_build_query` function to accept the query. $useQuery = self::recurseQueryValueBeforeComposingQs($useQuery, 0); // Compose a preliminary query string. $queryString = http_build_query($useQuery, "", self::$ms_fieldDelimiters[0], PHP_QUERY_RFC1738); if (!is_cstring($queryString)) { return ""; } // Break the string into fields. $fields = CString::split($queryString, self::$ms_fieldDelimiters[0]); // Adjust the result of `http_build_query` function. $len = CArray::length($fields); for ($i = 0; $i < $len; $i++) { if (CString::find($fields[$i], "=")) { // Revert excessive percent-encoding of the square brackets next to the identifiers of // multidimensional data. $fields[$i] = CRegex::replaceWithCallback($fields[$i], "/(?:%5B(?:[^%]++|%(?!5B|5D))*+%5D)+?=/i", function ($matches) { $value = $matches[0]; $value = CString::replace($value, "%5B", "["); $value = CString::replace($value, "%5D", "]"); return $value; }); // Remove redundant indexing next to the identifiers of simple arrays. $fields[$i] = CRegex::replaceWithCallback($fields[$i], "/^.+?=/", function ($matches) { return CRegex::replace($matches[0], "/\\[\\d+\\]/", "[]"); }); } } if ($sortFields) { // Normalize the order of fields. CArray::sortStringsNatCi($fields); } $queryString = CArray::join($fields, self::$ms_fieldDelimiters[0]); return $queryString; } else { return ""; } }