Added message embedded object system
authorMarcel Werk <burntime@woltlab.com>
Tue, 1 Jul 2014 17:55:16 +0000 (19:55 +0200)
committerMarcel Werk <burntime@woltlab.com>
Tue, 1 Jul 2014 17:55:16 +0000 (19:55 +0200)
16 files changed:
com.woltlab.wcf/objectType.xml
com.woltlab.wcf/objectTypeDefinition.xml
com.woltlab.wcf/templates/quoteBBCodeTag.tpl
wcfsetup/install/files/lib/data/attachment/GroupedAttachmentList.class.php
wcfsetup/install/files/lib/data/bbcode/MessagePreviewAction.class.php
wcfsetup/install/files/lib/system/attachment/AbstractAttachmentObjectType.class.php
wcfsetup/install/files/lib/system/attachment/IAttachmentObjectType.class.php
wcfsetup/install/files/lib/system/bbcode/AttachmentBBCode.class.php
wcfsetup/install/files/lib/system/bbcode/QuoteBBCode.class.php
wcfsetup/install/files/lib/system/message/embedded/object/AbstractMessageEmbeddedObjectHandler.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/message/embedded/object/AttachmentMessageEmbeddedObjectHandler.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/message/embedded/object/IMessageEmbeddedObjectHandler.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/message/embedded/object/MessageEmbeddedObjectManager.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/message/embedded/object/QuoteMessageEmbeddedObjectHandler.class.php [new file with mode: 0644]
wcfsetup/install/files/style/bbcode.less
wcfsetup/setup/db/install.sql

index 5609043636a0a74a6f3ffbc01a4416d182f1857a..ef5f327891bb45cff8067af9723cebe45743348f 100644 (file)
                        <classname><![CDATA[wcf\system\captcha\CaptchaQuestionHandler]]></classname>
                </type>
                <!-- captcha types -->
+               
+               <!-- embedded object handlers -->
+               <type>
+                       <name>com.woltlab.wcf.quote</name>
+                       <definitionname>com.woltlab.wcf.message.embeddedObject</definitionname>
+                       <classname><![CDATA[wcf\system\message\embedded\object\QuoteMessageEmbeddedObjectHandler]]></classname>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.attachment</name>
+                       <definitionname>com.woltlab.wcf.message.embeddedObject</definitionname>
+                       <classname><![CDATA[wcf\system\message\embedded\object\AttachmentMessageEmbeddedObjectHandler]]></classname>
+               </type>
+               <!-- embedded object handlers -->
        </import>
 </data>
index daebce74ab36b3e686424dbfea9de5b013f406cb..f1614aa7b303fecb1b78e83f84e957ed33a7ecef 100644 (file)
                        <name>com.woltlab.wcf.captcha</name>
                        <interfacename><![CDATA[wcf\system\captcha\ICaptchaHandler]]></interfacename>
                </definition>
+               
+               <definition>
+                       <name>com.woltlab.wcf.message.embeddedObject</name>
+                       <interfacename><![CDATA[wcf\system\message\embedded\object\IMessageEmbeddedObjectHandler]]></interfacename>
+               </definition>
+               
+               <definition>
+                       <name>com.woltlab.wcf.message</name>
+               </definition>
        </import>
 </data>
index d1001b360412ffc536d51ae466327373c9f72275..b655fccf428db42eb1d3fa8d28dea858efe3eae2 100644 (file)
@@ -1,17 +1,23 @@
-<blockquote class="container containerPadding quoteBox"{if $quoteLink} cite="{$quoteLink}"{/if}>
-       {if $quoteAuthor}
-               <header>
-                       <h3>
-                               {if $quoteLink}
-                                       <a href="{@$quoteLink}"{if $isExternalQuoteLink} class="externalURL"{if EXTERNAL_LINK_REL_NOFOLLOW} rel="nofollow"{/if}{if EXTERNAL_LINK_TARGET_BLANK} target="_blank"{/if}{/if}>{lang}wcf.bbcode.quote.title{/lang}</a>
-                               {else}
-                                       {lang}wcf.bbcode.quote.title{/lang}
-                               {/if}
-                       </h3>
-               </header>
+<blockquote class="quoteBox"{if $quoteLink} cite="{$quoteLink}"{/if}>
+       {if $quoteAuthorObject}
+               <div class="quoteAuthorAvatar"><a href="{link controller='User' object=$quoteAuthorObject}{/link}" class="userLink framed" data-user-id="{@$quoteAuthorObject->userID}">{@$quoteAuthorObject->getAvatar()->getImageTag(64)}</a></div>
        {/if}
        
