From: Alexander Ebert Date: Wed, 24 Feb 2021 17:22:45 +0000 (+0100) Subject: Merge branch '5.3' X-Git-Tag: 5.4.0_Alpha_1~229 X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=91f5554f4048c8326895d63680c4b14aa058f8d3;p=GitHub%2FWoltLab%2FWCF.git Merge branch '5.3' --- 91f5554f4048c8326895d63680c4b14aa058f8d3 diff --cc wcfsetup/install/files/lib/system/html/output/HtmlOutputProcessor.class.php index 6d2194c617,547c38f2b0..821d8371ee --- a/wcfsetup/install/files/lib/system/html/output/HtmlOutputProcessor.class.php +++ b/wcfsetup/install/files/lib/system/html/output/HtmlOutputProcessor.class.php @@@ -16,117 -14,125 +16,131 @@@ use wcf\system\message\embedded\object\ * @package WoltLabSuite\Core\System\Html\Output * @since 3.0 */ -class HtmlOutputProcessor extends AbstractHtmlProcessor { - /** - * Generate the table of contents, implicitly enable this for certain object types on demand. - * @var bool|null - * @since 5.2 - */ - public $enableToc; - - /** - * Removes any link contained inside the message text. - * @var bool - * @since 5.2 - */ - public $removeLinks = false; - - /** - * output node processor instance - * @var HtmlOutputNodeProcessor - */ - protected $htmlOutputNodeProcessor; - - /** - * content language id - * @var integer - */ - protected $languageID; - - /** - * desired output type - * @var string - */ - protected $outputType = 'text/html'; - - /** - * enables rel=ugc for external links - * @var bool - */ - public $enableUgc = true; - - /** - * Processes the input html string. - * - * @param string $html html string - * @param string $objectType object type identifier - * @param integer $objectID object id - * @param boolean $doKeywordHighlighting enable keyword highlighting - * @param integer $languageID content language id - */ - public function process($html, $objectType, $objectID, $doKeywordHighlighting = true, $languageID = null) { - $this->languageID = $languageID; - $this->setContext($objectType, $objectID); - - MessageEmbeddedObjectManager::getInstance()->setActiveMessage($objectType, $objectID, $this->languageID); - - try { - $this->getHtmlOutputNodeProcessor()->setOutputType($this->outputType); - $this->getHtmlOutputNodeProcessor()->enableKeywordHighlighting($doKeywordHighlighting); - $this->getHtmlOutputNodeProcessor()->load($this, $html); - $this->getHtmlOutputNodeProcessor()->process(); - } - finally { - MessageEmbeddedObjectManager::getInstance()->reset(); - } - } - - /** - * Sets the desired output type. - * - * @param string $outputType desired output type - * @throws \InvalidArgumentException - */ - public function setOutputType($outputType) { - if (!in_array($outputType, ['text/html', 'text/simplified-html', 'text/plain'])) { - throw new \InvalidArgumentException("Expected 'text/html', 'text/simplified-html' or 'text/plain', but received '" . $outputType . "'"); - } - - $this->outputType = $outputType; - } - - /** - * @inheritDoc - */ - public function getHtml() { - $context = $this->getContext(); - MessageEmbeddedObjectManager::getInstance()->setActiveMessage($context['objectType'], $context['objectID'], $this->languageID); - - try { - $html = $this->getHtmlOutputNodeProcessor()->getHtml(); - } - finally { - MessageEmbeddedObjectManager::getInstance()->reset(); - } - - return $html; - } - - /** - * @inheritdoc - * @throws \InvalidArgumentException - */ - public function setContext($objectType, $objectID) { - parent::setContext($objectType, $objectID); - - $objectType = ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.message', $objectType); - if ($this->enableToc === null) { - $this->enableToc = (!empty($objectType->additionalData['enableToc'])); - } - } - - /** - * Returns the output node processor instance. - * - * @return HtmlOutputNodeProcessor output node processor instance - */ - protected function getHtmlOutputNodeProcessor() { - if ($this->htmlOutputNodeProcessor === null) { - $this->htmlOutputNodeProcessor = new HtmlOutputNodeProcessor(); - } - - return $this->htmlOutputNodeProcessor; - } +class HtmlOutputProcessor extends AbstractHtmlProcessor +{ + /** + * Generate the table of contents, implicitly enable this for certain object types on demand. + * @var bool|null + * @since 5.2 + */ + public $enableToc; + + /** + * Removes any link contained inside the message text. + * @var bool + * @since 5.2 + */ + public $removeLinks = false; + + /** + * output node processor instance + * @var HtmlOutputNodeProcessor + */ + protected $htmlOutputNodeProcessor; + + /** + * content language id + * @var int + */ + protected $languageID; + + /** + * desired output type + * @var string + */ + protected $outputType = 'text/html'; + + /** + * enables rel=ugc for external links + * @var bool + */ + public $enableUgc = true; + + /** + * Processes the input html string. + * + * @param string $html html string + * @param string $objectType object type identifier + * @param int $objectID object id + * @param bool $doKeywordHighlighting enable keyword highlighting + * @param int $languageID content language id + */ + public function process($html, $objectType, $objectID, $doKeywordHighlighting = true, $languageID = null) + { + $this->languageID = $languageID; + $this->setContext($objectType, $objectID); + - $this->getHtmlOutputNodeProcessor()->setOutputType($this->outputType); - $this->getHtmlOutputNodeProcessor()->enableKeywordHighlighting($doKeywordHighlighting); - $this->getHtmlOutputNodeProcessor()->load($this, $html); - $this->getHtmlOutputNodeProcessor()->process(); ++ MessageEmbeddedObjectManager::getInstance()->setActiveMessage($objectType, $objectID, $this->languageID); ++ ++ try { ++ $this->getHtmlOutputNodeProcessor()->setOutputType($this->outputType); ++ $this->getHtmlOutputNodeProcessor()->enableKeywordHighlighting($doKeywordHighlighting); ++ $this->getHtmlOutputNodeProcessor()->load($this, $html); ++ $this->getHtmlOutputNodeProcessor()->process(); ++ } finally { ++ MessageEmbeddedObjectManager::getInstance()->reset(); ++ } + } + + /** + * Sets the desired output type. + * + * @param string $outputType desired output type + * @throws \InvalidArgumentException + */ + public function setOutputType($outputType) + { + if (!\in_array($outputType, ['text/html', 'text/simplified-html', 'text/plain'])) { + throw new \InvalidArgumentException( + "Expected 'text/html', 'text/simplified-html' or 'text/plain', but received '" . $outputType . "'" + ); + } + + $this->outputType = $outputType; + } + + /** + * @inheritDoc + */ + public function getHtml() + { - return $this->getHtmlOutputNodeProcessor()->getHtml(); ++ $context = $this->getContext(); ++ MessageEmbeddedObjectManager::getInstance()->setActiveMessage($context['objectType'], $context['objectID'], $this->languageID); ++ ++ try { ++ $html = $this->getHtmlOutputNodeProcessor()->getHtml(); ++ } finally { ++ MessageEmbeddedObjectManager::getInstance()->reset(); ++ } ++ ++ return $html; + } + + /** + * @inheritdoc + * @throws \InvalidArgumentException + */ + public function setContext($objectType, $objectID) + { + parent::setContext($objectType, $objectID); + - MessageEmbeddedObjectManager::getInstance()->setActiveMessage($objectType, $objectID, $this->languageID); + $objectType = ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.message', $objectType); + if ($this->enableToc === null) { + $this->enableToc = (!empty($objectType->additionalData['enableToc'])); + } + } + + /** + * Returns the output node processor instance. + * + * @return HtmlOutputNodeProcessor output node processor instance + */ + protected function getHtmlOutputNodeProcessor() + { + if ($this->htmlOutputNodeProcessor === null) { + $this->htmlOutputNodeProcessor = new HtmlOutputNodeProcessor(); + } + + return $this->htmlOutputNodeProcessor; + } } diff --cc wcfsetup/install/files/lib/system/message/embedded/object/MessageEmbeddedObjectManager.class.php index 80b8e22702,d9af669760..d4e04b597e --- a/wcfsetup/install/files/lib/system/message/embedded/object/MessageEmbeddedObjectManager.class.php +++ b/wcfsetup/install/files/lib/system/message/embedded/object/MessageEmbeddedObjectManager.class.php @@@ -11,443 -9,443 +11,476 @@@ use wcf\system\WCF /** * Default interface of embedded object handler. - * - * @author Marcel Werk - * @copyright 2001-2019 WoltLab GmbH - * @license GNU Lesser General Public License - * @package WoltLabSuite\Core\System\Message\Embedded\Object + * + * @author Marcel Werk + * @copyright 2001-2019 WoltLab GmbH + * @license GNU Lesser General Public License + * @package WoltLabSuite\Core\System\Message\Embedded\Object */ -class MessageEmbeddedObjectManager extends SingletonFactory { - /** - * caches message to embedded object assignments - * @var array - */ - protected $messageEmbeddedObjects = []; - - /** - * caches embedded objects - * @var array - */ - protected $embeddedObjects = []; - - /** - * object type of the active message - * @var integer - */ - protected $activeMessageObjectTypeID; - - /** - * id of the active message - * @var integer - */ - protected $activeMessageID; - - /** - * language id of the active message - * @var integer - */ - protected $activeMessageLanguageID; - - /** - * list of embedded object handlers - * @var array - */ - protected $embeddedObjectHandlers; - - /** - * content language id - * @var integer - */ - protected $contentLanguageID; - - /** - * local cache for bulk operations - * @var mixed[][] - */ - protected $bulkData = [ - 'insert' => [], - 'remove' => [] - ]; - - /** - * A list of previous active message settings used to restore - * the internal state in case of nested message processing. - */ - protected $activeMessageHistory = []; - - /** - * Registers the embedded objects found in given message. - * - * @param HtmlInputProcessor $htmlInputProcessor html input processor instance holding embedded object data - * @param boolean $isBulk true for bulk operations - * @return boolean true if at least one embedded object was found - */ - public function registerObjects(HtmlInputProcessor $htmlInputProcessor, $isBulk = false) { - $context = $htmlInputProcessor->getContext(); - - $messageObjectType = $context['objectType']; - $messageObjectTypeID = $context['objectTypeID']; - $messageID = $context['objectID']; - - // delete existing assignments - if ($isBulk) { - if (!isset($this->bulkData['remove'][$messageObjectType])) $this->bulkData['remove'][$messageObjectType] = []; - $this->bulkData['remove'][$messageObjectType][] = $messageID; - } - else { - $this->removeObjects($messageObjectType, [$messageID]); - } - - $statement = null; - if (!$isBulk) { - // prepare statement - $sql = "INSERT INTO wcf".WCF_N."_message_embedded_object - (messageObjectTypeID, messageID, embeddedObjectTypeID, embeddedObjectID) - VALUES (?, ?, ?, ?)"; - $statement = WCF::getDB()->prepareStatement($sql); - - WCF::getDB()->beginTransaction(); - } - - $embeddedData = $htmlInputProcessor->getEmbeddedContent(); - $returnValue = false; - - /** @var IMessageEmbeddedObjectHandler $handler */ - foreach ($this->getEmbeddedObjectHandlers() as $handler) { - $objectIDs = $handler->parse($htmlInputProcessor, $embeddedData); - - if (!empty($objectIDs)) { - foreach ($objectIDs as $objectID) { - $parameters = [$messageObjectTypeID, $messageID, $handler->objectTypeID, $objectID]; - if ($isBulk) { - $this->bulkData['insert'][] = $parameters; - } - else { - $statement->execute($parameters); - } - } - - $returnValue = true; - } - } - - if (!$isBulk) { - WCF::getDB()->commitTransaction(); - } - - return $returnValue; - } - - /** - * Commits the bulk operation by performing all deletes and inserts - * in one big transaction to save performance. - */ - public function commitBulkOperation() { - // delete existing data - WCF::getDB()->beginTransaction(); - foreach ($this->bulkData['remove'] as $objectType => $objectIDs) { - $this->removeObjects($objectType, $objectIDs); - } - WCF::getDB()->commitTransaction(); - - // prepare statement - $sql = "INSERT INTO wcf".WCF_N."_message_embedded_object - (messageObjectTypeID, messageID, embeddedObjectTypeID, embeddedObjectID) - VALUES (?, ?, ?, ?)"; - $statement = WCF::getDB()->prepareStatement($sql); - - WCF::getDB()->beginTransaction(); - foreach ($this->bulkData['insert'] as $parameters) { - $statement->execute($parameters); - } - WCF::getDB()->commitTransaction(); - - // reset cache - $this->bulkData = [ - 'insert' => [], - 'remove' => [] - ]; - } - - /** - * Registers the embedded objects found in a message using the simplified syntax. - * - * @param string $messageObjectType object type identifier - * @param integer $messageID object id - * @param integer[][] $embeddedContent list of object ids for embedded objects by object type id - * @return boolean true if at least one embedded object was found - */ - public function registerSimpleObjects($messageObjectType, $messageID, array $embeddedContent) { - $messageObjectTypeID = ObjectTypeCache::getInstance()->getObjectTypeIDByName('com.woltlab.wcf.message', $messageObjectType); - - // delete existing assignments - $this->removeObjects($messageObjectType, [$messageID]); - - if (empty($embeddedContent)) { - return false; - } - - // prepare statement - $sql = "INSERT INTO wcf".WCF_N."_message_embedded_object - (messageObjectTypeID, messageID, embeddedObjectTypeID, embeddedObjectID) - VALUES (?, ?, ?, ?)"; - $statement = WCF::getDB()->prepareStatement($sql); - - // call embedded object handlers - WCF::getDB()->beginTransaction(); - foreach ($embeddedContent as $objectTypeID => $objectIDs) { - foreach ($objectIDs as $objectID) { - $statement->execute([$messageObjectTypeID, $messageID, $objectTypeID, $objectID]); - } - } - WCF::getDB()->commitTransaction(); - - return true; - } - - /** - * Removes embedded object assignments for given messages. - * - * @param string $messageObjectType - * @param integer[] $messageIDs - */ - public function removeObjects($messageObjectType, array $messageIDs) { - $conditionBuilder = new PreparedStatementConditionBuilder(); - $conditionBuilder->add('messageObjectTypeID = ?', [ObjectTypeCache::getInstance()->getObjectTypeIDByName('com.woltlab.wcf.message', $messageObjectType)]); - $conditionBuilder->add('messageID IN (?)', [$messageIDs]); - - $sql = "DELETE FROM wcf".WCF_N."_message_embedded_object - ".$conditionBuilder; - $statement = WCF::getDB()->prepareStatement($sql); - $statement->execute($conditionBuilder->getParameters()); - } - - /** - * Loads the embedded objects for given messages. - * - * @param string $messageObjectType - * @param integer[] $messageIDs - * @param integer $contentLanguageID - * @throws InvalidObjectTypeException - */ - public function loadObjects($messageObjectType, array $messageIDs, $contentLanguageID = null) { - $messageObjectTypeID = ObjectTypeCache::getInstance()->getObjectTypeIDByName('com.woltlab.wcf.message', $messageObjectType); - if ($messageObjectTypeID === null) { - throw new InvalidObjectTypeException($messageObjectType, 'com.woltlab.wcf.message'); - } - - $conditionBuilder = new PreparedStatementConditionBuilder(); - $conditionBuilder->add('messageObjectTypeID = ?', [$messageObjectTypeID]); - $conditionBuilder->add('messageID IN (?)', [$messageIDs]); - - // get object ids - $sql = "SELECT * - FROM wcf".WCF_N."_message_embedded_object - ".$conditionBuilder; - $statement = WCF::getDB()->prepareStatement($sql); - $statement->execute($conditionBuilder->getParameters()); - $embeddedObjects = []; - while ($row = $statement->fetchArray()) { - if (!isset($this->embeddedObjects[$row['embeddedObjectTypeID']][$row['embeddedObjectID']])) { - // group objects by object type - if (!isset($embeddedObjects[$row['embeddedObjectTypeID']])) $embeddedObjects[$row['embeddedObjectTypeID']] = []; - $embeddedObjects[$row['embeddedObjectTypeID']][] = $row['embeddedObjectID']; - } - - // store message to embedded object assignment - if (!isset($this->messageEmbeddedObjects[$row['messageObjectTypeID']][$row['messageID']][$row['embeddedObjectTypeID']])) { - $this->messageEmbeddedObjects[$row['messageObjectTypeID']][$row['messageID']][$row['embeddedObjectTypeID']] = []; - } - $this->messageEmbeddedObjects[$row['messageObjectTypeID']][$row['messageID']][$row['embeddedObjectTypeID']][] = $row['embeddedObjectID']; - } - - $this->contentLanguageID = $contentLanguageID; - - // load objects - foreach ($embeddedObjects as $embeddedObjectTypeID => $objectIDs) { - if (!isset($this->embeddedObjects[$embeddedObjectTypeID])) $this->embeddedObjects[$embeddedObjectTypeID] = []; - foreach ($this->getEmbeddedObjectHandler($embeddedObjectTypeID)->loadObjects(array_unique($objectIDs)) as $objectID => $object) { - $this->embeddedObjects[$embeddedObjectTypeID][$objectID] = $object; - } - } - - $this->contentLanguageID = null; - } - - /** - * Returns the content language id or null. - * - * @return integer - */ - public function getContentLanguageID() { - return $this->contentLanguageID; - } - - /** - * Sets active message information. - * - * @param string $messageObjectType - * @param integer $messageID - * @param integer $languageID - */ - public function setActiveMessage($messageObjectType, $messageID, $languageID = null) { - if ($this->activeMessageObjectTypeID) { - $this->activeMessageHistory[] = [ - 'activeMessageID' => $this->activeMessageID, - 'activeMessageLanguageID' => $this->activeMessageLanguageID, - 'activeMessageObjectTypeID' => $this->activeMessageObjectTypeID, - ]; - } - - $this->activeMessageObjectTypeID = ObjectTypeCache::getInstance()->getObjectTypeIDByName('com.woltlab.wcf.message', $messageObjectType); - $this->activeMessageID = $messageID; - $this->activeMessageLanguageID = $languageID; - } - - /** - * Restores the internal state in case of nested message processing. - */ - public function reset() { - $newState = \array_pop($this->activeMessageHistory); - if ($newState === null) { - $newState = [ - 'activeMessageID' => null, - 'activeMessageLanguageID' => null, - 'activeMessageObjectTypeID' => null, - ]; - } - - $this->activeMessageID = $newState['activeMessageID']; - $this->activeMessageLanguageID = $newState['activeMessageLanguageID']; - $this->activeMessageObjectTypeID = $newState['activeMessageObjectTypeID']; - } - - /** - * Returns the language id of the active message. - * - * @return integer - */ - public function getActiveMessageLanguageID() { - return $this->activeMessageLanguageID; - } - - /** - * Returns all embedded objects of a specific type. - * - * @param string $embeddedObjectType - * @return array - */ - public function getObjects($embeddedObjectType) { - $embeddedObjectTypeID = ObjectTypeCache::getInstance()->getObjectTypeIDByName('com.woltlab.wcf.message.embeddedObject', $embeddedObjectType); - $returnValue = []; - if (!empty($this->messageEmbeddedObjects[$this->activeMessageObjectTypeID][$this->activeMessageID][$embeddedObjectTypeID])) { - foreach ($this->messageEmbeddedObjects[$this->activeMessageObjectTypeID][$this->activeMessageID][$embeddedObjectTypeID] as $embeddedObjectID) { - if (isset($this->embeddedObjects[$embeddedObjectTypeID][$embeddedObjectID])) { - $returnValue[] = $this->embeddedObjects[$embeddedObjectTypeID][$embeddedObjectID]; - } - } - } - - return $returnValue; - } - - /** - * Returns a specific embedded object. - * - * @param string $embeddedObjectType - * @param integer $objectID - * @return \wcf\data\DatabaseObject - */ - public function getObject($embeddedObjectType, $objectID) { - $embeddedObjectTypeID = ObjectTypeCache::getInstance()->getObjectTypeIDByName('com.woltlab.wcf.message.embeddedObject', $embeddedObjectType); - if (!empty($this->messageEmbeddedObjects[$this->activeMessageObjectTypeID][$this->activeMessageID][$embeddedObjectTypeID])) { - foreach ($this->messageEmbeddedObjects[$this->activeMessageObjectTypeID][$this->activeMessageID][$embeddedObjectTypeID] as $embeddedObjectID) { - if ($embeddedObjectID == $objectID) { - if (isset($this->embeddedObjects[$embeddedObjectTypeID][$embeddedObjectID])) { - return $this->embeddedObjects[$embeddedObjectTypeID][$embeddedObjectID]; - } - } - } - } - - return null; - } - - /** - * Temporarily registers a message, the parsed data will not be stored. - * - * @param HtmlInputProcessor $htmlInputProcessor html input processor - */ - public function registerTemporaryMessage(HtmlInputProcessor $htmlInputProcessor) { - $context = $htmlInputProcessor->getContext(); - - // set active message information - $this->activeMessageObjectTypeID = $context['objectTypeID']; - $this->activeMessageID = $context['objectID']; - - $embeddedData = $htmlInputProcessor->getEmbeddedContent(); - - /** @var IMessageEmbeddedObjectHandler $handler */ - foreach ($this->getEmbeddedObjectHandlers() as $handler) { - $objectIDs = $handler->parse($htmlInputProcessor, $embeddedData); - - if (!empty($objectIDs)) { - // save assignments - $this->messageEmbeddedObjects[$this->activeMessageObjectTypeID][$this->activeMessageID][$handler->objectTypeID] = $objectIDs; - - // loads objects - $this->embeddedObjects[$handler->objectTypeID] = $handler->loadObjects($objectIDs); - } - } - } - - /** - * @return ISimpleMessageEmbeddedObjectHandler[]; - */ - public function getSimpleMessageEmbeddedObjectHandlers() { - $handlers = []; - foreach ($this->getEmbeddedObjectHandlers() as $handler) { - if ($handler instanceof ISimpleMessageEmbeddedObjectHandler) { - $name = lcfirst(preg_replace('~^.*\\\\([A-Z][a-zA-Z]+)MessageEmbeddedObjectHandler$~', '$1', get_class($handler))); - $handlers[$name] = $handler; - } - } - - return $handlers; - } - - /** - * Returns all embedded object handlers. - * - * @return IMessageEmbeddedObjectHandler[] - */ - protected function getEmbeddedObjectHandlers() { - if ($this->embeddedObjectHandlers === null) { - $this->embeddedObjectHandlers = []; - foreach (ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.message.embeddedObject') as $objectType) { - $this->embeddedObjectHandlers[$objectType->objectTypeID] = $objectType->getProcessor(); - } - } - - return $this->embeddedObjectHandlers; - } - - /** - * Returns a specific embedded object handler. - * - * @param integer $objectTypeID - * @return IMessageEmbeddedObjectHandler - */ - protected function getEmbeddedObjectHandler($objectTypeID) { - $this->getEmbeddedObjectHandlers(); - - return $this->embeddedObjectHandlers[$objectTypeID]; - } - - /** - * @deprecated 3.0 - */ - public function parseTemporaryMessage() { - throw new \BadMethodCallException("parseTemporaryMessage() has been removed, please use registerTemporaryMessage() instead."); - } +class MessageEmbeddedObjectManager extends SingletonFactory +{ + /** + * caches message to embedded object assignments + * @var array + */ + protected $messageEmbeddedObjects = []; + + /** + * caches embedded objects + * @var array + */ + protected $embeddedObjects = []; + + /** + * object type of the active message + * @var int + */ + protected $activeMessageObjectTypeID; + + /** + * id of the active message + * @var int + */ + protected $activeMessageID; + + /** + * language id of the active message + * @var int + */ + protected $activeMessageLanguageID; + + /** + * list of embedded object handlers + * @var array + */ + protected $embeddedObjectHandlers; + + /** + * content language id + * @var int + */ + protected $contentLanguageID; + + /** + * local cache for bulk operations + * @var mixed[][] + */ + protected $bulkData = [ + 'insert' => [], + 'remove' => [], + ]; + ++ /** ++ * A list of previous active message settings used to restore ++ * the internal state in case of nested message processing. ++ */ ++ protected $activeMessageHistory = []; ++ + /** + * Registers the embedded objects found in given message. + * + * @param HtmlInputProcessor $htmlInputProcessor html input processor instance holding embedded object data + * @param bool $isBulk true for bulk operations + * @return bool true if at least one embedded object was found + */ + public function registerObjects(HtmlInputProcessor $htmlInputProcessor, $isBulk = false) + { + $context = $htmlInputProcessor->getContext(); + + $messageObjectType = $context['objectType']; + $messageObjectTypeID = $context['objectTypeID']; + $messageID = $context['objectID']; + + // delete existing assignments + if ($isBulk) { + if (!isset($this->bulkData['remove'][$messageObjectType])) { + $this->bulkData['remove'][$messageObjectType] = []; + } + $this->bulkData['remove'][$messageObjectType][] = $messageID; + } else { + $this->removeObjects($messageObjectType, [$messageID]); + } + + $statement = null; + if (!$isBulk) { + // prepare statement + $sql = "INSERT INTO wcf" . WCF_N . "_message_embedded_object + (messageObjectTypeID, messageID, embeddedObjectTypeID, embeddedObjectID) + VALUES (?, ?, ?, ?)"; + $statement = WCF::getDB()->prepareStatement($sql); + + WCF::getDB()->beginTransaction(); + } + + $embeddedData = $htmlInputProcessor->getEmbeddedContent(); + $returnValue = false; + + /** @var IMessageEmbeddedObjectHandler $handler */ + foreach ($this->getEmbeddedObjectHandlers() as $handler) { + $objectIDs = $handler->parse($htmlInputProcessor, $embeddedData); + + if (!empty($objectIDs)) { + foreach ($objectIDs as $objectID) { + $parameters = [$messageObjectTypeID, $messageID, $handler->objectTypeID, $objectID]; + if ($isBulk) { + $this->bulkData['insert'][] = $parameters; + } else { + $statement->execute($parameters); + } + } + + $returnValue = true; + } + } + + if (!$isBulk) { + WCF::getDB()->commitTransaction(); + } + + return $returnValue; + } + + /** + * Commits the bulk operation by performing all deletes and inserts + * in one big transaction to save performance. + */ + public function commitBulkOperation() + { + // delete existing data + WCF::getDB()->beginTransaction(); + foreach ($this->bulkData['remove'] as $objectType => $objectIDs) { + $this->removeObjects($objectType, $objectIDs); + } + WCF::getDB()->commitTransaction(); + + // prepare statement + $sql = "INSERT INTO wcf" . WCF_N . "_message_embedded_object + (messageObjectTypeID, messageID, embeddedObjectTypeID, embeddedObjectID) + VALUES (?, ?, ?, ?)"; + $statement = WCF::getDB()->prepareStatement($sql); + + WCF::getDB()->beginTransaction(); + foreach ($this->bulkData['insert'] as $parameters) { + $statement->execute($parameters); + } + WCF::getDB()->commitTransaction(); + + // reset cache + $this->bulkData = [ + 'insert' => [], + 'remove' => [], + ]; + } + + /** + * Registers the embedded objects found in a message using the simplified syntax. + * + * @param string $messageObjectType object type identifier + * @param int $messageID object id + * @param int[][] $embeddedContent list of object ids for embedded objects by object type id + * @return bool true if at least one embedded object was found + */ + public function registerSimpleObjects($messageObjectType, $messageID, array $embeddedContent) + { + $messageObjectTypeID = ObjectTypeCache::getInstance() + ->getObjectTypeIDByName('com.woltlab.wcf.message', $messageObjectType); + + // delete existing assignments + $this->removeObjects($messageObjectType, [$messageID]); + + if (empty($embeddedContent)) { + return false; + } + + // prepare statement + $sql = "INSERT INTO wcf" . WCF_N . "_message_embedded_object + (messageObjectTypeID, messageID, embeddedObjectTypeID, embeddedObjectID) + VALUES (?, ?, ?, ?)"; + $statement = WCF::getDB()->prepareStatement($sql); + + // call embedded object handlers + WCF::getDB()->beginTransaction(); + foreach ($embeddedContent as $objectTypeID => $objectIDs) { + foreach ($objectIDs as $objectID) { + $statement->execute([$messageObjectTypeID, $messageID, $objectTypeID, $objectID]); + } + } + WCF::getDB()->commitTransaction(); + + return true; + } + + /** + * Removes embedded object assignments for given messages. + * + * @param string $messageObjectType + * @param int[] $messageIDs + */ + public function removeObjects($messageObjectType, array $messageIDs) + { + $conditionBuilder = new PreparedStatementConditionBuilder(); + $conditionBuilder->add( + 'messageObjectTypeID = ?', + [ObjectTypeCache::getInstance()->getObjectTypeIDByName('com.woltlab.wcf.message', $messageObjectType)] + ); + $conditionBuilder->add('messageID IN (?)', [$messageIDs]); + + $sql = "DELETE FROM wcf" . WCF_N . "_message_embedded_object + " . $conditionBuilder; + $statement = WCF::getDB()->prepareStatement($sql); + $statement->execute($conditionBuilder->getParameters()); + } + + /** + * Loads the embedded objects for given messages. + * + * @param string $messageObjectType + * @param int[] $messageIDs + * @param int $contentLanguageID + * @throws InvalidObjectTypeException + */ + public function loadObjects($messageObjectType, array $messageIDs, $contentLanguageID = null) + { + $messageObjectTypeID = ObjectTypeCache::getInstance() + ->getObjectTypeIDByName('com.woltlab.wcf.message', $messageObjectType); + if ($messageObjectTypeID === null) { + throw new InvalidObjectTypeException($messageObjectType, 'com.woltlab.wcf.message'); + } + + $conditionBuilder = new PreparedStatementConditionBuilder(); + $conditionBuilder->add('messageObjectTypeID = ?', [$messageObjectTypeID]); + $conditionBuilder->add('messageID IN (?)', [$messageIDs]); + + // get object ids + $sql = "SELECT * + FROM wcf" . WCF_N . "_message_embedded_object + " . $conditionBuilder; + $statement = WCF::getDB()->prepareStatement($sql); + $statement->execute($conditionBuilder->getParameters()); + $embeddedObjects = []; + while ($row = $statement->fetchArray()) { + if (!isset($this->embeddedObjects[$row['embeddedObjectTypeID']][$row['embeddedObjectID']])) { + // group objects by object type + if (!isset($embeddedObjects[$row['embeddedObjectTypeID']])) { + $embeddedObjects[$row['embeddedObjectTypeID']] = []; + } + $embeddedObjects[$row['embeddedObjectTypeID']][] = $row['embeddedObjectID']; + } + + // store message to embedded object assignment + if (!isset($this->messageEmbeddedObjects[$row['messageObjectTypeID']][$row['messageID']][$row['embeddedObjectTypeID']])) { + $this->messageEmbeddedObjects[$row['messageObjectTypeID']][$row['messageID']][$row['embeddedObjectTypeID']] = []; + } + $this->messageEmbeddedObjects[$row['messageObjectTypeID']][$row['messageID']][$row['embeddedObjectTypeID']][] = $row['embeddedObjectID']; + } + + $this->contentLanguageID = $contentLanguageID; + + // load objects + foreach ($embeddedObjects as $embeddedObjectTypeID => $objectIDs) { + if (!isset($this->embeddedObjects[$embeddedObjectTypeID])) { + $this->embeddedObjects[$embeddedObjectTypeID] = []; + } + foreach ($this->getEmbeddedObjectHandler($embeddedObjectTypeID)->loadObjects(\array_unique($objectIDs)) as $objectID => $object) { + $this->embeddedObjects[$embeddedObjectTypeID][$objectID] = $object; + } + } + + $this->contentLanguageID = null; + } + + /** + * Returns the content language id or null. + * + * @return int + */ + public function getContentLanguageID() + { + return $this->contentLanguageID; + } + + /** + * Sets active message information. + * + * @param string $messageObjectType + * @param int $messageID + * @param int $languageID + */ + public function setActiveMessage($messageObjectType, $messageID, $languageID = null) + { ++ if ($this->activeMessageObjectTypeID) { ++ $this->activeMessageHistory[] = [ ++ 'activeMessageID' => $this->activeMessageID, ++ 'activeMessageLanguageID' => $this->activeMessageLanguageID, ++ 'activeMessageObjectTypeID' => $this->activeMessageObjectTypeID, ++ ]; ++ } ++ + $this->activeMessageObjectTypeID = ObjectTypeCache::getInstance() + ->getObjectTypeIDByName('com.woltlab.wcf.message', $messageObjectType); + $this->activeMessageID = $messageID; + $this->activeMessageLanguageID = $languageID; + } + + /** ++ * Restores the internal state in case of nested message processing. ++ */ ++ public function reset() ++ { ++ $newState = \array_pop($this->activeMessageHistory); ++ if ($newState === null) { ++ $newState = [ ++ 'activeMessageID' => null, ++ 'activeMessageLanguageID' => null, ++ 'activeMessageObjectTypeID' => null, ++ ]; ++ } ++ ++ $this->activeMessageID = $newState['activeMessageID']; ++ $this->activeMessageLanguageID = $newState['activeMessageLanguageID']; ++ $this->activeMessageObjectTypeID = $newState['activeMessageObjectTypeID']; ++ } ++ ++ /** + * Returns the language id of the active message. + * + * @return int + */ + public function getActiveMessageLanguageID() + { + return $this->activeMessageLanguageID; + } + + /** + * Returns all embedded objects of a specific type. + * + * @param string $embeddedObjectType + * @return array + */ + public function getObjects($embeddedObjectType) + { + $embeddedObjectTypeID = ObjectTypeCache::getInstance() + ->getObjectTypeIDByName('com.woltlab.wcf.message.embeddedObject', $embeddedObjectType); + $returnValue = []; + if (!empty($this->messageEmbeddedObjects[$this->activeMessageObjectTypeID][$this->activeMessageID][$embeddedObjectTypeID])) { + foreach ($this->messageEmbeddedObjects[$this->activeMessageObjectTypeID][$this->activeMessageID][$embeddedObjectTypeID] as $embeddedObjectID) { + if (isset($this->embeddedObjects[$embeddedObjectTypeID][$embeddedObjectID])) { + $returnValue[] = $this->embeddedObjects[$embeddedObjectTypeID][$embeddedObjectID]; + } + } + } + + return $returnValue; + } + + /** + * Returns a specific embedded object. + * + * @param string $embeddedObjectType + * @param int $objectID + * @return \wcf\data\DatabaseObject + */ + public function getObject($embeddedObjectType, $objectID) + { + $embeddedObjectTypeID = ObjectTypeCache::getInstance() + ->getObjectTypeIDByName('com.woltlab.wcf.message.embeddedObject', $embeddedObjectType); + if (!empty($this->messageEmbeddedObjects[$this->activeMessageObjectTypeID][$this->activeMessageID][$embeddedObjectTypeID])) { + foreach ($this->messageEmbeddedObjects[$this->activeMessageObjectTypeID][$this->activeMessageID][$embeddedObjectTypeID] as $embeddedObjectID) { + if ($embeddedObjectID == $objectID) { + if (isset($this->embeddedObjects[$embeddedObjectTypeID][$embeddedObjectID])) { + return $this->embeddedObjects[$embeddedObjectTypeID][$embeddedObjectID]; + } + } + } + } + } + + /** + * Temporarily registers a message, the parsed data will not be stored. + * + * @param HtmlInputProcessor $htmlInputProcessor html input processor + */ + public function registerTemporaryMessage(HtmlInputProcessor $htmlInputProcessor) + { + $context = $htmlInputProcessor->getContext(); + + // set active message information + $this->activeMessageObjectTypeID = $context['objectTypeID']; + $this->activeMessageID = $context['objectID']; + + $embeddedData = $htmlInputProcessor->getEmbeddedContent(); + + /** @var IMessageEmbeddedObjectHandler $handler */ + foreach ($this->getEmbeddedObjectHandlers() as $handler) { + $objectIDs = $handler->parse($htmlInputProcessor, $embeddedData); + + if (!empty($objectIDs)) { + // save assignments + $this->messageEmbeddedObjects[$this->activeMessageObjectTypeID][$this->activeMessageID][$handler->objectTypeID] = $objectIDs; + + // loads objects + $this->embeddedObjects[$handler->objectTypeID] = $handler->loadObjects($objectIDs); + } + } + } + + /** + * @return ISimpleMessageEmbeddedObjectHandler[]; + */ + public function getSimpleMessageEmbeddedObjectHandlers() + { + $handlers = []; + foreach ($this->getEmbeddedObjectHandlers() as $handler) { + if ($handler instanceof ISimpleMessageEmbeddedObjectHandler) { + $name = \lcfirst(\preg_replace( + '~^.*\\\\([A-Z][a-zA-Z]+)MessageEmbeddedObjectHandler$~', + '$1', + \get_class($handler) + )); + $handlers[$name] = $handler; + } + } + + return $handlers; + } + + /** + * Returns all embedded object handlers. + * + * @return IMessageEmbeddedObjectHandler[] + */ + protected function getEmbeddedObjectHandlers() + { + if ($this->embeddedObjectHandlers === null) { + $this->embeddedObjectHandlers = []; + foreach (ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.message.embeddedObject') as $objectType) { + $this->embeddedObjectHandlers[$objectType->objectTypeID] = $objectType->getProcessor(); + } + } + + return $this->embeddedObjectHandlers; + } + + /** + * Returns a specific embedded object handler. + * + * @param int $objectTypeID + * @return IMessageEmbeddedObjectHandler + */ + protected function getEmbeddedObjectHandler($objectTypeID) + { + $this->getEmbeddedObjectHandlers(); + + return $this->embeddedObjectHandlers[$objectTypeID]; + } + + /** + * @deprecated 3.0 + */ + public function parseTemporaryMessage() + { + throw new \BadMethodCallException( + "parseTemporaryMessage() has been removed, please use registerTemporaryMessage() instead." + ); + } } diff --cc wcfsetup/install/files/lib/system/user/signature/SignatureCache.class.php index 669356a473,85b52fb7c3..6259192e39 --- a/wcfsetup/install/files/lib/system/user/signature/SignatureCache.class.php +++ b/wcfsetup/install/files/lib/system/user/signature/SignatureCache.class.php @@@ -9,66 -7,62 +9,65 @@@ use wcf\system\SingletonFactory /** * Caches parsed user signatures. - * - * @author Marcel Werk - * @copyright 2001-2019 WoltLab GmbH - * @license GNU Lesser General Public License - * @package WoltLabSuite\Core\System\User\Signature + * + * @author Marcel Werk + * @copyright 2001-2019 WoltLab GmbH + * @license GNU Lesser General Public License + * @package WoltLabSuite\Core\System\User\Signature */ -class SignatureCache extends SingletonFactory { - /** - * @var HtmlOutputProcessor - */ - protected $htmlOutputProcessor; - - /** - * cached signatures - * @var string - */ - protected $signatures = []; - - /** - * The userIDs which are cached by the message embedded object manager. - * @var integer[] - */ - protected $cachedUserIDs = []; - - /** - * Returns a parsed user signature. - * - * @param User $user user object - * @return string parsed signature - */ - public function getSignature(User $user) { - if (!isset($this->signatures[$user->userID])) { - if ($this->htmlOutputProcessor === null) { - $this->htmlOutputProcessor = new HtmlOutputProcessor(); - } - - if (!in_array($user->userID, $this->cachedUserIDs)) { - $this->cacheUserSignature([$user->userID]); - } - - $this->htmlOutputProcessor->process($user->signature, 'com.woltlab.wcf.user.signature', $user->userID); - $this->signatures[$user->userID] = $this->htmlOutputProcessor->getHtml(); - } - - return $this->signatures[$user->userID]; - } - - /** - * Loads the embedded objects for the given users. - * - * @param integer[] $userIDs - * @since 5.2 - */ - public function cacheUserSignature(array $userIDs) { - $this->cachedUserIDs = array_merge($this->cachedUserIDs, $userIDs); - - MessageEmbeddedObjectManager::getInstance()->loadObjects('com.woltlab.wcf.user.signature', $userIDs); - } +class SignatureCache extends SingletonFactory +{ + /** + * @var HtmlOutputProcessor + */ + protected $htmlOutputProcessor; + + /** + * cached signatures + * @var string + */ + protected $signatures = []; + + /** + * The userIDs which are cached by the message embedded object manager. + * @var int[] + */ + protected $cachedUserIDs = []; + + /** + * Returns a parsed user signature. + * + * @param User $user user object + * @return string parsed signature + */ + public function getSignature(User $user) + { + if (!isset($this->signatures[$user->userID])) { + if ($this->htmlOutputProcessor === null) { + $this->htmlOutputProcessor = new HtmlOutputProcessor(); + } + + if (!\in_array($user->userID, $this->cachedUserIDs)) { + $this->cacheUserSignature([$user->userID]); + } + - $this->htmlOutputProcessor->setContext('com.woltlab.wcf.user.signature', $user->userID); + $this->htmlOutputProcessor->process($user->signature, 'com.woltlab.wcf.user.signature', $user->userID); + $this->signatures[$user->userID] = $this->htmlOutputProcessor->getHtml(); + } + + return $this->signatures[$user->userID]; + } + + /** + * Loads the embedded objects for the given users. + * + * @param int[] $userIDs + * @since 5.2 + */ + public function cacheUserSignature(array $userIDs) + { + $this->cachedUserIDs = \array_merge($this->cachedUserIDs, $userIDs); + + MessageEmbeddedObjectManager::getInstance()->loadObjects('com.woltlab.wcf.user.signature', $userIDs); + } }