/** * Saves log messages in files. * * @param array $logs list of log messages */ protected function processLogs($logs) { $logFile = IOHelper::normalizePathSeparators($this->getLogPath() . '/' . $this->getLogFile()); if (IOHelper::getFileSize($logFile) > $this->getMaxFileSize() * 1024) { $this->rotateFiles(); } $lock = craft()->config->get('useLockWhenWritingToFile') === true; $fp = @fopen($logFile, 'a'); if ($lock) { @flock($fp, LOCK_EX); } foreach ($logs as $log) { $message = LoggingHelper::redact($log[0]); $level = $log[1]; $category = $log[2]; $time = $log[3]; $force = isset($log[4]) && $log[4] == true ? true : false; @fwrite($fp, $this->formatLogMessageWithForce($message, $level, $category, $time, $force)); } @fwrite($fp, PHP_EOL . '******************************************************************************************************' . PHP_EOL); if ($lock) { @flock($fp, LOCK_UN); } @fclose($fp); }
/** * Looks for a missing translation string in Yii's core translations. * * @param \CMissingTranslationEvent $event * * @return null */ public static function findMissingTranslation(\CMissingTranslationEvent $event) { // Look for translation file from most to least specific. So nl_nl.php gets checked before nl.php, for example. $translationFiles = array(); $parts = explode('_', $event->language); $totalParts = count($parts); for ($i = 1; $i <= $totalParts; $i++) { $translationFiles[] = implode('_', array_slice($parts, 0, $i)); } $translationFiles = array_reverse($translationFiles); // First see if we have any cached info. foreach ($translationFiles as $translationFile) { // We've loaded the translation file already, just check for the translation. if (isset(static::$_translations[$translationFile])) { if (isset(static::$_translations[$translationFile][$event->message])) { $event->message = static::$_translations[$translationFile][$event->message]; } // No translation... just give up. return; } } // No luck in cache, check the file system. $frameworkMessagePath = IOHelper::normalizePathSeparators(Craft::getPathOfAlias('app.framework.messages')); foreach ($translationFiles as $translationFile) { $path = $frameworkMessagePath . $translationFile . '/yii.php'; if (IOHelper::fileExists($path)) { // Load it up. static::$_translations[$translationFile] = (include $path); if (isset(static::$_translations[$translationFile][$event->message])) { $event->message = static::$_translations[$translationFile][$event->message]; return; } } } }
/** * Saves log messages in files. * * @param array $logs The list of log messages * * @return null */ protected function processLogs($logs) { $types = array(); foreach ($logs as $log) { $message = LoggingHelper::redact($log[0]); $level = $log[1]; $category = $log[2]; $time = $log[3]; $force = isset($log[4]) && $log[4] == true ? true : false; $plugin = isset($log[5]) ? StringHelper::toLowerCase($log[5]) : 'craft'; if (isset($types[$plugin])) { $types[$plugin] .= $this->formatLogMessageWithForce($message, $level, $category, $time, $force, $plugin); } else { $types[$plugin] = $this->formatLogMessageWithForce($message, $level, $category, $time, $force, $plugin); } } foreach ($types as $plugin => $text) { $text .= PHP_EOL . '******************************************************************************************************' . PHP_EOL; $this->setLogFile($plugin . '.log'); $logFile = IOHelper::normalizePathSeparators($this->getLogPath() . '/' . $this->getLogFile()); // Check the config setting first. Is it set to true? if (craft()->config->get('useWriteFileLock') === true) { $lock = true; } else { if (craft()->config->get('useWriteFileLock') === false) { $lock = false; } else { if (craft()->cache->get('useWriteFileLock') === 'yes') { $lock = true; } else { $lock = false; } } } $fp = @fopen($logFile, 'a'); if ($lock) { @flock($fp, LOCK_EX); } if (IOHelper::getFileSize($logFile) > $this->getMaxFileSize() * 1024) { $this->rotateFiles(); if ($lock) { @flock($fp, LOCK_UN); } @fclose($fp); if ($lock) { IOHelper::writeToFile($logFile, $text, false, true, false); } else { IOHelper::writeToFile($logFile, $text, false, true, true); } } else { @fwrite($fp, $text); if ($lock) { @flock($fp, LOCK_UN); } @fclose($fp); } } }
/** * @static * * @param $manifestData * @param $sourceTempFolder * @return bool * @return bool */ public static function doFileUpdate($manifestData, $sourceTempFolder) { try { foreach ($manifestData as $row) { if (static::isManifestVersionInfoLine($row)) { continue; } $folder = false; $rowData = explode(';', $row); if (static::isManifestLineAFolder($rowData[0])) { $folder = true; $tempPath = static::cleanManifestFolderLine($rowData[0]); } else { $tempPath = $rowData[0]; } $destFile = IOHelper::normalizePathSeparators(craft()->path->getAppPath() . $tempPath); $sourceFile = IOHelper::getRealPath(IOHelper::normalizePathSeparators($sourceTempFolder . '/app/' . $tempPath)); switch (trim($rowData[1])) { // update the file case PatchManifestFileAction::Add: if ($folder) { Craft::log('Updating folder: ' . $destFile, LogLevel::Info, true); $tempFolder = rtrim($destFile, '/') . StringHelper::UUID() . '/'; $tempTempFolder = rtrim($destFile, '/') . '-tmp/'; IOHelper::createFolder($tempFolder); IOHelper::copyFolder($sourceFile, $tempFolder); IOHelper::rename($destFile, $tempTempFolder); IOHelper::rename($tempFolder, $destFile); IOHelper::clearFolder($tempTempFolder); IOHelper::deleteFolder($tempTempFolder); } else { Craft::log('Updating file: ' . $destFile, LogLevel::Info, true); IOHelper::copyFile($sourceFile, $destFile); } break; } } } catch (\Exception $e) { Craft::log('Error updating files: ' . $e->getMessage(), LogLevel::Error); UpdateHelper::rollBackFileChanges($manifestData); return false; } return true; }
/** * @inheritDoc IZip::add() * * @param string $sourceZip * @param string $pathToAdd * @param string $basePath * @param null $pathPrefix * * @return bool */ public function add($sourceZip, $pathToAdd, $basePath, $pathPrefix = null) { $zip = new \ZipArchive(); $zipContents = $zip->open($sourceZip); if ($zipContents !== true) { Craft::log('Unable to open zip file: ' . $sourceZip, LogLevel::Error); return false; } if (IOHelper::fileExists($pathToAdd)) { $folderContents = array($pathToAdd); } else { $folderContents = IOHelper::getFolderContents($pathToAdd, true); } foreach ($folderContents as $itemToZip) { if (IOHelper::isReadable($itemToZip)) { // Figure out the relative path we'll be adding to the zip. $relFilePath = mb_substr($itemToZip, mb_strlen($basePath)); if ($pathPrefix) { $pathPrefix = IOHelper::normalizePathSeparators($pathPrefix); $relFilePath = $pathPrefix . $relFilePath; } if (IOHelper::folderExists($itemToZip)) { if (IOHelper::isFolderEmpty($itemToZip)) { $zip->addEmptyDir($relFilePath); } } elseif (IOHelper::fileExists($itemToZip)) { // We can't use $zip->addFile() here but it's a terrible, horrible, POS method that's buggy on Windows. $fileContents = IOHelper::getFileContents($itemToZip); if (!$zip->addFromString($relFilePath, $fileContents)) { Craft::log('There was an error adding the file ' . $itemToZip . ' to the zip: ' . $itemToZip, LogLevel::Error); } } } } $zip->close(); return true; }
/** * @param $pathToTest * * @return bool */ private function _isPathInsideWebRoot($pathToTest) { $pathToTest = IOHelper::normalizePathSeparators($pathToTest); // Get the base path without the script name. $subBasePath = IOHelper::normalizePathSeparators(mb_substr(craft()->request->getScriptFile(), 0, -mb_strlen(craft()->request->getScriptUrl()))); if (mb_strpos($pathToTest, $subBasePath) !== false) { return true; } return false; }
/** * Decodes the path info. * * Replacement for Yii's {@link \CHttpRequest::decodePathInfo()}. * * @param string $pathInfo Encoded path info. * * @return string Decoded path info. */ public function decodePathInfo($pathInfo) { $pathInfo = urldecode($pathInfo); if (!StringHelper::isUTF8($pathInfo)) { $pathInfo = StringHelper::convertToUTF8($pathInfo); } return IOHelper::normalizePathSeparators($pathInfo); }
/** * Searches for a template files, and returns the first match if there is one. * * @param string $basePath The base path to be looking in. * @param string $name The name of the template to be looking for. * * @return string|null The matching file path, or `null`. */ private function _findTemplate($basePath, $name) { // Normalize the path and name $basePath = rtrim(IOHelper::normalizePathSeparators($basePath), '/') . '/'; $name = trim(IOHelper::normalizePathSeparators($name), '/'); // $name could be an empty string (e.g. to load the homepage template) if ($name) { // Maybe $name is already the full file path $testPath = $basePath . $name; if (IOHelper::fileExists($testPath)) { return $testPath; } foreach ($this->_defaultTemplateExtensions as $extension) { $testPath = $basePath . $name . '.' . $extension; if (IOHelper::fileExists($testPath)) { return $testPath; } } } foreach ($this->_indexTemplateFilenames as $filename) { foreach ($this->_defaultTemplateExtensions as $extension) { $testPath = $basePath . ($name ? $name . '/' : '') . $filename . '.' . $extension; if (IOHelper::fileExists($testPath)) { return $testPath; } } } }
/** * Searches for a template files, and returns the first match if there is one. * * @param string $basePath The base path to be looking in. * @param string $name The name of the template to be looking for. * * @return string|null The matching file path, or `null`. */ private function _findTemplate($basePath, $name) { // Normalize the path and name $basePath = rtrim(IOHelper::normalizePathSeparators($basePath), '/') . '/'; $name = trim(IOHelper::normalizePathSeparators($name), '/'); // Set the defaultTemplateExtensions and indexTemplateFilenames vars if (!isset($this->_defaultTemplateExtensions)) { if (craft()->request->isCpRequest()) { $this->_defaultTemplateExtensions = array('html', 'twig'); $this->_indexTemplateFilenames = array('index'); } else { $this->_defaultTemplateExtensions = craft()->config->get('defaultTemplateExtensions'); $this->_indexTemplateFilenames = craft()->config->get('indexTemplateFilenames'); } } // $name could be an empty string (e.g. to load the homepage template) if ($name) { // Maybe $name is already the full file path $testPath = $basePath . $name; if (IOHelper::fileExists($testPath)) { return $testPath; } foreach ($this->_defaultTemplateExtensions as $extension) { $testPath = $basePath . $name . '.' . $extension; if (IOHelper::fileExists($testPath)) { return $testPath; } } } foreach ($this->_indexTemplateFilenames as $filename) { foreach ($this->_defaultTemplateExtensions as $extension) { $testPath = $basePath . ($name ? $name . '/' : '') . $filename . '.' . $extension; if (IOHelper::fileExists($testPath)) { return $testPath; } } } }
/** * Attempt to backup each of the update manifest files by copying them to a file with the same name with a .bak * extension. If there is an exception thrown, we attempt to roll back all of the changes. * * @param string $unzipFolder * @param string $handle * * @return bool */ private function _backupFiles($unzipFolder, $handle) { $manifestData = UpdateHelper::getManifestData($unzipFolder, $handle); try { foreach ($manifestData as $row) { if (UpdateHelper::isManifestVersionInfoLine($row)) { continue; } // No need to back up migration files. if (UpdateHelper::isManifestMigrationLine($row)) { continue; } $rowData = explode(';', $row); $filePath = IOHelper::normalizePathSeparators(($handle == 'craft' ? craft()->path->getAppPath() : craft()->path->getPluginsPath() . $handle . '/') . $rowData[0]); // It's a folder if (UpdateHelper::isManifestLineAFolder($filePath)) { $folderPath = UpdateHelper::cleanManifestFolderLine($filePath); if (IOHelper::folderExists($folderPath)) { Craft::log('Backing up folder ' . $folderPath, LogLevel::Info, true); IOHelper::createFolder($folderPath . '.bak'); IOHelper::copyFolder($folderPath . '/', $folderPath . '.bak/'); } } else { // If the file doesn't exist, it's probably a new file. if (IOHelper::fileExists($filePath)) { Craft::log('Backing up file ' . $filePath, LogLevel::Info, true); IOHelper::copyFile($filePath, $filePath . '.bak'); } } } } catch (\Exception $e) { Craft::log('Error updating files: ' . $e->getMessage(), LogLevel::Error); UpdateHelper::rollBackFileChanges($manifestData, $handle); return false; } return true; }
/** * Gets migrations that have no been applied yet AND have a later timestamp than the current Craft release. * * @param $plugin * * @return array */ public function getNewMigrations($plugin = null) { $migrations = array(); $migrationPath = $this->getMigrationPath($plugin); if (IOHelper::folderExists($migrationPath) && IOHelper::isReadable($migrationPath)) { $applied = array(); foreach ($this->getMigrationHistory($plugin) as $migration) { $applied[] = $migration['version']; } $handle = opendir($migrationPath); while (($file = readdir($handle)) !== false) { if ($file[0] === '.') { continue; } $path = IOHelper::normalizePathSeparators($migrationPath . $file); $class = IOHelper::getFileName($path, false); // Have we already run this migration? if (in_array($class, $applied)) { continue; } if (preg_match('/^m(\\d\\d)(\\d\\d)(\\d\\d)_(\\d\\d)(\\d\\d)(\\d\\d)_\\w+\\.php$/', $file, $matches)) { $migrations[] = $class; } } closedir($handle); sort($migrations); } return $migrations; }
/** * Will copy the contents of one folder to another. * * @param string $path The source path to copy. * @param string $destination The destination path to copy to. * @param bool $validate Whether to compare the size of the folders after the copy is complete. * @param bool $suppressErrors Whether to suppress any PHP Notices/Warnings/Errors (usually permissions related). * * @return bool 'true' if the copy was successful, 'false' if it was not, or $validate is true and the size of the * folders do not match after the copy. */ public static function copyFolder($path, $destination, $validate = false, $suppressErrors = false) { $path = static::normalizePathSeparators($path); $destination = static::normalizePathSeparators($destination); if (static::folderExists($path, false, $suppressErrors)) { $folderContents = static::getFolderContents($path, true, null, true, $suppressErrors); if ($folderContents) { foreach ($folderContents as $item) { $itemDest = IOHelper::normalizePathSeparators($destination . '/' . str_replace($path, '', $item)); $destFolder = static::getFolderName($itemDest, true, $suppressErrors); if (!static::folderExists($destFolder, false, $suppressErrors)) { static::createFolder($destFolder, craft()->config->get('defaultFolderPermissions'), $suppressErrors); } if (static::fileExists($item, false, $suppressErrors)) { $result = $suppressErrors ? @copy($item, $itemDest) : copy($item, $itemDest); if (!$result) { Craft::log('Could not copy file from ' . $item . ' to ' . $itemDest . '.', LogLevel::Error); } } elseif (static::folderExists($item, false, $suppressErrors)) { if (!static::createFolder($itemDest, $suppressErrors)) { Craft::log('Could not create destination folder ' . $itemDest, LogLevel::Error); } } } } if ($validate) { if (static::getFolderSize($path, $suppressErrors) !== static::getFolderSize($destination, $suppressErrors)) { return false; } } return true; } else { Craft::log('Cannot copy folder ' . $path . ' to ' . $destination . ' because the source path does not exist.', LogLevel::Error); } return false; }
/** * Returns all plugins, whether they're installed or not. * * @param bool $enabledOnly * @return array */ public function getPlugins($enabledOnly = true) { if ($enabledOnly) { return $this->_enabledPlugins; } else { if (!isset($this->_allPlugins)) { $this->_allPlugins = array(); $paths = array(); // Find all of the plugins in the plugins folder $pluginsPath = craft()->path->getPluginsPath(); $pluginFolderContents = IOHelper::getFolderContents($pluginsPath, false); if ($pluginFolderContents) { foreach ($pluginFolderContents as $pluginFolderContent) { // Make sure it's actually a folder. if (IOHelper::folderExists($pluginFolderContent)) { $paths = array_merge($paths, IOHelper::getFolderContents($pluginFolderContent, false, ".*Plugin\\.php")); } } if (is_array($paths) && count($paths) > 0) { foreach ($paths as $path) { $path = IOHelper::normalizePathSeparators($path); $fileName = IOHelper::getFileName($path, false); // Chop off the "Plugin" suffix $handle = substr($fileName, 0, strlen($fileName) - 6); $plugin = $this->getPlugin($handle, false); if ($plugin) { $this->_allPlugins[strtolower($handle)] = $plugin; $names[] = $plugin->getName(); } } } if (!empty($names)) { // Sort plugins by name array_multisort($names, $this->_allPlugins); } } } return $this->_allPlugins; } }
/** * Gets migrations that have no been applied yet AND have a later timestamp than the current Craft release. * * @param $plugin * * @return array */ public function getNewMigrations($plugin = null) { $migrations = array(); $migrationPath = $this->getMigrationPath($plugin); if (IOHelper::folderExists($migrationPath) && IOHelper::isReadable($migrationPath)) { $applied = array(); foreach ($this->getMigrationHistory($plugin) as $migration) { $applied[] = $migration['version']; } $handle = opendir($migrationPath); if ($plugin) { $pluginInfo = craft()->plugins->getPluginInfo($plugin); $storedDate = $pluginInfo['installDate']->getTimestamp(); } else { $storedDate = Craft::getReleaseDate()->getTimestamp(); } while (($file = readdir($handle)) !== false) { if ($file[0] === '.') { continue; } $path = IOHelper::normalizePathSeparators($migrationPath . $file); $class = IOHelper::getFileName($path, false); // Have we already run this migration? if (in_array($class, $applied)) { continue; } if (preg_match('/^m(\\d\\d)(\\d\\d)(\\d\\d)_(\\d\\d)(\\d\\d)(\\d\\d)_\\w+\\.php$/', $file, $matches)) { // Check the migration timestamp against the Craft release date $time = strtotime('20' . $matches[1] . '-' . $matches[2] . '-' . $matches[3] . ' ' . $matches[4] . ':' . $matches[5] . ':' . $matches[6]); if ($time > $storedDate) { $migrations[] = $class; } } } closedir($handle); sort($migrations); } return $migrations; }
/** * @param $sourceZip * @param $pathToAdd * @param $basePath * @param null $pathPrefix * * @return bool */ public static function add($sourceZip, $pathToAdd, $basePath, $pathPrefix = null) { $sourceZip = IOHelper::normalizePathSeparators($sourceZip); $pathToAdd = IOHelper::normalizePathSeparators($pathToAdd); $basePath = IOHelper::normalizePathSeparators($basePath); if (!IOHelper::fileExists($sourceZip) || !IOHelper::fileExists($pathToAdd) && !IOHelper::folderExists($pathToAdd)) { Craft::log('Tried to add ' . $pathToAdd . ' to the zip file ' . $sourceZip . ', but one of them does not exist.', LogLevel::Error); return false; } craft()->config->maxPowerCaptain(); $zip = static::_getZipInstance($sourceZip); if ($zip->add($sourceZip, $pathToAdd, $basePath, $pathPrefix)) { return true; } return false; }
/** * @param $args * @return int */ public function actionCreate($args) { $pluginHandle = false; if (isset($args[1])) { // See if this is a plugin $plugin = craft()->plugins->getPlugin($args[1]); if ($plugin) { $name = $args[0]; $pluginHandle = $args[1]; } else { $name = $args[1]; } if (!preg_match('/^\\w+$/', $name)) { echo "Error: The name of the migration must contain letters, digits and/or underscore characters only.\n"; return 1; } $fullName = 'm' . gmdate('ymd_His') . '_' . $name; $migrationNameDesc = 'mYYMMDD_HHMMSS_migrationName'; } else { $this->usageError('Please provide a name for the new migration.'); return 1; } if ($pluginHandle) { $this->_validatePlugin($pluginHandle); if (!preg_match('/^\\w+$/', $pluginHandle)) { echo "Error: The name of the plugin must contain letters, digits and/or underscore characters only.\n"; return 1; } $fullName = 'm' . gmdate('ymd_His') . '_' . strtolower($pluginHandle) . '_' . $name; $migrationNameDesc = 'mYYMMDD_HHMMSS_pluginHandle_migrationName'; // The plugin path should always be the plugin's migration directory. $path = craft()->path->getMigrationsPath($pluginHandle); } else { // The plugin path for Craft can vary. $path = rtrim(IOHelper::normalizePathSeparators($args[0]), '/') . '/'; } $content = strtr(craft()->migrations->getTemplate(), array('{ClassName}' => $fullName, '{MigrationNameDesc}' => $migrationNameDesc)); $file = $path . $fullName . '.php'; if ($this->confirm("Create new migration '{$file}'?")) { IOHelper::writeToFile($file, $content); echo "New migration created successfully.\n"; } }
/** * Searches for localized template files, and returns the first match if there is one. * * @access private * @param string $path * @return mixed */ private function _findTemplate($path) { // Get the extension on the path, if there is one $extension = IOHelper::getExtension($path); $path = rtrim(IOHelper::normalizePathSeparators($path), '/'); if ($extension) { $testPaths = array($path); } else { if (!isset($this->_defaultTemplateExtensions)) { if (craft()->request->isCpRequest()) { $this->_defaultTemplateExtensions = array('html', 'twig'); $this->_indexTemplateFilenames = array('index'); } else { $this->_defaultTemplateExtensions = craft()->config->get('defaultTemplateExtensions'); $this->_indexTemplateFilenames = craft()->config->get('indexTemplateFilenames'); } } $testPaths = array(); foreach ($this->_defaultTemplateExtensions as $extension) { $testPaths[] = $path . '.' . $extension; } foreach ($this->_indexTemplateFilenames as $filename) { foreach ($this->_defaultTemplateExtensions as $extension) { $testPaths[] = $path . '/' . $filename . '.' . $extension; } } } foreach ($testPaths as $path) { if (IOHelper::fileExists(craft()->findLocalizedFile($path))) { return $path; } } return null; }
/** * Returns all the plugins. * * @param bool $enabledOnly Whether to only return plugins that are installed and enabled. Defaults to `true`. * * @return BasePlugin[] The plugins. */ public function getPlugins($enabledOnly = true) { if ($enabledOnly) { return $this->_enabledPlugins; } else { if (!isset($this->_allPlugins)) { $this->_allPlugins = array(); // Find all of the plugins in the plugins folder $pluginsPath = craft()->path->getPluginsPath(); $pluginFolderContents = IOHelper::getFolderContents($pluginsPath, false); if ($pluginFolderContents) { foreach ($pluginFolderContents as $pluginFolderContent) { // Make sure it's actually a folder. if (IOHelper::folderExists($pluginFolderContent)) { $pluginFolderContent = IOHelper::normalizePathSeparators($pluginFolderContent); $pluginFolderName = mb_strtolower(IOHelper::getFolderName($pluginFolderContent, false)); $pluginFilePath = IOHelper::getFolderContents($pluginFolderContent, false, ".*Plugin\\.php"); if (is_array($pluginFilePath) && count($pluginFilePath) > 0) { $pluginFileName = IOHelper::getFileName($pluginFilePath[0], false); // Chop off the "Plugin" suffix $handle = mb_substr($pluginFileName, 0, mb_strlen($pluginFileName) - 6); $lcHandle = mb_strtolower($handle); // Validate that the lowercase plugin class handle is the same as the folder name // and that we haven't already loaded a plugin with the same handle but different casing if ($lcHandle === $pluginFolderName && !isset($this->_allPlugins[$lcHandle])) { $plugin = $this->getPlugin($handle, false); if ($plugin) { $this->_allPlugins[$lcHandle] = $plugin; $names[] = $plugin->getName(); } } } } } if (!empty($names)) { // Sort plugins by name array_multisort($names, $this->_allPlugins); } } } return $this->_allPlugins; } }
/** * Wrapper for Yii's decodePathInfo, plus we clean up path separators. * * @param string $pathInfo * @return string */ public function decodePathInfo($pathInfo) { $pathInfo = urldecode($pathInfo); // is it UTF-8? // http://w3.org/International/questions/qa-forms-utf-8.html if (!preg_match('%^(?: [\\x09\\x0A\\x0D\\x20-\\x7E] # ASCII | [\\xC2-\\xDF][\\x80-\\xBF] # non-overlong 2-byte | \\xE0[\\xA0-\\xBF][\\x80-\\xBF] # excluding overlongs | [\\xE1-\\xEC\\xEE\\xEF][\\x80-\\xBF]{2} # straight 3-byte | \\xED[\\x80-\\x9F][\\x80-\\xBF] # excluding surrogates | \\xF0[\\x90-\\xBF][\\x80-\\xBF]{2} # planes 1-3 | [\\xF1-\\xF3][\\x80-\\xBF]{3} # planes 4-15 | \\xF4[\\x80-\\x8F][\\x80-\\xBF]{2} # plane 16 )*$%xs', $pathInfo)) { $pathInfo = utf8_encode($pathInfo); } return IOHelper::normalizePathSeparators($pathInfo); }