-       <div>
-               {@$content}
+       <div class="container containerPadding">
+               {if $quoteAuthor}
+                       <header>
+                               <h3>
+                                       {if $quoteLink}
+                                               <a href="{@$quoteLink}"{if $isExternalQuoteLink} class="externalURL"{if EXTERNAL_LINK_REL_NOFOLLOW} rel="nofollow"{/if}{if EXTERNAL_LINK_TARGET_BLANK} target="_blank"{/if}{/if}>{lang}wcf.bbcode.quote.title{/lang}</a>
+                                       {else}
+                                               {lang}wcf.bbcode.quote.title{/lang}
+                                       {/if}
+                               </h3>
+                       </header>
+               {/if}
+               
+               <div>
+                       {@$content}
+               </div>
        </div>
 </blockquote>
\ No newline at end of file
index 386faf61477c7ba8661fd6a383bb3722d0286bcb..a53237d21e0ff39a1cdf5c799cfb83b5837bad19 100644 (file)
@@ -45,6 +45,11 @@ class GroupedAttachmentList extends AttachmentList {
                
                $this->objectType = ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.attachment.objectType', $objectType);
                $this->getConditionBuilder()->add('attachment.objectTypeID = ?', array($this->objectType->objectTypeID));
+               
+               $this->getConditionBuilder()->add('(SELECT embeddedObjectID FROM wcf'.WCF_N.'_message_embedded_object WHERE messageObjectTypeID = ? AND messageID = attachment.objectID AND embeddedObjectTypeID = ? AND embeddedObjectID = attachment.attachmentID) IS NULL', array(
+                       ObjectTypeCache::getInstance()->getObjectTypeIDByName('com.woltlab.wcf.message', $objectType),
+                       ObjectTypeCache::getInstance()->getObjectTypeIDByName('com.woltlab.wcf.message.embeddedObject', 'com.woltlab.wcf.attachment')
+               ));
        }
        
        /**
index 3170dd997dbf5caf03ff7facfc6a08efee767b2c..dd7e6ce5f032d9973c3e10c650bda74d0ef9b151 100644 (file)
@@ -1,11 +1,9 @@
 <?php
 namespace wcf\data\bbcode;
-use wcf\data\attachment\GroupedAttachmentList;
-use wcf\data\object\type\ObjectTypeCache;
-use wcf\system\bbcode\AttachmentBBCode;
 use wcf\system\bbcode\MessageParser;
 use wcf\system\bbcode\PreParser;
 use wcf\system\exception\UserInputException;
+use wcf\system\message\embedded\object\MessageEmbeddedObjectManager;
 use wcf\system\WCF;
 use wcf\util\ArrayUtil;
 use wcf\util\StringUtil;
@@ -68,42 +66,12 @@ class MessagePreviewAction extends BBCodeAction {
                        }
                }
                
-               // get attachments
-               if (!empty($this->parameters['attachmentObjectType'])) {
-                       $attachmentList = new GroupedAttachmentList($this->parameters['attachmentObjectType']);
-                       if (!empty($this->parameters['attachmentObjectID'])) {
-                               $attachmentList->getConditionBuilder()->add('attachment.objectID = ?', array($this->parameters['attachmentObjectID']));
-                               AttachmentBBCode::setObjectID($this->parameters['attachmentObjectID']);
-                               
-                               $objectType = ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.attachment.objectType', $this->parameters['attachmentObjectType']);
-                               $processor = $objectType->getProcessor();
-                               if (!$processor->canDownload($this->parameters['attachmentObjectID']) && !$processor->canViewPreview($this->parameters['attachmentObjectID'])) {
-                                       if (WCF::getUser()->userID) {
-                                               $attachmentList->getConditionBuilder()->add('attachment.userID = ?', array(WCF::getUser()->userID));
-                                       }
-                                       else {
-                                               $attachmentList->getConditionBuilder()->add('attachment.userID IS NULL');
-                                       }
-                               }
-                       }
-                       else {
-                               $attachmentList->getConditionBuilder()->add('attachment.tmpHash = ?', array($this->parameters['tmpHash']));
-                               
-                               if (WCF::getUser()->userID) {
-                                       $attachmentList->getConditionBuilder()->add('attachment.userID = ?', array(WCF::getUser()->userID));
-                               }
-                               else {
-                                       $attachmentList->getConditionBuilder()->add('attachment.userID IS NULL');
-                               }
-                       }
-                       
-                       $attachmentList->readObjects();
-                       AttachmentBBCode::setAttachmentList($attachmentList);
-               }
-               
                // get message
                $message = StringUtil::trim($this->parameters['data']['message']);
                
+               // get embedded objects
+               MessageEmbeddedObjectManager::getInstance()->parseTemporaryMessage($message);
+               
                // parse URLs
                if ($preParse && $enableBBCodes) {
                        if ($allowedBBCodesPermission) {
index 6b60e8e0ead7ef0e3d01492e9ef63231bb39a7aa..d99221c63277246027896e7d3c7525f8cb67c643 100644 (file)
@@ -15,6 +15,12 @@ use wcf\util\ArrayUtil;
  * @category   Community Framework
  */
 abstract class AbstractAttachmentObjectType implements IAttachmentObjectType {
+       /**
+        * cached objects
+        * @var array<\wcf\data\DatabaseObject>
+        */
+       protected $cachedObjects = array();
+       
        /**
         * @see \wcf\system\attachment\IAttachmentObjectType::getMaxSize()
         */
@@ -47,11 +53,34 @@ abstract class AbstractAttachmentObjectType implements IAttachmentObjectType {
         * @see \wcf\system\attachment\IAttachmentObjectType::getObject()
         */
        public function getObject($objectID) {
+               if (isset($this->cachedObjects[$objectID])) return $this->cachedObjects[$objectID];
+               
                return null;
        }
        
+       /**
+        * @see \wcf\system\attachment\IAttachmentObjectType::setCachedObjects()
+        */
+       public function setCachedObjects(array $objects) {
+               foreach ($objects as $id => $object) {
+                       $this->cachedObjects[$id] = $object;
+               }
+       }
+       
        /**
         * @see \wcf\system\attachment\IAttachmentObjectType::getObject()
         */
        public function cacheObjects(array $objectIDs) {}
+       
+       /**
+        * @see \wcf\system\attachment\IAttachmentObjectType::setPermissions()
+        */
+       public function setPermissions(array $attachments) {
+               foreach ($attachments as $attachment) {
+                       $attachment->setPermissions(array(
+                               'canDownload' => $this->canDownload($attachment->objectID),
+                               'canViewPreview' => $this->canViewPreview($attachment->objectID)
+                       ));
+               }
+       }
 }
index 3e61ae7863126d1659b2cd7da428da0cebc84356..f257dfda16fd17a5f5d0d558064fbae332f152f9 100644 (file)
@@ -81,4 +81,11 @@ interface IAttachmentObjectType {
         * @param       array<integer>          $objectIDs
         */
        public function cacheObjects(array $objectIDs);
+       
+       /**
+        * Loads the permissions for given attachments.
+        *  
+        * @param       array<\wcf\data\attachment\Attachment>          $attachments
+        */
+       public function setPermissions(array $attachments);
 }
index dd082d799f5231ab51e82ba48c9f1fbcd25d541b..399fa51dedd8505add4adb94e44027f5a518739f 100644 (file)
@@ -3,6 +3,7 @@ namespace wcf\system\bbcode;
 use wcf\data\attachment\GroupedAttachmentList;
 use wcf\system\request\LinkHandler;
 use wcf\util\StringUtil;
+use wcf\system\message\embedded\object\MessageEmbeddedObjectManager;
 
 /**
  * Parses the [attach] bbcode tag.
@@ -18,12 +19,14 @@ class AttachmentBBCode extends AbstractBBCode {
        /**
         * list of attachments
         * @var \wcf\data\attachment\GroupedAttachmentList
+        * @deprecated
         */
        protected static $attachmentList = null;
        
        /**
         * active object id
         * @var integer
+        * @deprecated
         */
        protected static $objectID = 0;
        
@@ -37,18 +40,21 @@ class AttachmentBBCode extends AbstractBBCode {
                        $attachmentID = $openingTag['attributes'][0];
                }
                
-               // get attachment for active object
-               $attachments = array();
-               if (self::$attachmentList !== null) {
-                       $attachments = self::$attachmentList->getGroupedObjects(self::$objectID);
+               // get embedded object
+               $attachment = MessageEmbeddedObjectManager::getInstance()->getObject('com.woltlab.wcf.attachment', $attachmentID);
+               if ($attachment === null) {
+                       if (self::$attachmentList !== null) {
+                               $attachments = self::$attachmentList->getGroupedObjects(self::$objectID);
+                               if (isset($attachments[$attachmentID])) {
+                                       $attachment = $attachments[$attachmentID];
+                                       
+                                       // mark attachment as embedded
+                                       $attachment->markAsEmbedded();
+                               }
+                       }
                }
                
-               if (isset($attachments[$attachmentID])) {
-                       $attachment = $attachments[$attachmentID];
-                       
-                       // mark attachment as embedded
-                       $attachment->markAsEmbedded();
-                       
+               if ($attachment !== null) {
                        if ($attachment->showAsImage() && $parser->getOutputType() == 'text/html') {
                                // image
                                $linkParameters = array(
@@ -84,6 +90,7 @@ class AttachmentBBCode extends AbstractBBCode {
         * Sets the attachment list.
         * 
         * @param       \wcf\data\attachment\GroupedAttachmentList      $attachments
+        * @deprecated
         */
        public static function setAttachmentList(GroupedAttachmentList $attachmentList) {
                self::$attachmentList = $attachmentList;
@@ -93,6 +100,7 @@ class AttachmentBBCode extends AbstractBBCode {
         * Sets the active object id.
         * 
         * @param       integer         $objectID
+        * @deprecated
         */
        public static function setObjectID($objectID) {
                self::$objectID = $objectID;
index d38acb9b5af5284f870ea06c64f79de1b5142406..6b6b6c161eea07449452ed921ed7dc0abbbe1f0a 100644 (file)
@@ -3,6 +3,7 @@ namespace wcf\system\bbcode;
 use wcf\system\application\ApplicationHandler;
 use wcf\system\request\RouteHandler;
 use wcf\system\WCF;
+use wcf\system\message\embedded\object\MessageEmbeddedObjectManager;
 
 /**
  * Parses the [quote] bbcode tag.
@@ -25,11 +26,23 @@ class QuoteBBCode extends AbstractBBCode {
                        if (!$externalQuoteLink) {
                                $quoteLink = preg_replace('~^https?://~', RouteHandler::getProtocol(), $quoteLink);
                        }
+                       $quoteAuthor = (!empty($openingTag['attributes'][0]) ? $openingTag['attributes'][0] : '');
+                       $quoteAuthorObject = null;
+                       if ($quoteAuthor && !$externalQuoteLink) {
+                               $quoteAuthorLC = mb_strtolower($quoteAuthor);
+                               foreach (MessageEmbeddedObjectManager::getInstance()->getObjects('com.woltlab.wcf.quote') as $user) {
+                                       if (mb_strtolower($user->username) == $quoteAuthorLC) {
+                                               $quoteAuthorObject = $user;
+                                               break;
+                                       }
+                               }
+                       }
                        
                        WCF::getTPL()->assign(array(
                                'content' => $content,
                                'quoteLink' => $quoteLink,
-                               'quoteAuthor' => (!empty($openingTag['attributes'][0]) ? $openingTag['attributes'][0] : ''),
+                               'quoteAuthor' => $quoteAuthor,
+                               'quoteAuthorObject' => $quoteAuthorObject,
                                'isExternalQuoteLink' => $externalQuoteLink
                        ));
                        return WCF::getTPL()->fetch('quoteBBCodeTag');
diff --git a/wcfsetup/install/files/lib/system/message/embedded/object/AbstractMessageEmbeddedObjectHandler.class.php b/wcfsetup/install/files/lib/system/message/embedded/object/AbstractMessageEmbeddedObjectHandler.class.php
new file mode 100644 (file)
index 0000000..9b63f06
--- /dev/null
@@ -0,0 +1,63 @@
+<?php
+namespace wcf\system\message\embedded\object;
+use wcf\data\DatabaseObjectDecorator;
+use wcf\util\ArrayUtil;
+
+/**
+ * Provides default implementations for message embedded object handlers.
+ *
+ * @author     Marcel Werk
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.message.embedded.object
+ * @category   Community Framework
+ */
+abstract class AbstractMessageEmbeddedObjectHandler extends DatabaseObjectDecorator implements IMessageEmbeddedObjectHandler {
+       /**
+        * @see \wcf\data\DatabaseObjectDecorator::$baseClass
+        */
+       protected static $baseClass = 'wcf\data\object\type\ObjectType';
+       
+       /**
+        * Parses given message for specific bbcode parameters.
+        * 
+        * @param       string          $message
+        * @param       string          $bbcode         bbcode name
+        * @return      array
+        */
+       public static function getTextParameters($message, $bbcode) {
+               if (preg_match_all('~\['.$bbcode.'\](.*?)\[/'.$bbcode.'\]~i', $message, $matches)) {
+                       $results = ArrayUtil::trim($matches[1]);
+                       $results = array_unique($results);
+                               
+                       return $results;
+               }
+               
+               return array();
+       }
+       
+       /**
+        * Parses given message for specific bbcode parameters.
+        *
+        * @param       string          $message
+        * @param       string          $bbcode         bbcode name
+        * @return      array
+        */
+       public static function getFirstParameters($message, $bbcode) {
+               $pattern = '~\['.$bbcode.'=
+                               (?:\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'|([^,\]]*))
+                               (?:,(?:\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'|[^,\]]*))*
+                       \]~ix';
+               
+               if (preg_match_all($pattern, $message, $matches)) {
+                       $results = ArrayUtil::trim($matches[1]);
+                       $results = array_merge($results, ArrayUtil::trim($matches[2]));
+                       $results = array_unique($results);
+                       
+                       return $results;
+               }
+               
+               return array();
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/message/embedded/object/AttachmentMessageEmbeddedObjectHandler.class.php b/wcfsetup/install/files/lib/system/message/embedded/object/AttachmentMessageEmbeddedObjectHandler.class.php
new file mode 100644 (file)
index 0000000..d23abf9
--- /dev/null
@@ -0,0 +1,63 @@
+<?php
+namespace wcf\system\message\embedded\object;
+use wcf\data\attachment\AttachmentList;
+use wcf\util\ArrayUtil;
+use wcf\data\object\type\ObjectTypeCache;
+
+/**
+ * IMessageEmbeddedObjectHandler implementation for attachments.
+ *
+ * @author     Marcel Werk
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.message.embedded.object
+ * @category   Community Framework
+ */
+class AttachmentMessageEmbeddedObjectHandler extends AbstractMessageEmbeddedObjectHandler {
+       /**
+        * @see \wcf\system\message\embedded\object\IMessageEmbeddedObjectHandler::parseMessage()
+        */
+       public function parseMessage($message) {
+               $parsedAttachmentIDs = array_unique(ArrayUtil::toIntegerArray(array_merge(self::getFirstParameters($message, 'attach'), self::getTextParameters($message, 'attach'))));
+               if (!empty($parsedAttachmentIDs)) {
+                       $attachmentIDs = array();
+                       foreach ($parsedAttachmentIDs as $parsedAttachmentID) {
+                               if ($parsedAttachmentID) $attachmentIDs[] = $parsedAttachmentID;
+                       }
+                       
+                       $attachmentList = new AttachmentList();
+                       $attachmentList->getConditionBuilder()->add("attachment.attachmentID IN (?)", array($attachmentIDs));
+                       $attachmentList->readObjectIDs();
+                       return $attachmentList->getObjectIDs();
+               }
+               
+               return false;
+       }
+       
+       /**
+        * @see \wcf\system\message\embedded\object\IMessageEmbeddedObjectHandler::loadObjects()
+        */
+       public function loadObjects(array $objectIDs) {
+               $attachmentList = new AttachmentList();
+               $attachmentList->setObjectIDs($objectIDs);
+               $attachmentList->readObjects();
+               
+               // group attachments by object type
+               $groupedAttachments = array();
+               foreach ($attachmentList->getObjects() as $attachment) {
+                       if (!isset($groupedAttachments[$attachment->objectTypeID])) $groupedAttachments[$attachment->objectTypeID] = array();
+                       $groupedAttachments[$attachment->objectTypeID][] = $attachment;
+               }
+               
+               // check permissions
+               foreach ($groupedAttachments as $objectTypeID => $attachments) {
+                       $processor = ObjectTypeCache::getInstance()->getObjectType($objectTypeID)->getProcessor();
+                       if ($processor !== null) {
+                               $processor->setPermissions($attachments);
+                       }
+               }
+               
+               return $attachmentList->getObjects();
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/message/embedded/object/IMessageEmbeddedObjectHandler.class.php b/wcfsetup/install/files/lib/system/message/embedded/object/IMessageEmbeddedObjectHandler.class.php
new file mode 100644 (file)
index 0000000..607c90b
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+namespace wcf\system\message\embedded\object;
+
+/**
+ * Default interface of embedded object handler.
+ *
+ * @author     Marcel Werk
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.message.embedded.object
+ * @category   Community Framework
+ */
+interface IMessageEmbeddedObjectHandler {
+       /**
+        * Parses the given message to extract embedded objects.
+        * Returns the IDs of found embedded objects.
+        * 
+        * @param       string          $message
+        * @return      array<integer>
+        */
+       public function parseMessage($message);
+       
+       /**
+        * Loads and returns embedded objects.
+        * 
+        * @param       array           $objectIDs
+        * @return      array<\wcf\data\DatabaseObject>
+        */
+       public function loadObjects(array $objectIDs);
+}
diff --git a/wcfsetup/install/files/lib/system/message/embedded/object/MessageEmbeddedObjectManager.class.php b/wcfsetup/install/files/lib/system/message/embedded/object/MessageEmbeddedObjectManager.class.php
new file mode 100644 (file)
index 0000000..ad81560
--- /dev/null
@@ -0,0 +1,265 @@
+<?php
+namespace wcf\system\message\embedded\object;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\SingletonFactory;
+use wcf\system\WCF;
+
+/**
+ * Default interface of embedded object handler.
+ *
+ * @author     Marcel Werk
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.message.embedded.object
+ * @category   Community Framework
+ */
+class MessageEmbeddedObjectManager extends SingletonFactory {
+       /**
+        * caches message to embedded object assignments
+        * @var array
+        */
+       protected $messageEmbeddedObjects = array();
+       
+       /**
+        * caches embedded objects
+        * @var array
+        */
+       protected $embeddedObjects = array();
+       
+       /**
+        * object type of the active message
+        * @var integer
+        */
+       protected $activeMessageObjectTypeID = null;
+       
+       /**
+        * id of the active message
+        * @var integer
+        */
+       protected $activeMessageID = null;
+       
+       /**
+        * list of embedded object handlers
+        * @var array
+        */
+       protected $embeddedObjectHandlers = null;
+       
+       /**
+        * Registers the embedded objects found in given message.
+        * 
+        * @param       string          $messageObjectType
+        * @param       integer         $messageID
+        * @param       string          $message
+        * @return      boolean
+        */
+       public function registerObjects($messageObjectType, $messageID, $message) {
+               // remove [code] tags
+               $message = self::removeCodeTags($message);
+               
+               // delete existing assignments
+               $this->removeObjects($messageObjectType, array($messageID));
+               
+               // get object type id
+               $messageObjectTypeID = ObjectTypeCache::getInstance()->getObjectTypeIDByName('com.woltlab.wcf.message', $messageObjectType);
+               
+               // 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();
+               $returnValue = false;
+               foreach ($this->getEmbeddedObjectHandlers() as $handler) {
+                       $objectIDs = $handler->parseMessage($message);
+                       if (!empty($objectIDs)) {
+                               $returnValue = true;
+                               foreach ($objectIDs as $objectID) {
+                                       $statement->execute(array($messageObjectTypeID, $messageID, $handler->objectTypeID, $objectID));
+                               }
+                       }
+               }
+               WCF::getDB()->commitTransaction();
+               
+               return $returnValue;
+       }
+       
+       /**
+        * Removes embedded object assigments for given messages.
+        * 
+        * @param       string                  $messageObjectType
+        * @param       array<integer>          $messageIDs
+        */
+       public function removeObjects($messageObjectType, array $messageIDs) {
+               $conditionBuilder = new PreparedStatementConditionBuilder();
+               $conditionBuilder->add('messageObjectTypeID = ?', array(ObjectTypeCache::getInstance()->getObjectTypeIDByName('com.woltlab.wcf.message', $messageObjectType)));
+               $conditionBuilder->add('messageID IN (?)', array($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       array<integer>          $messageIDs
+        */
+       public function loadObjects($messageObjectType, array $messageIDs) {
+               $conditionBuilder = new PreparedStatementConditionBuilder();
+               $conditionBuilder->add('messageObjectTypeID = ?', array(ObjectTypeCache::getInstance()->getObjectTypeIDByName('com.woltlab.wcf.message', $messageObjectType)));
+               $conditionBuilder->add('messageID IN (?)', array($messageIDs));
+               
+               $sql = "SELECT  *
+                       FROM    wcf".WCF_N."_message_embedded_object
+                       ".$conditionBuilder;
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute($conditionBuilder->getParameters());
+               $embeddedObjects = array();
+               while ($row = $statement->fetchArray()) {
+                       if (!isset($embeddedObjects[$row['embeddedObjectTypeID']])) $embeddedObjects[$row['embeddedObjectTypeID']] = array();
+                       $embeddedObjects[$row['embeddedObjectTypeID']][] = $row['embeddedObjectID'];
+                       
+                       if (!isset($this->messageEmbeddedObjects[$row['messageObjectTypeID']][$row['messageID']][$row['embeddedObjectTypeID']])) {
+                               $this->messageEmbeddedObjects[$row['messageObjectTypeID']][$row['messageID']][$row['embeddedObjectTypeID']] = array();
+                       }
+                       $this->messageEmbeddedObjects[$row['messageObjectTypeID']][$row['messageID']][$row['embeddedObjectTypeID']][] = $row['embeddedObjectID'];
+               }
+               
+               foreach ($embeddedObjects as $embeddedObjectTypeID => $objectIDs) {
+                       if (!isset($this->embeddedObjects[$embeddedObjectTypeID])) $this->embeddedObjects[$embeddedObjectTypeID] = array();
+                       foreach ($this->getEmbeddedObjectHandler($embeddedObjectTypeID)->loadObjects(array_unique($objectIDs)) as $objectID => $object) {
+                               $this->embeddedObjects[$embeddedObjectTypeID][$objectID] = $object;
+                       }
+               }
+       }
+       
+       /**
+        * Sets active message information.
+        * 
+        * @param       string          $messageObjectType
+        * @param       integer         $messageID
+        */
+       public function setActiveMessage($messageObjectType, $messageID) {
+               $this->activeMessageObjectTypeID = ObjectTypeCache::getInstance()->getObjectTypeIDByName('com.woltlab.wcf.message', $messageObjectType);
+               $this->activeMessageID = $messageID;
+       }
+       
+       /**
+        * Gets 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 = array();
+               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);
+               $returnValue = array();
+               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;
+       }
+       
+       /**
+        * Parses a temporary message and loads found embedded objects.
+        * 
+        * @param       string          $message
+        */
+       public function parseTemporaryMessage($message) {
+               // remove [code] tags
+               $message = self::removeCodeTags($message);
+               
+               // set active message information
+               $this->activeMessageObjectTypeID = -1;
+               $this->activeMessageID = -1;
+               
+               // get embedded objects
+               foreach ($this->getEmbeddedObjectHandlers() as $handler) {
+                       $objectIDs = $handler->parseMessage($message);
+                       if (!empty($objectIDs)) {
+                               // save assignments
+                               $this->messageEmbeddedObjects[$this->activeMessageObjectTypeID][$this->activeMessageID][$handler->objectTypeID] = $objectIDs;
+                               
+                               // loads objects
+                               $this->embeddedObjects[$handler->objectTypeID] = $handler->loadObjects($objectIDs);
+                       }
+               }
+       }
+       
+       /**
+        * Returns all embedded object handlers.
+        * 
+        * @return      array
+        */
+       protected function getEmbeddedObjectHandlers() {
+               if ($this->embeddedObjectHandlers === null) {
+                       $this->embeddedObjectHandlers = array();
+                       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      object
+        */
+       protected function getEmbeddedObjectHandler($objectTypeID) {
+               $this->getEmbeddedObjectHandlers();
+               
+               return $this->embeddedObjectHandlers[$objectTypeID];
+       }
+       
+       /**
+        * Removes code bbcode occurrences in given message.
+        * 
+        * @param       string          $message
+        * @return      string
+        */
+       protected static function removeCodeTags($message) {
+               return preg_replace("~(\[code
+                       (?:=
+                               (?:\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'|[^,\]]*)
+                               (?:,(?:\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'|[^,\]]*))*
+                       )?\])
+                       (.*?)
+                       (?:\[/code\])~six", '', $message);
+       }
+}
\ No newline at end of file
diff --git a/wcfsetup/install/files/lib/system/message/embedded/object/QuoteMessageEmbeddedObjectHandler.class.php b/wcfsetup/install/files/lib/system/message/embedded/object/QuoteMessageEmbeddedObjectHandler.class.php
new file mode 100644 (file)
index 0000000..f1dd4e1
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+namespace wcf\system\message\embedded\object;
+use wcf\data\user\UserProfile;
+use wcf\data\user\UserList;
+
+/**
+ * IMessageEmbeddedObjectHandler implementation for quotes.
+ *
+ * @author     Marcel Werk
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.message.embedded.object
+ * @category   Community Framework
+ */
+class QuoteMessageEmbeddedObjectHandler extends AbstractMessageEmbeddedObjectHandler {
+       /**
+        * @see \wcf\system\message\embedded\object\IMessageEmbeddedObjectHandler::parseMessage()
+        */
+       public function parseMessage($message) {
+               $usernames = self::getFirstParameters($message, 'quote');
+               if (!empty($usernames)) {
+                       $userList = new UserList();
+                       $userList->getConditionBuilder()->add("user_table.username IN (?)", array($usernames));
+                       $userList->readObjectIDs();
+                       return $userList->getObjectIDs();
+               }
+               
+               return false;
+       }
+       
+       /**
+        * @see \wcf\system\message\embedded\object\IMessageEmbeddedObjectHandler::loadObjects()
+        */
+       public function loadObjects(array $objectIDs) {
+               return UserProfile::getUserProfiles($objectIDs);
+       }
+}
index 0c4ef91974b9105a3d59c4b308e800cbc1f9033a..ee1b57aaaae5eeacb6cf09e615e2d0e7eb045c12 100644 (file)
@@ -219,61 +219,141 @@ html[dir='rtl'] {
 
 /* Quote Box */
 .quoteBox {
-       background-color: @wcfContentBackgroundColor;
        clear: both;
-       min-height: 28px;
-       margin-bottom: @wcfGapTiny;
-       position: relative;
-       
-       &.containerPadding {
-               padding-left: 54px;
-       }
        
-       &::before {
-               content: "\f10d";
-               color: @wcfDimmedColor;
-               font-family: FontAwesome;
-               font-size: 28px;
-               position: absolute;
-               left: @wcfGapMedium;
-               top: @wcfGapSmall;
+       > .container {
+               background-color: @wcfContentBackgroundColor;
+               min-height: 28px;
+               margin-bottom: @wcfGapTiny;
+               position: relative;
+               
+               &.containerPadding {
+                       padding-left: 54px;
+               }
+               
+               > header {
+                       padding-bottom: @wcfGapTiny;
+                       border-bottom: 1px dotted @wcfContainerBorderColor;
+                       margin-bottom: @wcfGapSmall;
+                       
+                       > h3 {
+                               font-weight: bold;
+                       }
+               }
+               
+               > div {
+                       &::before {
+                               content: "\f10d";
+                               color: @wcfDimmedColor;
+                               font-family: FontAwesome;
+                               font-size: 28px;
+                               position: absolute;
+                               left: @wcfGapMedium;
+                               top: @wcfGapSmall;
+                       }
+               }
        }
        
-       > header {
-               padding-bottom: @wcfGapTiny;
-               border-bottom: 1px dotted @wcfContainerBorderColor;
-               margin-bottom: @wcfGapSmall;
+       > .quoteAuthorAvatar {
+               float: left;
+               
+               + .container {
+                       margin-left: 90px;
+                       padding-left: @wcfGapLarge;
+                       
+                       &::before,
+                       &::after {
+                               border-style: inset solid inset none;
+                               border-width: 15px;
+                               content: "";
+                               display: block;
+                               height: 0;
+                               position: absolute;
+                               top: 15px;
+                               width: 0;
+                       }
+                       
+                       &::before {
+                               border-color: transparent @wcfContainerBorderColor transparent transparent;
+                               left: -15px;
+                               z-index: 100;
+                       }
+                       
+                       &::after {
+                               border-color: transparent @wcfContentBackgroundColor transparent transparent;
+                               left: -14px;
+                               z-index: 101;
+                       }
+                       
+                       > div {
+                               &::before {
+                                       display: none;
+                               }
+                       }
+               }
                
-               > h3 {
-                       font-weight: bold;
+               > a {
+                       display: inline-block;
                }
        }
        
        /* nested quotes */
        .quoteBox {
-               background-image: none;
-               padding-left: @wcfGapLarge;
-               min-height: 0;
-               
-               &::before {
+               > .quoteAuthorAvatar {
                        display: none;
                }
+               
+               > .container {
+                       margin-left: 0;
+                       padding-left: @wcfGapLarge;
+                       min-height: 0;
+                       
+                       &::before,
+                       &::after {
+                               display: none;
+                       }
+                       
+                       > div {
+                               &::before {
+                                       display: none;
+                               }
+                       }
+               }
        }
 }
 
 @media only screen and (max-width: 800px) {
        .quoteBox {
-               &::before {
-                       font-size: 14px;
-                       left: @wcfGapSmall;
+               > .quoteAuthorAvatar {
+                       display: none;
                }
                
-               &.containerPadding {
-                       padding-left: 28px;
+               > .container {
+                       margin-left: 0 !important;
+                       
+                       &.containerPadding {
+                               padding-left: 28px;
+                       }
+                       
+                       > div {
+                               &::before {
+                                       display: block !important;
+                                       font-size: 14px;
+                                       left: @wcfGapSmall;
+                               }
+                       }
                }
                
                .quoteBox {
-                       padding-left: @wcfGapSmall;
+                       > .container {
+                               padding-left: @wcfGapSmall;
+                               
+                               > div {
+                                       &::before {
+                                               display: none !important;
+                                       }
+                               }
+                       }
                }
        }
 }
index c67034eb14437cea4b840948266ca5a2b5f38305..2a891c8b66aee4628a908790d974ae07d9efd59a 100644 (file)
@@ -470,6 +470,16 @@ CREATE TABLE wcf1_like_object (
        UNIQUE KEY (objectTypeID, objectID)
 );
 
+DROP TABLE IF EXISTS wcf1_message_embedded_object;
+CREATE TABLE wcf1_message_embedded_object (
+       messageObjectTypeID INT(10) NOT NULL,
+       messageID INT(10) NOT NULL,
+       embeddedObjectTypeID INT(10) NOT NULL,
+       embeddedObjectID INT(10) NOT NULL,
+       
+       KEY (messageObjectTypeID, messageID)
+);
+
 DROP TABLE IF EXISTS wcf1_moderation_queue;
 CREATE TABLE wcf1_moderation_queue (
        queueID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
@@ -1620,6 +1630,9 @@ ALTER TABLE wcf1_user_profile_visitor ADD FOREIGN KEY (userID) REFERENCES wcf1_u
 ALTER TABLE wcf1_user_object_watch ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;
 ALTER TABLE wcf1_user_object_watch ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
 
+ALTER TABLE wcf1_message_embedded_object ADD FOREIGN KEY (messageObjectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
+ALTER TABLE wcf1_message_embedded_object ADD FOREIGN KEY (embeddedObjectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
+
 ALTER TABLE wcf1_moderation_queue ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
 ALTER TABLE wcf1_moderation_queue ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE SET NULL;
 ALTER TABLE wcf1_moderation_queue ADD FOREIGN KEY (assignedUserID) REFERENCES wcf1_user (userID) ON DELETE SET NULL;