public function testEnterTd() { // ASCII. $res = CRegex::enterTd(".(]/", "/"); $this->assertTrue(CString::equals($res, "\\.\\(\\]\\/")); $res = CRegex::enterTd(".(]#", "#"); $this->assertTrue(CString::equals($res, "\\.\\(\\]\\#")); // Unicode. $res = CRegex::enterTd(".(señor]/", "/"); $this->assertTrue(CString::equals($res, "\\.\\(señor\\]\\/")); $res = CRegex::enterTd(".(señor]#", "#"); $this->assertTrue(CString::equals($res, "\\.\\(señor\\]\\#")); }
/** * 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; }
/** * Determines if a URL is equal to another URL, comparing them by normalized URL strings. * * @param CUrl $toUrl The second URL for comparison. * @param bool $ignoreFragmentId **OPTIONAL. Default is** `true`. Tells whether the fragment IDs of the URLs, if * any, should be ignored during the comparison. * * @return bool `true` if *this* URL is equal to the second URL, `false` otherwise. */ public function equals($toUrl, $ignoreFragmentId = true) { assert('$toUrl instanceof CUrl && is_bool($ignoreFragmentId)', vs(isset($this), get_defined_vars())); if ($ignoreFragmentId) { return CString::equals($this->m_normUrlWithoutFragmentId, $toUrl->m_normUrlWithoutFragmentId); } else { return CString::equals($this->m_normUrl, $toUrl->m_normUrl); } }
/** * Returns the HTTP method of the request, as an enumerand. * * @return enum The HTTP method of the request (see [Summary](#summary)). */ public static function method() { $method = $_SERVER["REQUEST_METHOD"]; $method = CString::toUpperCase($method); if (CString::equals($method, "GET")) { return self::GET; } if (CString::equals($method, "HEAD")) { return self::HEAD; } if (CString::equals($method, "POST")) { return self::POST; } if (CString::equals($method, "PUT")) { return self::PUT; } if (CString::equals($method, "DELETE")) { return self::DELETE; } if (CString::equals($method, "OPTIONS")) { return self::OPTIONS; } if (CString::equals($method, "TRACE")) { return self::TRACE; } if (CString::equals($method, "CONNECT")) { return self::CONNECT; } return self::UNKNOWN; }
/** * Wraps the text in a string to a specified width and returns the new string. * * @param string $string The string with the text to be wrapped. * @param int $width The wrapping width, in characters. * @param bitfield $wrappingFlags **OPTIONAL. Default is** `WRAPPING_DEFAULT`. The wrapping option(s). The * available options are `WRAPPING_BREAK_SPACELESS_LINES`, `WRAPPING_ALLOW_TRAILING_SPACES`, * `WRAPPING_DISALLOW_LEADING_SPACES`, and `WRAPPING_DONT_BREAK_SPACELESS_CJK_ENDING_LINES` * (see [Summary](#summary)). * @param string $newline **OPTIONAL. Default is** LF (U+000A). The newline character(s) to be used for making * new lines in the process of wrapping. * * @return string The wrapped text. */ public static function wordWrap($string, $width, $wrappingFlags = self::WRAPPING_DEFAULT, $newline = self::NEWLINE) { assert('is_cstring($string) && is_int($width) && is_bitfield($wrappingFlags) && is_cstring($newline)', vs(isset($this), get_defined_vars())); assert('$width > 0', vs(isset($this), get_defined_vars())); // Constant. Newline character that is used by the input string (after newline normalization). $normNl = self::NEWLINE; // Constant. Determines what characters should be considered spaces. // A character in the "Zs" Unicode category, an HT, or a Zero Width Space, except No-break Space and Narrow // No-break Space. $spaceSubjectRe = "(\\p{Zs}|\\x{0009}|\\x{200B})(?<!\\x{00A0}|\\x{202F})"; // Break enabling characters. // Soft Hyphen or Tibetan Mark Intersyllabic Tsheg. $breakAllowCharSubjectRe = "\\x{00AD}|\\x{0F0B}"; // Retrieve the wrapping options. $breakSpacelessLines = CBitField::isBitSet($wrappingFlags, self::WRAPPING_BREAK_SPACELESS_LINES); $allowTrailingSpaces = CBitField::isBitSet($wrappingFlags, self::WRAPPING_ALLOW_TRAILING_SPACES); $disallowLeadingSpaces = CBitField::isBitSet($wrappingFlags, self::WRAPPING_DISALLOW_LEADING_SPACES); $dontBreakSpacelessCjkEndingLines = CBitField::isBitSet($wrappingFlags, self::WRAPPING_DONT_BREAK_SPACELESS_CJK_ENDING_LINES); // Normalize newlines in the input string. $string = self::normNewlines($string, $normNl); $normNlLength = self::length($normNl); $newString = ""; $pos = 0; $bytePos = 0; $sLength = self::length($string); while (true) { $numCharsLeft = $sLength - $pos; // A portion begins at the very start or right after a newline, either it is native or added. The length of // a portion is the wrapping width or less. $portionLength = CMathi::min($width, $numCharsLeft); $portion = self::substr($string, $pos, $portionLength); $portionByteLength = CString::length($portion); if ($portionLength == $numCharsLeft) { // All done. $newString .= $portion; break; } // The starting position of the next portion. $nextPos = $pos + $portionLength; $nextBytePos = $bytePos + $portionByteLength; // Look for the first occurrence of a newline in the portion. $nlPos = self::indexOf($portion, $normNl); if ($nlPos != -1) { // This portion contains a newline, so the next portion is going to start right after this first found // newline. $subPLength = $nlPos + $normNlLength; $subP = self::substr($portion, 0, $subPLength); $newString .= $subP; $pos += $subPLength; $bytePos += CString::length($subP); continue; } // There are no newlines in this portion. Before the next step, make sure that the next portion is not // going to start with a newline. if ($numCharsLeft - $portionLength >= $normNlLength) { $nextPortionBeginning = self::substr($string, $nextPos, $normNlLength); if (self::indexOf($nextPortionBeginning, $normNl) == 0) { // The next portion is going to start with a newline, so no need to break this one, regardless of // whether or not it contains any spaces. $newString .= $portion; $pos = $nextPos; $bytePos = $nextBytePos; continue; } } // The next portion is not going to start with a newline. Look for the last occurrence of a space or // break-allow character in this portion. $lastSubjectBytePos = CRegex::lastIndexOf($portion, "/({$spaceSubjectRe})|({$breakAllowCharSubjectRe})/u", 0, $foundString); if ($lastSubjectBytePos != -1) { // Add a newline right after this last occurring space or break-allow character. $subP = CString::substring($portion, 0, $lastSubjectBytePos + CString::length($foundString)); $newString .= $subP; $newString .= $newline; $pos += self::length($subP); $bytePos += CString::length($subP); continue; } // There are no spaces or break-allow characters in this portion. Consider adding a newline right after the // portion. if ($breakSpacelessLines || !$dontBreakSpacelessCjkEndingLines && self::hasCjkChar(self::charAt($portion, $portionLength - 1))) { $newString .= $portion; $newString .= $newline; $pos = $nextPos; $bytePos = $nextBytePos; continue; } // There are no spaces or break-allow characters in this portion and it should go adjacent to the upcoming // text. Look for the first newline, space, or break-allow character in the upcoming text. $nextSubjectBytePos = CRegex::indexOf($string, "/{$normNl}|(({$spaceSubjectRe})|({$breakAllowCharSubjectRe}))(?!{$normNl})/u", $nextBytePos, $foundString); if ($nextSubjectBytePos != -1) { // Found a newline, space, or a break-allow character, so the next portion is going to start right // after it. $afterP = CString::substring($string, $nextBytePos, $nextSubjectBytePos + CString::length($foundString)); $newString .= $portion; $newString .= $afterP; if (!CString::equals($foundString, $normNl)) { // It is a space or break-allow character that was found, so add a newline after it. $newString .= $newline; } $pos += $portionLength + self::length($afterP); $bytePos += $portionByteLength + CString::length($afterP); continue; } // There are no spaces, newlines, or break-allow characters in the upcoming text. Finalize according to the // breaking options. if (!$breakSpacelessLines) { $newString .= $portion; $newString .= self::substr($string, $nextPos); } else { $newString .= $portion; $newString .= $newline; $pos = $nextPos; while (true) { $numCharsLeft = $sLength - $pos; $portionLength = CMathi::min($width, $numCharsLeft); $newString .= self::substr($string, $pos, $portionLength); if ($portionLength == $numCharsLeft) { break; } $newString .= $newline; $pos += $portionLength; } } break; } if (!$allowTrailingSpaces) { // Remove trailing spaces. $newString = CRegex::remove($newString, "/({$spaceSubjectRe})+(?={$normNl}|\\z)/u"); } if ($disallowLeadingSpaces) { // Remove leading spaces. $newString = CRegex::remove($newString, "/(?<={$normNl}|^)({$spaceSubjectRe})+/u"); } return $newString; }
/** * @ignore */ public static function onThirdPartyUpdateByPackageManager() { if (!self::isInCliMode()) { // This method can be run in CLI mode only. assert('false', vs(isset($this), get_defined_vars())); } $timeoutPause = new CTimeoutPause(); CShell::speak("Processing third-party components ..."); $tpDps = CFile::listDirectories(CFilePath::absolute($GLOBALS["PHRED_PATH_TO_THIRD_PARTY"])); $ignorePackages = CConfiguration::option("upd.thirdPartyOopWrappingIgnorePackages"); $ignorePackagesL2 = CArray::filter($ignorePackages, function ($package) { return CString::find($package, "/"); }); $newTpDps = CArray::make(); $len = CArray::length($tpDps); for ($i = 0; $i < $len; $i++) { $tpDp = $tpDps[$i]; $dirName = CFilePath::name($tpDp); if (!CArray::find($ignorePackages, $dirName)) { $dpHasL2DirsToIgnore = CArray::find($ignorePackagesL2, $dirName, function ($packageL2, $dirName) { return CString::equals(CFilePath::directory($packageL2), $dirName); }); if (!$dpHasL2DirsToIgnore) { CArray::push($newTpDps, $tpDp); } else { $tpSubDps = CFile::listDirectories($tpDp); $tpSubDps = CArray::filter($tpSubDps, function ($subDp) use($ignorePackagesL2) { return !CArray::find($ignorePackagesL2, $subDp, function ($packageL2, $subDp) { return CString::endsWith($subDp, $packageL2); }); }); CArray::pushArray($newTpDps, $tpSubDps); } } } $tpDps = $newTpDps; $wrapProtectedMethods = CConfiguration::option("upd.thirdPartyOopWrappingInProtectedMethods"); $wrapPrivateMethods = CConfiguration::option("upd.thirdPartyOopWrappingInPrivateMethods"); static $s_stdPhpTag = "<?php"; static $s_progressResolution = 0.05; $prevProgressDivR = 0; $tpDpsLen = CArray::length($tpDps); for ($i0 = 0; $i0 < $tpDpsLen; $i0++) { $tpFps = CFile::reFindFilesRecursive($tpDps[$i0], "/\\.php\\d?\\z/"); $tpFpsLen = CArray::length($tpFps); for ($i1 = 0; $i1 < $tpFpsLen; $i1++) { $fileCode = CFile::read($tpFps[$i1]); if (!CString::find($fileCode, self::$ms_thirdPartyAlreadyOopWrappedMark)) { $parser = new PhpParser\Parser(new PhpParser\Lexer()); try { // Parse the code. $statements = $parser->parse($fileCode); // Wrap the code into OOP. $traverser = new PhpParser\NodeTraverser(); $mainVisitor = new CMainVisitor($wrapProtectedMethods, $wrapPrivateMethods); $traverser->addVisitor($mainVisitor); $statements = $traverser->traverse($statements); $wrappedCode = (new PhpParser\PrettyPrinter\Standard())->prettyPrint($statements); $phpTagPos = CString::indexOf($wrappedCode, $s_stdPhpTag); if ($phpTagPos == -1) { $wrappedCode = "{$s_stdPhpTag}\n\n{$wrappedCode}"; $phpTagPos = 0; } $wrappedCode = CString::insert($wrappedCode, $phpTagPos + CString::length($s_stdPhpTag), "\n\n" . self::$ms_thirdPartyAlreadyOopWrappedMark); // Save. CFile::write($tpFps[$i1], $wrappedCode); } catch (PhpParser\Error $parserError) { CShell::say("\nPhpParser: " . $tpFps[$i1] . ", at line " . $parserError->getRawLine() . ": " . $parserError->getRawMessage()); } } $progress = (double) ($i0 / $tpDpsLen + 1 / $tpDpsLen * $i1 / $tpFpsLen); $progressDivR = CMathi::floor($progress / $s_progressResolution); if ($progressDivR != $prevProgressDivR) { $perc = CMathi::round($progressDivR * $s_progressResolution * 100); CShell::speak("{$perc}%"); } $prevProgressDivR = $progressDivR; } } CShell::speak("100%"); CShell::say("Done."); $timeoutPause->end(); }
/** * Exits the script issuing an error message if the script's user is lacking root privileges. * * @return void */ public static function exitIfNotRoot() { $user = self::user(); if (!CString::equals($user, "root")) { $message = "This script requires root privileges to run, while the current user is {$user}."; self::onError(true, $message); } }
/** * Sends a message to the recipient(s). * * @param reference $failedAddresses **OPTIONAL. OUTPUT.** After the method is called with this parameter * provided, the parameter's value, which is of type `CArrayObject`, is an array containing the email addresses of * the recipients who failed to receive the message. * * @return int The number of recipients who have successfully received the message. */ public function send(&$failedAddresses = null) { assert('isset($this->m_swiftMailer) && isset($this->m_swiftMessage)', vs(isset($this), get_defined_vars())); assert('(isset($this->m_from) || isset($this->m_sender) || isset($this->m_returnAddress)) && ' . '(isset($this->m_to) || isset($this->m_cc) || isset($this->m_bcc))', vs(isset($this), get_defined_vars())); $message = $this->m_swiftMessage; if (isset($this->m_from)) { $message->setFrom($this->m_from); } if (isset($this->m_to)) { $message->setTo($this->m_to); } if (isset($this->m_cc)) { $message->setCc($this->m_cc); } if (isset($this->m_bcc)) { $message->setBcc($this->m_bcc); } if (isset($this->m_sender)) { $message->setSender($this->m_sender); } if (isset($this->m_returnAddress)) { $message->setReturnPath($this->m_returnAddress); } if (isset($this->m_replyAddress)) { $message->setReplyTo($this->m_replyAddress); } if (isset($this->m_body)) { if (CString::equals($this->m_bodyType, CMimeType::PLAIN_TEXT)) { $this->m_body = $this->maybeWrapText($this->m_body); } $message->setBody($this->m_body, $this->m_bodyType); } if (isset($this->m_altBodiesAndTypes)) { $len = CArray::length($this->m_altBodiesAndTypes); for ($i = 0; $i < $len; $i++) { $bodyAndType = $this->m_altBodiesAndTypes[$i]; $body = $bodyAndType[0]; $type = $bodyAndType[1]; if (CString::equals($type, CMimeType::PLAIN_TEXT)) { $body = $this->maybeWrapText($body); } $message->addPart($body, $type); } } $paFailedAddresses; $res = $this->m_swiftMailer->send($message, $paFailedAddresses); if (is_cmap($paFailedAddresses)) { $failedAddresses = oop_a(CArray::fromPArray($paFailedAddresses)); } $res = is_int($res) ? $res : 0; return $res; }
/** * @ignore */ public static function isNameIcuCompatible($name) { assert('is_cstring($name)', vs(isset($this), get_defined_vars())); $itz = IntlTimeZone::createTimeZone($name); return CString::equals($name, $itz->getID()); }
protected static function basicProtocol($protocol) { $protocol = CString::toLowerCase($protocol); if (CString::equals($protocol, "https")) { $protocol = "http"; } else { if (CString::equals($protocol, "ftps")) { $protocol = "ftp"; } } return $protocol; }
protected static function recurseCopy($fileOrDirectoryPath, $copyPath, $copyPermissions) { if (is_link($fileOrDirectoryPath)) { // The source is a symlink, so make another symlink. $symlinkTargetPath = readlink($fileOrDirectoryPath); assert('is_cstring($symlinkTargetPath)', vs(isset($this), get_defined_vars())); $res = symlink($symlinkTargetPath, $copyPath); assert('$res', vs(isset($this), get_defined_vars())); return; } if ($copyPermissions) { clearstatcache(true, $fileOrDirectoryPath); } if (is_file($fileOrDirectoryPath)) { // A file. $res = copy($fileOrDirectoryPath, $copyPath); assert('$res', vs(isset($this), get_defined_vars())); if ($copyPermissions) { $res = chmod($copyPath, fileperms($fileOrDirectoryPath)); assert('$res', vs(isset($this), get_defined_vars())); } } else { // A directory. if (!is_dir($copyPath)) { // Create the destination directory. $res = mkdir($copyPath); assert('$res', vs(isset($this), get_defined_vars())); } if ($copyPermissions) { $res = chmod($copyPath, fileperms($fileOrDirectoryPath)); assert('$res', vs(isset($this), get_defined_vars())); } // Iterate through contained items. $dir = dir($fileOrDirectoryPath); assert('is_object($dir)', vs(isset($this), get_defined_vars())); while (false !== ($item = $dir->read())) { if (CString::equals($item, ".") || CString::equals($item, "..")) { continue; } self::recurseCopy("{$fileOrDirectoryPath}/{$item}", "{$copyPath}/{$item}", $copyPermissions); } $dir->close(); } }
/** * Determines if an ASCII string or binary data is equal to another ASCII string or binary data. * * This method is suitable for binary-safe comparison of strings and runs faster than `equals` method when * comparing strings known to contain no Unicode characters and is the method to be preferred for comparison of * binary data. * * @param string $toString The second string for comparison. * * @return bool `true` if the two strings are equal, comparing them byte-to-byte, and `false` otherwise. */ public function equalsBi($toString) { return CString::equals($this, $toString); }
/** * @ignore */ public static function initialize() { $configDp = CFilePath::add($GLOBALS["PHRED_PATH_TO_APP"], "Configuration"); $configEnvsDp = CFilePath::add($configDp, "Environments"); $configs = CArray::make(); $currEnv; if ($GLOBALS["PHRED_TESTS"]) { $currEnv = "tst"; } // Main configuration files. $configFps = CFile::findFiles(CFilePath::add($configDp, "*.json")); $numConfigs = CArray::length($configFps); for ($i = 0; $i < $numConfigs; $i++) { $configFp = $configFps[$i]; $configName = CFilePath::nameOnly($configFp); self::readAndAddConfig($configFp, $configName, $configs); if (!isset($currEnv) && CString::equals($configName, "Application")) { $config = CArray::last($configs); $currEnv = $config[self::$ms_configAliases["Application"]]["environment"]; } } assert('is_cstring($currEnv)', vs(isset($this), get_defined_vars())); // The configuration files from the current environment's directory. $currEnvDp = CFilePath::add($configEnvsDp, $currEnv); assert('CFile::exists($currEnvDp)', vs(isset($this), get_defined_vars())); $configFps = CFile::findFiles(CFilePath::add($currEnvDp, "*.json")); $numConfigs = CArray::length($configFps); for ($i = 0; $i < $numConfigs; $i++) { $configFp = $configFps[$i]; $configName = CFilePath::nameOnly($configFp); self::readAndAddConfig($configFp, $configName, $configs); } self::$ms_config = call_user_func_array("CMap::merge", CArray::toPArray($configs)); self::$ms_isInitialized = true; }
/** * Filters a string or a collection of strings according to the expected output type(s) and returns the output * value(s). * * @param mixed $inputStringOrDecodedCollection The string to be filtered or the array or map containing the * strings to be filtered. If the parameter's value is a JSON-encoded string, the output value is going to be * either an array or map. * @param reference $success **OUTPUT.** After the method is called, the value of this parameter tells whether * the filtering was successful. * * @return mixed The output value or a collection of values of the expected type(s) after having been put through * the filter. */ public function filter($inputStringOrDecodedCollection, &$success) { assert('is_cstring($inputStringOrDecodedCollection) || is_collection($inputStringOrDecodedCollection)', vs(isset($this), get_defined_vars())); $success = true; if ($this->m_expectedType != self::CARRAY && $this->m_expectedType != self::CMAP) { // The expected output type is not a collection; the input value must be of string type. if (!is_cstring($inputStringOrDecodedCollection)) { $success = false; return oop_x($this->m_defaultValue); } $inputString = $inputStringOrDecodedCollection; if ($this->m_expectedType == self::BOOL || $this->m_expectedType == self::INT || $this->m_expectedType == self::FLOAT || $this->m_expectedType == self::EMAIL || $this->m_expectedType == self::URL || $this->m_expectedType == self::IP) { // Trim the input string on both sides from whitespace, including Unicode whitespace and control // characters. $trimmingSubjectRe = CUString::TRIMMING_AND_SPACING_NORM_SUBJECT_RE; $inputString = CRegex::remove($inputString, "/^({$trimmingSubjectRe})+|({$trimmingSubjectRe})+\\z/u"); } // Pre-process the string for integer and floating-point types. $looksLikeHex; if ($this->m_expectedType == self::INT || $this->m_expectedType == self::FLOAT) { if (CString::startsWith($inputString, "+")) { // Remove the plus sign. $inputString = CString::substr($inputString, 1); } $looksLikeHex = CRegex::find($inputString, "/^-?0x/i"); if ($this->m_allowLeadingZeros && !($this->m_expectedType == self::INT && $this->m_allowHex && $looksLikeHex)) { // Remove any leading zeros (except for special cases). $inputString = CRegex::replace($inputString, "/^(\\D*)0*(?!\\b)/", "\$1"); } if ($this->m_allowComma) { $inputString = CRegex::remove($inputString, "/,(?=\\d{3}\\b)/"); } } // Validate and sanitize the value according to its expected type. if ($this->m_expectedType == self::BOOL) { if (!CRegex::find($inputString, "/^(1|true|yes|on|0|false|no|off)\\z/i")) { $success = false; return $this->m_defaultValue; } return CString::equals($inputString, "1") || CString::equalsCi($inputString, "true") || CString::equalsCi($inputString, "yes") || CString::equalsCi($inputString, "on"); } if ($this->m_expectedType == self::INT) { $value; if (!($this->m_allowHex && $looksLikeHex)) { // Regular. if (!CRegex::find($inputString, "/^-?(?!0(?!\\b))\\d+\\z/")) { $success = false; return $this->m_defaultValue; } $value = CString::toInt($inputString); } else { // Hex. if (!CRegex::find($inputString, "/^-?0x[0-9A-F]+\\z/i")) { $success = false; return $this->m_defaultValue; } $value = CString::toIntFromHex($inputString); } if (isset($this->m_intValidMin) && $value < $this->m_intValidMin || isset($this->m_intValidMax) && $value > $this->m_intValidMax) { $success = false; return $this->m_defaultValue; } if (isset($this->m_intClampingMin) && $value < $this->m_intClampingMin) { $value = $this->m_intClampingMin; } if (isset($this->m_intClampingMax) && $value > $this->m_intClampingMax) { $value = $this->m_intClampingMax; } return $value; } if ($this->m_expectedType == self::FLOAT) { if (!CRegex::find($inputString, "/^-?(?!0(?!\\b))\\d*\\.?\\d+(e[\\-+]?\\d+)?\\z/i")) { $success = false; return $this->m_defaultValue; } $value = CString::toFloat($inputString); if (isset($this->m_floatValidMin) && $value < $this->m_floatValidMin || isset($this->m_floatValidMax) && $value > $this->m_floatValidMax) { $success = false; return $this->m_defaultValue; } if (isset($this->m_floatClampingMin) && $value < $this->m_floatClampingMin) { $value = $this->m_floatClampingMin; } if (isset($this->m_floatClampingMax) && $value > $this->m_floatClampingMax) { $value = $this->m_floatClampingMax; } return $value; } if ($this->m_expectedType == self::CSTRING) { $value = $inputString; if (!CString::isValid($value)) { $success = false; return $this->m_defaultValue; } if (!$this->m_keepAbnormalNewlines) { $value = CString::normNewlines($value); } if (!$this->m_keepNonPrintable) { if (!$this->m_keepTabsAndNewlines) { $value = CRegex::remove($value, "/[\\x00-\\x1F\\x7F-\\xFF]/"); } else { $value = CRegex::remove($value, "/[\\x00-\\x1F\\x7F-\\xFF](?<![\\x09\\x0A\\x0D])/"); } } else { if (!$this->m_keepTabsAndNewlines) { $value = CRegex::remove($value, "/[\\x09\\x0A\\x0D]/"); } } if (!$this->m_keepSideSpacing) { $value = CString::trim($value); } if (!$this->m_keepExtraSpacing) { $value = CString::normSpacing($value); } return $value; } if ($this->m_expectedType == self::CUSTRING) { $value = $inputString; if (!CUString::isValid($value)) { $success = false; return $this->m_defaultValue; } if (!$this->m_keepAbnormalNewlines) { $value = CUString::normNewlines($value); } if (!$this->m_keepNonPrintable) { if (!$this->m_keepTabsAndNewlines) { $value = CRegex::remove($value, "/\\p{C}|\\p{Zl}|\\p{Zp}/u"); } else { $value = CRegex::remove($value, "/\\p{C}(?<!\\x{0009}|\\x{000A}|\\x{000D})/u"); } } else { if (!$this->m_keepTabsAndNewlines) { $value = CRegex::remove($value, "/\\x{0009}|\\x{000A}|\\x{000D}|\\p{Zl}|\\p{Zp}/u"); } } if (!$this->m_keepSideSpacing) { $value = CUString::trim($value); } if (!$this->m_keepExtraSpacing) { $value = CUString::normSpacing($value); } return $value; } if ($this->m_expectedType == self::EMAIL) { $value = filter_var($inputString, FILTER_VALIDATE_EMAIL); if (!is_cstring($value)) { $success = false; return $this->m_defaultValue; } return $value; } if ($this->m_expectedType == self::URL) { $value = $inputString; if (!CUrl::isValid($value, $this->m_ignoreProtocolAbsence)) { $success = false; return $this->m_defaultValue; } if ($this->m_ignoreProtocolAbsence) { $value = CUrl::ensureProtocol($value); } return $value; } if ($this->m_expectedType == self::IP) { $value = $inputString; $options = CBitField::ALL_UNSET; if (!$this->m_allowPrivateRange) { $options |= CIp::DISALLOW_PRIVATE_RANGE; } if (!$this->m_allowReservedRange) { $options |= CIp::DISALLOW_RESERVED_RANGE; } $isValid; if (!$this->m_ipV6 && !$this->m_ipV4OrV6) { $isValid = CIp::isValidV4($value, $options); } else { if (!$this->m_ipV4OrV6) { $isValid = CIp::isValidV6($value, $options); } else { $isValid = CIp::isValidV4($value, $options) || CIp::isValidV6($value, $options); } } if (!$isValid) { $success = false; return $this->m_defaultValue; } return $value; } } else { if ($this->m_expectedType == self::CARRAY) { if (!is_cstring($inputStringOrDecodedCollection) && !is_carray($inputStringOrDecodedCollection)) { $success = false; return oop_x($this->m_defaultValue); } $value; if (is_cstring($inputStringOrDecodedCollection)) { // Assume JSON format for the input string. $json = new CJson($inputStringOrDecodedCollection, $this->m_jsonStrictness); $value = $json->decode($success); if (!$success) { return oop_x($this->m_defaultValue); } if (!is_carray($value)) { $success = false; return oop_x($this->m_defaultValue); } } else { $value = $inputStringOrDecodedCollection; } $value = self::recurseCollectionFiltering($value, $this->m_collectionInputFilters, $success, 0); if (!$success) { return oop_x($this->m_defaultValue); } return $value; } else { if (!is_cstring($inputStringOrDecodedCollection) && !is_cmap($inputStringOrDecodedCollection)) { $success = false; return oop_x($this->m_defaultValue); } $value; if (is_cstring($inputStringOrDecodedCollection)) { // Assume JSON format for the input string. $json = new CJson($inputStringOrDecodedCollection, $this->m_jsonStrictness); $value = $json->decode($success); if (!$success) { return oop_x($this->m_defaultValue); } if (!is_cmap($value)) { $success = false; return oop_x($this->m_defaultValue); } } else { $value = $inputStringOrDecodedCollection; } $value = self::recurseCollectionFiltering($value, $this->m_collectionInputFilters, $success, 0); if (!$success) { return oop_x($this->m_defaultValue); } return $value; } } }
/** * Decodes the JSON-encoded string provided earlier to the decoder and returns the result. * * @param reference $success **OPTIONAL. OUTPUT.** After the method is called with this parameter provided, the * parameter's value tells whether the decoding was successful. * * @return mixed The decoded value of type `CMapObject` or `CArrayObject`. */ public function decode(&$success = null) { assert('is_cstring($this->m_source)', vs(isset($this), get_defined_vars())); $success = true; $source = $this->m_source; if ($this->m_decodingStrictness == self::LENIENT && !CUString::isValid($source)) { // Change the character encoding or try fixing it. if (CEString::looksLikeLatin1($source)) { $source = CEString::convertLatin1ToUtf8($source); } else { $source = CEString::fixUtf8($source); } } if ($this->m_decodingStrictness == self::STRICT_WITH_COMMENTS || $this->m_decodingStrictness == self::LENIENT) { if (CRegex::find($source, "/\\/\\/|\\/\\*/u")) { // Remove "//..." and "/*...*/" comments. $source = CRegex::remove($source, "/(?<!\\\\)\"(?:[^\\\\\"]++|\\\\{2}|\\\\\\C)*\"(*SKIP)(*FAIL)|" . "\\/\\/.*|\\/\\*\\C*?\\*\\//u"); } } if ($this->m_decodingStrictness == self::LENIENT) { if (CRegex::find($source, "/[:\\[,]\\s*'([^\\\\']++|\\\\{2}|\\\\\\C)*'(?=\\s*[,}\\]])/u")) { // Convert single-quoted string values into double-quoted, taking care of double quotes within such // strings before and single quotes after. This needs to go in front of the rest of the leniency fixes. while (true) { $prevSource = $source; $source = CRegex::replace($source, "/(?<!\\\\)\"(?:[^\\\\\"]++|\\\\{2}|\\\\\\C)*\"(*SKIP)(*FAIL)|" . "([:\\[,]\\s*'(?:[^\\\\'\"]++|\\\\{2}|\\\\\\C)*)\"((?:[^\\\\']++|\\\\{2}|\\\\\\C)*')/u", "\$1\\\"\$2"); if (CString::equals($source, $prevSource) || is_null($source)) { break; } } if (is_null($source)) { $source = ""; } $source = CRegex::replace($source, "/(?<!\\\\)\"(?:[^\\\\\"]++|\\\\{2}|\\\\\\C)*\"(*SKIP)(*FAIL)|" . "([:\\[,]\\s*)'((?:[^\\\\']++|\\\\{2}|\\\\\\C)*)'(?=\\s*[,}\\]])/u", "\$1\"\$2\""); while (true) { $prevSource = $source; $source = CRegex::replace($source, "/([:\\[,]\\s*\"(?:[^\\\\\"]++|\\\\{2}|\\\\[^'])*)\\\\'((?:[^\\\\\"]++|\\\\{2}|\\\\\\C)*\")" . "(?=\\s*[,}\\]])/u", "\$1'\$2"); if (CString::equals($source, $prevSource) || is_null($source)) { break; } } if (is_null($source)) { $source = ""; } } if (CRegex::find($source, "/[{,]\\s*[\\w\\-.]+\\s*:/u")) { // Put property names in double quotes. $source = CRegex::replace($source, "/(?<!\\\\)\"(?:[^\\\\\"]++|\\\\{2}|\\\\\\C)*\"(*SKIP)(*FAIL)|" . "([{,]\\s*)([\\w\\-.]+)(\\s*:)/u", "\$1\"\$2\"\$3"); } if (CRegex::find($source, "/[{,]\\s*'[\\w\\-.]+'\\s*:/u")) { // Put property names that are in single quotes in double quotes. $source = CRegex::replace($source, "/(?<!\\\\)\"(?:[^\\\\\"]++|\\\\{2}|\\\\\\C)*\"(*SKIP)(*FAIL)|" . "([{,]\\s*)'([\\w\\-.]+)'(\\s*:)/u", "\$1\"\$2\"\$3"); } if (CRegex::find($source, "/,\\s*[}\\]]/u")) { // Remove trailing commas. $source = CRegex::remove($source, "/(?<!\\\\)\"(?:[^\\\\\"]++|\\\\{2}|\\\\\\C)*\"(*SKIP)(*FAIL)|" . ",(?=\\s*[}\\]])/u"); } // Within string values, convert byte values for BS, FF, LF, CR, and HT, which are prohibited in JSON, // to their escaped equivalents. $stringValueSubjectRe = "/(?<!\\\\)\"(?:[^\\\\\"]++|\\\\{2}|\\\\\\C)*\"/u"; $source = CRegex::replaceWithCallback($source, $stringValueSubjectRe, function ($matches) { return CRegex::replace($matches[0], "/\\x{0008}/u", "\\b"); }); $source = CRegex::replaceWithCallback($source, $stringValueSubjectRe, function ($matches) { return CRegex::replace($matches[0], "/\\x{000C}/u", "\\f"); }); $source = CRegex::replaceWithCallback($source, $stringValueSubjectRe, function ($matches) { return CRegex::replace($matches[0], "/\\x{000A}/u", "\\n"); }); $source = CRegex::replaceWithCallback($source, $stringValueSubjectRe, function ($matches) { return CRegex::replace($matches[0], "/\\x{000D}/u", "\\r"); }); $source = CRegex::replaceWithCallback($source, $stringValueSubjectRe, function ($matches) { return CRegex::replace($matches[0], "/\\x{0009}/u", "\\t"); }); } $decodedValue = @json_decode($source, false, self::$ms_maxRecursionDepth); if (is_null($decodedValue)) { if ($this->m_decodingStrictness == self::STRICT || $this->m_decodingStrictness == self::STRICT_WITH_COMMENTS) { $success = false; } else { if (CRegex::find($source, "/^\\s*[\\w.]+\\s*\\(/u")) { // The source string appears to be a JSONP. Extract the function's argument and try decoding again. $source = CRegex::replace($source, "/^\\s*[\\w.]+\\s*\\((\\C+)\\)/u", "\$1"); $decodedValue = @json_decode($source, false, self::$ms_maxRecursionDepth); if (is_null($decodedValue)) { $success = false; } } } } if (!$success) { return; } if ($this->m_decodingStrictness == self::STRICT || $this->m_decodingStrictness == self::STRICT_WITH_COMMENTS) { if (!is_object($decodedValue) && !is_array($decodedValue)) { $success = false; return; } } // Recursively convert any object into a CMapObject/CMap and any PHP array into a CArrayObject/CArray. $decodedValue = self::recurseValueAfterDecoding($decodedValue, 0); return $decodedValue; }