From 05f5957d37c47b41fc5d72a9ce8123e0bdaeb137 Mon Sep 17 00:00:00 2001 From: Matthias Schmidt Date: Fri, 22 May 2015 21:39:02 +0200 Subject: [PATCH] Integrate PreParserAtUserListener into PreParser directly --- com.woltlab.wcf/eventListener.xml | 5 +- .../lib/system/bbcode/PreParser.class.php | 140 +++++++++++++++ .../PreParserAtUserListener.class.php | 160 ------------------ 3 files changed, 144 insertions(+), 161 deletions(-) delete mode 100644 wcfsetup/install/files/lib/system/event/listener/PreParserAtUserListener.class.php diff --git a/com.woltlab.wcf/eventListener.xml b/com.woltlab.wcf/eventListener.xml index 004609bfc5..29ef8c40ed 100644 --- a/com.woltlab.wcf/eventListener.xml +++ b/com.woltlab.wcf/eventListener.xml @@ -22,11 +22,14 @@ 1 + + + beforeParsing user - + diff --git a/wcfsetup/install/files/lib/system/bbcode/PreParser.class.php b/wcfsetup/install/files/lib/system/bbcode/PreParser.class.php index de6194ebcf..45b9e4ea09 100644 --- a/wcfsetup/install/files/lib/system/bbcode/PreParser.class.php +++ b/wcfsetup/install/files/lib/system/bbcode/PreParser.class.php @@ -3,7 +3,9 @@ namespace wcf\system\bbcode; use wcf\data\bbcode\media\provider\BBCodeMediaProvider; use wcf\data\bbcode\BBCode; use wcf\data\bbcode\BBCodeCache; +use wcf\data\user\UserList; use wcf\system\event\EventHandler; +use wcf\system\request\LinkHandler; use wcf\system\Callback; use wcf\system\Regex; use wcf\system\SingletonFactory; @@ -75,6 +77,11 @@ class PreParser extends SingletonFactory { // call event EventHandler::getInstance()->fireAction($this, 'beforeParsing'); + // parse user mentions + if ($this->allowedBBCodes === null || BBCode::isAllowedBBCode('url', $this->allowedBBCodes)) { + $this->parseUserMentions(); + } + // parse urls if ($this->allowedBBCodes === null || BBCode::isAllowedBBCode('media', $this->allowedBBCodes) || BBCode::isAllowedBBCode('url', $this->allowedBBCodes)) { $this->parseURLs(); @@ -161,6 +168,121 @@ class PreParser extends SingletonFactory { $this->text = $urlPattern->replace($this->text, $callback); } + /** + * Parses user mentions. + */ + protected function parseUserMentions() { + static $userRegex = null; + if ($userRegex === null) { + $userRegex = new Regex(" + (?:^|(?<=\s|\])) # either at start of string, or after whitespace + @ + ( + ([^',\s][^,\s]{2,})(?:\s[^,\s]+)? # either at most two strings, not containing + # whitespace or the comma, not starting with a single quote + # separated by a single whitespace character + | + '(?:''|[^']){3,}' # or a string delimited by single quotes + ) + ", Regex::IGNORE_WHITESPACE); + } + + // cache quotes + // @see \wcf\system\bbcode\BBCodeParser::buildTagArray() + $pattern = '~\[(?:/(?:quote)|(?:quote) + (?:= + (?:\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'|[^,\]]*) + (?:,(?:\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'|[^,\]]*))* + )?)\]~ix'; + preg_match_all($pattern, $this->text, $quoteMatches); + $textArray = preg_split($pattern, $this->text); + $text = $textArray[0]; + + $openQuotes = 0; + $quote = ''; + foreach ($quoteMatches[0] as $i => $quoteTag) { + if (mb_substr($quoteTag, 1, 1) == '/') { + $openQuotes--; + + $quote .= $quoteTag; + if ($openQuotes) { + $quote .= $textArray[$i + 1]; + } + else { + $text .= StringStack::pushToStringStack($quote, 'preParserUserMentions', '@@@').$textArray[$i + 1]; + $quote = ''; + } + } + else { + $openQuotes++; + $quote .= $quoteTag.$textArray[$i + 1]; + } + } + + if ($quote) { + $text .= $quote; + } + + $userRegex->match($text, true, Regex::ORDER_MATCH_BY_SET); + $matches = $userRegex->getMatches(); + + if (!empty($matches)) { + $usernames = array(); + foreach ($matches as $match) { + // we don't care about the full match + array_shift($match); + + foreach ($match as $username) { + $username = self::getUsername($username); + if (!in_array($username, $usernames)) $usernames[] = $username; + } + } + + if (!empty($usernames)) { + // fetch users + $userList = new UserList(); + $userList->getConditionBuilder()->add('user_table.username IN (?)', array($usernames)); + $userList->readObjects(); + $users = array(); + foreach ($userList as $user) { + $users[mb_strtolower($user->username)] = $user; + } + + $text = $userRegex->replace($text, new Callback(function ($matches) use ($users) { + // containing the full match + $usernames = array($matches[1]); + + // containing only the part before the first space + if (isset($matches[2])) $usernames[] = $matches[2]; + + $usernames = array_map(array(PreParser::class, 'getUsername'), $usernames); + + foreach ($usernames as $username) { + if (!isset($users[$username])) continue; + $link = LinkHandler::getInstance()->getLink('User', array( + 'appendSession' => false, + 'object' => $users[$username] + )); + + $mention = "[url='".$link."']@".$users[$username]->username.'[/url]'; + + // check if only the part before the first space matched, in that case append the second word + if (isset($matches[2]) && strcasecmp($matches[2], $username) === 0) { + $mention .= mb_substr($matches[1], strlen($matches[2])); + } + + return $mention; + } + + return $matches[0]; + })); + } + } + + // reinsert cached quotes + $this->text = StringStack::reinsertStrings($text, 'preParserUserMentions'); + } + /** * Caches code bbcodes to avoid parsing inside them. */ @@ -232,4 +354,22 @@ class PreParser extends SingletonFactory { protected function insertCachedURLBBCodes() { $this->text = StringStack::reinsertStrings($this->text, 'urlBBCodes'); } + + /** + * Returns the username for the given regular expression match. + * + * @param string $match + * @return string + */ + public static function getUsername($match) { + // remove escaped single quotation mark + $match = str_replace("''", "'", $match); + + // remove single quotation marks + if ($match{0} == "'") { + $match = mb_substr($match, 1, -1); + } + + return mb_strtolower($match); + } } diff --git a/wcfsetup/install/files/lib/system/event/listener/PreParserAtUserListener.class.php b/wcfsetup/install/files/lib/system/event/listener/PreParserAtUserListener.class.php deleted file mode 100644 index 9a68014fc9..0000000000 --- a/wcfsetup/install/files/lib/system/event/listener/PreParserAtUserListener.class.php +++ /dev/null @@ -1,160 +0,0 @@ - - * @package com.woltlab.wcf - * @subpackage system.event.listener - * @category Community Framework - */ -class PreParserAtUserListener implements IParameterizedEventListener { - /** - * @see \wcf\system\event\listener\IParameterizedEventListener::execute() - */ - public function execute($eventObj, $className, $eventName, array &$parameters) { - if (!$eventObj->text) return; - - // check if needed url BBCode is allowed - if ($eventObj->allowedBBCodes !== null && !BBCode::isAllowedBBCode('url', $eventObj->allowedBBCodes)) { - return; - } - - static $userRegex = null; - if ($userRegex === null) { - $userRegex = new Regex(" - (?:^|(?<=\s|\])) # either at start of string, or after whitespace - @ - ( - ([^',\s][^,\s]{2,})(?:\s[^,\s]+)? # either at most two strings, not containing - # whitespace or the comma, not starting with a single quote - # separated by a single whitespace character - | - '(?:''|[^']){3,}' # or a string delimited by single quotes - ) - ", Regex::IGNORE_WHITESPACE); - } - - // cache quotes - // @see \wcf\system\bbcode\BBCodeParser::buildTagArray() - $pattern = '~\[(?:/(?:quote)|(?:quote) - (?:= - (?:\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'|[^,\]]*) - (?:,(?:\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'|[^,\]]*))* - )?)\]~ix'; - preg_match_all($pattern, $eventObj->text, $quoteMatches); - $textArray = preg_split($pattern, $eventObj->text); - $text = $textArray[0]; - - $openQuotes = 0; - $quote = ''; - foreach ($quoteMatches[0] as $i => $quoteTag) { - if (mb_substr($quoteTag, 1, 1) == '/') { - $openQuotes--; - - $quote .= $quoteTag; - if ($openQuotes) { - $quote .= $textArray[$i + 1]; - } - else { - $text .= StringStack::pushToStringStack($quote, 'preParserUserMentions', '@@@').$textArray[$i + 1]; - $quote = ''; - } - } - else { - $openQuotes++; - $quote .= $quoteTag.$textArray[$i + 1]; - } - } - - if ($quote) { - $text .= $quote; - } - - $userRegex->match($text, true, Regex::ORDER_MATCH_BY_SET); - $matches = $userRegex->getMatches(); - - if (!empty($matches)) { - $usernames = array(); - foreach ($matches as $match) { - // we don't care about the full match - array_shift($match); - - foreach ($match as $username) { - $username = self::getUsername($username); - if (!in_array($username, $usernames)) $usernames[] = $username; - } - } - - if (!empty($usernames)) { - // fetch users - $userList = new UserList(); - $userList->getConditionBuilder()->add('user_table.username IN (?)', array($usernames)); - $userList->readObjects(); - $users = array(); - foreach ($userList as $user) { - $users[mb_strtolower($user->username)] = $user; - } - - $text = $userRegex->replace($text, new Callback(function ($matches) use ($users) { - // containing the full match - $usernames = array($matches[1]); - - // containing only the part before the first space - if (isset($matches[2])) $usernames[] = $matches[2]; - - $usernames = array_map(array('\wcf\system\event\listener\PreParserAtUserListener', 'getUsername'), $usernames); - - foreach ($usernames as $username) { - if (!isset($users[$username])) continue; - $link = LinkHandler::getInstance()->getLink('User', array( - 'appendSession' => false, - 'object' => $users[$username] - )); - - $mention = "[url='".$link."']@".$users[$username]->username.'[/url]'; - - // check if only the part before the first space matched, in that case append the second word - if (isset($matches[2]) && strcasecmp($matches[2], $username) === 0) { - $mention .= mb_substr($matches[1], strlen($matches[2])); - } - - return $mention; - } - - return $matches[0]; - })); - } - } - - // reinsert cached quotes - $eventObj->text = StringStack::reinsertStrings($text, 'preParserUserMentions'); - } - - /** - * Returns the username for the given regular expression match. - * - * @param string $match - * @return string - */ - public static function getUsername($match) { - // remove escaped single quotation mark - $match = str_replace("''", "'", $match); - - // remove single quotation marks - if ($match{0} == "'") { - $match = mb_substr($match, 1, -1); - } - - return mb_strtolower($match); - } -} -- 2.20.1