/** * Creates a URL query from a query string or as empty. * * Just like in any valid URL, a provided query string is not expected to contain characters that cannot be * represented literally and percent-encoding is expected to be used for any such characters. The query string * should not contain a leading "?" because it's rather a delimiter that is used to separate the query string from * the preceding URL part. * * @param string $queryString **OPTIONAL. Default is** *create an empty URL query*. The source query string. * @param reference $parsingWasFruitful **OPTIONAL. OUTPUT.** After the object is constructed, this parameter, * which is of type `bool`, tells whether the parsing of the query string resulted in any valid fields. */ public function __construct($queryString = null, &$parsingWasFruitful = null) { assert('!isset($queryString) || is_cstring($queryString)', vs(isset($this), get_defined_vars())); if (isset($queryString)) { if (!CString::isEmpty($queryString)) { // Before parsing, normalize field delimiters to the default one (e.g. make ";" to be "&"). $delimiters = CString::splitIntoChars(self::$ms_fieldDelimiters); $fields = CString::split($queryString, $delimiters); $queryString = CArray::join($fields, self::$ms_fieldDelimiters[0]); // Parse the query string. parse_str($queryString, $this->m_query); if (!is_cmap($this->m_query)) { $this->m_query = CMap::make(); } // Recursively convert any PHP's associative array with sequential keys into a CArray. foreach ($this->m_query as &$value) { $value = self::recurseQueryValueAfterParsing($value, 0); } unset($value); } else { $this->m_query = CMap::make(); } $parsingWasFruitful = !CMap::isEmpty($this->m_query); } else { $this->m_query = CMap::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 requestField($map, $fieldName, CInputFilter $inputFilter, &$success) { $success = true; // Account for the fact that, with PHP, GET and POST (and cookie) fields arrive having "." replaced with "_" // in their names. $fieldName = CString::replace($fieldName, ".", "_"); $value; $hasField = false; if (CMap::hasKey($map, $fieldName)) { $value = $map[$fieldName]; if (isset($value)) { if (!is_cstring($value) && !is_cmap($value)) { // Should not happen in the known versions of PHP. assert('false', vs(isset($this), get_defined_vars())); $success = false; return $inputFilter->defaultValue(); } if (!self::$ms_treatEmptyRequestValuesAsAbsent) { $hasField = true; } else { if (is_cstring($value)) { $hasField = !CString::isEmpty($value); } else { $hasField = !CMap::isEmpty($value) && !(CMap::length($value) == 1 && CMap::hasKey($value, 0) && is_cstring($value[0]) && CString::isEmpty($value[0])); } } } } if (!$hasField) { $success = false; return $inputFilter->defaultValue(); } $inputFilterOrFilterCollection; if ($inputFilter->expectedType() != CInputFilter::CARRAY && $inputFilter->expectedType() != CInputFilter::CMAP) { $inputFilterOrFilterCollection = $inputFilter; } else { $inputFilterOrFilterCollection = $inputFilter->collectionInputFilters(); } // Recursively convert any PHP array that has sequential keys and for which CArray type is expected into a // CArray, while leaving PHP arrays for which CMap type is expected untouched. $value = self::recurseValueBeforeFiltering($value, $inputFilterOrFilterCollection, $success, 0); if (!$success) { return $inputFilter->defaultValue(); } return $inputFilter->filter($value, $success); }
/** * Determines if a locale has a script code. * * @return bool `true` if the locale has a script code, `false` otherwise. */ public function hasScriptCode() { return !CString::isEmpty(Locale::getScript($this->m_name)); }
/** * @ignore */ public static function maybeUpdateThirdParty() { if (!self::isInCliMode()) { // This method can be run in CLI mode only. assert('false', vs(isset($this), get_defined_vars())); return false; } $updates = CConfiguration::option("updates"); $updatesAreEnabled = $updates["enable"]; if ($updatesAreEnabled) { $minTimeBetweenDoUpdatesDays = $updates["minTimeBetweenDoUpdatesDays"]; $components = $updates["components"]; assert('is_int($minTimeBetweenDoUpdatesDays)', vs(isset($this), get_defined_vars())); // Logging. $logging = $updates["logging"]; $loggingIsEnabled = $logging["enable"]; $logFp = $logging["logFilePath"]; if ($loggingIsEnabled) { assert('!CString::isEmpty($logFp)', vs(isset($this), get_defined_vars())); $logFp = CFilePath::frameworkPath($logFp); CShell::setLogging($logFp); } // Mailing. $mailing = $updates["mailing"]; $mailingIsEnabled = $mailing["enable"]; if ($mailingIsEnabled) { $adminMail = CConfiguration::option("admin.mail"); $to = $adminMail["to"]; $from = $adminMail["from"]; $transport = $adminMail["transport"]; assert('!CString::isEmpty($to) && !CString::isEmpty($from) && !CString::isEmpty($transport)', vs(isset($this), get_defined_vars())); $mail; if (CString::equalsCi($transport, "smtp")) { $smtpOutgoingServer = $adminMail["smtpOutgoingServer"]; $smtpUsername = $adminMail["smtpUsername"]; $smtpPassword = $adminMail["smtpPassword"]; assert('!CString::isEmpty($smtpOutgoingServer) && !CString::isEmpty($smtpUsername) && ' . '!CString::isEmpty($smtpPassword)', vs(isset($this), get_defined_vars())); $mail = CMail::makeSmtp($smtpOutgoingServer, $smtpUsername, $smtpPassword, $from, $to); } else { if (CString::equalsCi($transport, "system")) { $mail = CMail::makeSystem($from, $to); } else { assert('false', vs(isset($this), get_defined_vars())); } } CShell::setMailing($mail); } $thirdPartyDp = $GLOBALS["PHRED_PATH_TO_THIRD_PARTY"]; $lastUpdateTimeFp = CFilePath::add($thirdPartyDp, self::$ms_thirdPartyLastUpdateTimeFn); // Read the file containing the Unix seconds of the last update time stamp (if exists) and compare that // time with the current time. $numDaysSinceLastUpdate; if (CFile::exists($lastUpdateTimeFp)) { $lastUpdateTime = new CTime(CString::toInt(CFile::read($lastUpdateTimeFp))); $currTime = CTime::now(); if ($lastUpdateTime->isBefore($currTime)) { $numDaysSinceLastUpdate = $currTime->diffInDays($lastUpdateTime); if ($numDaysSinceLastUpdate < $minTimeBetweenDoUpdatesDays) { // It is too early for updates yet. return false; } } else { assert('false', vs(isset($this), get_defined_vars())); } } $date = CShell::currentDate(); CShell::say("Started on {$date}."); if (isset($numDaysSinceLastUpdate)) { CShell::say("It has been {$numDaysSinceLastUpdate} day(s) since last successful update."); } $concurrLockFp = CFilePath::add($thirdPartyDp, self::$ms_thirdPartyConcurrLockFn); // Try locking the operation. if (!self::setLock($concurrLockFp, false)) { assert('false', vs(isset($this), get_defined_vars())); CShell::onError(false, "Could not obtain a lock on the operation."); CShell::writeToLog("\n"); return false; } $phpConfigNeedsReload = false; $totalNumComponents = CMap::length($components); $numComponentsUpdated = 0; // The Browser Capabilities Project (BrowsCap). if (CMap::hasKey($components, "browsCap")) { $browsCap = $components["browsCap"]; $skip = $browsCap["skip"]; if (!$skip) { CShell::say("Updating the Browser Capabilities Project (BrowsCap) ..."); $lookupFileUrl = $browsCap["lookupFileUrl"]; assert('!CString::isEmpty($lookupFileUrl)', vs(isset($this), get_defined_vars())); // Component-related constants. static $s_configOptName = "browscap"; static $s_lookupFileDownloadTimeoutSeconds = 120; if (self::hasConfigOption($s_configOptName)) { $browsCapLookupFp = CString::trim(self::configOption($s_configOptName)); if (!CString::isEmpty($browsCapLookupFp)) { $browsCapDp = CFilePath::directory($browsCapLookupFp); CShell::say("Downloading a BrowsCap lookup file from '{$lookupFileUrl}' ..."); $temporaryFp = CFile::createTemporary($browsCapDp); $downloadRes = CInetRequest::downloadFile($lookupFileUrl, $temporaryFp, $s_lookupFileDownloadTimeoutSeconds); if ($downloadRes) { // After the file is downloaded into a temporary one, move it to the destination, // safely replacing the existing file, if any. CFile::move($temporaryFp, $browsCapLookupFp); $numComponentsUpdated++; $phpConfigNeedsReload = true; $downloadedFileSizeKB = CUUnit::convertStoragef((double) CFile::size($browsCapLookupFp), CUUnit::BYTE, CUUnit::KILOBYTE); $downloadedFileSizeKB = CMathf::round($downloadedFileSizeKB, 2); CShell::say("Done. The downloaded file is {$downloadedFileSizeKB} KB in size."); } else { CShell::onError(false, "Could not download a BrowsCap lookup file from '{$lookupFileUrl}'."); } // Just in case, check for any temporary files that could have been left by any previous // operations in the directory. $leftoverFiles = CFile::findFiles(CFilePath::add($browsCapDp, CFile::DEFAULT_TEMPORARY_FILE_PREFIX . "*")); if (!CArray::isEmpty($leftoverFiles)) { // Cleanup the directory from the temporary files. $len = CArray::length($leftoverFiles); for ($i = 0; $i < $len; $i++) { CFile::delete($leftoverFiles[$i]); } } } else { CShell::onError(false, "Could not read the value of '{$s_configOptName}' option " . "in the PHP CLI configuration file."); } } else { CShell::onError(false, "Could not find '{$s_configOptName}' option in the PHP CLI configuration file."); } } else { CShell::say("Skipping the Browser Capabilities Project (BrowsCap)."); } } // All the components have been processed. Unlock the operation. self::unsetLock($concurrLockFp); $date = CShell::currentDate(); if ($numComponentsUpdated != 0) { // One or more third-party components have been updated. Put a time stamp on the directory where the // components are located. CFile::write($lastUpdateTimeFp, CString::fromInt(CTime::currentUTime())); if ($numComponentsUpdated == $totalNumComponents) { CShell::speak("Success. All {$totalNumComponents} third-party component(s)"); } else { CShell::speak("Partial success. {$numComponentsUpdated} out of {$totalNumComponents} third-party component(s)"); } CShell::say("have been updated. Completed on {$date}."); } else { CShell::say("No third-party components have been updated. Completed on {$date}."); } return $phpConfigNeedsReload; } else { return false; } }
/** * @ignore */ public static function onError($exit = true, $message) { if ($exit) { if (!CString::isEmpty($message)) { $message .= !CString::find($message, "\n") ? " " : "\n"; } $message .= "Exiting."; } if (!CString::isEmpty($message)) { self::say($message); } if (isset(self::$ms_mail)) { self::sendMail($message); } if ($exit) { self::exitScript(false); } }
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); }
/** * Splits a string into substrings using a specified pattern or patterns as the delimiter(s) and returns the * resulting strings as an array. * * If no delimiter patterns were found, the resulting array contains just one element, which is the original * string. If a delimiter is located at the very start or at the very end of the string or next to another * delimiter, it will accordingly cause some string(s) in the resulting array to be empty. * * As a special case, the delimiter pattern can be empty, which will split the string into its constituting * characters. * * @param string $string The string to be split. * @param string|array|map $delimiterPatternOrPatterns The pattern or array of patterns to be recognized as the * delimiter(s). * * @return CArray The resulting strings. */ public static function split($string, $delimiterPatternOrPatterns) { assert('is_cstring($string) && (is_cstring($delimiterPatternOrPatterns) || ' . 'is_collection($delimiterPatternOrPatterns))', vs(isset($this), get_defined_vars())); if (is_cstring($delimiterPatternOrPatterns)) { $numIdt = self::findGroups($delimiterPatternOrPatterns, "/^([^0-9A-Za-z\\s\\\\])(.*)\\1/", $foundGroups); assert('$numIdt == 2', vs(isset($this), get_defined_vars())); $idt = $foundGroups[1]; if (CString::isEmpty($idt)) { // Special case. if (CString::isEmpty($string)) { $resStrings = CArray::fromElements(""); return $resStrings; } else { if (preg_match("/^([^0-9A-Za-z\\s\\\\])\\1[A-Za-z]*u[A-Za-z]*\\z/", $delimiterPatternOrPatterns) !== 1) { $resStrings = CArray::make(strlen($string)); for ($i = 0; $i < strlen($string); $i++) { $resStrings[$i] = $string[$i]; } return $resStrings; } else { return CUString::splitIntoChars($string); } } } $paResStrings = preg_split($delimiterPatternOrPatterns, $string); $qty = count($paResStrings); $resStrings = CArray::make($qty); for ($i = 0; $i < $qty; $i++) { $resStrings[$i] = $paResStrings[$i]; } return $resStrings; } else { $resStrings = CArray::fromElements($string); foreach ($delimiterPatternOrPatterns as $delimiterPattern) { assert('is_cstring($delimiterPattern)', vs(isset($this), get_defined_vars())); $resStringsNew = CArray::make(); $len = CArray::length($resStrings); for ($i = 0; $i < $len; $i++) { CArray::pushArray($resStringsNew, self::split($resStrings[$i], $delimiterPattern)); } $resStrings = $resStringsNew; } return $resStrings; } }
/** * Starts a session by sending out the added requests. * * @param reference $success **OPTIONAL. OUTPUT.** After the method is called with this parameter provided, the * parameter's value tells whether the session was successful. * * @return void */ public function start(&$success = null) { $success = true; if ($this->m_hasError) { $success = false; return; } if (CArray::isEmpty($this->m_requestRecordsQueue)) { // Nothing to do. return; } // Current policy is to disable HTTP pipelining. $res = curl_multi_setopt($this->m_multiCurl, CURLMOPT_PIPELINING, 0); if (!$res) { // Should never get in here as long as cURL options are being set correctly, hence the assertion. assert('false', vs(isset($this), get_defined_vars())); $this->m_hasError = true; $this->m_errorMessage = "The 'curl_multi_setopt' function failed."; $success = false; $this->finalize(); return; } $anySuccessfulRequests = false; // Disable the script's execution timeout before getting into the session. $timeoutPause = new CTimeoutPause(); $numRunningRequests = 0; // also the index of the next request to send while (true) { // From the request queue, add as many normal cURL handles to the multi cURL handle as it is allowed by the // maximum number of concurrent requests, priorly setting internal options for every request. while ($numRunningRequests < CArray::length($this->m_requestRecordsQueue) && $numRunningRequests < $this->m_maxNumConcurrentRequests) { $requestRecord = $this->m_requestRecordsQueue[$numRunningRequests]; $request = $requestRecord[0]; $onCompleteCallback = $requestRecord[1]; $newCookieSession = $requestRecord[2]; $requestCurl = $request->curl(); // Set cURL options for the normal cURL handle, having created a temporary file for cookie storage if // needed. $requestSetOptSuccess; if ($this->m_cookiesAreEnabled && $request->isHttp()) { if (!isset($this->m_cookiesFp)) { $this->m_cookiesFp = CFile::createTemporary(); } $request->setInternalOptions($requestSetOptSuccess, $this->m_cookiesFp, $newCookieSession); } else { $request->setInternalOptions($requestSetOptSuccess); } if (!$requestSetOptSuccess) { if (isset($onCompleteCallback)) { call_user_func($onCompleteCallback, false, "", $request, $this); } CArray::remove($this->m_requestRecordsQueue, $numRunningRequests); continue; } // Add the normal cURL handle to the multi cURL handle. $res = curl_multi_add_handle($this->m_multiCurl, $requestCurl); if ($res != 0) { $this->m_hasError = true; $curlError = curl_multi_strerror($res); $this->m_errorMessage = is_cstring($curlError) && !CString::isEmpty($curlError) ? $curlError : "The 'curl_multi_add_handle' function failed."; $success = false; $timeoutPause->end(); $this->finalize(); return; } $numRunningRequests++; } if ($numRunningRequests == 0) { break; } // Process the currently added requests until complete or no more data is available. Although // `CURLM_CALL_MULTI_PERFORM` is deprecated since libcurl 7.20, keep it for compatibility reasons. $numRunningTransfers; do { $multiExecRes = curl_multi_exec($this->m_multiCurl, $numRunningTransfers); } while ($multiExecRes == CURLM_CALL_MULTI_PERFORM); if ($multiExecRes != CURLM_OK) { $this->m_hasError = true; $curlError = curl_multi_strerror($multiExecRes); $this->m_errorMessage = is_cstring($curlError) && !CString::isEmpty($curlError) ? $curlError : "The 'curl_multi_exec' function failed."; $success = false; $timeoutPause->end(); $this->finalize(); return; } // Check for completed requests, call the callback function for any completed one (if such a function is // defined), finalize completed requests, and remove completed requests from the queue. while (true) { $completedRequestInfo = curl_multi_info_read($this->m_multiCurl); if (!is_cmap($completedRequestInfo)) { break; } // A request has completed. assert('$completedRequestInfo["msg"] == CURLMSG_DONE', vs(isset($this), get_defined_vars())); $requestCurl = $completedRequestInfo["handle"]; $requestRes = $completedRequestInfo["result"]; $requestRecordPos; $found = CArray::find($this->m_requestRecordsQueue, $requestCurl, function ($requestRecord, $requestCurl) { $request = $requestRecord[0]; return $request->curl() == $requestCurl; }, $requestRecordPos); assert('$found', vs(isset($this), get_defined_vars())); $requestRecord = $this->m_requestRecordsQueue[$requestRecordPos]; $request = $requestRecord[0]; $onCompleteCallback = $requestRecord[1]; // Remove the normal cURL handle from the multi cURL handle. $res = curl_multi_remove_handle($this->m_multiCurl, $requestCurl); if ($res != 0) { $this->m_hasError = true; $curlError = curl_multi_strerror($res); $this->m_errorMessage = is_cstring($curlError) && !CString::isEmpty($curlError) ? $curlError : "The 'curl_multi_remove_handle' function failed."; $success = false; $timeoutPause->end(); $this->finalize(); return; } if ($requestRes == CURLE_OK) { // The request has succeeded. if (isset($onCompleteCallback)) { $response; if ($request->isReturnTransferSet()) { $response = curl_multi_getcontent($requestCurl); assert('is_cstring($response)', vs(isset($this), get_defined_vars())); } else { $response = ""; } $request->onRequestCompleteOk(); // also close the normal cURL handle call_user_func($onCompleteCallback, true, $response, $request, $this); } else { $request->onRequestCompleteOk(); // also close the normal cURL handle } $anySuccessfulRequests = true; } else { // The request has failed. $curlError = curl_strerror($requestRes); if (!is_cstring($curlError)) { $curlError = ""; } $request->onRequestCompleteWithError($curlError); // also close the normal cURL handle if (isset($onCompleteCallback)) { call_user_func($onCompleteCallback, false, "", $request, $this); } } CArray::remove($this->m_requestRecordsQueue, $requestRecordPos); $numRunningRequests--; } assert('$numRunningRequests == $numRunningTransfers', vs(isset($this), get_defined_vars())); if ($numRunningTransfers > 0) { // Some requests are still being processed (by remote machines). Wait for more data to appear on // sockets, without getting hard on the CPU. do { $multiSelectRes = curl_multi_select($this->m_multiCurl); } while ($multiSelectRes == -1); } else { // No requests are being processed. Check if any requests are pending. if (CArray::isEmpty($this->m_requestRecordsQueue)) { // No requests are pending. break; } } } // Set the script's execution time limit like the session has never happened. $timeoutPause->end(); if (!$anySuccessfulRequests) { $this->m_hasError = true; $this->m_errorMessage = "None of the session's requests succeeded."; $success = false; } $this->finalize(); }