From ac9b0f6e74ed0eba56df3bf0385f48d9aec0c9da Mon Sep 17 00:00:00 2001 From: =?utf8?q?Tim=20D=C3=BCsterhus?= Date: Sat, 17 Dec 2011 13:39:32 +0100 Subject: [PATCH] Adding \wcf\system\Regex That way we can typehint regexes and have object-oriented access on regexes. There is no longer a need to add or escape the delimiters, this is all done by the Regex-class. The modifiers are added via a bitmask as the second parameter: Valid modifiers are: Regex::CASE_INSENSITIVE (add i) Regex::EVAL_REPLACEMENT (add e) Regex::UNGREEDY (add U) Regex::NO_ANALYSE (don't add s) Usage: ``` $regex = new Regex('^http(s)?://(www\.)woltlab\.(com|de|info)/', Regex::CASE_INSENSITIVE); var_dump($regex->match('http://www.woltlab.com/pluginstore/')); // int(1) var_dump($regex->getMatches()); // array with all matched substrings var_dump(Regex::compile('[a-z]')->replace('asdf345', '')); // string(3) "345" var_dump(Regex::compile('[a-z]')->replace('asdf345', new Callback(function ($matches) { return 'x'; })); // string(7) "xxxx345" ``` --- .../data/language/LanguageEditor.class.php | 7 +- .../data/template/TemplateEditor.class.php | 3 +- .../install/files/lib/system/Regex.class.php | 180 ++++++++++++++++++ .../files/lib/system/WCFSetup.class.php | 3 +- .../cache/source/DiskCacheSource.class.php | 5 +- .../system/template/TemplateEngine.class.php | 3 +- .../files/lib/util/DirectoryUtil.class.php | 35 ++-- 7 files changed, 212 insertions(+), 24 deletions(-) create mode 100644 wcfsetup/install/files/lib/system/Regex.class.php diff --git a/wcfsetup/install/files/lib/data/language/LanguageEditor.class.php b/wcfsetup/install/files/lib/data/language/LanguageEditor.class.php index 7b60cb67ea..75293ad9b0 100644 --- a/wcfsetup/install/files/lib/data/language/LanguageEditor.class.php +++ b/wcfsetup/install/files/lib/data/language/LanguageEditor.class.php @@ -10,6 +10,7 @@ use wcf\system\database\util\PreparedStatementConditionBuilder; use wcf\system\io\File; use wcf\system\language\LanguageFactory; use wcf\system\package\PackageDependencyHandler; +use wcf\system\Regex; use wcf\system\WCF; use wcf\util\XML; use wcf\util\DirectoryUtil; @@ -290,7 +291,7 @@ class LanguageEditor extends DatabaseObjectEditor { if ($languageID != '.*') $languageID = intval($languageID); if ($packageID != '.*') $packageID = intval($packageID); - DirectoryUtil::getInstance(WCF_DIR.'language/')->removePattern('~'.$packageID.'_'.$languageID.'_'.$category.'\.php$~'); + DirectoryUtil::getInstance(WCF_DIR.'language/')->removePattern(new Regex($packageID.'_'.$languageID.'_'.$category.'\.php$')); } /** @@ -298,9 +299,9 @@ class LanguageEditor extends DatabaseObjectEditor { */ public function deleteCompiledTemplates() { // templates - DirectoryUtil::getInstance(WCF_DIR.'templates/compiled/')->removePattern('~.*_'.$this->languageID.'_.*\.php$~'); + DirectoryUtil::getInstance(WCF_DIR.'templates/compiled/')->removePattern(new Regex('.*_'.$this->languageID.'_.*\.php$')); // acp templates - DirectoryUtil::getInstance(WCF_DIR.'acp/templates/compiled/')->removePattern('~.*_'.$this->languageID.'_.*\.php$~'); + DirectoryUtil::getInstance(WCF_DIR.'acp/templates/compiled/')->removePattern(new Regex('.*_'.$this->languageID.'_.*\.php$')); } /** diff --git a/wcfsetup/install/files/lib/data/template/TemplateEditor.class.php b/wcfsetup/install/files/lib/data/template/TemplateEditor.class.php index 1c58518f73..c9f6efd7e3 100644 --- a/wcfsetup/install/files/lib/data/template/TemplateEditor.class.php +++ b/wcfsetup/install/files/lib/data/template/TemplateEditor.class.php @@ -2,6 +2,7 @@ namespace wcf\data\template; use wcf\data\DatabaseObjectEditor; use wcf\system\io\File; +use wcf\system\Regex; use wcf\system\WCF; use wcf\util\DirectoryUtil; @@ -112,6 +113,6 @@ class TemplateEditor extends DatabaseObjectEditor { * Deletes the compiled files of this template. */ public function deleteCompiledFiles() { - DirectoryUtil::getInstance(WCF_DIR . 'templates/compiled/')->removePattern('~' . intval($this->packageID) . '_.*_' . preg_quote($this->templateName, '~') . '.php~'); + DirectoryUtil::getInstance(WCF_DIR . 'templates/compiled/')->removePattern(new Regex(intval($this->packageID) . '_.*_' . preg_quote($this->templateName) . '.php$')); } } diff --git a/wcfsetup/install/files/lib/system/Regex.class.php b/wcfsetup/install/files/lib/system/Regex.class.php new file mode 100644 index 0000000000..5f9ade7c63 --- /dev/null +++ b/wcfsetup/install/files/lib/system/Regex.class.php @@ -0,0 +1,180 @@ + + * @package com.woltlab.wcf + * @subpackage system + * @category Community Framework + */ +final class Regex { + /** + * The delimiter that is used internally. + * + * @var string + */ + const REGEX_DELIMITER = '/'; + + /** + * Do not apply any modifiers. + * + * @var integer + */ + const MODIFIER_NONE = 0; + + /** + * Case insensitive matching. + * + * @var integer + */ + const CASE_INSENSITIVE = 1; + + /** + * Ungreedy matching. + * + * @var integer + */ + const UNGREEDY = 2; + + /** + * eval() replacement of Regex::replace() + * + * @var integer + */ + const EVAL_REPLACEMENT = 4; + + /** + * Do not spend extra time on analysing. + * + * @var integer + */ + const NO_ANALYSE = 8; + + /** + * The compiled regex (:D) + * + * @var string + */ + private $regex = ''; + + /** + * The last matches + * + * @var array + */ + private $matches = array(); + + /** + * Creates a regex. + * + * @param string $regex + * @param integer $modifier + */ + public function __construct($regex, $modifier = self::MODIFIER_NONE) { + // escape delimiter + $regex = str_replace(self::REGEX_DELIMITER, '\\'.self::REGEX_DELIMITER, $regex); + + // add delimiter + $this->regex = self::REGEX_DELIMITER.$regex.self::REGEX_DELIMITER; + + // add modifiers + if ($modifier & self::CASE_INSENSITIVE) $this->regex .= 'i'; + if ($modifier & self::UNGREEDY) $this->regex .= 'U'; + if ($modifier & self::EVAL_REPLACEMENT) $this->regex .= 'e'; + if (~$modifier & self::NO_ANALYSE) $this->regex .= 's'; + } + + /** + * @see Regex::__construct() + */ + public static function compile($regex, $modifier = self::MODIFIER_NONE) { + return new self($regex, $modifier); + } + + /** + * @see Regex::match() + */ + public function __invoke($string) { + return $this->match($string); + } + + /** + * Checks whether the regex matches the given string. + * + * @param string $string String to match. + * @param boolean $all Find all matches. + * @return integer Return value of preg_match(_all) + */ + public function match($string, $all = false) { + if ($all) { + $result = preg_match_all($this->regex, $string, $this->matches); + } + else { + $result = preg_match($this->regex, $string, $this->matches); + } + + if ($result === false) { + throw new SystemException('Could not execute match on '.$this->regex); + } + return $result; + } + + /** + * Replaces part of the string with the regex. + * + * @param string $string String to work on. + * @param mixed $replacement Either replacement-string or instance of \wcf\system\Callback + * @return string + */ + public function replace($string, $replacement) { + if ($replacement instanceof Callback) { + $result = preg_replace_callback($this->regex, $replacement, $string); + } + else { + $result = preg_replace($this->regex, $replacement, $string); + } + + if ($result === false) { + throw new SystemException('Could not execute replace on '.$this->regex); + } + return $result; + } + + /** + * Splits the string with the regex. + * + * @param string $string String to split. + * @return array + */ + public function split($string) { + $result = preg_split($this->regex, $string); + + if ($result === false) { + throw new SystemException('Could not execute split on '.$this->regex); + } + return $result; + } + + /** + * Returns the matches of the last string. + * + * @return array + */ + public function getMatches() { + return $this->matches; + } + + /** + * Returns the compiled regex. + * + * @return string + */ + public function getRegex() { + return $this->regex; + } +} diff --git a/wcfsetup/install/files/lib/system/WCFSetup.class.php b/wcfsetup/install/files/lib/system/WCFSetup.class.php index 3646644399..9457492f14 100644 --- a/wcfsetup/install/files/lib/system/WCFSetup.class.php +++ b/wcfsetup/install/files/lib/system/WCFSetup.class.php @@ -13,6 +13,7 @@ use wcf\system\io\File; use wcf\system\io\Tar; use wcf\system\language\LanguageFactory; use wcf\system\package\PackageArchive; +use wcf\system\Regex; use wcf\system\session\ACPSessionFactory; use wcf\system\session\SessionHandler; use wcf\system\setup\Installer; @@ -1037,7 +1038,7 @@ class WCFSetup extends WCF { // delete tmp files $directory = TMP_DIR.'/'; - DirectoryUtil::getInstance($directory)->removePattern('~\.tar(\.gz)?$~', true); + DirectoryUtil::getInstance($directory)->removePattern(new Regex('\.tar(\.gz)?$'), true); } /** diff --git a/wcfsetup/install/files/lib/system/cache/source/DiskCacheSource.class.php b/wcfsetup/install/files/lib/system/cache/source/DiskCacheSource.class.php index 04bcc94a18..5ba9642a01 100644 --- a/wcfsetup/install/files/lib/system/cache/source/DiskCacheSource.class.php +++ b/wcfsetup/install/files/lib/system/cache/source/DiskCacheSource.class.php @@ -3,6 +3,7 @@ namespace wcf\system\cache\source; use wcf\system\exception\SystemException; use wcf\system\io\File; use wcf\system\Callback; +use wcf\system\Regex; use wcf\system\WCF; use wcf\util\FileUtil; use wcf\util\DirectoryUtil; @@ -106,7 +107,7 @@ class DiskCacheSource implements ICacheSource { if (!@touch($filename, 1)) { @unlink($filename); } - }), '%^'.$directory.$filepattern.'$%i'); + }), new Regex('^'.$directory.$filepattern.'$', Regex::CASE_INSENSITIVE)); } /** @@ -207,7 +208,7 @@ class DiskCacheSource implements ICacheSource { while ($row = $statement->fetchArray()) { $packageDir = FileUtil::getRealPath(WCF_DIR.$row['packageDir']); $cacheDir = $packageDir.'cache'; - DirectoryUtil::getInstance($cacheDir)->removePattern('~.*\.php$~'); + DirectoryUtil::getInstance($cacheDir)->removePattern(new Regex('.*\.php$')); } } } diff --git a/wcfsetup/install/files/lib/system/template/TemplateEngine.class.php b/wcfsetup/install/files/lib/system/template/TemplateEngine.class.php index b9757dbc2f..441e6b201d 100644 --- a/wcfsetup/install/files/lib/system/template/TemplateEngine.class.php +++ b/wcfsetup/install/files/lib/system/template/TemplateEngine.class.php @@ -3,6 +3,7 @@ namespace wcf\system\template; use wcf\system\cache\CacheHandler; use wcf\system\event\EventHandler; use wcf\system\exception\SystemException; +use wcf\system\Regex; use wcf\system\SingletonFactory; use wcf\util\HeaderUtil; use wcf\util\StringUtil; @@ -586,7 +587,7 @@ class TemplateEngine extends SingletonFactory { if (empty($compileDir)) $compileDir = WCF_DIR.'templates/compiled/'; // delete compiled templates - DirectoryUtil::getInstance($compileDir)->removePattern('~.*_.*_.*\.php$~'); + DirectoryUtil::getInstance($compileDir)->removePattern(new Regex('.*_.*_.*\.php$')); } /** diff --git a/wcfsetup/install/files/lib/util/DirectoryUtil.class.php b/wcfsetup/install/files/lib/util/DirectoryUtil.class.php index 7e09a2c044..8586b50c0b 100644 --- a/wcfsetup/install/files/lib/util/DirectoryUtil.class.php +++ b/wcfsetup/install/files/lib/util/DirectoryUtil.class.php @@ -1,6 +1,7 @@ */ - public function getFiles($order = SORT_ASC, $pattern = '', $negativeMatch = false) { + public function getFiles($order = SORT_ASC, Regex $pattern = null, $negativeMatch = false) { // scan the folder $this->scanFiles(); $files = $this->files; // sort out non matching files - if (!empty($pattern)) { + if ($pattern !== null) { foreach ($files as $filename => $value) { - if (((bool) preg_match($pattern, $filename)) == $negativeMatch) unset($files[$filename]); + if (((bool) $pattern->match($filename)) === $negativeMatch) unset($files[$filename]); } } @@ -160,19 +161,19 @@ class DirectoryUtil { * Returns a sorted list of files, with DirectoryIterator object as value * * @param integer $order sort order - * @param string $pattern pattern to match + * @param wcf\system\Regex $pattern pattern to match * @param boolean $negativeMatch should the pattern be inversed * @return array<\DirectoryIterator> */ - public function getFileObjects($order = SORT_ASC, $pattern = '', $negativeMatch = false) { + public function getFileObjects($order = SORT_ASC, Regex $pattern = null, $negativeMatch = false) { // scan the folder $this->scanFileObjects(); $objects = $this->fileObjects; // sort out non matching files - if (!empty($pattern)) { + if ($pattern !== null) { foreach ($objects as $filename => $value) { - if (((bool) preg_match($pattern, $filename)) == $negativeMatch) unset($objects[$filename]); + if (((bool) $pattern->match($filename)) === $negativeMatch) unset($objects[$filename]); } } @@ -256,11 +257,13 @@ class DirectoryUtil { * Executes a callback on each file and returns false if callback is invalid. * * @param wcf\system\Callback $callback - * @param string $pattern callback is only applied to files matching the given pattern + * @param wcf\system\Regex $pattern callback is only applied to files matching the given pattern * @return boolean */ - public function executeCallback(Callback $callback, $pattern = '') { - $files = $this->getFileObjects(self::SORT_NONE, $pattern); + public function executeCallback(Callback $callback, Regex $pattern = null) { + if ($pattern !== null) $files = $this->getFileObjects(self::SORT_NONE, $pattern); + else $files = $this->getFileObjects(self::SORT_NONE); + foreach ($files as $filename => $obj) { $callback($filename, $obj); } @@ -272,7 +275,7 @@ class DirectoryUtil { * Recursive remove of directory. */ public function removeAll() { - $this->removePattern(''); + $this->removePattern(new Regex('.')); // destroy cached instance unset(static::$instances[$this->recursive][$this->directory]); @@ -281,10 +284,10 @@ class DirectoryUtil { /** * Removes all files that match the given pattern. * - * @param string $pattern pattern to match + * @param wcf\system\Regex $pattern pattern to match * @param boolean $negativeMatch should the pattern be inversed */ - public function removePattern($pattern, $negativeMatch = false) { + public function removePattern(Regex $pattern, $negativeMatch = false) { if (!$this->recursive) throw new SystemException('Removing of files only works in recursive mode'); $files = $this->getFileObjects(self::SORT_NONE, $pattern, $negativeMatch); -- 2.20.1