Apply PSR-12 code style (#145)
authorTim Düsterhus <duesterhus@woltlab.com>
Mon, 18 Jan 2021 12:41:55 +0000 (13:41 +0100)
committerGitHub <noreply@github.com>
Mon, 18 Jan 2021 12:41:55 +0000 (13:41 +0100)
Co-authored-by: Alexander Ebert <ebert@woltlab.com>
69 files changed:
.github/workflows/codestyle.yml
.php_cs.dist [new file with mode: 0644]
.phpcs.xml
constants.php
files/acp/install_com.woltlab.wcf.conversation.php
files/acp/update_com.woltlab.wcf.conversation_5.2.php
files/lib/data/conversation/Conversation.class.php
files/lib/data/conversation/ConversationAction.class.php
files/lib/data/conversation/ConversationEditor.class.php
files/lib/data/conversation/ConversationList.class.php
files/lib/data/conversation/ConversationParticipantList.class.php
files/lib/data/conversation/FeedConversation.class.php
files/lib/data/conversation/FeedConversationList.class.php
files/lib/data/conversation/UserConversationList.class.php
files/lib/data/conversation/ViewableConversation.class.php
files/lib/data/conversation/label/ConversationLabel.class.php
files/lib/data/conversation/label/ConversationLabelAction.class.php
files/lib/data/conversation/label/ConversationLabelEditor.class.php
files/lib/data/conversation/label/ConversationLabelList.class.php
files/lib/data/conversation/message/ConversationMessage.class.php
files/lib/data/conversation/message/ConversationMessageAction.class.php
files/lib/data/conversation/message/ConversationMessageEditor.class.php
files/lib/data/conversation/message/ConversationMessageList.class.php
files/lib/data/conversation/message/SearchResultConversationMessage.class.php
files/lib/data/conversation/message/SearchResultConversationMessageList.class.php
files/lib/data/conversation/message/SimplifiedViewableConversationMessageList.class.php
files/lib/data/conversation/message/ViewableConversationMessage.class.php
files/lib/data/conversation/message/ViewableConversationMessageList.class.php
files/lib/data/modification/log/ConversationLogModificationLogList.class.php
files/lib/data/modification/log/ViewableConversationModificationLog.class.php
files/lib/form/ConversationAddForm.class.php
files/lib/form/ConversationDraftEditForm.class.php
files/lib/page/ConversationFeedPage.class.php
files/lib/page/ConversationListPage.class.php
files/lib/page/ConversationPage.class.php
files/lib/system/attachment/ConversationMessageAttachmentObjectType.class.php
files/lib/system/cache/runtime/ConversationRuntimeCache.class.php
files/lib/system/cache/runtime/UserConversationRuntimeCache.class.php
files/lib/system/clipboard/action/ConversationClipboardAction.class.php
files/lib/system/conversation/ConversationHandler.class.php
files/lib/system/event/listener/ConversationPruneIpAddressesCronjobListener.class.php
files/lib/system/event/listener/ConversationUserActionRenameListener.class.php
files/lib/system/event/listener/ConversationUserMergeListener.class.php
files/lib/system/event/listener/UserGroupAddCanBeAddedAsConversationParticipantListener.class.php
files/lib/system/importer/ConversationAttachmentImporter.class.php
files/lib/system/importer/ConversationImporter.class.php
files/lib/system/importer/ConversationLabelImporter.class.php
files/lib/system/importer/ConversationMessageImporter.class.php
files/lib/system/importer/ConversationUserImporter.class.php
files/lib/system/log/modification/ConversationModificationLogHandler.class.php
files/lib/system/message/quote/ConversationMessageQuoteHandler.class.php
files/lib/system/moderation/queue/report/ConversationMessageModerationQueueReportHandler.class.php
files/lib/system/page/handler/ConversationListPageHandler.class.php
files/lib/system/page/handler/DefaultConversationRelatedPageHandler.class.php
files/lib/system/page/handler/TConversationOnlineLocationPageHandler.class.php
files/lib/system/search/ConversationMessageSearch.class.php
files/lib/system/stat/ConversationMessageStatDailyHandler.class.php
files/lib/system/stat/ConversationStatDailyHandler.class.php
files/lib/system/user/content/provider/ConversationMessageUserContentProvider.class.php
files/lib/system/user/content/provider/ConversationUserContentProvider.class.php
files/lib/system/user/notification/event/ConversationMessageUserNotificationEvent.class.php
files/lib/system/user/notification/event/ConversationUserNotificationEvent.class.php
files/lib/system/user/notification/event/TTestableConversationRelatedUserNotificationEvent.class.php
files/lib/system/user/notification/object/ConversationMessageUserNotificationObject.class.php
files/lib/system/user/notification/object/ConversationUserNotificationObject.class.php
files/lib/system/user/notification/object/type/ConversationMessageNotificationObjectType.class.php
files/lib/system/user/notification/object/type/ConversationNotificationObjectType.class.php
files/lib/system/worker/ConversationMessageRebuildDataWorker.class.php
files/lib/system/worker/ConversationRebuildDataWorker.class.php

index 964e5360c29b460d4a20ce3767f4b9bd078d4d05..5ae031c2b3df11c447a8f0ed91666e56d5794d2a 100644 (file)
@@ -10,9 +10,13 @@ on:
 
 jobs:
   php:
-    name: PHP CodeSniffer
+    name: PHP
     runs-on: ubuntu-latest
     steps:
     - uses: actions/checkout@v2
-    - run: git clone --branch=master --depth=1 --quiet git://github.com/WoltLab/WCF.git WCF
-    - uses: chekalsky/phpcs-action@e269c2f264f400adcda7c6b24c8550302350d495
+    - name: phpcs
+      uses: chekalsky/phpcs-action@e269c2f264f400adcda7c6b24c8550302350d495
+    - name: php-cs-fixer
+      uses: docker://oskarstark/php-cs-fixer-ga
+      with:
+        args: --dry-run --diff-format udiff
diff --git a/.php_cs.dist b/.php_cs.dist
new file mode 100644 (file)
index 0000000..adcc3be
--- /dev/null
@@ -0,0 +1,121 @@
+<?php
+$finder = PhpCsFixer\Finder::create()
+    ->exclude('*/vendor/*')
+    ->in(__DIR__.'/files/');
+
+return (new PhpCsFixer\Config())
+    ->setRiskyAllowed(true)
+    ->setRules([
+        '@PSR1' => true,
+        '@PSR2' => true,
+
+        'array_push' => true,
+        'backtick_to_shell_exec' => true,
+        'no_alias_language_construct_call' => true,
+        'no_mixed_echo_print' => true,
+        'pow_to_exponentiation' => true,
+        'random_api_migration' => true,
+
+        'array_syntax' => ['syntax' => 'short'],
+        'no_multiline_whitespace_around_double_arrow' => true,
+        'no_trailing_comma_in_singleline_array' => true,
+        'no_whitespace_before_comma_in_array' => true,
+        'normalize_index_brace' => true,
+        'trailing_comma_in_multiline_array' => true,
+        'whitespace_after_comma_in_array' => true,
+
+        'non_printable_character' => ['use_escape_sequences_in_strings' => true],
+
+        'lowercase_static_reference' => true,
+        'magic_constant_casing' => true,
+        'magic_method_casing' => true,
+        'native_function_casing' => true,
+        'native_function_type_declaration_casing' => true,
+
+        'cast_spaces' => ['space' => 'none'],
+        'lowercase_cast' => true,
+        'no_unset_cast' => true,
+        'short_scalar_cast' => true,
+
+        'class_attributes_separation' => true,
+        'no_blank_lines_after_class_opening' => true,
+        'no_null_property_initialization' => true,
+        'self_accessor' => true,
+        'single_class_element_per_statement' => true,
+        'single_trait_insert_per_statement' => true,
+
+        'no_empty_comment' => true,
+        'single_line_comment_style' => ['comment_types' => ['hash']],
+
+        'native_constant_invocation' => true,
+
+        'no_alternative_syntax' => true,
+        'no_trailing_comma_in_list_call' => true,
+        'no_unneeded_control_parentheses' => ['statements' => ['break', 'clone', 'continue', 'echo_print', 'return', 'switch_case', 'yield', 'yield_from']],
+        'no_unneeded_curly_braces' => ['namespaces' => true],
+        'switch_continue_to_break' => true,
+
+        'function_typehint_space' => true,
+        'lambda_not_used_import' => true,
+        'native_function_invocation' => true,
+        'no_unreachable_default_argument_value' => true,
+        'nullable_type_declaration_for_default_null_value' => true,
+        'return_type_declaration' => true,
+        'static_lambda' => true,
+
+        'fully_qualified_strict_types' => true,
+        'no_leading_import_slash' => true,
+        'no_unused_imports' => true,
+        'ordered_imports' => true,
+
+        'declare_equal_normalize' => true,
+        'dir_constant' => true,
+        'explicit_indirect_variable' => true,
+        'function_to_constant' => true,
+        'is_null' => true,
+        'no_unset_on_property' => true,
+
+        'list_syntax' => ['syntax' => 'short'],
+
+        'clean_namespace' => true,
+        'no_leading_namespace_whitespace' => true,
+        'single_blank_line_before_namespace' => true,
+
+        'no_homoglyph_names' => true,
+
+        'binary_operator_spaces' => true,
+        'concat_space' => ['spacing' => 'one'],
+        'increment_style' => ['style' => 'post'],
+        'logical_operators' => true,
+        'object_operator_without_whitespace' => true,
+        'operator_linebreak' => true,
+        'standardize_increment' => true,
+        'standardize_not_equals' => true,
+        'ternary_operator_spaces' => true,
+        'ternary_to_elvis_operator' => true,
+        'ternary_to_null_coalescing' => true,
+        'unary_operator_spaces' => true,
+
+        'no_useless_return' => true,
+        'return_assignment' => true,
+        'simplified_null_return' => true,
+
+        'multiline_whitespace_before_semicolons' => true,
+        'no_empty_statement' => true,
+        'no_singleline_whitespace_before_semicolons' => true,
+        'space_after_semicolon' => ['remove_in_empty_for_expressions' => true],
+
+        'escape_implicit_backslashes' => true,
+        'explicit_string_variable' => true,
+        'heredoc_to_nowdoc' => true,
+        'no_binary_string' => true,
+        'simple_to_complex_string_variable' => true,
+
+        'array_indentation' => true,
+        'blank_line_before_statement' => ['statements' => ['return', 'exit']],
+        'compact_nullable_typehint' => true,
+        'method_chaining_indentation' => true,
+        'no_extra_blank_lines' => ['tokens' => ['case', 'continue', 'curly_brace_block', 'default', 'extra', 'parenthesis_brace_block', 'square_brace_block', 'switch', 'throw', 'use', 'use_trait']],
+        'no_spaces_around_offset' => true,
+    ])
+    ->setFinder($finder);
index 57a48ad9db2aadc3d523af2b93b5a5c35957291e..52e8202fe279642a7decddde57d3f349c11ecd47 100644 (file)
@@ -1,9 +1,10 @@
 <?xml version="1.0"?>
 <ruleset>
        <file>files/</file>
+       <exclude-pattern>*/vendor/*</exclude-pattern>
        <arg name="extensions" value="php" />
        <arg value="p"/>
        <arg name="basepath" value="."/>
 
-       <rule ref="./WCF/CodeSniff/WCF/ruleset.xml"/>
+       <rule ref="PSR12"/>
 </ruleset>
index 487355fe33180957b1bf43b745610b075196536b..ff6452cd643f45213f60a80a39754541ab33a545 100644 (file)
@@ -2,10 +2,11 @@
 /**
  * Defines constants for autocompletion in IDEs. This file is not meant to be actively used anywhere!
  *
- * @author     Matthias Schmidt
  * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core
+ * @author    Matthias Schmidt
+ * @copyright    2001-2019 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core
  */
 
 // option constants
index a56bfc3f17bbef9cb0f202d1701cd23fc8378738..7527b59e50e7a487a1564816eb002118fb24c790 100644 (file)
@@ -1,31 +1,33 @@
 <?php
-use wcf\data\user\group\UserGroup;
-use wcf\system\WCF;
 
 /**
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @author  Matthias Schmidt
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  */
+
+use wcf\data\user\group\UserGroup;
+use wcf\system\WCF;
+
 // set default mod permissions
 $group = new UserGroup(5);
 if ($group->groupID) {
-       $sql = "REPLACE INTO    wcf".WCF_N."_user_group_option_value
+    $sql = "REPLACE INTO       wcf" . WCF_N . "_user_group_option_value
                                (groupID, optionID, optionValue)
                SELECT          5, optionID, 1
-               FROM            wcf".WCF_N."_user_group_option
+               FROM            wcf" . WCF_N . "_user_group_option
                WHERE           optionName LIKE 'mod.conversation.%'";
-       $statement = WCF::getDB()->prepareStatement($sql);
-       $statement->execute();
+    $statement = WCF::getDB()->prepareStatement($sql);
+    $statement->execute();
 }
 
 $group = new UserGroup(6);
 if ($group->groupID) {
-       $sql = "REPLACE INTO    wcf".WCF_N."_user_group_option_value
+    $sql = "REPLACE INTO       wcf" . WCF_N . "_user_group_option_value
                                (groupID, optionID, optionValue)
                SELECT          6, optionID, 1
-               FROM            wcf".WCF_N."_user_group_option
+               FROM            wcf" . WCF_N . "_user_group_option
                WHERE           optionName LIKE 'mod.conversation.%'";
-       $statement = WCF::getDB()->prepareStatement($sql);
-       $statement->execute();
+    $statement = WCF::getDB()->prepareStatement($sql);
+    $statement->execute();
 }
index ee50c434dfce4d5bffec16dd29884a409d4721e0..e4c93c3b0a5199a3df12dc74fadf4db143673e7c 100644 (file)
@@ -1,10 +1,4 @@
 <?php
-use wcf\system\database\table\column\DefaultFalseBooleanDatabaseTableColumn;
-use wcf\system\database\table\column\DefaultTrueBooleanDatabaseTableColumn;
-use wcf\system\database\table\DatabaseTable;
-use wcf\system\database\table\DatabaseTableChangeProcessor;
-use wcf\system\package\plugin\ScriptPackageInstallationPlugin;
-use wcf\system\WCF;
 
 /**
  * @author      Alexander Ebert
@@ -12,21 +6,29 @@ use wcf\system\WCF;
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  */
 
+use wcf\system\database\table\column\DefaultFalseBooleanDatabaseTableColumn;
+use wcf\system\database\table\column\DefaultTrueBooleanDatabaseTableColumn;
+use wcf\system\database\table\DatabaseTable;
+use wcf\system\database\table\DatabaseTableChangeProcessor;
+use wcf\system\package\plugin\ScriptPackageInstallationPlugin;
+use wcf\system\WCF;
+
 $tables = [
-       DatabaseTable::create('wcf1_conversation_to_user')
-               ->columns([
-                       DefaultTrueBooleanDatabaseTableColumn::create('leftByOwnChoice')
-               ]),
-       
-       DatabaseTable::create('wcf1_user_group')
-               ->columns([
-                       DefaultFalseBooleanDatabaseTableColumn::create('canBeAddedAsConversationParticipant'),
-               ]),
+    DatabaseTable::create('wcf1_conversation_to_user')
+        ->columns([
+            DefaultTrueBooleanDatabaseTableColumn::create('leftByOwnChoice'),
+        ]),
+
+    DatabaseTable::create('wcf1_user_group')
+        ->columns([
+            DefaultFalseBooleanDatabaseTableColumn::create('canBeAddedAsConversationParticipant'),
+        ]),
 ];
 
 (new DatabaseTableChangeProcessor(
-       /** @var ScriptPackageInstallationPlugin $this */
-       $this->installation->getPackage(),
-       $tables,
-       WCF::getDB()->getEditor())
+/** @var ScriptPackageInstallationPlugin $this */
+    $this->installation->getPackage(),
+    $tables,
+    WCF::getDB()->getEditor()
+)
 )->process();
index 1c05a0d36ac96b6a8aed1d9801e94dd5fdb1efae..5624c604793235e93e48adf3953c557b9099b478 100644 (file)
@@ -1,10 +1,12 @@
 <?php
+
 namespace wcf\data\conversation;
+
 use wcf\data\conversation\message\ConversationMessage;
+use wcf\data\DatabaseObject;
 use wcf\data\IPopoverObject;
 use wcf\data\user\group\UserGroup;
 use wcf\data\user\UserProfile;
-use wcf\data\DatabaseObject;
 use wcf\system\cache\runtime\UserProfileRuntimeCache;
 use wcf\system\conversation\ConversationHandler;
 use wcf\system\database\util\PreparedStatementConditionBuilder;
@@ -17,621 +19,675 @@ use wcf\util\ArrayUtil;
 
 /**
  * Represents a conversation.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\Data\Conversation
- * 
- * @property-read      integer         $conversationID         unique id of the conversation
- * @property-read      string          $subject                subject of the conversation
- * @property-read      integer         $time                   timestamp at which the conversation has been started
- * @property-read      integer         $firstMessageID         id of the first conversation message
- * @property-read      integer|null    $userID                 id of the user who started the conversation or `null` if the user does not exist anymore
- * @property-read      string          $username               name of the user who started the conversation
- * @property-read      integer         $lastPostTime           timestamp at which the conversation's last message has been written
- * @property-read      integer|null    $lastPosterID           id of the user who wrote the conversation's last message or `null` if the user does not exist anymore
- * @property-read      string          $lastPoster             name of the user who wrote the conversation's last message
- * @property-read      integer         $replies                number of replies on the conversation
- * @property-read      integer         $attachments            total number of attachments in all messages of the conversation
- * @property-read      integer         $participants           number of participants of the conversations
- * @property-read      string          $participantSummary     serialized data of five of the conversation participants (sorted by username)
- * @property-read      integer         $participantCanInvite   is `1` if participants can invite other users to join the conversation, otherwise `0`
- * @property-read      integer         $isClosed               is `1` if the conversation is closed for new messages, otherwise `0`
- * @property-read      integer         $isDraft                is `1` if the conversation is a draft only, thus not sent to any participant, otherwise `0`
- * @property-read      string          $draftData              serialized ids of the participants and invisible participants if conversation is a draft, otherwise `0`
- * @property-read      integer|null    $participantID          id of the user whose conversations are fetched via `UserConversationList`, otherwise `null` 
- * @property-read      integer|null    $hideConversation       is `1` if the user has hidden conversation, otherwise `0`; is `null` if the conversation has not been fetched via `UserConversationList`
- * @property-read      integer|null    $isInvisible            is `1` if the user is invisible in conversation, otherwise `0`; is `null` if the conversation has not been fetched via `UserConversationList`
- * @property-read      integer|null    $lastVisitTime          timestamp at which the user last visited the conversation after a new messsage had been written or `0` if they have not visited it at all; is `null` if the conversation has not been fetched via `UserConversationList`
- * @property-read      integer|null    $joinedAt               timestamp at which the user joined the conversation; is `null` if the conversation has not been fetched via `UserConversationList`
- * @property-read      integer|null    $leftAt                 timestamp at which the user left the conversation or `0` if they did not leave the conversation; is `null` if the conversation has not been fetched via `UserConversationList`
- * @property-read      integer|null    $lastMessageID          id of the last message written before the user left the conversation or `0` if they did not leave the conversation; is `null` if the conversation has not been fetched via `UserConversationList`
+ *
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Data\Conversation
+ *
+ * @property-read   integer $conversationID     unique id of the conversation
+ * @property-read   string $subject        subject of the conversation
+ * @property-read   integer $time           timestamp at which the conversation has been started
+ * @property-read   integer $firstMessageID     id of the first conversation message
+ * @property-read   integer|null $userID         id of the user who started the conversation or `null` if the user does not exist anymore
+ * @property-read   string $username       name of the user who started the conversation
+ * @property-read   integer $lastPostTime       timestamp at which the conversation's last message has been written
+ * @property-read   integer|null $lastPosterID       id of the user who wrote the conversation's last message or `null` if the user does not exist anymore
+ * @property-read   string $lastPoster     name of the user who wrote the conversation's last message
+ * @property-read   integer $replies        number of replies on the conversation
+ * @property-read   integer $attachments        total number of attachments in all messages of the conversation
+ * @property-read   integer $participants       number of participants of the conversations
+ * @property-read   string $participantSummary serialized data of five of the conversation participants (sorted by username)
+ * @property-read   integer $participantCanInvite   is `1` if participants can invite other users to join the conversation, otherwise `0`
+ * @property-read   integer $isClosed       is `1` if the conversation is closed for new messages, otherwise `0`
+ * @property-read   integer $isDraft        is `1` if the conversation is a draft only, thus not sent to any participant, otherwise `0`
+ * @property-read   string $draftData      serialized ids of the participants and invisible participants if conversation is a draft, otherwise `0`
+ * @property-read   integer|null $participantID      id of the user whose conversations are fetched via `UserConversationList`, otherwise `null`
+ * @property-read   integer|null $hideConversation   is `1` if the user has hidden conversation, otherwise `0`; is `null` if the conversation has not been fetched via `UserConversationList`
+ * @property-read   integer|null $isInvisible        is `1` if the user is invisible in conversation, otherwise `0`; is `null` if the conversation has not been fetched via `UserConversationList`
+ * @property-read   integer|null $lastVisitTime      timestamp at which the user last visited the conversation after a new messsage had been written or `0` if they have not visited it at all; is `null` if the conversation has not been fetched via `UserConversationList`
+ * @property-read   integer|null $joinedAt       timestamp at which the user joined the conversation; is `null` if the conversation has not been fetched via `UserConversationList`
+ * @property-read   integer|null $leftAt         timestamp at which the user left the conversation or `0` if they did not leave the conversation; is `null` if the conversation has not been fetched via `UserConversationList`
+ * @property-read   integer|null $lastMessageID      id of the last message written before the user left the conversation or `0` if they did not leave the conversation; is `null` if the conversation has not been fetched via `UserConversationList`
  */
-class Conversation extends DatabaseObject implements IPopoverObject, IRouteController {
-       /**
-        * default participation state
-        * @var integer
-        */
-       const STATE_DEFAULT = 0;
-       
-       /**
-        * conversation is hidden but returns visible upon new message
-        * @var integer
-        */
-       const STATE_HIDDEN = 1;
-       
-       /**
-        * conversation was left permanently
-        * @var integer
-        */
-       const STATE_LEFT/*4DEAD*/ = 2;
-       
-       /**
-        * true if the current user can add users without limitations
-        * @var boolean
-        */
-       protected $canAddUnrestricted;
-       
-       /**
-        * first message object
-        * @var ConversationMessage
-        */
-       protected $firstMessage;
-       
-       /**
-        * true if the current user is an active participant of this conversation
-        * @var boolean
-        */
-       protected $isActiveParticipant;
-       
-       /**
-        * @inheritDoc
-        */
-       public function getTitle() {
-               return $this->subject;
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getLink() {
-               return LinkHandler::getInstance()->getLink('Conversation', [
-                       'object' => $this,
-                       'forceFrontend' => true
-               ]);
-       }
-       
-       /**
-        * Returns true if this conversation is new for the active user.
-        * 
-        * @return      boolean
-        */
-       public function isNew() {
-               if (!$this->isDraft && $this->lastPostTime > $this->lastVisitTime) {
-                       return true;
-               }
-               
-               return false;
-       }
-       
-       /**
-        * Returns true if the active user doesn't have read the given message.
-        * 
-        * @param       ConversationMessage     $message
-        * @return      boolean
-        */
-       public function isNewMessage(ConversationMessage $message) {
-               if (!$this->isDraft && $message->time > $this->lastVisitTime) {
-                       return true;
-               }
-               
-               return false;
-       }
-       
-       /**
-        * Returns true if the conversation is not closed or the user was not removed.
-        * 
-        * @return      boolean
-        */
-       public function canReply() {
-               return !$this->isClosed && !$this->leftAt && WCF::getSession()->getPermission('user.conversation.canReplyToConversation');
-       }
-       
-       /**
-        * Overrides the last message data, used when `leftAt < lastPostTime`.
-        * 
-        * @param       integer         $userID
-        * @param       string          $username
-        * @param       integer         $time
-        */
-       public function setLastMessage($userID, $username, $time) {
-               $this->data['lastPostTime'] = $time;
-               $this->data['lastPosterID'] = $userID;
-               $this->data['lastPoster'] = $username;
-       }
-       
-       /**
-        * Loads participation data for given user id (default: current user) on runtime.
-        * You should use Conversation::getUserConversation() instead if possible.
-        *
-        * @param       integer         $userID
-        */
-       public function loadUserParticipation($userID = null) {
-               if ($userID === null) {
-                       $userID = WCF::getUser()->userID;
-               }
-               
-               $sql = "SELECT  *
-                       FROM    wcf".WCF_N."_conversation_to_user
+class Conversation extends DatabaseObject implements IPopoverObject, IRouteController
+{
+    /**
+     * default participation state
+     * @var integer
+     */
+    const STATE_DEFAULT = 0;
+
+    /**
+     * conversation is hidden but returns visible upon new message
+     * @var integer
+     */
+    const STATE_HIDDEN = 1;
+
+    /**
+     * conversation was left permanently
+     * @var integer
+     */
+    const STATE_LEFT/*4DEAD*/ = 2;
+
+    /**
+     * true if the current user can add users without limitations
+     * @var boolean
+     */
+    protected $canAddUnrestricted;
+
+    /**
+     * first message object
+     * @var ConversationMessage
+     */
+    protected $firstMessage;
+
+    /**
+     * true if the current user is an active participant of this conversation
+     * @var boolean
+     */
+    protected $isActiveParticipant;
+
+    /**
+     * @inheritDoc
+     */
+    public function getTitle()
+    {
+        return $this->subject;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getLink()
+    {
+        return LinkHandler::getInstance()->getLink('Conversation', [
+            'object' => $this,
+            'forceFrontend' => true,
+        ]);
+    }
+
+    /**
+     * Returns true if this conversation is new for the active user.
+     *
+     * @return  boolean
+     */
+    public function isNew()
+    {
+        if (!$this->isDraft && $this->lastPostTime > $this->lastVisitTime) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns true if the active user doesn't have read the given message.
+     *
+     * @param ConversationMessage $message
+     * @return  boolean
+     */
+    public function isNewMessage(ConversationMessage $message)
+    {
+        if (!$this->isDraft && $message->time > $this->lastVisitTime) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns true if the conversation is not closed or the user was not removed.
+     *
+     * @return      boolean
+     */
+    public function canReply()
+    {
+        return !$this->isClosed && !$this->leftAt && WCF::getSession()->getPermission('user.conversation.canReplyToConversation');
+    }
+
+    /**
+     * Overrides the last message data, used when `leftAt < lastPostTime`.
+     *
+     * @param integer $userID
+     * @param string $username
+     * @param integer $time
+     */
+    public function setLastMessage($userID, $username, $time)
+    {
+        $this->data['lastPostTime'] = $time;
+        $this->data['lastPosterID'] = $userID;
+        $this->data['lastPoster'] = $username;
+    }
+
+    /**
+     * Loads participation data for given user id (default: current user) on runtime.
+     * You should use Conversation::getUserConversation() instead if possible.
+     *
+     * @param integer $userID
+     */
+    public function loadUserParticipation($userID = null)
+    {
+        if ($userID === null) {
+            $userID = WCF::getUser()->userID;
+        }
+
+        $sql = "SELECT *
+                       FROM    wcf" . WCF_N . "_conversation_to_user
                        WHERE   participantID = ?
                                AND conversationID = ?";
-               $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute([$userID, $this->conversationID]);
-               $row = $statement->fetchArray();
-               if ($row !== false) {
-                       $this->data = array_merge($this->data, $row);
-               }
-       }
-       
-       /**
-        * Returns a specific user conversation.
-        * 
-        * @param       integer         $conversationID
-        * @param       integer         $userID
-        * @return      Conversation
-        */
-       public static function getUserConversation($conversationID, $userID) {
-               $sql = "SELECT          conversation_to_user.*, conversation.*
-                       FROM            wcf".WCF_N."_conversation conversation
-                       LEFT JOIN       wcf".WCF_N."_conversation_to_user conversation_to_user
+        $statement = WCF::getDB()->prepareStatement($sql);
+        $statement->execute([$userID, $this->conversationID]);
+        $row = $statement->fetchArray();
+        if ($row !== false) {
+            $this->data = \array_merge($this->data, $row);
+        }
+    }
+
+    /**
+     * Returns a specific user conversation.
+     *
+     * @param integer $conversationID
+     * @param integer $userID
+     * @return  Conversation
+     */
+    public static function getUserConversation($conversationID, $userID)
+    {
+        $sql = "SELECT         conversation_to_user.*, conversation.*
+                       FROM            wcf" . WCF_N . "_conversation conversation
+                       LEFT JOIN       wcf" . WCF_N . "_conversation_to_user conversation_to_user
                        ON              (conversation_to_user.participantID = ? AND conversation_to_user.conversationID = conversation.conversationID)
                        WHERE           conversation.conversationID = ?";
-               $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute([$userID, $conversationID]);
-               $row = $statement->fetchArray();
-               if ($row !== false) {
-                       return new Conversation(null, $row);
-               }
-               
-               return null;
-       }
-       
-       /**
-        * Returns a list of user conversations.
-        * 
-        * @param       integer[]               $conversationIDs
-        * @param       integer                 $userID
-        * @return      Conversation[]
-        */
-       public static function getUserConversations(array $conversationIDs, $userID) {
-               $conditionBuilder = new PreparedStatementConditionBuilder();
-               $conditionBuilder->add('conversation.conversationID IN (?)', [$conversationIDs]);
-               $sql = "SELECT          conversation_to_user.*, conversation.*
-                       FROM            wcf".WCF_N."_conversation conversation
-                       LEFT JOIN       wcf".WCF_N."_conversation_to_user conversation_to_user
-                       ON              (conversation_to_user.participantID = ".$userID." AND conversation_to_user.conversationID = conversation.conversationID)
-                       ".$conditionBuilder;
-               $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute($conditionBuilder->getParameters());
-               $conversations = [];
-               while ($row = $statement->fetchArray()) {
-                       $conversations[$row['conversationID']] = new Conversation(null, $row);
-               }
-               
-               return $conversations;
-       }
-       
-       /**
-        * Returns true if the active user has the permission to read this conversation.
-        * 
-        * @return      boolean
-        */
-       public function canRead() {
-               if (!WCF::getUser()->userID) return false;
-               
-               if ($this->isDraft && $this->userID == WCF::getUser()->userID) return true;
-               
-               if ($this->participantID == WCF::getUser()->userID && $this->hideConversation != self::STATE_LEFT) return true;
-               
-               return false;
-       }
-       
-       /**
-        * Returns true if the current user can add new participants to this conversation.
-        * 
-        * @return      boolean
-        */
-       public function canAddParticipants() {
-               if ($this->isDraft) {
-                       return false;
-               }
-               
-               // check permissions
-               if (WCF::getUser()->userID != $this->userID) {
-                       if (!$this->participantCanInvite && !WCF::getSession()->getPermission('mod.conversation.canAlwaysInviteUsers')) {
-                               return false;
-                       }
-               }
-               
-               // check for maximum number of participants
-               // note: 'participants' does not track invisible participants, this will be checked on the fly!
-               if ($this->participants >= WCF::getSession()->getPermission('user.conversation.maxParticipants')) {
-                       return false;
-               }
-               
-               if (!$this->isActiveParticipant()) {
-                       return false;
-               }
-               
-               return true;
-       }
-       
-       /**
-        * Returns true if the current user can add participants without limitations.
-        * 
-        * @return      boolean
-        */
-       public function canAddParticipantsUnrestricted() {
-               if ($this->canAddUnrestricted === null) {
-                       $this->canAddUnrestricted = false;
-                       if ($this->isActiveParticipant()) {
-                               $sql = "SELECT  joinedAt
-                                       FROM    wcf".WCF_N."_conversation_to_user
+        $statement = WCF::getDB()->prepareStatement($sql);
+        $statement->execute([$userID, $conversationID]);
+        $row = $statement->fetchArray();
+        if ($row !== false) {
+            return new self(null, $row);
+        }
+    }
+
+    /**
+     * Returns a list of user conversations.
+     *
+     * @param integer[] $conversationIDs
+     * @param integer $userID
+     * @return  Conversation[]
+     */
+    public static function getUserConversations(array $conversationIDs, $userID)
+    {
+        $conditionBuilder = new PreparedStatementConditionBuilder();
+        $conditionBuilder->add('conversation.conversationID IN (?)', [$conversationIDs]);
+        $sql = "SELECT         conversation_to_user.*, conversation.*
+                       FROM            wcf" . WCF_N . "_conversation conversation
+                       LEFT JOIN       wcf" . WCF_N . "_conversation_to_user conversation_to_user
+                       ON              (conversation_to_user.participantID = " . $userID . " AND conversation_to_user.conversationID = conversation.conversationID)
+                       " . $conditionBuilder;
+        $statement = WCF::getDB()->prepareStatement($sql);
+        $statement->execute($conditionBuilder->getParameters());
+        $conversations = [];
+        while ($row = $statement->fetchArray()) {
+            $conversations[$row['conversationID']] = new self(null, $row);
+        }
+
+        return $conversations;
+    }
+
+    /**
+     * Returns true if the active user has the permission to read this conversation.
+     *
+     * @return  boolean
+     */
+    public function canRead()
+    {
+        if (!WCF::getUser()->userID) {
+            return false;
+        }
+
+        if ($this->isDraft && $this->userID == WCF::getUser()->userID) {
+            return true;
+        }
+
+        if ($this->participantID == WCF::getUser()->userID && $this->hideConversation != self::STATE_LEFT) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns true if the current user can add new participants to this conversation.
+     *
+     * @return  boolean
+     */
+    public function canAddParticipants()
+    {
+        if ($this->isDraft) {
+            return false;
+        }
+
+        // check permissions
+        if (WCF::getUser()->userID != $this->userID) {
+            if (
+                !$this->participantCanInvite
+                && !WCF::getSession()->getPermission('mod.conversation.canAlwaysInviteUsers')
+            ) {
+                return false;
+            }
+        }
+
+        // check for maximum number of participants
+        // note: 'participants' does not track invisible participants, this will be checked on the fly!
+        if ($this->participants >= WCF::getSession()->getPermission('user.conversation.maxParticipants')) {
+            return false;
+        }
+
+        if (!$this->isActiveParticipant()) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns true if the current user can add participants without limitations.
+     *
+     * @return      boolean
+     */
+    public function canAddParticipantsUnrestricted()
+    {
+        if ($this->canAddUnrestricted === null) {
+            $this->canAddUnrestricted = false;
+            if ($this->isActiveParticipant()) {
+                $sql = "SELECT  joinedAt
+                                       FROM    wcf" . WCF_N . "_conversation_to_user
                                        WHERE   conversationID = ?
                                                AND participantID = ?";
-                               $statement = WCF::getDB()->prepareStatement($sql);
-                               $statement->execute([
-                                       $this->conversationID,
-                                       WCF::getUser()->userID
-                               ]);
-                               $joinedAt = $statement->fetchSingleColumn();
-                               
-                               if ($joinedAt !== false && $joinedAt == 0) {
-                                       $this->canAddUnrestricted = true;
-                               }
-                       }
-               }
-               
-               return $this->canAddUnrestricted;
-       }
-       
-       /**
-        * Returns the first message in this conversation.
-        * 
-        * @return      ConversationMessage
-        */
-       public function getFirstMessage() {
-               if ($this->firstMessage === null) {
-                       $this->firstMessage = new ConversationMessage($this->firstMessageID);
-               }
-               
-               return $this->firstMessage;
-       }
-       
-       /**
-        * Sets the first message.
-        * 
-        * @param       ConversationMessage     $message
-        */
-       public function setFirstMessage(ConversationMessage $message) {
-               $this->firstMessage = $message;
-       }
-       
-       /**
-        * Returns a list of the ids of all participants.
-        * 
-        * @param       boolean         $excludeLeftParticipants
-        * @return      integer[]
-        */
-       public function getParticipantIDs($excludeLeftParticipants = false) {
-               $conditions = new PreparedStatementConditionBuilder();
-               $conditions->add("conversationID = ?", [$this->conversationID]);
-               $conditions->add("participantID IS NOT NULL");
-               if ($excludeLeftParticipants) $conditions->add("(hideConversation <> ? AND leftAt = ?)", [self::STATE_LEFT, 0]);
-               
-               $sql = "SELECT          participantID
-                       FROM            wcf".WCF_N."_conversation_to_user
-                       ".$conditions;
-               $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute($conditions->getParameters());
-               
-               return $statement->fetchAll(\PDO::FETCH_COLUMN);
-       }
-       
-       /**
-        * Returns a list of the usernames of all participants.
-        *
-        * @param       boolean         $excludeSelf
-        * @param       boolean         $leftByOwnChoice                           
-        * @return      string[]
-        */
-       public function getParticipantNames($excludeSelf = false, $leftByOwnChoice = false) {
-               $conditions = new PreparedStatementConditionBuilder();
-               $conditions->add("conversationID = ?", [$this->conversationID]);
-               if ($excludeSelf) $conditions->add("conversation_to_user.participantID <> ?", [WCF::getUser()->userID]);
-               if ($leftByOwnChoice) $conditions->add("conversation_to_user.leftByOwnChoice = ?", [1]);
-
-               $sql = "SELECT          user_table.username
-                       FROM            wcf".WCF_N."_conversation_to_user conversation_to_user
-                       LEFT JOIN       wcf".WCF_N."_user user_table
+                $statement = WCF::getDB()->prepareStatement($sql);
+                $statement->execute([
+                    $this->conversationID,
+                    WCF::getUser()->userID,
+                ]);
+                $joinedAt = $statement->fetchSingleColumn();
+
+                if ($joinedAt !== false && $joinedAt == 0) {
+                    $this->canAddUnrestricted = true;
+                }
+            }
+        }
+
+        return $this->canAddUnrestricted;
+    }
+
+    /**
+     * Returns the first message in this conversation.
+     *
+     * @return  ConversationMessage
+     */
+    public function getFirstMessage()
+    {
+        if ($this->firstMessage === null) {
+            $this->firstMessage = new ConversationMessage($this->firstMessageID);
+        }
+
+        return $this->firstMessage;
+    }
+
+    /**
+     * Sets the first message.
+     *
+     * @param ConversationMessage $message
+     */
+    public function setFirstMessage(ConversationMessage $message)
+    {
+        $this->firstMessage = $message;
+    }
+
+    /**
+     * Returns a list of the ids of all participants.
+     *
+     * @param boolean $excludeLeftParticipants
+     * @return  integer[]
+     */
+    public function getParticipantIDs($excludeLeftParticipants = false)
+    {
+        $conditions = new PreparedStatementConditionBuilder();
+        $conditions->add("conversationID = ?", [$this->conversationID]);
+        $conditions->add("participantID IS NOT NULL");
+        if ($excludeLeftParticipants) {
+            $conditions->add("(hideConversation <> ? AND leftAt = ?)", [self::STATE_LEFT, 0]);
+        }
+
+        $sql = "SELECT         participantID
+                       FROM            wcf" . WCF_N . "_conversation_to_user
+                       " . $conditions;
+        $statement = WCF::getDB()->prepareStatement($sql);
+        $statement->execute($conditions->getParameters());
+
+        return $statement->fetchAll(\PDO::FETCH_COLUMN);
+    }
+
+    /**
+     * Returns a list of the usernames of all participants.
+     *
+     * @param boolean $excludeSelf
+     * @param boolean $leftByOwnChoice
+     * @return  string[]
+     */
+    public function getParticipantNames($excludeSelf = false, $leftByOwnChoice = false)
+    {
+        $conditions = new PreparedStatementConditionBuilder();
+        $conditions->add("conversationID = ?", [$this->conversationID]);
+        if ($excludeSelf) {
+            $conditions->add("conversation_to_user.participantID <> ?", [WCF::getUser()->userID]);
+        }
+        if ($leftByOwnChoice) {
+            $conditions->add("conversation_to_user.leftByOwnChoice = ?", [1]);
+        }
+
+        $sql = "SELECT         user_table.username
+                       FROM            wcf" . WCF_N . "_conversation_to_user conversation_to_user
+                       LEFT JOIN       wcf" . WCF_N . "_user user_table
                        ON              (user_table.userID = conversation_to_user.participantID)
-                       ".$conditions;
-               $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute($conditions->getParameters());
-               
-               return $statement->fetchAll(\PDO::FETCH_COLUMN);
-       }
-       
-       /**
-        * Returns false if the active user is the last participant of this conversation.
-        * 
-        * @return      boolean
-        */
-       public function hasOtherParticipants() {
-               if ($this->userID == WCF::getUser()->userID) {
-                       // author
-                       if ($this->participants == 0) return false;
-                       return true;
-               }
-               else {
-                       if ($this->participants > 1) return true;
-                       if ($this->isInvisible && $this->participants > 0) return true;
-                       
-                       if ($this->userID) {
-                               // check if author has left the conversation
-                               $sql = "SELECT  hideConversation
-                                       FROM    wcf".WCF_N."_conversation_to_user
+                       " . $conditions;
+        $statement = WCF::getDB()->prepareStatement($sql);
+        $statement->execute($conditions->getParameters());
+
+        return $statement->fetchAll(\PDO::FETCH_COLUMN);
+    }
+
+    /**
+     * Returns false if the active user is the last participant of this conversation.
+     *
+     * @return  boolean
+     */
+    public function hasOtherParticipants()
+    {
+        if ($this->userID == WCF::getUser()->userID) {
+            // author
+            if ($this->participants == 0) {
+                return false;
+            }
+
+            return true;
+        } else {
+            if ($this->participants > 1) {
+                return true;
+            }
+            if ($this->isInvisible && $this->participants > 0) {
+                return true;
+            }
+
+            if ($this->userID) {
+                // check if author has left the conversation
+                $sql = "SELECT hideConversation
+                                       FROM    wcf" . WCF_N . "_conversation_to_user
                                        WHERE   conversationID = ?
                                                AND participantID = ?";
-                               $statement = WCF::getDB()->prepareStatement($sql);
-                               $statement->execute([$this->conversationID, $this->userID]);
-                               $row = $statement->fetchArray();
-                               if ($row !== false) {
-                                       if ($row['hideConversation'] != self::STATE_LEFT) return true;
-                               }
-                       }
-                       
-                       return false;
-               }
-       }
-       
-       /**
-        * Returns true if the current user is an active participant of this conversation.
-        * 
-        * @return      boolean
-        */
-       public function isActiveParticipant() {
-               if ($this->isActiveParticipant === null) {
-                       $sql = "SELECT  leftAt
+                $statement = WCF::getDB()->prepareStatement($sql);
+                $statement->execute([$this->conversationID, $this->userID]);
+                $row = $statement->fetchArray();
+                if ($row !== false) {
+                    if ($row['hideConversation'] != self::STATE_LEFT) {
+                        return true;
+                    }
+                }
+            }
+
+            return false;
+        }
+    }
+
+    /**
+     * Returns true if the current user is an active participant of this conversation.
+     *
+     * @return      boolean
+     */
+    public function isActiveParticipant()
+    {
+        if ($this->isActiveParticipant === null) {
+            $sql = "SELECT  leftAt
                                FROM    wcf" . WCF_N . "_conversation_to_user
                                WHERE   conversationID = ?
                                        AND participantID = ?";
-                       $statement = WCF::getDB()->prepareStatement($sql);
-                       $statement->execute([
-                               $this->conversationID,
-                               WCF::getUser()->userID
-                       ]);
-                       $leftAt = $statement->fetchSingleColumn();
-                       
-                       $this->isActiveParticipant = ($leftAt !== false && $leftAt == 0);
-               }
-               
-               return $this->isActiveParticipant;
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getPopoverLinkClass() {
-               return 'conversationLink';
-       }
-       
-       /**
-        * Returns true if the given user id (default: current user) is participant
-        * of all given conversation ids.
-        * 
-        * @param       integer[]       $conversationIDs
-        * @param       integer         $userID
-        * @return      boolean
-        */
-       public static function isParticipant(array $conversationIDs, $userID = null) {
-               if ($userID === null) $userID = WCF::getUser()->userID;
-               
-               // check if user is the initial author
-               $conditions = new PreparedStatementConditionBuilder();
-               $conditions->add("conversationID IN (?)", [$conversationIDs]);
-               $conditions->add("userID = ?", [$userID]);
-               
-               $sql = "SELECT  conversationID
-                       FROM    wcf".WCF_N."_conversation
-                       ".$conditions;
-               $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute($conditions->getParameters());
-               while ($row = $statement->fetchArray()) {
-                       $index = array_search($row['conversationID'], $conversationIDs);
-                       unset($conversationIDs[$index]);
-               }
-               
-               // check for participation
-               if (!empty($conversationIDs)) {
-                       $conditions = new PreparedStatementConditionBuilder();
-                       $conditions->add("conversationID IN (?)", [$conversationIDs]);
-                       $conditions->add("participantID = ?", [$userID]);
-                       $conditions->add("hideConversation <> ?", [self::STATE_LEFT]);
-                       
-                       $sql = "SELECT  conversationID
-                               FROM    wcf".WCF_N."_conversation_to_user
-                               ".$conditions;
-                       $statement = WCF::getDB()->prepareStatement($sql);
-                       $statement->execute($conditions->getParameters());
-                       while ($row = $statement->fetchArray()) {
-                               $index = array_search($row['conversationID'], $conversationIDs);
-                               unset($conversationIDs[$index]);
-                       }
-               }
-               
-               if (!empty($conversationIDs)) {
-                       return false;
-               }
-               
-               return true;
-       }
-       
-       /**
-        * Validates the participants.
-        * 
-        * @param       mixed           $participants
-        * @param       string          $field
-        * @param       integer[]       $existingParticipants
-        * @return      array           $result
-        * @throws      UserInputException
-        */
-       public static function validateParticipants($participants, $field = 'participants', array $existingParticipants = []) {
-               $result = [];
-               $error = [];
-               
-               // loop through participants and check their settings
-               $participantList = UserProfile::getUserProfilesByUsername((is_array($participants) ? $participants : ArrayUtil::trim(explode(',', $participants))));
-               
-               // load user storage at once to avoid multiple queries
-               $userIDs = [];
-               foreach ($participantList as $user) {
-                       if ($user) {
-                               $userIDs[] = $user->userID;
-                       }
-               }
-               UserStorageHandler::getInstance()->loadStorage($userIDs);
-               
-               foreach ($participantList as $participant => $user) {
-                       try {
-                               if ($user === null) {
-                                       throw new UserInputException($field, 'notFound');
-                               }
-                               
-                               // user is author
-                               if ($user->userID == WCF::getUser()->userID) {
-                                       throw new UserInputException($field, 'isAuthor');
-                               }
-                               else if (in_array($user->userID, $existingParticipants)) {
-                                       throw new UserInputException($field, 'duplicate');
-                               }
-                               
-                               // validate user
-                               self::validateParticipant($user, $field);
-                               
-                               // no error
-                               $existingParticipants[] = $result[] = $user->userID;
-                       }
-                       catch (UserInputException $e) {
-                               $error[] = ['type' => $e->getType(), 'username' => $participant];
-                       }
-               }
-               
-               if (!empty($error)) {
-                       throw new UserInputException($field, $error);
-               }
-               
-               return $result;
-       }
-       
-       /**
-        * Validates the group participants.
-        *
-        * @param       mixed           $participants
-        * @param       string          $field
-        * @param       integer[]       $existingParticipants
-        * @return      array           $result
-        */
-       public static function validateGroupParticipants($participants, $field = 'participants', array $existingParticipants = []) {
-               $groupIDs = is_array($participants) ? $participants : ArrayUtil::toIntegerArray(explode(',', $participants));
-               $validGroupIDs = [];
-               $result = [];
-               
-               foreach ($groupIDs as $groupID) {
-                       $group = UserGroup::getGroupByID($groupID);
-                       /** @noinspection PhpUndefinedFieldInspection */
-                       if ($group !== null && $group->canBeAddedAsConversationParticipant) {
-                               $validGroupIDs[] = $groupID;
-                       }
-               }
-               
-               if (!empty($validGroupIDs)) {
-                       $userIDs = [];
-                       $conditionBuilder = new PreparedStatementConditionBuilder();
-                       $conditionBuilder->add('groupID IN (?)', [$validGroupIDs]);
-                       $sql = "SELECT  DISTINCT userID
-                               FROM    wcf".WCF_N."_user_to_group
-                               ".$conditionBuilder;
-                       $statement = WCF::getDB()->prepareStatement($sql);
-                       $statement->execute($conditionBuilder->getParameters());
-                       while ($userID = $statement->fetchColumn()) $userIDs[] = $userID;
-                       
-                       if (!empty($userIDs)) {
-                               $users = UserProfileRuntimeCache::getInstance()->getObjects($userIDs);
-                               UserStorageHandler::getInstance()->loadStorage($userIDs);
-                               
-                               foreach ($users as $user) {
-                                       // user is author
-                                       if ($user->userID == WCF::getUser()->userID) {
-                                               continue;
-                                       }
-                                       else if (in_array($user->userID, $existingParticipants)) {
-                                               continue;
-                                       }
-                                       
-                                       try {
-                                               // validate user
-                                               self::validateParticipant($user, $field);
-                                               
-                                               // no error
-                                               $result[] = $user->userID;
-                                       }
-                                       catch (UserInputException $e) {}
-                               }
-                       }
-               }
-               
-               return $result;
-       }
-       
-       /**
-        * Validates the given participant.
-        * 
-        * @param       UserProfile     $user
-        * @param       string          $field
-        * @throws      UserInputException
-        */
-       public static function validateParticipant(UserProfile $user, $field = 'participants') {
-               // check participant's settings and permissions
-               if (!$user->getPermission('user.conversation.canUseConversation')) {
-                       throw new UserInputException($field, 'canNotUseConversation');
-               }
-               
-               if (!WCF::getSession()->getPermission('user.profile.cannotBeIgnored')) {
-                       // check if user wants to receive any conversations
-                       /** @noinspection PhpUndefinedFieldInspection */
-                       if ($user->canSendConversation == 2) {
-                               throw new UserInputException($field, 'doesNotAcceptConversation');
-                       }
-                       
-                       // check if user only wants to receive conversations by
-                       // users they are following and if the active user is followed
-                       // by the relevant user
-                       /** @noinspection PhpUndefinedFieldInspection */
-                       if ($user->canSendConversation == 1 && !$user->isFollowing(WCF::getUser()->userID)) {
-                               throw new UserInputException($field, 'doesNotAcceptConversation');
-                       }
-                       
-                       // active user is ignored by participant
-                       if ($user->isIgnoredUser(WCF::getUser()->userID)) {
-                               throw new UserInputException($field, 'ignoresYou');
-                       }
-                       
-                       // check participant's mailbox quota
-                       if (ConversationHandler::getInstance()->getConversationCount($user->userID) >= $user->getPermission('user.conversation.maxConversations')) {
-                               throw new UserInputException($field, 'mailboxIsFull');
-                       }
-               }
-       }
+            $statement = WCF::getDB()->prepareStatement($sql);
+            $statement->execute([
+                $this->conversationID,
+                WCF::getUser()->userID,
+            ]);
+            $leftAt = $statement->fetchSingleColumn();
+
+            $this->isActiveParticipant = ($leftAt !== false && $leftAt == 0);
+        }
+
+        return $this->isActiveParticipant;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getPopoverLinkClass()
+    {
+        return 'conversationLink';
+    }
+
+    /**
+     * Returns true if the given user id (default: current user) is participant
+     * of all given conversation ids.
+     *
+     * @param integer[] $conversationIDs
+     * @param integer $userID
+     * @return  boolean
+     */
+    public static function isParticipant(array $conversationIDs, $userID = null)
+    {
+        if ($userID === null) {
+            $userID = WCF::getUser()->userID;
+        }
+
+        // check if user is the initial author
+        $conditions = new PreparedStatementConditionBuilder();
+        $conditions->add("conversationID IN (?)", [$conversationIDs]);
+        $conditions->add("userID = ?", [$userID]);
+
+        $sql = "SELECT conversationID
+                       FROM    wcf" . WCF_N . "_conversation
+                       " . $conditions;
+        $statement = WCF::getDB()->prepareStatement($sql);
+        $statement->execute($conditions->getParameters());
+        while ($row = $statement->fetchArray()) {
+            $index = \array_search($row['conversationID'], $conversationIDs);
+            unset($conversationIDs[$index]);
+        }
+
+        // check for participation
+        if (!empty($conversationIDs)) {
+            $conditions = new PreparedStatementConditionBuilder();
+            $conditions->add("conversationID IN (?)", [$conversationIDs]);
+            $conditions->add("participantID = ?", [$userID]);
+            $conditions->add("hideConversation <> ?", [self::STATE_LEFT]);
+
+            $sql = "SELECT     conversationID
+                               FROM    wcf" . WCF_N . "_conversation_to_user
+                               " . $conditions;
+            $statement = WCF::getDB()->prepareStatement($sql);
+            $statement->execute($conditions->getParameters());
+            while ($row = $statement->fetchArray()) {
+                $index = \array_search($row['conversationID'], $conversationIDs);
+                unset($conversationIDs[$index]);
+            }
+        }
+
+        if (!empty($conversationIDs)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Validates the participants.
+     *
+     * @param mixed $participants
+     * @param string $field
+     * @param integer[] $existingParticipants
+     * @return  array       $result
+     * @throws  UserInputException
+     */
+    public static function validateParticipants(
+        $participants,
+        $field = 'participants',
+        array $existingParticipants = []
+    ) {
+        $result = [];
+        $error = [];
+
+        // loop through participants and check their settings
+        $participantList = UserProfile::getUserProfilesByUsername(
+            (\is_array($participants) ? $participants : ArrayUtil::trim(\explode(',', $participants)))
+        );
+
+        // load user storage at once to avoid multiple queries
+        $userIDs = [];
+        foreach ($participantList as $user) {
+            if ($user) {
+                $userIDs[] = $user->userID;
+            }
+        }
+        UserStorageHandler::getInstance()->loadStorage($userIDs);
+
+        foreach ($participantList as $participant => $user) {
+            try {
+                if ($user === null) {
+                    throw new UserInputException($field, 'notFound');
+                }
+
+                // user is author
+                if ($user->userID == WCF::getUser()->userID) {
+                    throw new UserInputException($field, 'isAuthor');
+                } elseif (\in_array($user->userID, $existingParticipants)) {
+                    throw new UserInputException($field, 'duplicate');
+                }
+
+                // validate user
+                self::validateParticipant($user, $field);
+
+                // no error
+                $existingParticipants[] = $result[] = $user->userID;
+            } catch (UserInputException $e) {
+                $error[] = ['type' => $e->getType(), 'username' => $participant];
+            }
+        }
+
+        if (!empty($error)) {
+            throw new UserInputException($field, $error);
+        }
+
+        return $result;
+    }
+
+    /**
+     * Validates the group participants.
+     *
+     * @param mixed $participants
+     * @param string $field
+     * @param integer[] $existingParticipants
+     * @return  array       $result
+     */
+    public static function validateGroupParticipants(
+        $participants,
+        $field = 'participants',
+        array $existingParticipants = []
+    ) {
+        $groupIDs = \is_array($participants) ? $participants : ArrayUtil::toIntegerArray(\explode(',', $participants));
+        $validGroupIDs = [];
+        $result = [];
+
+        foreach ($groupIDs as $groupID) {
+            $group = UserGroup::getGroupByID($groupID);
+            /** @noinspection PhpUndefinedFieldInspection */
+            if ($group !== null && $group->canBeAddedAsConversationParticipant) {
+                $validGroupIDs[] = $groupID;
+            }
+        }
+
+        if (!empty($validGroupIDs)) {
+            $userIDs = [];
+            $conditionBuilder = new PreparedStatementConditionBuilder();
+            $conditionBuilder->add('groupID IN (?)', [$validGroupIDs]);
+            $sql = "SELECT  DISTINCT userID
+                               FROM    wcf" . WCF_N . "_user_to_group
+                               " . $conditionBuilder;
+            $statement = WCF::getDB()->prepareStatement($sql);
+            $statement->execute($conditionBuilder->getParameters());
+            while ($userID = $statement->fetchColumn()) {
+                $userIDs[] = $userID;
+            }
+
+            if (!empty($userIDs)) {
+                $users = UserProfileRuntimeCache::getInstance()->getObjects($userIDs);
+                UserStorageHandler::getInstance()->loadStorage($userIDs);
+
+                foreach ($users as $user) {
+                    // user is author
+                    if ($user->userID == WCF::getUser()->userID) {
+                        continue;
+                    } elseif (\in_array($user->userID, $existingParticipants)) {
+                        continue;
+                    }
+
+                    try {
+                        // validate user
+                        self::validateParticipant($user, $field);
+
+                        // no error
+                        $result[] = $user->userID;
+                    } catch (UserInputException $e) {
+                    }
+                }
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * Validates the given participant.
+     *
+     * @param UserProfile $user
+     * @param string $field
+     * @throws  UserInputException
+     */
+    public static function validateParticipant(UserProfile $user, $field = 'participants')
+    {
+        // check participant's settings and permissions
+        if (!$user->getPermission('user.conversation.canUseConversation')) {
+            throw new UserInputException($field, 'canNotUseConversation');
+        }
+
+        if (!WCF::getSession()->getPermission('user.profile.cannotBeIgnored')) {
+            // check if user wants to receive any conversations
+            /** @noinspection PhpUndefinedFieldInspection */
+            if ($user->canSendConversation == 2) {
+                throw new UserInputException($field, 'doesNotAcceptConversation');
+            }
+
+            // check if user only wants to receive conversations by
+            // users they are following and if the active user is followed
+            // by the relevant user
+            /** @noinspection PhpUndefinedFieldInspection */
+            if ($user->canSendConversation == 1 && !$user->isFollowing(WCF::getUser()->userID)) {
+                throw new UserInputException($field, 'doesNotAcceptConversation');
+            }
+
+            // active user is ignored by participant
+            if ($user->isIgnoredUser(WCF::getUser()->userID)) {
+                throw new UserInputException($field, 'ignoresYou');
+            }
+
+            // check participant's mailbox quota
+            if (ConversationHandler::getInstance()->getConversationCount($user->userID) >= $user->getPermission('user.conversation.maxConversations')) {
+                throw new UserInputException($field, 'mailboxIsFull');
+            }
+        }
+    }
 }
index 27d01ca40f5a40106576a41515d4232204e862b7..a69056da74acd6eb2cbb78e9b3d55f47e4e1d940 100644 (file)
@@ -1,10 +1,12 @@
 <?php
+
 namespace wcf\data\conversation;
+
+use wcf\data\AbstractDatabaseObjectAction;
 use wcf\data\conversation\label\ConversationLabel;
 use wcf\data\conversation\message\ConversationMessageAction;
 use wcf\data\conversation\message\ConversationMessageList;
 use wcf\data\conversation\message\SimplifiedViewableConversationMessageList;
-use wcf\data\AbstractDatabaseObjectAction;
 use wcf\data\IClipboardAction;
 use wcf\data\IPopoverAction;
 use wcf\data\IVisitableObjectAction;
@@ -25,1061 +27,1184 @@ use wcf\system\WCF;
 
 /**
  * Executes conversation-related actions.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\Data\Conversation
- * 
- * @method     ConversationEditor[]    getObjects()
- * @method     ConversationEditor      getSingleObject()
+ *
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Data\Conversation
+ *
+ * @method  ConversationEditor[]    getObjects()
+ * @method  ConversationEditor  getSingleObject()
  */
-class ConversationAction extends AbstractDatabaseObjectAction implements IClipboardAction, IPopoverAction, IVisitableObjectAction {
-       /**
-        * @inheritDoc
-        */
-       protected $className = ConversationEditor::class;
-       
-       /**
-        * conversation object
-        * @var ConversationEditor
-        */
-       public $conversation;
-       
-       /**
-        * list of conversation data modifications
-        * @var mixed[][]
-        */
-       protected $conversationData = [];
-       
-       /** @noinspection PhpMissingParentCallCommonInspection */
-       /**
-        * @inheritDoc
-        * @return      Conversation
-        */
-       public function create() {
-               // create conversation
-               $data = $this->parameters['data'];
-               $data['lastPosterID'] = $data['userID'];
-               $data['lastPoster'] = $data['username'];
-               $data['lastPostTime'] = $data['time'];
-               // count participants
-               if (!empty($this->parameters['participants'])) {
-                       $data['participants'] = count($this->parameters['participants']);
-               }
-               // count attachments
-               if (isset($this->parameters['attachmentHandler']) && $this->parameters['attachmentHandler'] !== null) {
-                       $data['attachments'] = count($this->parameters['attachmentHandler']);
-               }
-               $conversation = call_user_func([$this->className, 'create'], $data);
-               $conversationEditor = new ConversationEditor($conversation);
-               
-               if (!$conversation->isDraft) {
-                       // save participants
-                       $conversationEditor->updateParticipants(
-                               (!empty($this->parameters['participants']) ? $this->parameters['participants'] : []),
-                               (!empty($this->parameters['invisibleParticipants']) ? $this->parameters['invisibleParticipants'] : []),
-                               'all'
-                       );
-                       
-                       // add author
-                       if ($data['userID'] !== null) {
-                               $conversationEditor->updateParticipants([$data['userID']], [], 'all');
-                       }
-                       
-                       // update conversation count
-                       UserStorageHandler::getInstance()->reset($conversation->getParticipantIDs(), 'conversationCount');
-                       
-                       // mark conversation as read for the author
-                       $sql = "UPDATE  wcf".WCF_N."_conversation_to_user
+class ConversationAction extends AbstractDatabaseObjectAction implements
+    IClipboardAction,
+    IPopoverAction,
+    IVisitableObjectAction
+{
+    /**
+     * @inheritDoc
+     */
+    protected $className = ConversationEditor::class;
+
+    /**
+     * conversation object
+     * @var ConversationEditor
+     */
+    public $conversation;
+
+    /**
+     * list of conversation data modifications
+     * @var mixed[][]
+     */
+    protected $conversationData = [];
+
+    /** @noinspection PhpMissingParentCallCommonInspection */
+
+    /**
+     * @inheritDoc
+     * @return  Conversation
+     */
+    public function create()
+    {
+        // create conversation
+        $data = $this->parameters['data'];
+        $data['lastPosterID'] = $data['userID'];
+        $data['lastPoster'] = $data['username'];
+        $data['lastPostTime'] = $data['time'];
+        // count participants
+        if (!empty($this->parameters['participants'])) {
+            $data['participants'] = \count($this->parameters['participants']);
+        }
+        // count attachments
+        if (isset($this->parameters['attachmentHandler']) && $this->parameters['attachmentHandler'] !== null) {
+            $data['attachments'] = \count($this->parameters['attachmentHandler']);
+        }
+        $conversation = \call_user_func([$this->className, 'create'], $data);
+        $conversationEditor = new ConversationEditor($conversation);
+
+        if (!$conversation->isDraft) {
+            // save participants
+            $conversationEditor->updateParticipants(
+                (!empty($this->parameters['participants']) ? $this->parameters['participants'] : []),
+                (!empty($this->parameters['invisibleParticipants']) ? $this->parameters['invisibleParticipants'] : []),
+                'all'
+            );
+
+            // add author
+            if ($data['userID'] !== null) {
+                $conversationEditor->updateParticipants([$data['userID']], [], 'all');
+            }
+
+            // update conversation count
+            UserStorageHandler::getInstance()->reset($conversation->getParticipantIDs(), 'conversationCount');
+
+            // mark conversation as read for the author
+            $sql = "UPDATE     wcf" . WCF_N . "_conversation_to_user
                                SET     lastVisitTime = ?
                                WHERE   participantID = ?
                                        AND conversationID = ?";
-                       $statement = WCF::getDB()->prepareStatement($sql);
-                       $statement->execute([$data['time'], $data['userID'], $conversation->conversationID]);
-               }
-               else {
-                       // update conversation count
-                       UserStorageHandler::getInstance()->reset([$data['userID']], 'conversationCount');
-               }
-               
-               // update participant summary
-               $conversationEditor->updateParticipantSummary();
-               
-               // create message
-               $messageData = $this->parameters['messageData'];
-               $messageData['conversationID'] = $conversation->conversationID;
-               $messageData['time'] = $this->parameters['data']['time'];
-               $messageData['userID'] = $this->parameters['data']['userID'];
-               $messageData['username'] = $this->parameters['data']['username'];
-               
-               $messageAction = new ConversationMessageAction([], 'create', [
-                       'data' => $messageData,
-                       'conversation' => $conversation,
-                       'isFirstPost' => true,
-                       'attachmentHandler' => isset($this->parameters['attachmentHandler']) ? $this->parameters['attachmentHandler'] : null,
-                       'htmlInputProcessor' => isset($this->parameters['htmlInputProcessor']) ? $this->parameters['htmlInputProcessor'] : null
-               ]);
-               $resultValues = $messageAction->executeAction();
-               
-               // update first message id
-               $conversationEditor->update([
-                       'firstMessageID' => $resultValues['returnValues']->messageID
-               ]);
-               
-               $conversation->setFirstMessage($resultValues['returnValues']);
-               if (!$conversation->isDraft) {
-                       // fire notification event
-                       $notificationRecipients = array_merge((!empty($this->parameters['participants']) ? $this->parameters['participants'] : []), (!empty($this->parameters['invisibleParticipants']) ? $this->parameters['invisibleParticipants'] : []));
-                       UserNotificationHandler::getInstance()->fireEvent(
-                               'conversation',
-                               'com.woltlab.wcf.conversation.notification',
-                               new ConversationUserNotificationObject($conversation),
-                               $notificationRecipients
-                       );
-               }
-               
-               return $conversation;
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function delete() {
-               // deletes messages
-               $messageList = new ConversationMessageList();
-               $messageList->getConditionBuilder()->add('conversation_message.conversationID IN (?)', [$this->objectIDs]);
-               $messageList->readObjectIDs();
-               $action = new ConversationMessageAction($messageList->getObjectIDs(), 'delete');
-               $action->executeAction();
-               
-               // get the list of participants in order to reset the 'unread conversation'-counter
-               $participantIDs = [];
-               if (!empty($this->objectIDs)) {
-                       $conditions = new PreparedStatementConditionBuilder();
-                       $conditions->add("conversationID IN (?)", [$this->objectIDs]);
-                       $sql = "SELECT  DISTINCT participantID
+            $statement = WCF::getDB()->prepareStatement($sql);
+            $statement->execute([$data['time'], $data['userID'], $conversation->conversationID]);
+        } else {
+            // update conversation count
+            UserStorageHandler::getInstance()->reset([$data['userID']], 'conversationCount');
+        }
+
+        // update participant summary
+        $conversationEditor->updateParticipantSummary();
+
+        // create message
+        $messageData = $this->parameters['messageData'];
+        $messageData['conversationID'] = $conversation->conversationID;
+        $messageData['time'] = $this->parameters['data']['time'];
+        $messageData['userID'] = $this->parameters['data']['userID'];
+        $messageData['username'] = $this->parameters['data']['username'];
+
+        $messageAction = new ConversationMessageAction([], 'create', [
+            'data' => $messageData,
+            'conversation' => $conversation,
+            'isFirstPost' => true,
+            'attachmentHandler' => $this->parameters['attachmentHandler'] ?? null,
+            'htmlInputProcessor' => $this->parameters['htmlInputProcessor'] ?? null,
+        ]);
+        $resultValues = $messageAction->executeAction();
+
+        // update first message id
+        $conversationEditor->update([
+            'firstMessageID' => $resultValues['returnValues']->messageID,
+        ]);
+
+        $conversation->setFirstMessage($resultValues['returnValues']);
+        if (!$conversation->isDraft) {
+            // fire notification event
+            $notificationRecipients = \array_merge(
+                (!empty($this->parameters['participants']) ? $this->parameters['participants'] : [])(!empty($this->parameters['invisibleParticipants']) ? $this->parameters['invisibleParticipants'] : [])
+            );
+            UserNotificationHandler::getInstance()->fireEvent(
+                'conversation',
+                'com.woltlab.wcf.conversation.notification',
+                new ConversationUserNotificationObject($conversation),
+                $notificationRecipients
+            );
+        }
+
+        return $conversation;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function delete()
+    {
+        // deletes messages
+        $messageList = new ConversationMessageList();
+        $messageList->getConditionBuilder()->add('conversation_message.conversationID IN (?)', [$this->objectIDs]);
+        $messageList->readObjectIDs();
+        $action = new ConversationMessageAction($messageList->getObjectIDs(), 'delete');
+        $action->executeAction();
+
+        // get the list of participants in order to reset the 'unread conversation'-counter
+        $participantIDs = [];
+        if (!empty($this->objectIDs)) {
+            $conditions = new PreparedStatementConditionBuilder();
+            $conditions->add("conversationID IN (?)", [$this->objectIDs]);
+            $sql = "SELECT  DISTINCT participantID
                        FROM    wcf" . WCF_N . "_conversation_to_user
                        " . $conditions;
-                       $statement = WCF::getDB()->prepareStatement($sql);
-                       $statement->execute($conditions->getParameters());
-                       
-                       while ($participantID = $statement->fetchColumn()) {
-                               $participantIDs[] = $participantID;
-                       }
-               }
-               
-               // delete conversations
-               parent::delete();
-               
-               if (!empty($this->objectIDs)) {
-                       // delete notifications
-                       UserNotificationHandler::getInstance()->removeNotifications('com.woltlab.wcf.conversation.notification', $this->objectIDs);
-                       
-                       // remove modification logs
-                       ConversationModificationLogHandler::getInstance()->deleteLogs($this->objectIDs);
-                       
-                       // reset the number of unread conversations
-                       if (!empty($participantIDs)) {
-                               UserStorageHandler::getInstance()->reset($participantIDs, 'unreadConversationCount');
-                       }
-               }
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function update() {
-               if (!isset($this->parameters['participants'])) $this->parameters['participants'] = [];
-               if (!isset($this->parameters['invisibleParticipants'])) $this->parameters['invisibleParticipants'] = [];
-               
-               // count participants
-               if (!empty($this->parameters['participants'])) {
-                       $this->parameters['data']['participants'] = count($this->parameters['participants']);
-               }
-               
-               parent::update();
-               
-               foreach ($this->getObjects() as $conversation) {
-                       // participants
-                       if (!empty($this->parameters['participants']) || !empty($this->parameters['invisibleParticipants'])) {
-                               // get current participants
-                               $participantIDs = $conversation->getParticipantIDs();
-                               
-                               $conversation->updateParticipants(
-                                       (!empty($this->parameters['participants']) ? $this->parameters['participants'] : []),
-                                       (!empty($this->parameters['invisibleParticipants']) ? $this->parameters['invisibleParticipants'] : []),
-                                       (!empty($this->parameters['visibility']) ? $this->parameters['visibility'] : 'all')
-                               );
-                               $conversation->updateParticipantSummary();
-                               
-                               // check if new participants have been added
-                               $newParticipantIDs = array_diff(array_merge($this->parameters['participants'], $this->parameters['invisibleParticipants']), $participantIDs);
-                               if (!empty($newParticipantIDs)) {
-                                       // update conversation count
-                                       UserStorageHandler::getInstance()->reset($newParticipantIDs, 'unreadConversationCount');
-                                       UserStorageHandler::getInstance()->reset($newParticipantIDs, 'conversationCount');
-                                       
-                                       // fire notification event
-                                       UserNotificationHandler::getInstance()->fireEvent(
-                                               'conversation',
-                                               'com.woltlab.wcf.conversation.notification',
-                                               new ConversationUserNotificationObject($conversation->getDecoratedObject()),
-                                               $newParticipantIDs
-                                       );
-                               }
-                       }
-                       
-                       // draft status
-                       if (isset($this->parameters['data']['isDraft'])) {
-                               if ($conversation->isDraft && !$this->parameters['data']['isDraft']) {
-                                       // add author
-                                       $conversation->updateParticipants([$conversation->userID], [], 'all');
-                                       
-                                       // update conversation count
-                                       UserStorageHandler::getInstance()->reset($conversation->getParticipantIDs(), 'unreadConversationCount');
-                                       UserStorageHandler::getInstance()->reset($conversation->getParticipantIDs(), 'conversationCount');
-                               }
-                       }
-               }
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function markAsRead() {
-               if (empty($this->parameters['visitTime'])) {
-                       $this->parameters['visitTime'] = TIME_NOW;
-               }
-
-               // in case this is a call via PHP and the userID parameter is missing, set it to the userID of the current user
-               if (!isset($this->parameters['userID'])) {
-                       $this->parameters['userID'] = WCF::getUser()->userID;
-               }
-               
-               if (empty($this->objects)) {
-                       $this->readObjects();
-               }
-               
-               $conversationIDs = [];
-               $sql = "UPDATE  wcf".WCF_N."_conversation_to_user
+            $statement = WCF::getDB()->prepareStatement($sql);
+            $statement->execute($conditions->getParameters());
+
+            while ($participantID = $statement->fetchColumn()) {
+                $participantIDs[] = $participantID;
+            }
+        }
+
+        // delete conversations
+        parent::delete();
+
+        if (!empty($this->objectIDs)) {
+            // delete notifications
+            UserNotificationHandler::getInstance()
+                ->removeNotifications('com.woltlab.wcf.conversation.notification', $this->objectIDs);
+
+            // remove modification logs
+            ConversationModificationLogHandler::getInstance()->deleteLogs($this->objectIDs);
+
+            // reset the number of unread conversations
+            if (!empty($participantIDs)) {
+                UserStorageHandler::getInstance()->reset($participantIDs, 'unreadConversationCount');
+            }
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function update()
+    {
+        if (!isset($this->parameters['participants'])) {
+            $this->parameters['participants'] = [];
+        }
+        if (!isset($this->parameters['invisibleParticipants'])) {
+            $this->parameters['invisibleParticipants'] = [];
+        }
+
+        // count participants
+        if (!empty($this->parameters['participants'])) {
+            $this->parameters['data']['participants'] = \count($this->parameters['participants']);
+        }
+
+        parent::update();
+
+        foreach ($this->getObjects() as $conversation) {
+            // participants
+            if (!empty($this->parameters['participants']) || !empty($this->parameters['invisibleParticipants'])) {
+                // get current participants
+                $participantIDs = $conversation->getParticipantIDs();
+
+                $conversation->updateParticipants(
+                    (!empty($this->parameters['participants']) ? $this->parameters['participants'] : []),
+                    (!empty($this->parameters['invisibleParticipants']) ? $this->parameters['invisibleParticipants'] : []),
+                    (!empty($this->parameters['visibility']) ? $this->parameters['visibility'] : 'all')
+                );
+                $conversation->updateParticipantSummary();
+
+                // check if new participants have been added
+                $newParticipantIDs = \array_diff(\array_merge(
+                    $this->parameters['participants'],
+                    $this->parameters['invisibleParticipants']
+                ), $participantIDs);
+                if (!empty($newParticipantIDs)) {
+                    // update conversation count
+                    UserStorageHandler::getInstance()->reset($newParticipantIDs, 'unreadConversationCount');
+                    UserStorageHandler::getInstance()->reset($newParticipantIDs, 'conversationCount');
+
+                    // fire notification event
+                    UserNotificationHandler::getInstance()->fireEvent(
+                        'conversation',
+                        'com.woltlab.wcf.conversation.notification',
+                        new ConversationUserNotificationObject($conversation->getDecoratedObject()),
+                        $newParticipantIDs
+                    );
+                }
+            }
+
+            // draft status
+            if (isset($this->parameters['data']['isDraft'])) {
+                if ($conversation->isDraft && !$this->parameters['data']['isDraft']) {
+                    // add author
+                    $conversation->updateParticipants([$conversation->userID], [], 'all');
+
+                    // update conversation count
+                    UserStorageHandler::getInstance()
+                        ->reset($conversation->getParticipantIDs(), 'unreadConversationCount');
+                    UserStorageHandler::getInstance()
+                        ->reset($conversation->getParticipantIDs(), 'conversationCount');
+                }
+            }
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function markAsRead()
+    {
+        if (empty($this->parameters['visitTime'])) {
+            $this->parameters['visitTime'] = TIME_NOW;
+        }
+
+        // in case this is a call via PHP and the userID parameter is missing, set it to the userID of the current user
+        if (!isset($this->parameters['userID'])) {
+            $this->parameters['userID'] = WCF::getUser()->userID;
+        }
+
+        if (empty($this->objects)) {
+            $this->readObjects();
+        }
+
+        $conversationIDs = [];
+        $sql = "UPDATE wcf" . WCF_N . "_conversation_to_user
                        SET     lastVisitTime = ?
                        WHERE   participantID = ?
                                AND conversationID = ?";
-               $statement = WCF::getDB()->prepareStatement($sql);
-               WCF::getDB()->beginTransaction();
-               foreach ($this->getObjects() as $conversation) {
-                       $statement->execute([
-                               $this->parameters['visitTime'],
-                               $this->parameters['userID'],
-                               $conversation->conversationID
-                       ]);
-                       $conversationIDs[] = $conversation->conversationID;
-               }
-               WCF::getDB()->commitTransaction();
-               
-               // reset storage
-               UserStorageHandler::getInstance()->reset([$this->parameters['userID']], 'unreadConversationCount');
-               
-               // mark notifications as confirmed
-               if (!empty($conversationIDs)) {
-                       // conversation start notification
-                       $conditionBuilder = new PreparedStatementConditionBuilder();
-                       $conditionBuilder->add('notification.eventID = ?', [UserNotificationHandler::getInstance()->getEvent('com.woltlab.wcf.conversation.notification', 'conversation')->eventID]);
-                       $conditionBuilder->add('notification.objectID = conversation.conversationID');
-                       $conditionBuilder->add('notification.userID = ?', [$this->parameters['userID']]);
-                       $conditionBuilder->add('conversation.conversationID IN (?)', [$conversationIDs]);
-                       $conditionBuilder->add('conversation.time <= ?', [$this->parameters['visitTime']]);
-                       
-                       $sql = "SELECT          conversation.conversationID
-                               FROM            wcf".WCF_N."_conversation conversation,
-                                               wcf".WCF_N."_user_notification notification
-                               ".$conditionBuilder;
-                       $statement = WCF::getDB()->prepareStatement($sql);
-                       $statement->execute($conditionBuilder->getParameters());
-                       $notificationObjectIDs = $statement->fetchAll(\PDO::FETCH_COLUMN);
-                       
-                       if (!empty($notificationObjectIDs)) {
-                               UserNotificationHandler::getInstance()->markAsConfirmed('conversation', 'com.woltlab.wcf.conversation.notification', [$this->parameters['userID']], $notificationObjectIDs);
-                       }
-                       
-                       // conversation reply notification
-                       $conditionBuilder = new PreparedStatementConditionBuilder();
-                       $conditionBuilder->add('notification.eventID = ?', [UserNotificationHandler::getInstance()->getEvent('com.woltlab.wcf.conversation.message.notification', 'conversationMessage')->eventID]);
-                       $conditionBuilder->add('notification.objectID = conversation_message.messageID');
-                       $conditionBuilder->add('notification.userID = ?', [$this->parameters['userID']]);
-                       $conditionBuilder->add('conversation_message.conversationID IN (?)', [$conversationIDs]);
-                       $conditionBuilder->add('conversation_message.time <= ?', [$this->parameters['visitTime']]);
-                       
-                       $sql = "SELECT          conversation_message.messageID
-                               FROM            wcf".WCF_N."_conversation_message conversation_message,
-                                               wcf".WCF_N."_user_notification notification
-                               ".$conditionBuilder;
-                       $statement = WCF::getDB()->prepareStatement($sql);
-                       $statement->execute($conditionBuilder->getParameters());
-                       $notificationObjectIDs = $statement->fetchAll(\PDO::FETCH_COLUMN);
-                       
-                       if (!empty($notificationObjectIDs)) {
-                               UserNotificationHandler::getInstance()->markAsConfirmed('conversationMessage', 'com.woltlab.wcf.conversation.message.notification', [$this->parameters['userID']], $notificationObjectIDs);
-                       }
-               }
-               
-               if (!empty($conversationIDs)) {
-                       $this->unmarkItems($conversationIDs);
-               }
-               
-               $returnValues = [
-                       'totalCount' => ConversationHandler::getInstance()->getUnreadConversationCount($this->parameters['userID'], true)
-               ];
-               
-               if (count($conversationIDs) == 1) {
-                       $returnValues['markAsRead'] = reset($conversationIDs);
-               }
-               
-               return $returnValues;
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function validateMarkAsRead() {
-               // visitTime might not be in the future
-               if (isset($this->parameters['visitTime'])) {
-                       $this->parameters['visitTime'] = intval($this->parameters['visitTime']);
-                       if ($this->parameters['visitTime'] > TIME_NOW) {
-                               $this->parameters['visitTime'] = TIME_NOW;
-                       }
-               }
-
-               // userID should always be equal to the userID of the current user when called via AJAX
-               $this->parameters['userID'] = WCF::getUser()->userID;
-               
-               if (empty($this->objects)) {
-                       $this->readObjects();
-               }
-               
-               // check participation
-               $conversationIDs = [];
-               foreach ($this->getObjects() as $conversation) {
-                       $conversationIDs[] = $conversation->conversationID;
-               }
-               
-               if (empty($conversationIDs)) {
-                       throw new UserInputException('objectIDs');
-               }
-               
-               if (!Conversation::isParticipant($conversationIDs)) {
-                       throw new PermissionDeniedException();
-               }
-       }
-       
-       /**
-        * Marks all conversations as read.
-        */
-       public function markAllAsRead() {
-               $sql = "UPDATE  wcf".WCF_N."_conversation_to_user
+        $statement = WCF::getDB()->prepareStatement($sql);
+        WCF::getDB()->beginTransaction();
+        foreach ($this->getObjects() as $conversation) {
+            $statement->execute([
+                $this->parameters['visitTime'],
+                $this->parameters['userID'],
+                $conversation->conversationID,
+            ]);
+            $conversationIDs[] = $conversation->conversationID;
+        }
+        WCF::getDB()->commitTransaction();
+
+        // reset storage
+        UserStorageHandler::getInstance()->reset([$this->parameters['userID']], 'unreadConversationCount');
+
+        // mark notifications as confirmed
+        if (!empty($conversationIDs)) {
+            // conversation start notification
+            $conditionBuilder = new PreparedStatementConditionBuilder();
+            $conditionBuilder->add('notification.eventID = ?', [
+                UserNotificationHandler::getInstance()
+                    ->getEvent('com.woltlab.wcf.conversation.notification', 'conversation')
+                    ->eventID,
+            ]);
+            $conditionBuilder->add('notification.objectID = conversation.conversationID');
+            $conditionBuilder->add('notification.userID = ?', [$this->parameters['userID']]);
+            $conditionBuilder->add('conversation.conversationID IN (?)', [$conversationIDs]);
+            $conditionBuilder->add('conversation.time <= ?', [$this->parameters['visitTime']]);
+
+            $sql = "SELECT             conversation.conversationID
+                               FROM            wcf" . WCF_N . "_conversation conversation,
+                                               wcf" . WCF_N . "_user_notification notification
+                               " . $conditionBuilder;
+            $statement = WCF::getDB()->prepareStatement($sql);
+            $statement->execute($conditionBuilder->getParameters());
+            $notificationObjectIDs = $statement->fetchAll(\PDO::FETCH_COLUMN);
+
+            if (!empty($notificationObjectIDs)) {
+                UserNotificationHandler::getInstance()->markAsConfirmed(
+                    'conversation',
+                    'com.woltlab.wcf.conversation.notification',
+                    [$this->parameters['userID']],
+                    $notificationObjectIDs
+                );
+            }
+
+            // conversation reply notification
+            $conditionBuilder = new PreparedStatementConditionBuilder();
+            $conditionBuilder->add('notification.eventID = ?', [
+                UserNotificationHandler::getInstance()
+                    ->getEvent('com.woltlab.wcf.conversation.message.notification', 'conversationMessage')
+                    ->eventID,
+            ]);
+            $conditionBuilder->add('notification.objectID = conversation_message.messageID');
+            $conditionBuilder->add('notification.userID = ?', [$this->parameters['userID']]);
+            $conditionBuilder->add('conversation_message.conversationID IN (?)', [$conversationIDs]);
+            $conditionBuilder->add('conversation_message.time <= ?', [$this->parameters['visitTime']]);
+
+            $sql = "SELECT             conversation_message.messageID
+                               FROM            wcf" . WCF_N . "_conversation_message conversation_message,
+                                               wcf" . WCF_N . "_user_notification notification
+                               " . $conditionBuilder;
+            $statement = WCF::getDB()->prepareStatement($sql);
+            $statement->execute($conditionBuilder->getParameters());
+            $notificationObjectIDs = $statement->fetchAll(\PDO::FETCH_COLUMN);
+
+            if (!empty($notificationObjectIDs)) {
+                UserNotificationHandler::getInstance()->markAsConfirmed(
+                    'conversationMessage',
+                    'com.woltlab.wcf.conversation.message.notification',
+                    [$this->parameters['userID']],
+                    $notificationObjectIDs
+                );
+            }
+        }
+
+        if (!empty($conversationIDs)) {
+            $this->unmarkItems($conversationIDs);
+        }
+
+        $returnValues = [
+            'totalCount' => ConversationHandler::getInstance()
+                ->getUnreadConversationCount($this->parameters['userID'], true),
+        ];
+
+        if (\count($conversationIDs) == 1) {
+            $returnValues['markAsRead'] = \reset($conversationIDs);
+        }
+
+        return $returnValues;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function validateMarkAsRead()
+    {
+        // visitTime might not be in the future
+        if (isset($this->parameters['visitTime'])) {
+            $this->parameters['visitTime'] = \intval($this->parameters['visitTime']);
+            if ($this->parameters['visitTime'] > TIME_NOW) {
+                $this->parameters['visitTime'] = TIME_NOW;
+            }
+        }
+
+        // userID should always be equal to the userID of the current user when called via AJAX
+        $this->parameters['userID'] = WCF::getUser()->userID;
+
+        if (empty($this->objects)) {
+            $this->readObjects();
+        }
+
+        // check participation
+        $conversationIDs = [];
+        foreach ($this->getObjects() as $conversation) {
+            $conversationIDs[] = $conversation->conversationID;
+        }
+
+        if (empty($conversationIDs)) {
+            throw new UserInputException('objectIDs');
+        }
+
+        if (!Conversation::isParticipant($conversationIDs)) {
+            throw new PermissionDeniedException();
+        }
+    }
+
+    /**
+     * Marks all conversations as read.
+     */
+    public function markAllAsRead()
+    {
+        $sql = "UPDATE wcf" . WCF_N . "_conversation_to_user
                        SET     lastVisitTime = ?
                        WHERE   participantID = ?";
-               $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute([
-                       TIME_NOW,
-                       WCF::getUser()->userID
-               ]);
-               
-               // reset storage
-               UserStorageHandler::getInstance()->reset([WCF::getUser()->userID], 'unreadConversationCount');
-               
-               // confirm obsolete notifications
-               UserNotificationHandler::getInstance()->markAsConfirmed('conversation', 'com.woltlab.wcf.conversation.notification', [WCF::getUser()->userID]);
-               UserNotificationHandler::getInstance()->markAsConfirmed('conversationMessage', 'com.woltlab.wcf.conversation.message.notification', [WCF::getUser()->userID]);
-               
-               return [
-                       'markAllAsRead' => true
-               ];
-       }
-       
-       /**
-        * Validates the markAllAsRead action.
-        */
-       public function validateMarkAllAsRead() {
-               // does nothing
-       }
-       
-       /**
-        * Validates user access for label management.
-        * 
-        * @throws      PermissionDeniedException
-        */
-       public function validateGetLabelManagement() {
-               if (!WCF::getSession()->getPermission('user.conversation.canUseConversation')) {
-                       throw new PermissionDeniedException();
-               }
-       }
-       
-       /**
-        * Returns the conversation label management.
-        * 
-        * @return      array
-        */
-       public function getLabelManagement() {
-               WCF::getTPL()->assign([
-                       'cssClassNames' => ConversationLabel::getLabelCssClassNames(),
-                       'labelList' => ConversationLabel::getLabelsByUser()
-               ]);
-               
-               return [
-                       'actionName' => 'getLabelManagement',
-                       'template' => WCF::getTPL()->fetch('conversationLabelManagement'),
-                       'maxLabels' => WCF::getSession()->getPermission('user.conversation.maxLabels'),
-                       'labelCount' => count(ConversationLabel::getLabelsByUser())
-               ];
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function validateGetPopover() {
-               $this->conversation = $this->getSingleObject();
-               if (!Conversation::isParticipant([$this->conversation->conversationID])) {
-                       throw new PermissionDeniedException();
-               }
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getPopover() {
-               $messageList = new SimplifiedViewableConversationMessageList();
-               $messageList->getConditionBuilder()->add("conversation_message.messageID = ?", [$this->conversation->firstMessageID]);
-               $messageList->readObjects();
-               
-               return [
-                       'template' => WCF::getTPL()->fetch('conversationMessagePreview', 'wcf', [
-                               'message' => $messageList->getSingleObject(),
-                       ]),
-               ];
-       }
-       
-       /**
-        * Validates the get message preview action.
-        * 
-        * @throws      PermissionDeniedException
-        * @deprecated  5.3     Use `validateGetPopover()` instead.
-        */
-       public function validateGetMessagePreview() {
-               $this->validateGetPopover();
-       }
-       
-       /**
-        * Returns a preview of a message in a specific conversation.
-        * 
-        * @return      string[]
-        * @deprecated  5.3     Use `getPopover()` instead.
-        */
-       public function getMessagePreview() {
-               return $this->getPopover();
-       }
-       
-       /**
-        * Validates parameters to close conversations.
-        * 
-        * @throws      PermissionDeniedException
-        * @throws      UserInputException
-        */
-       public function validateClose() {
-               // read objects
-               if (empty($this->objects)) {
-                       $this->readObjects();
-                       
-                       if (empty($this->objects)) {
-                               throw new UserInputException('objectIDs');
-                       }
-               }
-               
-               // validate ownership
-               foreach ($this->getObjects() as $conversation) {
-                       if ($conversation->isClosed || ($conversation->userID != WCF::getUser()->userID)) {
-                               throw new PermissionDeniedException();
-                       }
-               }
-       }
-       
-       /**
-        * Closes conversations.
-        * 
-        * @return      mixed[][]
-        */
-       public function close() {
-               foreach ($this->getObjects() as $conversation) {
-                       $conversation->update(['isClosed' => 1]);
-                       $this->addConversationData($conversation->getDecoratedObject(), 'isClosed', 1);
-                       
-                       ConversationModificationLogHandler::getInstance()->close($conversation->getDecoratedObject());
-               }
-               
-               $this->unmarkItems();
-               
-               return $this->getConversationData();
-       }
-       
-       /**
-        * Validates parameters to open conversations.
-        * 
-        * @throws      PermissionDeniedException
-        * @throws      UserInputException
-        */
-       public function validateOpen() {
-               // read objects
-               if (empty($this->objects)) {
-                       $this->readObjects();
-                       
-                       if (empty($this->objects)) {
-                               throw new UserInputException('objectIDs');
-                       }
-               }
-               
-               // validate ownership
-               foreach ($this->getObjects() as $conversation) {
-                       if (!$conversation->isClosed || ($conversation->userID != WCF::getUser()->userID)) {
-                               throw new PermissionDeniedException();
-                       }
-               }
-       }
-       
-       /**
-        * Opens conversations.
-        * 
-        * @return      mixed[][]
-        */
-       public function open() {
-               foreach ($this->getObjects() as $conversation) {
-                       $conversation->update(['isClosed' => 0]);
-                       $this->addConversationData($conversation->getDecoratedObject(), 'isClosed', 0);
-                       
-                       ConversationModificationLogHandler::getInstance()->open($conversation->getDecoratedObject());
-               }
-               
-               $this->unmarkItems();
-               
-               return $this->getConversationData();
-       }
-       
-       /**
-        * Validates conversations for leave form.
-        * 
-        * @throws      PermissionDeniedException
-        * @throws      UserInputException
-        */
-       public function validateGetLeaveForm() {
-               if (empty($this->objectIDs)) {
-                       throw new UserInputException('objectIDs');
-               }
-               
-               // validate participation
-               if (!Conversation::isParticipant($this->objectIDs)) {
-                       throw new PermissionDeniedException();
-               }
-       }
-       
-       /**
-        * Returns dialog form to leave conversations.
-        * 
-        * @return      array
-        */
-       public function getLeaveForm() {
-               // get hidden state from first conversation (all others have the same state)
-               $sql = "SELECT  hideConversation
-                       FROM    wcf".WCF_N."_conversation_to_user
+        $statement = WCF::getDB()->prepareStatement($sql);
+        $statement->execute([
+            TIME_NOW,
+            WCF::getUser()->userID,
+        ]);
+
+        // reset storage
+        UserStorageHandler::getInstance()->reset([WCF::getUser()->userID], 'unreadConversationCount');
+
+        // confirm obsolete notifications
+        UserNotificationHandler::getInstance()->markAsConfirmed(
+            'conversation',
+            'com.woltlab.wcf.conversation.notification',
+            [WCF::getUser()->userID]
+        );
+        UserNotificationHandler::getInstance()->markAsConfirmed(
+            'conversationMessage',
+            'com.woltlab.wcf.conversation.message.notification',
+            [WCF::getUser()->userID]
+        );
+
+        return [
+            'markAllAsRead' => true,
+        ];
+    }
+
+    /**
+     * Validates the markAllAsRead action.
+     */
+    public function validateMarkAllAsRead()
+    {
+        // does nothing
+    }
+
+    /**
+     * Validates user access for label management.
+     *
+     * @throws  PermissionDeniedException
+     */
+    public function validateGetLabelManagement()
+    {
+        if (!WCF::getSession()->getPermission('user.conversation.canUseConversation')) {
+            throw new PermissionDeniedException();
+        }
+    }
+
+    /**
+     * Returns the conversation label management.
+     *
+     * @return  array
+     */
+    public function getLabelManagement()
+    {
+        WCF::getTPL()->assign([
+            'cssClassNames' => ConversationLabel::getLabelCssClassNames(),
+            'labelList' => ConversationLabel::getLabelsByUser(),
+        ]);
+
+        return [
+            'actionName' => 'getLabelManagement',
+            'template' => WCF::getTPL()->fetch('conversationLabelManagement'),
+            'maxLabels' => WCF::getSession()->getPermission('user.conversation.maxLabels'),
+            'labelCount' => \count(ConversationLabel::getLabelsByUser()),
+        ];
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function validateGetPopover()
+    {
+        $this->conversation = $this->getSingleObject();
+        if (!Conversation::isParticipant([$this->conversation->conversationID])) {
+            throw new PermissionDeniedException();
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getPopover()
+    {
+        $messageList = new SimplifiedViewableConversationMessageList();
+        $messageList->getConditionBuilder()
+            ->add("conversation_message.messageID = ?", [$this->conversation->firstMessageID]);
+        $messageList->readObjects();
+
+        return [
+            'template' => WCF::getTPL()->fetch('conversationMessagePreview', 'wcf', [
+                'message' => $messageList->getSingleObject(),
+            ]),
+        ];
+    }
+
+    /**
+     * Validates the get message preview action.
+     *
+     * @throws  PermissionDeniedException
+     * @deprecated  5.3     Use `validateGetPopover()` instead.
+     */
+    public function validateGetMessagePreview()
+    {
+        $this->validateGetPopover();
+    }
+
+    /**
+     * Returns a preview of a message in a specific conversation.
+     *
+     * @return  string[]
+     * @deprecated  5.3     Use `getPopover()` instead.
+     */
+    public function getMessagePreview()
+    {
+        return $this->getPopover();
+    }
+
+    /**
+     * Validates parameters to close conversations.
+     *
+     * @throws  PermissionDeniedException
+     * @throws  UserInputException
+     */
+    public function validateClose()
+    {
+        // read objects
+        if (empty($this->objects)) {
+            $this->readObjects();
+
+            if (empty($this->objects)) {
+                throw new UserInputException('objectIDs');
+            }
+        }
+
+        // validate ownership
+        foreach ($this->getObjects() as $conversation) {
+            if ($conversation->isClosed || ($conversation->userID != WCF::getUser()->userID)) {
+                throw new PermissionDeniedException();
+            }
+        }
+    }
+
+    /**
+     * Closes conversations.
+     *
+     * @return  mixed[][]
+     */
+    public function close()
+    {
+        foreach ($this->getObjects() as $conversation) {
+            $conversation->update(['isClosed' => 1]);
+            $this->addConversationData($conversation->getDecoratedObject(), 'isClosed', 1);
+
+            ConversationModificationLogHandler::getInstance()->close($conversation->getDecoratedObject());
+        }
+
+        $this->unmarkItems();
+
+        return $this->getConversationData();
+    }
+
+    /**
+     * Validates parameters to open conversations.
+     *
+     * @throws  PermissionDeniedException
+     * @throws  UserInputException
+     */
+    public function validateOpen()
+    {
+        // read objects
+        if (empty($this->objects)) {
+            $this->readObjects();
+
+            if (empty($this->objects)) {
+                throw new UserInputException('objectIDs');
+            }
+        }
+
+        // validate ownership
+        foreach ($this->getObjects() as $conversation) {
+            if (!$conversation->isClosed || ($conversation->userID != WCF::getUser()->userID)) {
+                throw new PermissionDeniedException();
+            }
+        }
+    }
+
+    /**
+     * Opens conversations.
+     *
+     * @return  mixed[][]
+     */
+    public function open()
+    {
+        foreach ($this->getObjects() as $conversation) {
+            $conversation->update(['isClosed' => 0]);
+            $this->addConversationData($conversation->getDecoratedObject(), 'isClosed', 0);
+
+            ConversationModificationLogHandler::getInstance()->open($conversation->getDecoratedObject());
+        }
+
+        $this->unmarkItems();
+
+        return $this->getConversationData();
+    }
+
+    /**
+     * Validates conversations for leave form.
+     *
+     * @throws  PermissionDeniedException
+     * @throws  UserInputException
+     */
+    public function validateGetLeaveForm()
+    {
+        if (empty($this->objectIDs)) {
+            throw new UserInputException('objectIDs');
+        }
+
+        // validate participation
+        if (!Conversation::isParticipant($this->objectIDs)) {
+            throw new PermissionDeniedException();
+        }
+    }
+
+    /**
+     * Returns dialog form to leave conversations.
+     *
+     * @return  array
+     */
+    public function getLeaveForm()
+    {
+        // get hidden state from first conversation (all others have the same state)
+        $sql = "SELECT hideConversation
+                       FROM    wcf" . WCF_N . "_conversation_to_user
                        WHERE   conversationID = ?
                                AND participantID = ?";
-               $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute([
-                       current($this->objectIDs),
-                       WCF::getUser()->userID
-               ]);
-               $row = $statement->fetchArray();
-               
-               WCF::getTPL()->assign('hideConversation', ($row !== false ? $row['hideConversation'] : 0));
-               
-               return [
-                       'actionName' => 'getLeaveForm',
-                       'template' => WCF::getTPL()->fetch('conversationLeave')
-               ];
-       }
-       
-       /**
-        * Validates parameters to hide conversations.
-        * 
-        * @throws      PermissionDeniedException
-        * @throws      UserInputException
-        */
-       public function validateHideConversation() {
-               $this->parameters['hideConversation'] = isset($this->parameters['hideConversation']) ? intval($this->parameters['hideConversation']) : null;
-               if ($this->parameters['hideConversation'] === null || !in_array($this->parameters['hideConversation'], [Conversation::STATE_DEFAULT, Conversation::STATE_HIDDEN, Conversation::STATE_LEFT])) {
-                       throw new UserInputException('hideConversation');
-               }
-               
-               if (empty($this->objectIDs)) {
-                       throw new UserInputException('objectIDs');
-               }
-               
-               // validate participation
-               if (!Conversation::isParticipant($this->objectIDs)) {
-                       throw new PermissionDeniedException();
-               }
-       }
-       
-       /**
-        * Hides or restores conversations.
-        * 
-        * @return      string[]
-        */
-       public function hideConversation() {
-               $sql = "UPDATE  wcf".WCF_N."_conversation_to_user
+        $statement = WCF::getDB()->prepareStatement($sql);
+        $statement->execute([
+            \current($this->objectIDs),
+            WCF::getUser()->userID,
+        ]);
+        $row = $statement->fetchArray();
+
+        WCF::getTPL()->assign('hideConversation', ($row !== false ? $row['hideConversation'] : 0));
+
+        return [
+            'actionName' => 'getLeaveForm',
+            'template' => WCF::getTPL()->fetch('conversationLeave'),
+        ];
+    }
+
+    /**
+     * Validates parameters to hide conversations.
+     *
+     * @throws  PermissionDeniedException
+     * @throws  UserInputException
+     */
+    public function validateHideConversation()
+    {
+        $this->parameters['hideConversation'] = isset($this->parameters['hideConversation']) ? \intval($this->parameters['hideConversation']) : null;
+        if (
+            $this->parameters['hideConversation'] === null
+            || !\in_array(
+                $this->parameters['hideConversation'],
+                [Conversation::STATE_DEFAULT, Conversation::STATE_HIDDEN, Conversation::STATE_LEFT]
+            )
+        ) {
+            throw new UserInputException('hideConversation');
+        }
+
+        if (empty($this->objectIDs)) {
+            throw new UserInputException('objectIDs');
+        }
+
+        // validate participation
+        if (!Conversation::isParticipant($this->objectIDs)) {
+            throw new PermissionDeniedException();
+        }
+    }
+
+    /**
+     * Hides or restores conversations.
+     *
+     * @return  string[]
+     */
+    public function hideConversation()
+    {
+        $sql = "UPDATE wcf" . WCF_N . "_conversation_to_user
                        SET     hideConversation = ?
                        WHERE   conversationID = ?
                                AND participantID = ?";
-               $statement = WCF::getDB()->prepareStatement($sql);
-               
-               WCF::getDB()->beginTransaction();
-               foreach ($this->objectIDs as $conversationID) {
-                       $statement->execute([
-                               $this->parameters['hideConversation'],
-                               $conversationID,
-                               WCF::getUser()->userID
-                       ]);
-               }
-               WCF::getDB()->commitTransaction();
-               
-               // reset user's conversation counters if user leaves conversation
-               // permanently
-               if ($this->parameters['hideConversation'] == Conversation::STATE_LEFT) {
-                       UserStorageHandler::getInstance()->reset([WCF::getUser()->userID], 'conversationCount');
-                       UserStorageHandler::getInstance()->reset([WCF::getUser()->userID], 'unreadConversationCount');
-               }
-               
-               // add modification log entry
-               if ($this->parameters['hideConversation'] == Conversation::STATE_LEFT) {
-                       if (empty($this->objects)) $this->readObjects();
-                       
-                       foreach ($this->getObjects() as $conversation) {
-                               ConversationModificationLogHandler::getInstance()->leave($conversation->getDecoratedObject());
-                       }
-               }
-               
-               // unmark items
-               $this->unmarkItems();
-               
-               if ($this->parameters['hideConversation'] == Conversation::STATE_LEFT) {
-                       // update participants count and participant summary
-                       ConversationEditor::updateParticipantCounts($this->objectIDs);
-                       ConversationEditor::updateParticipantSummaries($this->objectIDs);
-                       
-                       // delete conversation if all users have left it
-                       $conditionBuilder = new PreparedStatementConditionBuilder();
-                       $conditionBuilder->add('conversation.conversationID IN (?)', [$this->objectIDs]);
-                       $conditionBuilder->add('conversation_to_user.conversationID IS NULL');
-                       $sql = "SELECT          DISTINCT conversation.conversationID
-                               FROM            wcf".WCF_N."_conversation conversation
-                               LEFT JOIN       wcf".WCF_N."_conversation_to_user conversation_to_user
+        $statement = WCF::getDB()->prepareStatement($sql);
+
+        WCF::getDB()->beginTransaction();
+        foreach ($this->objectIDs as $conversationID) {
+            $statement->execute([
+                $this->parameters['hideConversation'],
+                $conversationID,
+                WCF::getUser()->userID,
+            ]);
+        }
+        WCF::getDB()->commitTransaction();
+
+        // reset user's conversation counters if user leaves conversation
+        // permanently
+        if ($this->parameters['hideConversation'] == Conversation::STATE_LEFT) {
+            UserStorageHandler::getInstance()->reset([WCF::getUser()->userID], 'conversationCount');
+            UserStorageHandler::getInstance()->reset([WCF::getUser()->userID], 'unreadConversationCount');
+        }
+
+        // add modification log entry
+        if ($this->parameters['hideConversation'] == Conversation::STATE_LEFT) {
+            if (empty($this->objects)) {
+                $this->readObjects();
+            }
+
+            foreach ($this->getObjects() as $conversation) {
+                ConversationModificationLogHandler::getInstance()->leave($conversation->getDecoratedObject());
+            }
+        }
+
+        // unmark items
+        $this->unmarkItems();
+
+        if ($this->parameters['hideConversation'] == Conversation::STATE_LEFT) {
+            // update participants count and participant summary
+            ConversationEditor::updateParticipantCounts($this->objectIDs);
+            ConversationEditor::updateParticipantSummaries($this->objectIDs);
+
+            // delete conversation if all users have left it
+            $conditionBuilder = new PreparedStatementConditionBuilder();
+            $conditionBuilder->add('conversation.conversationID IN (?)', [$this->objectIDs]);
+            $conditionBuilder->add('conversation_to_user.conversationID IS NULL');
+            $sql = "SELECT             DISTINCT conversation.conversationID
+                               FROM            wcf" . WCF_N . "_conversation conversation
+                               LEFT JOIN       wcf" . WCF_N . "_conversation_to_user conversation_to_user
                                ON              (       conversation_to_user.conversationID = conversation.conversationID
-                                               AND     conversation_to_user.hideConversation <> ".Conversation::STATE_LEFT."
+                                               AND     conversation_to_user.hideConversation <> " . Conversation::STATE_LEFT . "
                                                AND     conversation_to_user.participantID IS NOT NULL)
-                               ".$conditionBuilder;
-                       $statement = WCF::getDB()->prepareStatement($sql);
-                       $statement->execute($conditionBuilder->getParameters());
-                       $conversationIDs = $statement->fetchAll(\PDO::FETCH_COLUMN);
-                       
-                       if (!empty($conversationIDs)) {
-                               $action = new ConversationAction($conversationIDs, 'delete');
-                               $action->executeAction();
-                       }
-               }
-               
-               return [
-                       'actionName' => 'hideConversation',
-                       'redirectURL' => LinkHandler::getInstance()->getLink('ConversationList')
-               ];
-       }
-       
-       /**
-        * Validates parameters to return the mixed conversation list.
-        */
-       public function validateGetMixedConversationList() {
-               // does nothing
-       }
-       
-       /**
-        * Returns a mixed conversation list with up to 10 unread conversations.
-        * 
-        * @return      mixed[][]
-        */
-       public function getMixedConversationList() {
-               $sqlSelect = '  , (SELECT participantID FROM wcf'.WCF_N.'_conversation_to_user WHERE conversationID = conversation.conversationID AND participantID <> conversation.userID AND isInvisible = 0 ORDER BY username, participantID LIMIT 1) AS otherParticipantID
-                               , (SELECT username FROM wcf'.WCF_N.'_conversation_to_user WHERE conversationID = conversation.conversationID AND participantID <> conversation.userID AND isInvisible = 0 ORDER BY username, participantID LIMIT 1) AS otherParticipant';
-               
-               $unreadConversationList = new UserConversationList(WCF::getUser()->userID);
-               $unreadConversationList->sqlSelects .= $sqlSelect;
-               $unreadConversationList->getConditionBuilder()->add('conversation_to_user.lastVisitTime < lastPostTime');
-               $unreadConversationList->sqlLimit = 10;
-               $unreadConversationList->sqlOrderBy = 'lastPostTime DESC';
-               $unreadConversationList->readObjects();
-               
-               $conversations = [];
-               $count = 0;
-               foreach ($unreadConversationList as $conversation) {
-                       $conversations[] = $conversation;
-                       $count++;
-               }
-               
-               if ($count < 10) {
-                       $conversationList = new UserConversationList(WCF::getUser()->userID);
-                       $conversationList->sqlSelects .= $sqlSelect;
-                       $conversationList->getConditionBuilder()->add('conversation_to_user.lastVisitTime >= lastPostTime');
-                       $conversationList->sqlLimit = (10 - $count);
-                       $conversationList->sqlOrderBy = 'lastPostTime DESC';
-                       $conversationList->readObjects();
-                       
-                       foreach ($conversationList as $conversation) {
-                               $conversations[] = $conversation;
-                       }
-               }
-               
-               WCF::getTPL()->assign([
-                       'conversations' => $conversations
-               ]);
-               
-               $totalCount = ConversationHandler::getInstance()->getUnreadConversationCount();
-               if ($count < 10 && $count < $totalCount) {
-                       UserStorageHandler::getInstance()->reset([WCF::getUser()->userID], 'unreadConversationCount');
-               }
-               
-               return [
-                       'template' => WCF::getTPL()->fetch('conversationListUserPanel'),
-                       'totalCount' => $totalCount
-               ];
-       }
-       
-       /**
-        * Validates the 'unmarkAll' action.
-        */
-       public function validateUnmarkAll() {
-               // does nothing
-       }
-       
-       /**
-        * Unmarks all conversations.
-        */
-       public function unmarkAll() {
-               ClipboardHandler::getInstance()->removeItems(ClipboardHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.conversation.conversation'));
-       }
-       
-       /**
-        * Validates parameters to display the 'add participants' form.
-        * 
-        * @throws      PermissionDeniedException
-        */
-       public function validateGetAddParticipantsForm() {
-               $this->conversation = $this->getSingleObject();
-               if (!Conversation::isParticipant([$this->conversation->conversationID]) || !$this->conversation->canAddParticipants()) {
-                       throw new PermissionDeniedException();
-               }
-       }
-       
-       /**
-        * Shows the 'add participants' form.
-        * 
-        * @return      array
-        */
-       public function getAddParticipantsForm() {
-               $restrictUserGroupIDs = [];
-               foreach (UserGroup::getAllGroups() as $group) {
-                       if ($group->canBeAddedAsConversationParticipant) {
-                               $restrictUserGroupIDs[] = $group->groupID;
-                       }
-               }
-               
-               return [
-                       'excludedSearchValues' => $this->conversation->getParticipantNames(false, true),
-                       'maxItems' => WCF::getSession()->getPermission('user.conversation.maxParticipants') - $this->conversation->participants,
-                       'canAddGroupParticipants' => WCF::getSession()->getPermission('user.conversation.canAddGroupParticipants'),
-                       'template' => WCF::getTPL()->fetch('conversationAddParticipants', 'wcf', ['conversation' => $this->conversation]),
-                       'restrictUserGroupIDs' => $restrictUserGroupIDs,
-               ];
-       }
-       
-       /**
-        * Validates parameters to add new participants.
-        */
-       public function validateAddParticipants() {
-               $this->validateGetAddParticipantsForm();
-               
-               // validate participants
-               $this->readStringArray('participants', true);
-               $this->readIntegerArray('participantsGroupIDs', true);
-               
-               if (!$this->conversation->getDecoratedObject()->isDraft) {
-                       $this->readString('visibility');
-                       if (!in_array($this->parameters['visibility'], ['all', 'new'])) {
-                               throw new UserInputException('visibility');
-                       }
-                       
-                       if ($this->parameters['visibility'] === 'all' && !$this->conversation->canAddParticipantsUnrestricted()) {
-                               throw new UserInputException('visibility');
-                       }
-               }
-       }
-       
-       /**
-        * Adds new participants.
-        * 
-        * @return      array
-        */
-       public function addParticipants() {
-               try {
-                       $existingParticipants = $this->conversation->getParticipantIDs(true);
-                       $participantIDs = Conversation::validateParticipants($this->parameters['participants'], 'participants', $existingParticipants);
-                       if (!empty($this->parameters['participantsGroupIDs']) && WCF::getSession()->getPermission('user.conversation.canAddGroupParticipants')) {
-                               $validGroupParticipants = Conversation::validateGroupParticipants($this->parameters['participantsGroupIDs'], 'participants', $existingParticipants);
-                               $validGroupParticipants = array_diff($validGroupParticipants, $participantIDs);
-                               if (empty($validGroupParticipants)) {
-                                       throw new UserInputException('participants', 'emptyGroup');
-                               }
-                               $participantIDs = array_merge($participantIDs, $validGroupParticipants);
-                       }
-                       
-                       $parameters = [
-                               'participantIDs' => $participantIDs,
-                       ];
-                       EventHandler::getInstance()->fireAction($this, 'addParticipants_validateParticipants', $parameters);
-                       $participantIDs = $parameters['participantIDs'];
-               }
-               catch (UserInputException $e) {
-                       $errorMessage = '';
-                       $errors = is_array($e->getType()) ? $e->getType() : [['type' => $e->getType()]];
-                       foreach ($errors as $type) {
-                               if (!empty($errorMessage)) $errorMessage .= ' ';
-                               $errorMessage .= WCF::getLanguage()->getDynamicVariable('wcf.conversation.participants.error.'.$type['type'], ['errorData' => $type]);
-                       }
-                       
-                       return [
-                               'actionName' => 'addParticipants',
-                               'errorMessage' => $errorMessage
-                       ];
-               }
-               
-               // validate limit
-               $newCount = $this->conversation->participants + count($participantIDs);
-               if ($newCount > WCF::getSession()->getPermission('user.conversation.maxParticipants')) {
-                       return [
-                               'actionName' => 'addParticipants',
-                               'errorMessage' => WCF::getLanguage()->getDynamicVariable('wcf.conversation.participants.error.tooManyParticipants')
-                       ];
-               }
-               
-               $count = 0;
-               $successMessage = '';
-               if (!empty($participantIDs)) {
-                       // check for already added participants
-                       if ($this->conversation->isDraft) {
-                               $draftData = unserialize($this->conversation->draftData);
-                               $draftData['participants'] = array_merge($draftData['participants'], $participantIDs);
-                               $data = ['data' => ['draftData' => serialize($draftData)]];
-                       }
-                       else {
-                               $data = [
-                                       'participants' => $participantIDs,
-                                       'visibility' => (isset($this->parameters['visibility'])) ? $this->parameters['visibility'] : 'all'
-                               ];
-                       }
-                       
-                       $conversationAction = new ConversationAction([$this->conversation], 'update', $data);
-                       $conversationAction->executeAction();
-                       
-                       $count = count($participantIDs);
-                       $successMessage = WCF::getLanguage()->getDynamicVariable('wcf.conversation.edit.addParticipants.success', ['count' => $count]);
-                       
-                       ConversationModificationLogHandler::getInstance()->addParticipants($this->conversation->getDecoratedObject(), $participantIDs);
-                       
-                       if (!$this->conversation->isDraft) {
-                               // update participant summary
-                               $this->conversation->updateParticipantSummary();
-                       }
-               }
-               
-               return [
-                       'count' => $count,
-                       'successMessage' => $successMessage
-               ];
-       }
-       
-       /**
-        * Validates parameters to remove a participant from a conversation.
-        * 
-        * @throws      PermissionDeniedException
-        * @throws      UserInputException
-        */
-       public function validateRemoveParticipant() {
-               $this->readInteger('userID');
-               
-               // validate conversation
-               $this->conversation = $this->getSingleObject();
-               if (!$this->conversation->conversationID) {
-                       throw new UserInputException('objectIDs');
-               }
-               
-               // check ownership
-               if ($this->conversation->userID != WCF::getUser()->userID) {
-                       throw new PermissionDeniedException();
-               }
-               
-               // validate participants
-               if ($this->parameters['userID'] == WCF::getUser()->userID || !Conversation::isParticipant([$this->conversation->conversationID]) || !Conversation::isParticipant([$this->conversation->conversationID], $this->parameters['userID'])) {
-                       throw new PermissionDeniedException();
-               }
-               
-       }
-       
-       /**
-        * Removes a participant from a conversation.
-        */
-       public function removeParticipant() {
-               $this->conversation->removeParticipant($this->parameters['userID']);
-               $this->conversation->updateParticipantSummary();
-               
-               ConversationModificationLogHandler::getInstance()->removeParticipant($this->conversation->getDecoratedObject(), $this->parameters['userID']);
-               
-               // reset storage
-               UserStorageHandler::getInstance()->reset([$this->parameters['userID']], 'unreadConversationCount');
-               
-               return [
-                       'userID' => $this->parameters['userID']
-               ];
-       }
-       
-       /**
-        * Rebuilds the conversation data of the relevant conversations.
-        */
-       public function rebuild() {
-               if (empty($this->objects)) {
-                       $this->readObjects();
-               }
-               
-               // collect number of messages for each conversation
-               $conditionBuilder = new PreparedStatementConditionBuilder();
-               $conditionBuilder->add('conversation_message.conversationID IN (?)', [$this->objectIDs]);
-               $sql = "SELECT          conversationID, COUNT(messageID) AS messages, SUM(attachments) AS attachments
-                       FROM            wcf".WCF_N."_conversation_message conversation_message
-                       ".$conditionBuilder."
+                               " . $conditionBuilder;
+            $statement = WCF::getDB()->prepareStatement($sql);
+            $statement->execute($conditionBuilder->getParameters());
+            $conversationIDs = $statement->fetchAll(\PDO::FETCH_COLUMN);
+
+            if (!empty($conversationIDs)) {
+                $action = new self($conversationIDs, 'delete');
+                $action->executeAction();
+            }
+        }
+
+        return [
+            'actionName' => 'hideConversation',
+            'redirectURL' => LinkHandler::getInstance()->getLink('ConversationList'),
+        ];
+    }
+
+    /**
+     * Validates parameters to return the mixed conversation list.
+     */
+    public function validateGetMixedConversationList()
+    {
+        // does nothing
+    }
+
+    /**
+     * Returns a mixed conversation list with up to 10 unread conversations.
+     *
+     * @return  mixed[][]
+     */
+    public function getMixedConversationList()
+    {
+        $sqlSelect = '  , (SELECT participantID FROM wcf' . WCF_N . '_conversation_to_user WHERE conversationID = conversation.conversationID AND participantID <> conversation.userID AND isInvisible = 0 ORDER BY username, participantID LIMIT 1) AS otherParticipantID
+                               , (SELECT username FROM wcf' . WCF_N . '_conversation_to_user WHERE conversationID = conversation.conversationID AND participantID <> conversation.userID AND isInvisible = 0 ORDER BY username, participantID LIMIT 1) AS otherParticipant';
+
+        $unreadConversationList = new UserConversationList(WCF::getUser()->userID);
+        $unreadConversationList->sqlSelects .= $sqlSelect;
+        $unreadConversationList->getConditionBuilder()->add('conversation_to_user.lastVisitTime < lastPostTime');
+        $unreadConversationList->sqlLimit = 10;
+        $unreadConversationList->sqlOrderBy = 'lastPostTime DESC';
+        $unreadConversationList->readObjects();
+
+        $conversations = [];
+        $count = 0;
+        foreach ($unreadConversationList as $conversation) {
+            $conversations[] = $conversation;
+            $count++;
+        }
+
+        if ($count < 10) {
+            $conversationList = new UserConversationList(WCF::getUser()->userID);
+            $conversationList->sqlSelects .= $sqlSelect;
+            $conversationList->getConditionBuilder()->add('conversation_to_user.lastVisitTime >= lastPostTime');
+            $conversationList->sqlLimit = (10 - $count);
+            $conversationList->sqlOrderBy = 'lastPostTime DESC';
+            $conversationList->readObjects();
+
+            foreach ($conversationList as $conversation) {
+                $conversations[] = $conversation;
+            }
+        }
+
+        WCF::getTPL()->assign([
+            'conversations' => $conversations,
+        ]);
+
+        $totalCount = ConversationHandler::getInstance()->getUnreadConversationCount();
+        if ($count < 10 && $count < $totalCount) {
+            UserStorageHandler::getInstance()->reset([WCF::getUser()->userID], 'unreadConversationCount');
+        }
+
+        return [
+            'template' => WCF::getTPL()->fetch('conversationListUserPanel'),
+            'totalCount' => $totalCount,
+        ];
+    }
+
+    /**
+     * Validates the 'unmarkAll' action.
+     */
+    public function validateUnmarkAll()
+    {
+        // does nothing
+    }
+
+    /**
+     * Unmarks all conversations.
+     */
+    public function unmarkAll()
+    {
+        ClipboardHandler::getInstance()->removeItems(
+            ClipboardHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.conversation.conversation')
+        );
+    }
+
+    /**
+     * Validates parameters to display the 'add participants' form.
+     *
+     * @throws  PermissionDeniedException
+     */
+    public function validateGetAddParticipantsForm()
+    {
+        $this->conversation = $this->getSingleObject();
+        if (
+            !Conversation::isParticipant([$this->conversation->conversationID])
+            || !$this->conversation->canAddParticipants()
+        ) {
+            throw new PermissionDeniedException();
+        }
+    }
+
+    /**
+     * Shows the 'add participants' form.
+     *
+     * @return  array
+     */
+    public function getAddParticipantsForm()
+    {
+        $restrictUserGroupIDs = [];
+        foreach (UserGroup::getAllGroups() as $group) {
+            if ($group->canBeAddedAsConversationParticipant) {
+                $restrictUserGroupIDs[] = $group->groupID;
+            }
+        }
+
+        return [
+            'excludedSearchValues' => $this->conversation->getParticipantNames(false, true),
+            'maxItems' => WCF::getSession()->getPermission('user.conversation.maxParticipants') - $this->conversation->participants,
+            'canAddGroupParticipants' => WCF::getSession()->getPermission('user.conversation.canAddGroupParticipants'),
+            'template' => WCF::getTPL()->fetch(
+                'conversationAddParticipants',
+                'wcf',
+                ['conversation' => $this->conversation]
+            ),
+            'restrictUserGroupIDs' => $restrictUserGroupIDs,
+        ];
+    }
+
+    /**
+     * Validates parameters to add new participants.
+     */
+    public function validateAddParticipants()
+    {
+        $this->validateGetAddParticipantsForm();
+
+        // validate participants
+        $this->readStringArray('participants', true);
+        $this->readIntegerArray('participantsGroupIDs', true);
+
+        if (!$this->conversation->getDecoratedObject()->isDraft) {
+            $this->readString('visibility');
+            if (!\in_array($this->parameters['visibility'], ['all', 'new'])) {
+                throw new UserInputException('visibility');
+            }
+
+            if ($this->parameters['visibility'] === 'all' && !$this->conversation->canAddParticipantsUnrestricted()) {
+                throw new UserInputException('visibility');
+            }
+        }
+    }
+
+    /**
+     * Adds new participants.
+     *
+     * @return  array
+     */
+    public function addParticipants()
+    {
+        try {
+            $existingParticipants = $this->conversation->getParticipantIDs(true);
+            $participantIDs = Conversation::validateParticipants(
+                $this->parameters['participants'],
+                'participants',
+                $existingParticipants
+            );
+            if (
+                !empty($this->parameters['participantsGroupIDs'])
+                && WCF::getSession()->getPermission('user.conversation.canAddGroupParticipants')
+            ) {
+                $validGroupParticipants = Conversation::validateGroupParticipants(
+                    $this->parameters['participantsGroupIDs'],
+                    'participants',
+                    $existingParticipants
+                );
+                $validGroupParticipants = \array_diff($validGroupParticipants, $participantIDs);
+                if (empty($validGroupParticipants)) {
+                    throw new UserInputException('participants', 'emptyGroup');
+                }
+                $participantIDs = \array_merge($participantIDs, $validGroupParticipants);
+            }
+
+            $parameters = [
+                'participantIDs' => $participantIDs,
+            ];
+            EventHandler::getInstance()->fireAction($this, 'addParticipants_validateParticipants', $parameters);
+            $participantIDs = $parameters['participantIDs'];
+        } catch (UserInputException $e) {
+            $errorMessage = '';
+            $errors = \is_array($e->getType()) ? $e->getType() : [['type' => $e->getType()]];
+            foreach ($errors as $type) {
+                if (!empty($errorMessage)) {
+                    $errorMessage .= ' ';
+                }
+                $errorMessage .= WCF::getLanguage()->getDynamicVariable(
+                    'wcf.conversation.participants.error.' . $type['type'],
+                    ['errorData' => $type]
+                );
+            }
+
+            return [
+                'actionName' => 'addParticipants',
+                'errorMessage' => $errorMessage,
+            ];
+        }
+
+        // validate limit
+        $newCount = $this->conversation->participants + \count($participantIDs);
+        if ($newCount > WCF::getSession()->getPermission('user.conversation.maxParticipants')) {
+            return [
+                'actionName' => 'addParticipants',
+                'errorMessage' => WCF::getLanguage()->getDynamicVariable('wcf.conversation.participants.error.tooManyParticipants'),
+            ];
+        }
+
+        $count = 0;
+        $successMessage = '';
+        if (!empty($participantIDs)) {
+            // check for already added participants
+            if ($this->conversation->isDraft) {
+                $draftData = \unserialize($this->conversation->draftData);
+                $draftData['participants'] = \array_merge($draftData['participants'], $participantIDs);
+                $data = ['data' => ['draftData' => \serialize($draftData)]];
+            } else {
+                $data = [
+                    'participants' => $participantIDs,
+                    'visibility' => (isset($this->parameters['visibility'])) ? $this->parameters['visibility'] : 'all',
+                ];
+            }
+
+            $conversationAction = new self([$this->conversation], 'update', $data);
+            $conversationAction->executeAction();
+
+            $count = \count($participantIDs);
+            $successMessage = WCF::getLanguage()->getDynamicVariable(
+                'wcf.conversation.edit.addParticipants.success',
+                ['count' => $count]
+            );
+
+            ConversationModificationLogHandler::getInstance()
+                ->addParticipants($this->conversation->getDecoratedObject(), $participantIDs);
+
+            if (!$this->conversation->isDraft) {
+                // update participant summary
+                $this->conversation->updateParticipantSummary();
+            }
+        }
+
+        return [
+            'count' => $count,
+            'successMessage' => $successMessage,
+        ];
+    }
+
+    /**
+     * Validates parameters to remove a participant from a conversation.
+     *
+     * @throws  PermissionDeniedException
+     * @throws  UserInputException
+     */
+    public function validateRemoveParticipant()
+    {
+        $this->readInteger('userID');
+
+        // validate conversation
+        $this->conversation = $this->getSingleObject();
+        if (!$this->conversation->conversationID) {
+            throw new UserInputException('objectIDs');
+        }
+
+        // check ownership
+        if ($this->conversation->userID != WCF::getUser()->userID) {
+            throw new PermissionDeniedException();
+        }
+
+        // validate participants
+        if (
+            $this->parameters['userID'] == WCF::getUser()->userID
+            || !Conversation::isParticipant([$this->conversation->conversationID])
+            || !Conversation::isParticipant([$this->conversation->conversationID], $this->parameters['userID'])
+        ) {
+            throw new PermissionDeniedException();
+        }
+    }
+
+    /**
+     * Removes a participant from a conversation.
+     */
+    public function removeParticipant()
+    {
+        $this->conversation->removeParticipant($this->parameters['userID']);
+        $this->conversation->updateParticipantSummary();
+
+        ConversationModificationLogHandler::getInstance()
+            ->removeParticipant($this->conversation->getDecoratedObject(), $this->parameters['userID']);
+
+        // reset storage
+        UserStorageHandler::getInstance()->reset([$this->parameters['userID']], 'unreadConversationCount');
+
+        return [
+            'userID' => $this->parameters['userID'],
+        ];
+    }
+
+    /**
+     * Rebuilds the conversation data of the relevant conversations.
+     */
+    public function rebuild()
+    {
+        if (empty($this->objects)) {
+            $this->readObjects();
+        }
+
+        // collect number of messages for each conversation
+        $conditionBuilder = new PreparedStatementConditionBuilder();
+        $conditionBuilder->add('conversation_message.conversationID IN (?)', [$this->objectIDs]);
+        $sql = "SELECT         conversationID, COUNT(messageID) AS messages, SUM(attachments) AS attachments
+                       FROM            wcf" . WCF_N . "_conversation_message conversation_message
+                       " . $conditionBuilder . "
                        GROUP BY        conversationID";
-               $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute($conditionBuilder->getParameters());
-               
-               $objectIDs = [];
-               while ($row = $statement->fetchArray()) {
-                       if (!$row['messages']) {
-                               continue;
-                       }
-                       $objectIDs[] = $row['conversationID'];
-                       
-                       $conversationEditor = new ConversationEditor(new Conversation(null, [
-                               'conversationID' => $row['conversationID']
-                       ]));
-                       $conversationEditor->update([
-                               'attachments' => $row['attachments'],
-                               'replies' => $row['messages'] - 1
-                       ]);
-                       $conversationEditor->updateFirstMessage();
-                       $conversationEditor->updateLastMessage();
-               }
-               
-               // delete conversations without messages
-               $deleteConversationIDs = array_diff($this->objectIDs, $objectIDs);
-               if (!empty($deleteConversationIDs)) {
-                       $conversationAction = new ConversationAction($deleteConversationIDs, 'delete');
-                       $conversationAction->executeAction();
-               }
-       }
-       
-       /**
-        * Validates the parameters to edit a conversation's subject.
-        * 
-        * @throws      PermissionDeniedException
-        */
-       public function validateEditSubject() {
-               $this->readString('subject');
-               
-               $this->conversation = $this->getSingleObject();
-               if ($this->conversation->userID != WCF::getUser()->userID) {
-                       throw new PermissionDeniedException();
-               }
-       }
-       
-       /**
-        * Edits a conversation's subject.
-        * 
-        * @return      string[]
-        */
-       public function editSubject() {
-               $subject = mb_substr($this->parameters['subject'], 0, 255);
-               
-               $this->conversation->update([
-                       'subject' => $subject
-               ]);
-               
-               $message = $this->conversation->getFirstMessage();
-               
-               SearchIndexManager::getInstance()->set(
-                       'com.woltlab.wcf.conversation.message',
-                       $message->messageID,
-                       $message->message,
-                       $subject,
-                       $message->time,
-                       $message->userID,
-                       $message->username
-               );
-               
-               return [
-                       'subject' => $subject
-               ];
-       }
-       
-       /**
-        * Adds conversation modification data.
-        * 
-        * @param       Conversation    $conversation
-        * @param       string          $key
-        * @param       mixed           $value
-        */
-       protected function addConversationData(Conversation $conversation, $key, $value) {
-               if (!isset($this->conversationData[$conversation->conversationID])) {
-                       $this->conversationData[$conversation->conversationID] = [];
-               }
-               
-               $this->conversationData[$conversation->conversationID][$key] = $value;
-       }
-       
-       /**
-        * Returns conversation data.
-        * 
-        * @return      mixed[][]
-        */
-       protected function getConversationData() {
-               return [
-                       'conversationData' => $this->conversationData
-               ];
-       }
-       
-       /**
-        * Unmarks conversations.
-        * 
-        * @param       integer[]               $conversationIDs
-        */
-       protected function unmarkItems(array $conversationIDs = []) {
-               if (empty($conversationIDs)) {
-                       $conversationIDs = $this->objectIDs;
-               }
-               
-               ClipboardHandler::getInstance()->unmark($conversationIDs, ClipboardHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.conversation.conversation'));
-       }
+        $statement = WCF::getDB()->prepareStatement($sql);
+        $statement->execute($conditionBuilder->getParameters());
+
+        $objectIDs = [];
+        while ($row = $statement->fetchArray()) {
+            if (!$row['messages']) {
+                continue;
+            }
+            $objectIDs[] = $row['conversationID'];
+
+            $conversationEditor = new ConversationEditor(new Conversation(null, [
+                'conversationID' => $row['conversationID'],
+            ]));
+            $conversationEditor->update([
+                'attachments' => $row['attachments'],
+                'replies' => $row['messages'] - 1,
+            ]);
+            $conversationEditor->updateFirstMessage();
+            $conversationEditor->updateLastMessage();
+        }
+
+        // delete conversations without messages
+        $deleteConversationIDs = \array_diff($this->objectIDs, $objectIDs);
+        if (!empty($deleteConversationIDs)) {
+            $conversationAction = new self($deleteConversationIDs, 'delete');
+            $conversationAction->executeAction();
+        }
+    }
+
+    /**
+     * Validates the parameters to edit a conversation's subject.
+     *
+     * @throws      PermissionDeniedException
+     */
+    public function validateEditSubject()
+    {
+        $this->readString('subject');
+
+        $this->conversation = $this->getSingleObject();
+        if ($this->conversation->userID != WCF::getUser()->userID) {
+            throw new PermissionDeniedException();
+        }
+    }
+
+    /**
+     * Edits a conversation's subject.
+     *
+     * @return      string[]
+     */
+    public function editSubject()
+    {
+        $subject = \mb_substr($this->parameters['subject'], 0, 255);
+
+        $this->conversation->update([
+            'subject' => $subject,
+        ]);
+
+        $message = $this->conversation->getFirstMessage();
+
+        SearchIndexManager::getInstance()->set(
+            'com.woltlab.wcf.conversation.message',
+            $message->messageID,
+            $message->message,
+            $subject,
+            $message->time,
+            $message->userID,
+            $message->username
+        );
+
+        return [
+            'subject' => $subject,
+        ];
+    }
+
+    /**
+     * Adds conversation modification data.
+     *
+     * @param Conversation $conversation
+     * @param string $key
+     * @param mixed $value
+     */
+    protected function addConversationData(Conversation $conversation, $key, $value)
+    {
+        if (!isset($this->conversationData[$conversation->conversationID])) {
+            $this->conversationData[$conversation->conversationID] = [];
+        }
+
+        $this->conversationData[$conversation->conversationID][$key] = $value;
+    }
+
+    /**
+     * Returns conversation data.
+     *
+     * @return  mixed[][]
+     */
+    protected function getConversationData()
+    {
+        return [
+            'conversationData' => $this->conversationData,
+        ];
+    }
+
+    /**
+     * Unmarks conversations.
+     *
+     * @param integer[] $conversationIDs
+     */
+    protected function unmarkItems(array $conversationIDs = [])
+    {
+        if (empty($conversationIDs)) {
+            $conversationIDs = $this->objectIDs;
+        }
+
+        ClipboardHandler::getInstance()->unmark(
+            $conversationIDs,
+            ClipboardHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.conversation.conversation')
+        );
+    }
 }
index 3b49e5731341e1094a27171d4721449cbb3b9df9..13b6706181a85c4471f4f2672f58ed98b729ed80 100644 (file)
@@ -1,5 +1,7 @@
 <?php
+
 namespace wcf\data\conversation;
+
 use wcf\data\conversation\message\ConversationMessage;
 use wcf\data\DatabaseObjectEditor;
 use wcf\system\database\util\PreparedStatementConditionBuilder;
@@ -7,270 +9,281 @@ use wcf\system\WCF;
 
 /**
  * Extends the conversation object with functions to create, update and delete conversations.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\Data\Conversation
- * 
- * @method static      Conversation    create(array $parameters = [])
- * @method             Conversation    getDecoratedObject()
- * @mixin              Conversation
+ *
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Data\Conversation
+ *
+ * @method static Conversation    create(array $parameters = [])
+ * @method      Conversation    getDecoratedObject()
+ * @mixin       Conversation
  */
-class ConversationEditor extends DatabaseObjectEditor {
-       /**
-        * @inheritDoc
-        */
-       protected static $baseClass = Conversation::class;
-       
-       /**
-        * Adds a new message to this conversation.
-        * 
-        * @param       ConversationMessage     $message
-        */
-       public function addMessage(ConversationMessage $message) {
-               $this->update([
-                       'lastPoster' => $message->username,
-                       'lastPostTime' => $message->time,
-                       'lastPosterID' => $message->userID,
-                       'replies' => $this->replies + 1,
-                       'attachments' => $this->attachments + $message->attachments
-               ]);
-       }
-       
-       /**
-        * Resets the participants of this conversation.
-        */
-       public function resetParticipants() {
-               $sql = "DELETE FROM     wcf".WCF_N."_conversation_to_user
+class ConversationEditor extends DatabaseObjectEditor
+{
+    /**
+     * @inheritDoc
+     */
+    protected static $baseClass = Conversation::class;
+
+    /**
+     * Adds a new message to this conversation.
+     *
+     * @param ConversationMessage $message
+     */
+    public function addMessage(ConversationMessage $message)
+    {
+        $this->update([
+            'lastPoster' => $message->username,
+            'lastPostTime' => $message->time,
+            'lastPosterID' => $message->userID,
+            'replies' => $this->replies + 1,
+            'attachments' => $this->attachments + $message->attachments,
+        ]);
+    }
+
+    /**
+     * Resets the participants of this conversation.
+     */
+    public function resetParticipants()
+    {
+        $sql = "DELETE FROM    wcf" . WCF_N . "_conversation_to_user
                        WHERE           conversationID = ?
                                        AND participantID <> ?";
-               $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute([$this->conversationID, $this->userID]);
-       }
-       
-       /**
-        * Updates the participants of this conversation.
-        * 
-        * @param       integer[]       $participantIDs
-        * @param       integer[]       $invisibleParticipantIDs
-        * @param       string          $visibility
-        */
-       public function updateParticipants(array $participantIDs, array $invisibleParticipantIDs = [], $visibility = 'all') {
-               $usernames = [];
-               if (!empty($participantIDs) || !empty($invisibleParticipantIDs)) {
-                       $conditions = new PreparedStatementConditionBuilder();
-                       $conditions->add("userID IN (?)", [array_merge($participantIDs, $invisibleParticipantIDs)]);
-                       
-                       $sql = "SELECT  userID, username
-                               FROM    wcf".WCF_N."_user
-                               ".$conditions;
-                       $statement = WCF::getDB()->prepareStatement($sql);
-                       $statement->execute($conditions->getParameters());
-                       while ($row = $statement->fetchArray()) {
-                               $usernames[$row['userID']] = $row['username'];
-                       }
-               }
-               
-               if (!empty($participantIDs)) {
-                       WCF::getDB()->beginTransaction();
-                       $sql = "INSERT INTO             wcf".WCF_N."_conversation_to_user
+        $statement = WCF::getDB()->prepareStatement($sql);
+        $statement->execute([$this->conversationID, $this->userID]);
+    }
+
+    /**
+     * Updates the participants of this conversation.
+     *
+     * @param integer[] $participantIDs
+     * @param integer[] $invisibleParticipantIDs
+     * @param string $visibility
+     */
+    public function updateParticipants(array $participantIDs, array $invisibleParticipantIDs = [], $visibility = 'all')
+    {
+        $usernames = [];
+        if (!empty($participantIDs) || !empty($invisibleParticipantIDs)) {
+            $conditions = new PreparedStatementConditionBuilder();
+            $conditions->add("userID IN (?)", [\array_merge($participantIDs, $invisibleParticipantIDs)]);
+
+            $sql = "SELECT     userID, username
+                               FROM    wcf" . WCF_N . "_user
+                               " . $conditions;
+            $statement = WCF::getDB()->prepareStatement($sql);
+            $statement->execute($conditions->getParameters());
+            while ($row = $statement->fetchArray()) {
+                $usernames[$row['userID']] = $row['username'];
+            }
+        }
+
+        if (!empty($participantIDs)) {
+            WCF::getDB()->beginTransaction();
+            $sql = "INSERT INTO                wcf" . WCF_N . "_conversation_to_user
                                                        (conversationID, participantID, username, isInvisible, joinedAt)
                                VALUES                  (?, ?, ?, ?, ?)
                                ON DUPLICATE KEY
                                UPDATE                  hideConversation = 0, leftAt = 0, leftByOwnChoice = 1";
-                       $statement = WCF::getDB()->prepareStatement($sql);
-                       
-                       foreach ($participantIDs as $userID) {
-                               $statement->execute([
-                                       $this->conversationID,
-                                       $userID,
-                                       $usernames[$userID],
-                                       0,
-                                       ($visibility === 'all') ? 0 : TIME_NOW
-                               ]);
-                       }
-                       WCF::getDB()->commitTransaction();
-               }
-               
-               if (!empty($invisibleParticipantIDs)) {
-                       WCF::getDB()->beginTransaction();
-                       $sql = "INSERT INTO             wcf".WCF_N."_conversation_to_user
+            $statement = WCF::getDB()->prepareStatement($sql);
+
+            foreach ($participantIDs as $userID) {
+                $statement->execute([
+                    $this->conversationID,
+                    $userID,
+                    $usernames[$userID],
+                    0,
+                    ($visibility === 'all') ? 0 : TIME_NOW,
+                ]);
+            }
+            WCF::getDB()->commitTransaction();
+        }
+
+        if (!empty($invisibleParticipantIDs)) {
+            WCF::getDB()->beginTransaction();
+            $sql = "INSERT INTO                wcf" . WCF_N . "_conversation_to_user
                                                        (conversationID, participantID, username, isInvisible)
                                VALUES                  (?, ?, ?, ?)";
-                       $statement = WCF::getDB()->prepareStatement($sql);
-                       
-                       foreach ($invisibleParticipantIDs as $userID) {
-                               $statement->execute([
-                                       $this->conversationID,
-                                       $userID,
-                                       $usernames[$userID],
-                                       1
-                               ]);
-                       }
-                       WCF::getDB()->commitTransaction();
-               }
-               
-               $this->updateParticipantCount();
-       }
-       
-       /**
-        * Updates participant count.
-        */
-       public function updateParticipantCount() {
-               $sql = "UPDATE  wcf".WCF_N."_conversation conversation
+            $statement = WCF::getDB()->prepareStatement($sql);
+
+            foreach ($invisibleParticipantIDs as $userID) {
+                $statement->execute([
+                    $this->conversationID,
+                    $userID,
+                    $usernames[$userID],
+                    1,
+                ]);
+            }
+            WCF::getDB()->commitTransaction();
+        }
+
+        $this->updateParticipantCount();
+    }
+
+    /**
+     * Updates participant count.
+     */
+    public function updateParticipantCount()
+    {
+        $sql = "UPDATE wcf" . WCF_N . "_conversation conversation
                        SET     participants = (
                                        SELECT  COUNT(*) AS count
-                                       FROM    wcf".WCF_N."_conversation_to_user conversation_to_user
+                                       FROM    wcf" . WCF_N . "_conversation_to_user conversation_to_user
                                        WHERE   conversation_to_user.conversationID = conversation.conversationID
                                                AND conversation_to_user.hideConversation <> ?
                                                AND conversation_to_user.participantID <> ?
                                                AND conversation_to_user.isInvisible = ?
                                )
                        WHERE   conversation.conversationID = ?";
-               $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute([
-                       Conversation::STATE_LEFT,
-                       $this->userID,
-                       0,
-                       $this->conversationID
-               ]);
-       }
-       
-       /**
-        * Updates the participant summary of this conversation.
-        */
-       public function updateParticipantSummary() {
-               $sql = "SELECT          participantID AS userID, hideConversation, username
-                       FROM            wcf".WCF_N."_conversation_to_user
+        $statement = WCF::getDB()->prepareStatement($sql);
+        $statement->execute([
+            Conversation::STATE_LEFT,
+            $this->userID,
+            0,
+            $this->conversationID,
+        ]);
+    }
+
+    /**
+     * Updates the participant summary of this conversation.
+     */
+    public function updateParticipantSummary()
+    {
+        $sql = "SELECT         participantID AS userID, hideConversation, username
+                       FROM            wcf" . WCF_N . "_conversation_to_user
                        WHERE           conversationID = ?
                                        AND participantID <> ?
                                        AND isInvisible = 0
                        ORDER BY        username";
-               $statement = WCF::getDB()->prepareStatement($sql, 5);
-               $statement->execute([$this->conversationID, $this->userID]);
-               
-               $this->update(['participantSummary' => serialize($statement->fetchAll(\PDO::FETCH_ASSOC))]);
-       }
-       
-       /**
-        * Removes a participant from this conversation.
-        * 
-        * @param       integer         $userID
-        */
-       public function removeParticipant($userID) {
-               $sql = "SELECT  joinedAt
-                       FROM    wcf".WCF_N."_conversation_to_user
+        $statement = WCF::getDB()->prepareStatement($sql, 5);
+        $statement->execute([$this->conversationID, $this->userID]);
+
+        $this->update(['participantSummary' => \serialize($statement->fetchAll(\PDO::FETCH_ASSOC))]);
+    }
+
+    /**
+     * Removes a participant from this conversation.
+     *
+     * @param integer $userID
+     */
+    public function removeParticipant($userID)
+    {
+        $sql = "SELECT  joinedAt
+                       FROM    wcf" . WCF_N . "_conversation_to_user
                        WHERE   conversationID = ?
                                AND participantID = ?";
-               $statement = WCF::getDB()->prepareStatement($sql, 1);
-               $statement->execute([$this->conversationID, $userID]);
-               $joinedAt = $statement->fetchSingleColumn();
-               
-               $sql = "SELECT  messageID
-                       FROM    wcf".WCF_N."_conversation_message
+        $statement = WCF::getDB()->prepareStatement($sql, 1);
+        $statement->execute([$this->conversationID, $userID]);
+        $joinedAt = $statement->fetchSingleColumn();
+
+        $sql = "SELECT  messageID
+                       FROM    wcf" . WCF_N . "_conversation_message
                        WHERE   conversationID = ?
                                AND time >= ?
                                AND time <= ?";
-               $statement = WCF::getDB()->prepareStatement($sql, 1);
-               $statement->execute([
-                       $this->conversationID,
-                       $joinedAt,
-                       TIME_NOW
-               ]);
-               $lastMessageID = $statement->fetchSingleColumn();
-               
-               $sql = "UPDATE  wcf".WCF_N."_conversation_to_user
+        $statement = WCF::getDB()->prepareStatement($sql, 1);
+        $statement->execute([
+            $this->conversationID,
+            $joinedAt,
+            TIME_NOW,
+        ]);
+        $lastMessageID = $statement->fetchSingleColumn();
+
+        $sql = "UPDATE wcf" . WCF_N . "_conversation_to_user
                        SET     leftAt = ?,
                                lastMessageID = ?,
                                leftByOwnChoice = ?
                        WHERE   conversationID = ?
                                AND participantID = ?";
-               $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute([
-                       TIME_NOW,
-                       $lastMessageID ?: null,
-                       0,
-                       $this->conversationID,
-                       $userID
-               ]);
-               
-               // decrease participant count unless it is the author
-               if ($userID != $this->userID) {
-                       $this->updateCounters([
-                               'participants' => -1
-                       ]);
-               }
-       }
-       
-       /**
-        * Updates the first message of this conversation.
-        */
-       public function updateFirstMessage() {
-               $sql = "SELECT          messageID
-                       FROM            wcf".WCF_N."_conversation_message
+        $statement = WCF::getDB()->prepareStatement($sql);
+        $statement->execute([
+            TIME_NOW,
+            $lastMessageID ?: null,
+            0,
+            $this->conversationID,
+            $userID,
+        ]);
+
+        // decrease participant count unless it is the author
+        if ($userID != $this->userID) {
+            $this->updateCounters([
+                'participants' => -1,
+            ]);
+        }
+    }
+
+    /**
+     * Updates the first message of this conversation.
+     */
+    public function updateFirstMessage()
+    {
+        $sql = "SELECT         messageID
+                       FROM            wcf" . WCF_N . "_conversation_message
                        WHERE           conversationID = ?
                        ORDER BY        time ASC";
-               $statement = WCF::getDB()->prepareStatement($sql, 1);
-               $statement->execute([
-                       $this->conversationID
-               ]);
-               
-               $this->update([
-                       'firstMessageID' => $statement->fetchColumn()
-               ]);
-       }
-       
-       /**
-        * Updates the last message of this conversation.
-        */
-       public function updateLastMessage() {
-               $sql = "SELECT          time, userID, username
-                       FROM            wcf".WCF_N."_conversation_message
+        $statement = WCF::getDB()->prepareStatement($sql, 1);
+        $statement->execute([
+            $this->conversationID,
+        ]);
+
+        $this->update([
+            'firstMessageID' => $statement->fetchColumn(),
+        ]);
+    }
+
+    /**
+     * Updates the last message of this conversation.
+     */
+    public function updateLastMessage()
+    {
+        $sql = "SELECT         time, userID, username
+                       FROM            wcf" . WCF_N . "_conversation_message
                        WHERE           conversationID = ?
                        ORDER BY        time DESC";
-               $statement = WCF::getDB()->prepareStatement($sql, 1);
-               $statement->execute([
-                       $this->conversationID
-               ]);
-               $row = $statement->fetchArray();
-               
-               $this->update([
-                       'lastPostTime' => $row['time'],
-                       'lastPosterID' => $row['userID'],
-                       'lastPoster' => $row['username']
-               ]);
-       }
-       
-       /**
-        * Updates the participant summary of the given conversations.
-        * 
-        * @param       integer[]               $conversationIDs
-        */
-       public static function updateParticipantSummaries(array $conversationIDs) {
-               $conversationList = new ConversationList();
-               $conversationList->setObjectIDs($conversationIDs);
-               $conversationList->readObjects();
-               
-               foreach ($conversationList as $conversation) {
-                       $editor = new ConversationEditor($conversation);
-                       $editor->updateParticipantSummary();
-               }
-       }
-       
-       /**
-        * Updates the participant counts of the given conversations.
-        * 
-        * @param       integer[]               $conversationIDs
-        */
-       public static function updateParticipantCounts(array $conversationIDs) {
-               $conversationList = new ConversationList();
-               $conversationList->setObjectIDs($conversationIDs);
-               $conversationList->readObjects();
-               
-               foreach ($conversationList as $conversation) {
-                       $editor = new ConversationEditor($conversation);
-                       $editor->updateParticipantCount();
-               }
-       }
+        $statement = WCF::getDB()->prepareStatement($sql, 1);
+        $statement->execute([
+            $this->conversationID,
+        ]);
+        $row = $statement->fetchArray();
+
+        $this->update([
+            'lastPostTime' => $row['time'],
+            'lastPosterID' => $row['userID'],
+            'lastPoster' => $row['username'],
+        ]);
+    }
+
+    /**
+     * Updates the participant summary of the given conversations.
+     *
+     * @param integer[] $conversationIDs
+     */
+    public static function updateParticipantSummaries(array $conversationIDs)
+    {
+        $conversationList = new ConversationList();
+        $conversationList->setObjectIDs($conversationIDs);
+        $conversationList->readObjects();
+
+        foreach ($conversationList as $conversation) {
+            $editor = new self($conversation);
+            $editor->updateParticipantSummary();
+        }
+    }
+
+    /**
+     * Updates the participant counts of the given conversations.
+     *
+     * @param integer[] $conversationIDs
+     */
+    public static function updateParticipantCounts(array $conversationIDs)
+    {
+        $conversationList = new ConversationList();
+        $conversationList->setObjectIDs($conversationIDs);
+        $conversationList->readObjects();
+
+        foreach ($conversationList as $conversation) {
+            $editor = new self($conversation);
+            $editor->updateParticipantCount();
+        }
+    }
 }
index 80d99ba38dbc65e7023237a11988d3af5dadad19..e7499c2a3047ff5531996013aec0f42ad74d8d8b 100644 (file)
@@ -1,23 +1,26 @@
 <?php
+
 namespace wcf\data\conversation;
+
 use wcf\data\DatabaseObjectList;
 
 /**
  * Represents a list of conversations.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\Data\Conversation
  *
- * @method     Conversation            current()
- * @method     Conversation[]          getObjects()
- * @method     Conversation|null       search($objectID)
- * @property   Conversation[]          $objects
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Data\Conversation
+ *
+ * @method  Conversation        current()
+ * @method  Conversation[]      getObjects()
+ * @method  Conversation|null   search($objectID)
+ * @property    Conversation[] $objects
  */
-class ConversationList extends DatabaseObjectList {
-       /**
-        * @inheritDoc
-        */
-       public $className = Conversation::class;
+class ConversationList extends DatabaseObjectList
+{
+    /**
+     * @inheritDoc
+     */
+    public $className = Conversation::class;
 }
index 60eb827facb5dc3a80f14db8389e3d738b1dc83c..575cf22c0338cae886e84b8f42251c8a2c3f133d 100644 (file)
 <?php
+
 namespace wcf\data\conversation;
+
 use wcf\data\user\UserProfile;
 use wcf\data\user\UserProfileList;
 use wcf\system\WCF;
 
 /**
  * Represents a list of conversation participants.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\Data\Conversation
+ *
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Data\Conversation
  */
-class ConversationParticipantList extends UserProfileList {
-       /**
-        * conversation id
-        * @var integer
-        */
-       public $conversationID = 0;
-       
-       /**
-        * @inheritDoc
-        */
-       public $sqlLimit = 0;
-       
-       /**
-        * Creates a new ConversationParticipantList object.
-        * 
-        * @param       integer         $conversationID
-        * @param       integer         $userID
-        * @param       boolean         $isAuthor               true if given user is the author of this conversation
-        */
-       public function __construct($conversationID, $userID = 0, $isAuthor = false) {
-               parent::__construct();
-               
-               $this->conversationID = $conversationID;
-               $this->getConditionBuilder()->add('conversation_to_user.conversationID = ?', [$conversationID]);
-               if (!$isAuthor) {
-                       if ($userID) {
-                               $this->getConditionBuilder()->add('(conversation_to_user.isInvisible = 0 OR conversation_to_user.participantID = ?)', [$userID]);
-                       }
-                       else {
-                               $this->getConditionBuilder()->add('conversation_to_user.isInvisible = 0');
-                       }
-               }
-               $this->sqlConditionJoins .= " LEFT JOIN wcf".WCF_N."_user user_table ON (user_table.userID = conversation_to_user.participantID)";
-               
-               if (!empty($this->sqlSelects)) $this->sqlSelects .= ',';
-               $this->sqlSelects = 'conversation_to_user.*';
-               $this->sqlJoins .= " LEFT JOIN wcf".WCF_N."_conversation_to_user conversation_to_user ON (conversation_to_user.participantID = user_table.userID AND conversation_to_user.conversationID = ".$conversationID.")";
-       }
-       
-       /** @noinspection PhpMissingParentCallCommonInspection */
-       /**
-        * @inheritDoc
-        */
-       public function countObjects() {
-               $sql = "SELECT  COUNT(*) AS count
-                       FROM    wcf".WCF_N."_conversation_to_user conversation_to_user
-                       ".$this->sqlConditionJoins."
-                       ".$this->getConditionBuilder()->__toString();
-               $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute($this->getConditionBuilder()->getParameters());
-               $row = $statement->fetchArray();
-               return $row['count'];
-       }
-       
-       /** @noinspection PhpMissingParentCallCommonInspection */
-       /**
-        * @inheritDoc
-        */
-       public function readObjectIDs() {
-               $this->objectIDs = [];
-               $sql = "SELECT  conversation_to_user.participantID AS objectID
-                       FROM    wcf".WCF_N."_conversation_to_user conversation_to_user
-                               ".$this->sqlConditionJoins."
-                               ".$this->getConditionBuilder()->__toString()."
-                               ".(!empty($this->sqlOrderBy) ? "ORDER BY ".$this->sqlOrderBy : '');
-               $statement = WCF::getDB()->prepareStatement($sql, $this->sqlLimit, $this->sqlOffset);
-               $statement->execute($this->getConditionBuilder()->getParameters());
-               $this->objectIDs = $statement->fetchAll(\PDO::FETCH_COLUMN);
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function readObjects() {
-               parent::readObjects();
-               
-               // check for deleted users
-               $sql = "SELECT  username
-                       FROM    wcf".WCF_N."_conversation_to_user
+class ConversationParticipantList extends UserProfileList
+{
+    /**
+     * conversation id
+     * @var integer
+     */
+    public $conversationID = 0;
+
+    /**
+     * @inheritDoc
+     */
+    public $sqlLimit = 0;
+
+    /**
+     * Creates a new ConversationParticipantList object.
+     *
+     * @param integer $conversationID
+     * @param integer $userID
+     * @param boolean $isAuthor true if given user is the author of this conversation
+     */
+    public function __construct($conversationID, $userID = 0, $isAuthor = false)
+    {
+        parent::__construct();
+
+        $this->conversationID = $conversationID;
+        $this->getConditionBuilder()->add('conversation_to_user.conversationID = ?', [$conversationID]);
+        if (!$isAuthor) {
+            if ($userID) {
+                $this->getConditionBuilder()->add(
+                    '(conversation_to_user.isInvisible = 0 OR conversation_to_user.participantID = ?)',
+                    [$userID]
+                );
+            } else {
+                $this->getConditionBuilder()->add(
+                    'conversation_to_user.isInvisible = 0'
+                );
+            }
+        }
+        $this->sqlConditionJoins .= " LEFT JOIN wcf" . WCF_N . "_user user_table ON (user_table.userID = conversation_to_user.participantID)";
+
+        if (!empty($this->sqlSelects)) {
+            $this->sqlSelects .= ',';
+        }
+        $this->sqlSelects = 'conversation_to_user.*';
+        $this->sqlJoins .= " LEFT JOIN wcf" . WCF_N . "_conversation_to_user conversation_to_user ON (conversation_to_user.participantID = user_table.userID AND conversation_to_user.conversationID = " . $conversationID . ")";
+    }
+
+    /** @noinspection PhpMissingParentCallCommonInspection */
+
+    /**
+     * @inheritDoc
+     */
+    public function countObjects()
+    {
+        $sql = "SELECT COUNT(*) AS count
+                       FROM    wcf" . WCF_N . "_conversation_to_user conversation_to_user
+                       " . $this->sqlConditionJoins . "
+                       " . $this->getConditionBuilder()->__toString();
+        $statement = WCF::getDB()->prepareStatement($sql);
+        $statement->execute($this->getConditionBuilder()->getParameters());
+        $row = $statement->fetchArray();
+
+        return $row['count'];
+    }
+
+    /** @noinspection PhpMissingParentCallCommonInspection */
+
+    /**
+     * @inheritDoc
+     */
+    public function readObjectIDs()
+    {
+        $this->objectIDs = [];
+        $sql = "SELECT conversation_to_user.participantID AS objectID
+                       FROM    wcf" . WCF_N . "_conversation_to_user conversation_to_user
+                               " . $this->sqlConditionJoins . "
+                               " . $this->getConditionBuilder()->__toString() . "
+                               " . (!empty($this->sqlOrderBy) ? "ORDER BY " . $this->sqlOrderBy : '');
+        $statement = WCF::getDB()->prepareStatement($sql, $this->sqlLimit, $this->sqlOffset);
+        $statement->execute($this->getConditionBuilder()->getParameters());
+        $this->objectIDs = $statement->fetchAll(\PDO::FETCH_COLUMN);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function readObjects()
+    {
+        parent::readObjects();
+
+        // check for deleted users
+        $sql = "SELECT username
+                       FROM    wcf" . WCF_N . "_conversation_to_user
                        WHERE   conversationID = ?
                                AND participantID IS NULL";
-               $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute([$this->conversationID]);
-               $i = 0;
-               while ($row = $statement->fetchArray()) {
-                       // create fake user profiles
-                       $this->objects['x'.(++$i)] = UserProfile::getGuestUserProfile($row['username']);
-                       $this->indexToObject[] = 'x'.$i;
-               }
-       }
+        $statement = WCF::getDB()->prepareStatement($sql);
+        $statement->execute([$this->conversationID]);
+        $i = 0;
+        while ($row = $statement->fetchArray()) {
+            // create fake user profiles
+            $this->objects['x' . (++$i)] = UserProfile::getGuestUserProfile($row['username']);
+            $this->indexToObject[] = 'x' . $i;
+        }
+    }
 }
index 062a1ef2ede3c734a18565d523732d36150800b7..94dabc3ddbe9b701ddbe9015385453d266c0696d 100644 (file)
 <?php
+
 namespace wcf\data\conversation;
+
 use wcf\data\DatabaseObjectDecorator;
 use wcf\data\IFeedEntry;
 use wcf\system\request\LinkHandler;
 
 /**
  * Represents a conversation for RSS feeds.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\Data\Conversation
  *
- * @method     Conversation    getDecoratedObject()
- * @mixin      Conversation
+ * @author  Alexander Ebert
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Data\Conversation
+ *
+ * @method  Conversation    getDecoratedObject()
+ * @mixin   Conversation
  */
-class FeedConversation extends DatabaseObjectDecorator implements IFeedEntry {
-       /**
-        * @inheritDoc
-        */
-       protected static $baseClass = Conversation::class;
-       
-       /**
-        * @inheritDoc
-        */
-       public function getLink() {
-               return LinkHandler::getInstance()->getLink('Conversation', [
-                       'object' => $this->getDecoratedObject(),
-                       'encodeTitle' => true
-               ]);
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getTitle() {
-               return $this->getDecoratedObject()->getTitle();
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getFormattedMessage() {
-               return '';
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getMessage() {
-               return '';
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getExcerpt($maxLength = 255) {
-               return '';
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getUserID() {
-               return $this->getDecoratedObject()->lastPosterID;
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getUsername() {
-               return $this->getDecoratedObject()->lastPoster;
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getTime() {
-               return $this->getDecoratedObject()->lastPostTime;
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function __toString() {
-               return $this->getFormattedMessage();
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getComments() {
-               return $this->replies;
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getCategories() {
-               return [];
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function isVisible() {
-               return $this->canRead();
-       }
+class FeedConversation extends DatabaseObjectDecorator implements IFeedEntry
+{
+    /**
+     * @inheritDoc
+     */
+    protected static $baseClass = Conversation::class;
+
+    /**
+     * @inheritDoc
+     */
+    public function getLink()
+    {
+        return LinkHandler::getInstance()->getLink('Conversation', [
+            'object' => $this->getDecoratedObject(),
+            'encodeTitle' => true,
+        ]);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getTitle()
+    {
+        return $this->getDecoratedObject()->getTitle();
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getFormattedMessage()
+    {
+        return '';
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getMessage()
+    {
+        return '';
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getExcerpt($maxLength = 255)
+    {
+        return '';
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getUserID()
+    {
+        return $this->getDecoratedObject()->lastPosterID;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getUsername()
+    {
+        return $this->getDecoratedObject()->lastPoster;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getTime()
+    {
+        return $this->getDecoratedObject()->lastPostTime;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function __toString()
+    {
+        return $this->getFormattedMessage();
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getComments()
+    {
+        return $this->replies;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getCategories()
+    {
+        return [];
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function isVisible()
+    {
+        return $this->canRead();
+    }
 }
index 4ace14d79de0e7b666b2b171bad812f6d6c1725c..9f9c54f7b74bec3793f0e5ca3268b1aa05bc2015 100644 (file)
@@ -1,54 +1,60 @@
 <?php
+
 namespace wcf\data\conversation;
+
 use wcf\system\WCF;
 
 /**
  * Represents a list of conversations for RSS feeds.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\Data\Conversation
  *
- * @method     FeedConversation        current()
- * @method     FeedConversation[]      getObjects()
- * @method     FeedConversation|null   search($objectID)
- * @property   FeedConversation[]      $objects
+ * @author  Alexander Ebert
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Data\Conversation
+ *
+ * @method  FeedConversation    current()
+ * @method  FeedConversation[]  getObjects()
+ * @method  FeedConversation|null   search($objectID)
+ * @property    FeedConversation[] $objects
  */
-class FeedConversationList extends ConversationList {
-       /**
-        * @inheritDoc
-        */
-       public $decoratorClassName = FeedConversation::class;
-       
-       /**
-        * @inheritDoc
-        */
-       public $sqlOrderBy = 'conversation.lastPostTime DESC';
-       
-       /** @noinspection PhpMissingParentCallCommonInspection */
-       /**
-        * @inheritDoc
-        */
-       public function readObjectIDs() {
-               $sql = "SELECT  conversation_to_user.conversationID AS objectID
-                       FROM    wcf".WCF_N."_conversation_to_user conversation_to_user
-                               ".$this->sqlConditionJoins."
-                               ".$this->getConditionBuilder()."
-                               ".(!empty($this->sqlOrderBy) ? "ORDER BY ".$this->sqlOrderBy : '');
-               $statement = WCF::getDB()->prepareStatement($sql, $this->sqlLimit, $this->sqlOffset);
-               $statement->execute($this->getConditionBuilder()->getParameters());
-               $this->objectIDs = $statement->fetchAll(\PDO::FETCH_COLUMN);
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function readObjects() {
-               if ($this->objectIDs === null) {
-                       $this->readObjectIDs();
-               }
-               
-               parent::readObjects();
-       }
+class FeedConversationList extends ConversationList
+{
+    /**
+     * @inheritDoc
+     */
+    public $decoratorClassName = FeedConversation::class;
+
+    /**
+     * @inheritDoc
+     */
+    public $sqlOrderBy = 'conversation.lastPostTime DESC';
+
+    /** @noinspection PhpMissingParentCallCommonInspection */
+
+    /**
+     * @inheritDoc
+     */
+    public function readObjectIDs()
+    {
+        $sql = "SELECT conversation_to_user.conversationID AS objectID
+                       FROM    wcf" . WCF_N . "_conversation_to_user conversation_to_user
+                               " . $this->sqlConditionJoins . "
+                               " . $this->getConditionBuilder() . "
+                               " . (!empty($this->sqlOrderBy) ? "ORDER BY " . $this->sqlOrderBy : '');
+        $statement = WCF::getDB()->prepareStatement($sql, $this->sqlLimit, $this->sqlOffset);
+        $statement->execute($this->getConditionBuilder()->getParameters());
+        $this->objectIDs = $statement->fetchAll(\PDO::FETCH_COLUMN);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function readObjects()
+    {
+        if ($this->objectIDs === null) {
+            $this->readObjectIDs();
+        }
+
+        parent::readObjects();
+    }
 }
index 717c1ebf326c8bc9168cdac854a965d36ec33407..0c7db3119a710bddc098a81b0f3270bdd731a0e0 100644 (file)
@@ -1,5 +1,7 @@
 <?php
+
 namespace wcf\data\conversation;
+
 use wcf\data\conversation\label\ConversationLabel;
 use wcf\data\conversation\label\ConversationLabelList;
 use wcf\system\cache\runtime\UserProfileRuntimeCache;
@@ -8,244 +10,258 @@ use wcf\system\WCF;
 
 /**
  * Represents a list of conversations.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\Data\Conversation
- * 
- * @method     ViewableConversation            current()
- * @method     ViewableConversation[]          getObjects()
- * @method     ViewableConversation|null       search($objectID)
- * @property   ViewableConversation[]          $objects
+ *
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Data\Conversation
+ *
+ * @method  ViewableConversation        current()
+ * @method  ViewableConversation[]      getObjects()
+ * @method  ViewableConversation|null   search($objectID)
+ * @property    ViewableConversation[] $objects
  */
-class UserConversationList extends ConversationList {
-       /**
-        * list of available filters
-        * @var string[]
-        */
-       public static $availableFilters = ['hidden', 'draft', 'outbox'];
-       
-       /**
-        * active filter
-        * @var string
-        */
-       public $filter = '';
-       
-       /**
-        * label list object
-        * @var ConversationLabelList
-        */
-       public $labelList;
-       
-       /**
-        * @inheritDoc
-        */
-       public $decoratorClassName = ViewableConversation::class;
-       
-       /**
-        * Creates a new UserConversationList
-        * 
-        * @param       integer         $userID
-        * @param       string          $filter
-        * @param       integer         $labelID
-        */
-       public function __construct($userID, $filter = '', $labelID = 0) {
-               parent::__construct();
-               
-               $this->filter = $filter;
-               
-               // apply filter
-               if ($this->filter === 'draft') {
-                       $this->getConditionBuilder()->add('conversation.userID = ?', [$userID]);
-                       $this->getConditionBuilder()->add('conversation.isDraft = 1');
-               }
-               else {
-                       $this->getConditionBuilder()->add('conversation_to_user.participantID = ?', [$userID]);
-                       $this->getConditionBuilder()->add('conversation_to_user.hideConversation = ?', [$this->filter == 'hidden' ? 1 : 0]);
-                       $this->sqlConditionJoins = "LEFT JOIN wcf".WCF_N."_conversation conversation ON (conversation.conversationID = conversation_to_user.conversationID)";
-                       if ($this->filter == 'outbox') $this->getConditionBuilder()->add('conversation.userID = ?', [$userID]);
-               }
-               
-               // filter by label id
-               if ($labelID) {
-                       $this->getConditionBuilder()->add("conversation.conversationID IN (
+class UserConversationList extends ConversationList
+{
+    /**
+     * list of available filters
+     * @var string[]
+     */
+    public static $availableFilters = ['hidden', 'draft', 'outbox'];
+
+    /**
+     * active filter
+     * @var string
+     */
+    public $filter = '';
+
+    /**
+     * label list object
+     * @var ConversationLabelList
+     */
+    public $labelList;
+
+    /**
+     * @inheritDoc
+     */
+    public $decoratorClassName = ViewableConversation::class;
+
+    /**
+     * Creates a new UserConversationList
+     *
+     * @param integer $userID
+     * @param string $filter
+     * @param integer $labelID
+     */
+    public function __construct($userID, $filter = '', $labelID = 0)
+    {
+        parent::__construct();
+
+        $this->filter = $filter;
+
+        // apply filter
+        if ($this->filter === 'draft') {
+            $this->getConditionBuilder()->add('conversation.userID = ?', [$userID]);
+            $this->getConditionBuilder()->add('conversation.isDraft = 1');
+        } else {
+            $this->getConditionBuilder()->add('conversation_to_user.participantID = ?', [$userID]);
+            $this->getConditionBuilder()
+                ->add('conversation_to_user.hideConversation = ?', [$this->filter == 'hidden' ? 1 : 0]);
+            $this->sqlConditionJoins = "LEFT JOIN wcf" . WCF_N . "_conversation conversation ON (conversation.conversationID = conversation_to_user.conversationID)";
+            if ($this->filter == 'outbox') {
+                $this->getConditionBuilder()->add('conversation.userID = ?', [$userID]);
+            }
+        }
+
+        // filter by label id
+        if ($labelID) {
+            $this->getConditionBuilder()->add("conversation.conversationID IN (
                                SELECT  conversationID
-                               FROM    wcf".WCF_N."_conversation_label_to_object
+                               FROM    wcf" . WCF_N . "_conversation_label_to_object
                                WHERE   labelID = ?
                        )", [$labelID]);
-               }
-               
-               // own posts
-               $this->sqlSelects = "DISTINCT conversation_message.userID AS ownPosts";
-               $this->sqlJoins = "LEFT JOIN wcf".WCF_N."_conversation_message conversation_message ON (conversation_message.conversationID = conversation.conversationID AND conversation_message.userID = ".$userID.")";
-               
-               // user info
-               if (!empty($this->sqlSelects)) $this->sqlSelects .= ',';
-               $this->sqlSelects .= "conversation_to_user.*";
-               $this->sqlJoins .= "LEFT JOIN wcf".WCF_N."_conversation_to_user conversation_to_user ON (conversation_to_user.participantID = ".$userID." AND conversation_to_user.conversationID = conversation.conversationID)";
-               
-               if ($this->filter !== 'draft') {
-                       $this->sqlSelects .= ", conversation.*, CASE WHEN conversation_to_user.leftAt <> 0 THEN conversation_to_user.leftAt ELSE conversation.lastPostTime END AS lastPostTime";
-                       // this avoids appending `conversation.*` to the SELECT list
-                       $this->useQualifiedShorthand = false;
-               }
-       }
-       
-       /**
-        * Sets the label list of the user the conversations belong to.
-        * 
-        * @param       ConversationLabelList   $labelList
-        */
-       public function setLabelList(ConversationLabelList $labelList) {
-               $this->labelList = $labelList;
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function countObjects() {
-               if ($this->filter == 'draft') return parent::countObjects();
-               
-               $sql = "SELECT  COUNT(*) AS count
-                       FROM    wcf".WCF_N."_conversation_to_user conversation_to_user
-                       ".$this->sqlConditionJoins."
-                       ".$this->getConditionBuilder()->__toString();
-               $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute($this->getConditionBuilder()->getParameters());
-               $row = $statement->fetchArray();
-               return $row['count'];
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function readObjectIDs() {
-               if ($this->filter === 'draft') {
-                       parent::readObjectIDs();
-                       
-                       return;
-               }
-               
-               $sql = "SELECT  conversation_to_user.conversationID AS objectID
-                       FROM    wcf".WCF_N."_conversation_to_user conversation_to_user
-                               ".$this->sqlConditionJoins."
-                               ".$this->getConditionBuilder()->__toString()."
-                               ".(!empty($this->sqlOrderBy) ? "ORDER BY ".$this->sqlOrderBy : '');
-               $statement = WCF::getDB()->prepareStatement($sql, $this->sqlLimit, $this->sqlOffset);
-               $statement->execute($this->getConditionBuilder()->getParameters());
-               $this->objectIDs = $statement->fetchAll(\PDO::FETCH_COLUMN);
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function readObjects() {
-               if ($this->objectIDs === null) {
-                       $this->readObjectIDs();
-               }
-               
-               parent::readObjects();
-               
-               if (!empty($this->objects)) {
-                       $messageIDs = [];
-                       foreach ($this->objects as $conversation) {
-                               if ($conversation->lastMessageID) {
-                                       $messageIDs[] = $conversation->lastMessageID;
-                               }
-                       }
-                       if (!empty($messageIDs)) {
-                               $conditions = new PreparedStatementConditionBuilder();
-                               $conditions->add("messageID IN (?)", [$messageIDs]);
-                               $sql = "SELECT  messageID, userID, username, time
-                                       FROM    wcf".WCF_N."_conversation_message
-                                       ".$conditions;
-                               $statement = WCF::getDB()->prepareStatement($sql);
-                               $statement->execute($conditions->getParameters());
-                               $messageData = [];
-                               while ($row = $statement->fetchArray()) {
-                                       $messageData[$row['messageID']] = $row;
-                               }
-                               
-                               foreach ($this->objects as $conversation) {
-                                       if ($conversation->lastMessageID) {
-                                               $data = (isset($messageData[$conversation->lastMessageID])) ? $messageData[$conversation->lastMessageID] : null;
-                                               if ($data !== null) {
-                                                       $conversation->setLastMessage($data['userID'], $data['username'], $data['time']);
-                                               }
-                                               else {
-                                                       $conversation->setLastMessage(null, '', 0);
-                                               }
-                                       }
-                               }
-                       }
-                       
-                       $labels = $this->loadLabelAssignments();
-                       
-                       $userIDs = [];
-                       foreach ($this->objects as $conversationID => $conversation) {
-                               if (isset($labels[$conversationID])) {
-                                       foreach ($labels[$conversationID] as $label) {
-                                               $conversation->assignLabel($label);
-                                       }
-                               }
-                               
-                               if ($conversation->userID) {
-                                       $userIDs[] = $conversation->userID;
-                               }
-                               if ($conversation->lastPosterID) {
-                                       $userIDs[] = $conversation->lastPosterID;
-                               }
-                       }
-                       
-                       if (!empty($userIDs)) {
-                               UserProfileRuntimeCache::getInstance()->cacheObjectIDs($userIDs);
-                       }
-               }
-       }
-       
-       /**
-        * Returns a list of conversation labels.
-        * 
-        * @return      ConversationLabel[]
-        */
-       protected function getLabels() {
-               if ($this->labelList === null) {
-                       $this->labelList = ConversationLabel::getLabelsByUser();
-               }
-               
-               return $this->labelList->getObjects();
-       }
-       
-       /**
-        * Returns label assignments per conversation.
-        * 
-        * @return      ConversationLabel[][]
-        */
-       protected function loadLabelAssignments() {
-               $labels = $this->getLabels();
-               if (empty($labels)) {
-                       return [];
-               }
-               
-               $conditions = new PreparedStatementConditionBuilder();
-               $conditions->add("conversationID IN (?)", [array_keys($this->objects)]);
-               $conditions->add("labelID IN (?)", [array_keys($labels)]);
-               
-               $sql = "SELECT  labelID, conversationID
-                       FROM    wcf".WCF_N."_conversation_label_to_object
-                       ".$conditions;
-               $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute($conditions->getParameters());
-               $data = [];
-               while ($row = $statement->fetchArray()) {
-                       if (!isset($data[$row['conversationID']])) {
-                               $data[$row['conversationID']] = [];
-                       }
-                       
-                       $data[$row['conversationID']][$row['labelID']] = $labels[$row['labelID']];
-               }
-               
-               return $data;
-       }
+        }
+
+        // own posts
+        $this->sqlSelects = "DISTINCT conversation_message.userID AS ownPosts";
+        $this->sqlJoins = "LEFT JOIN wcf" . WCF_N . "_conversation_message conversation_message ON (conversation_message.conversationID = conversation.conversationID AND conversation_message.userID = " . $userID . ")";
+
+        // user info
+        if (!empty($this->sqlSelects)) {
+            $this->sqlSelects .= ',';
+        }
+        $this->sqlSelects .= "conversation_to_user.*";
+        $this->sqlJoins .= "LEFT JOIN wcf" . WCF_N . "_conversation_to_user conversation_to_user ON (conversation_to_user.participantID = " . $userID . " AND conversation_to_user.conversationID = conversation.conversationID)";
+
+        if ($this->filter !== 'draft') {
+            $this->sqlSelects .= ", conversation.*, CASE WHEN conversation_to_user.leftAt <> 0 THEN conversation_to_user.leftAt ELSE conversation.lastPostTime END AS lastPostTime";
+            // this avoids appending `conversation.*` to the SELECT list
+            $this->useQualifiedShorthand = false;
+        }
+    }
+
+    /**
+     * Sets the label list of the user the conversations belong to.
+     *
+     * @param ConversationLabelList $labelList
+     */
+    public function setLabelList(ConversationLabelList $labelList)
+    {
+        $this->labelList = $labelList;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function countObjects()
+    {
+        if ($this->filter == 'draft') {
+            return parent::countObjects();
+        }
+
+        $sql = "SELECT COUNT(*) AS count
+                       FROM    wcf" . WCF_N . "_conversation_to_user conversation_to_user
+                       " . $this->sqlConditionJoins . "
+                       " . $this->getConditionBuilder()->__toString();
+        $statement = WCF::getDB()->prepareStatement($sql);
+        $statement->execute($this->getConditionBuilder()->getParameters());
+        $row = $statement->fetchArray();
+
+        return $row['count'];
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function readObjectIDs()
+    {
+        if ($this->filter === 'draft') {
+            parent::readObjectIDs();
+
+            return;
+        }
+
+        $sql = "SELECT conversation_to_user.conversationID AS objectID
+                       FROM    wcf" . WCF_N . "_conversation_to_user conversation_to_user
+                               " . $this->sqlConditionJoins . "
+                               " . $this->getConditionBuilder()->__toString() . "
+                               " . (!empty($this->sqlOrderBy) ? "ORDER BY " . $this->sqlOrderBy : '');
+        $statement = WCF::getDB()->prepareStatement($sql, $this->sqlLimit, $this->sqlOffset);
+        $statement->execute($this->getConditionBuilder()->getParameters());
+        $this->objectIDs = $statement->fetchAll(\PDO::FETCH_COLUMN);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function readObjects()
+    {
+        if ($this->objectIDs === null) {
+            $this->readObjectIDs();
+        }
+
+        parent::readObjects();
+
+        if (!empty($this->objects)) {
+            $messageIDs = [];
+            foreach ($this->objects as $conversation) {
+                if ($conversation->lastMessageID) {
+                    $messageIDs[] = $conversation->lastMessageID;
+                }
+            }
+            if (!empty($messageIDs)) {
+                $conditions = new PreparedStatementConditionBuilder();
+                $conditions->add("messageID IN (?)", [$messageIDs]);
+                $sql = "SELECT  messageID, userID, username, time
+                                       FROM    wcf" . WCF_N . "_conversation_message
+                                       " . $conditions;
+                $statement = WCF::getDB()->prepareStatement($sql);
+                $statement->execute($conditions->getParameters());
+                $messageData = [];
+                while ($row = $statement->fetchArray()) {
+                    $messageData[$row['messageID']] = $row;
+                }
+
+                foreach ($this->objects as $conversation) {
+                    if ($conversation->lastMessageID) {
+                        $data = (isset($messageData[$conversation->lastMessageID])) ? $messageData[$conversation->lastMessageID] : null;
+                        if ($data !== null) {
+                            $conversation->setLastMessage($data['userID'], $data['username'], $data['time']);
+                        } else {
+                            $conversation->setLastMessage(null, '', 0);
+                        }
+                    }
+                }
+            }
+
+            $labels = $this->loadLabelAssignments();
+
+            $userIDs = [];
+            foreach ($this->objects as $conversationID => $conversation) {
+                if (isset($labels[$conversationID])) {
+                    foreach ($labels[$conversationID] as $label) {
+                        $conversation->assignLabel($label);
+                    }
+                }
+
+                if ($conversation->userID) {
+                    $userIDs[] = $conversation->userID;
+                }
+                if ($conversation->lastPosterID) {
+                    $userIDs[] = $conversation->lastPosterID;
+                }
+            }
+
+            if (!empty($userIDs)) {
+                UserProfileRuntimeCache::getInstance()->cacheObjectIDs($userIDs);
+            }
+        }
+    }
+
+    /**
+     * Returns a list of conversation labels.
+     *
+     * @return  ConversationLabel[]
+     */
+    protected function getLabels()
+    {
+        if ($this->labelList === null) {
+            $this->labelList = ConversationLabel::getLabelsByUser();
+        }
+
+        return $this->labelList->getObjects();
+    }
+
+    /**
+     * Returns label assignments per conversation.
+     *
+     * @return  ConversationLabel[][]
+     */
+    protected function loadLabelAssignments()
+    {
+        $labels = $this->getLabels();
+        if (empty($labels)) {
+            return [];
+        }
+
+        $conditions = new PreparedStatementConditionBuilder();
+        $conditions->add("conversationID IN (?)", [\array_keys($this->objects)]);
+        $conditions->add("labelID IN (?)", [\array_keys($labels)]);
+
+        $sql = "SELECT labelID, conversationID
+                       FROM    wcf" . WCF_N . "_conversation_label_to_object
+                       " . $conditions;
+        $statement = WCF::getDB()->prepareStatement($sql);
+        $statement->execute($conditions->getParameters());
+        $data = [];
+        while ($row = $statement->fetchArray()) {
+            if (!isset($data[$row['conversationID']])) {
+                $data[$row['conversationID']] = [];
+            }
+
+            $data[$row['conversationID']][$row['labelID']] = $labels[$row['labelID']];
+        }
+
+        return $data;
+    }
 }
index f4da146c0c6f5243df36b068f5730131a494f63e..cf5ec1669e8fef482fe80cfa64f64e83a30e3ac9 100644 (file)
 <?php
+
 namespace wcf\data\conversation;
+
 use wcf\data\conversation\label\ConversationLabel;
 use wcf\data\conversation\label\ConversationLabelList;
-use wcf\data\user\User;
-use wcf\data\user\UserProfile;
 use wcf\data\DatabaseObjectDecorator;
 use wcf\data\TLegacyUserPropertyAccess;
+use wcf\data\user\User;
+use wcf\data\user\UserProfile;
 use wcf\system\cache\runtime\UserProfileRuntimeCache;
 use wcf\system\database\util\PreparedStatementConditionBuilder;
 use wcf\system\WCF;
 
 /**
  * Represents a viewable conversation.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\Data\Conversation
- * 
- * @method     Conversation    getDecoratedObject()
- * @mixin      Conversation
- * @property-read      integer|null    $otherParticipantID
- * @property-read      string|null     $otherParticipant
+ *
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Data\Conversation
+ *
+ * @method  Conversation    getDecoratedObject()
+ * @mixin   Conversation
+ * @property-read   integer|null $otherParticipantID
+ * @property-read   string|null $otherParticipant
  */
-class ViewableConversation extends DatabaseObjectDecorator {
-       use TLegacyUserPropertyAccess;
-       
-       /**
-        * participant summary
-        * @var string
-        */
-       protected $__participantSummary;
-       
-       /**
-        * user profile object
-        * @var UserProfile
-        */
-       protected $userProfile;
-       
-       /**
-        * last poster's profile
-        * @var UserProfile
-        */
-       protected $lastPosterProfile;
-       
-       /**
-        * other participant's profile
-        * @var UserProfile
-        */
-       protected $otherParticipantProfile;
-       
-       /**
-        * list of assigned labels
-        * @var ConversationLabel[]
-        */
-       protected $labels = [];
-       
-       /**
-        * @inheritDoc
-        */
-       protected static $baseClass = Conversation::class;
-       
-       /**
-        * maps legacy direct access to last poster's user profile data to the real
-        * user profile property names
-        * @var string[]
-        * @deprecated
-        */
-       protected static $__lastUserAvatarPropertyMapping = [
-               'lastPosterAvatarID' => 'avatarID',
-               'lastPosterAvatarName' => 'avatarName',
-               'lastPosterAvatarExtension' => 'avatarExtension',
-               'lastPosterAvatarWidth' => 'width',
-               'lastPosterAvatarHeight' => 'height',
-               'lastPosterEmail' => 'email',
-               'lastPosterDisableAvatar' => 'disableAvatar',
-               'lastPosterEnableGravatar' => 'enableGravatar',
-               'lastPosterGravatarFileExtension' => 'gravatarFileExtension',
-               'lastPosterAvatarFileHash' => 'fileHash'
-       ];
-       
-       /**
-        * @inheritDoc
-        * @deprecated
-        */
-       public function __get($name) {
-               $value = parent::__get($name);
-               if ($value !== null) {
-                       return $value;
-               }
-               else if (array_key_exists($name, $this->object->data)) {
-                       return null;
-               }
-               
-               /** @noinspection PhpVariableVariableInspection */
-               $value = $this->getUserProfile()->$name;
-               if ($value !== null) {
-                       return $value;
-               }
-               
-               if (isset(static::$__lastUserAvatarPropertyMapping[$name])) {
-                       return $this->getLastPosterProfile()->getAvatar()->{static::$__lastUserAvatarPropertyMapping[$name]};
-               }
-               
-               return null;
-       }
-       
-       /**
-        * Returns the user profile object.
-        * 
-        * @return      UserProfile
-        */
-       public function getUserProfile() {
-               if ($this->userProfile === null) {
-                       if ($this->userID) {
-                               $this->userProfile = UserProfileRuntimeCache::getInstance()->getObject($this->userID);
-                       }
-                       else {
-                               $this->userProfile = UserProfile::getGuestUserProfile($this->username);
-                       }
-               }
-               
-               return $this->userProfile;
-       }
-       
-       /**
-        * Returns the last poster's profile object.
-        * 
-        * @return      UserProfile
-        */
-       public function getLastPosterProfile() {
-               if ($this->lastPosterProfile === null) {
-                       if ($this->lastPosterID) {
-                               $this->lastPosterProfile = UserProfileRuntimeCache::getInstance()->getObject($this->lastPosterID);
-                       }
-                       else {
-                               $this->lastPosterProfile = UserProfile::getGuestUserProfile($this->lastPoster);
-                       }
-               }
-               
-               return $this->lastPosterProfile;
-       }
-       
-       /**
-        * Returns the number of pages in this conversation.
-        * 
-        * @return      integer
-        */
-       public function getPages() {
-               /** @noinspection PhpUndefinedFieldInspection */
-               if (WCF::getUser()->conversationMessagesPerPage) {
-                       /** @noinspection PhpUndefinedFieldInspection */
-                       $messagesPerPage = WCF::getUser()->conversationMessagesPerPage;
-               }
-               else {
-                       $messagesPerPage = CONVERSATION_MESSAGES_PER_PAGE;
-               }
-               
-               return intval(ceil(($this->replies + 1) / $messagesPerPage));
-       }
-       
-       /**
-        * Returns a summary of the participants.
-        * 
-        * @return      User[]
-        */
-       public function getParticipantSummary() {
-               if ($this->__participantSummary === null) {
-                       $this->__participantSummary = [];
-                       
-                       if ($this->participantSummary) {
-                               $data = unserialize($this->participantSummary);
-                               if ($data !== false) {
-                                       foreach ($data as $userData) {
-                                               $this->__participantSummary[] = new User(null, [
-                                                       'userID' => $userData['userID'],
-                                                       'username' => $userData['username'],
-                                                       'hideConversation' => $userData['hideConversation']
-                                               ]);
-                                       }
-                               }
-                       }
-               }
-               
-               return $this->__participantSummary;
-       }
-       
-       /**
-        * Returns the other participant's profile object.
-        *
-        * @return      UserProfile
-        */
-       public function getOtherParticipantProfile() {
-               if ($this->otherParticipantProfile === null) {
-                       if ($this->otherParticipantID) {
-                               $this->otherParticipantProfile = UserProfileRuntimeCache::getInstance()->getObject($this->otherParticipantID);
-                       }
-                       else {
-                               $this->otherParticipantProfile = UserProfile::getGuestUserProfile($this->otherParticipant);
-                       }
-               }
-               
-               return $this->otherParticipantProfile;
-       }
-       
-       /**
-        * Assigns a label.
-        * 
-        * @param       ConversationLabel       $label
-        */
-       public function assignLabel(ConversationLabel $label) {
-               $this->labels[$label->labelID] = $label;
-       }
-       
-       /**
-        * Returns a list of assigned labels.
-        * 
-        * @return      ConversationLabel[]
-        */
-       public function getAssignedLabels() {
-               return $this->labels;
-       }
-       
-       /**
-        * Converts a conversation into a viewable conversation.
-        * 
-        * @param       Conversation            $conversation
-        * @param       ConversationLabelList   $labelList
-        * @return      ViewableConversation
-        */
-       public static function getViewableConversation(Conversation $conversation, ConversationLabelList $labelList = null) {
-               $conversation = new ViewableConversation($conversation);
-               
-               if ($labelList === null) {
-                       $labelList = ConversationLabel::getLabelsByUser();
-               }
-               
-               $labels = $labelList->getObjects();
-               if (!empty($labels)) {
-                       $conditions = new PreparedStatementConditionBuilder();
-                       $conditions->add("conversationID = ?", [$conversation->conversationID]);
-                       $conditions->add("labelID IN (?)", [array_keys($labels)]);
-                       
-                       $sql = "SELECT  labelID
-                               FROM    wcf".WCF_N."_conversation_label_to_object
-                               ".$conditions;
-                       $statement = WCF::getDB()->prepareStatement($sql);
-                       $statement->execute($conditions->getParameters());
-                       while ($row = $statement->fetchArray()) {
-                               $conversation->assignLabel($labels[$row['labelID']]);
-                       }
-               }
-               
-               return $conversation;
-       }
+class ViewableConversation extends DatabaseObjectDecorator
+{
+    use TLegacyUserPropertyAccess;
+
+    /**
+     * participant summary
+     * @var string
+     */
+    protected $__participantSummary;
+
+    /**
+     * user profile object
+     * @var UserProfile
+     */
+    protected $userProfile;
+
+    /**
+     * last poster's profile
+     * @var UserProfile
+     */
+    protected $lastPosterProfile;
+
+    /**
+     * other participant's profile
+     * @var UserProfile
+     */
+    protected $otherParticipantProfile;
+
+    /**
+     * list of assigned labels
+     * @var ConversationLabel[]
+     */
+    protected $labels = [];
+
+    /**
+     * @inheritDoc
+     */
+    protected static $baseClass = Conversation::class;
+
+    /**
+     * maps legacy direct access to last poster's user profile data to the real
+     * user profile property names
+     * @var string[]
+     * @deprecated
+     */
+    protected static $__lastUserAvatarPropertyMapping = [
+        'lastPosterAvatarID' => 'avatarID',
+        'lastPosterAvatarName' => 'avatarName',
+        'lastPosterAvatarExtension' => 'avatarExtension',
+        'lastPosterAvatarWidth' => 'width',
+        'lastPosterAvatarHeight' => 'height',
+        'lastPosterEmail' => 'email',
+        'lastPosterDisableAvatar' => 'disableAvatar',
+        'lastPosterEnableGravatar' => 'enableGravatar',
+        'lastPosterGravatarFileExtension' => 'gravatarFileExtension',
+        'lastPosterAvatarFileHash' => 'fileHash',
+    ];
+
+    /**
+     * @inheritDoc
+     * @deprecated
+     */
+    public function __get($name)
+    {
+        $value = parent::__get($name);
+        if ($value !== null) {
+            return $value;
+        } elseif (\array_key_exists($name, $this->object->data)) {
+            return;
+        }
+
+        /** @noinspection PhpVariableVariableInspection */
+        $value = $this->getUserProfile()->{$name};
+        if ($value !== null) {
+            return $value;
+        }
+
+        if (isset(static::$__lastUserAvatarPropertyMapping[$name])) {
+            return $this->getLastPosterProfile()->getAvatar()->{static::$__lastUserAvatarPropertyMapping[$name]};
+        }
+    }
+
+    /**
+     * Returns the user profile object.
+     *
+     * @return  UserProfile
+     */
+    public function getUserProfile()
+    {
+        if ($this->userProfile === null) {
+            if ($this->userID) {
+                $this->userProfile = UserProfileRuntimeCache::getInstance()->getObject($this->userID);
+            } else {
+                $this->userProfile = UserProfile::getGuestUserProfile($this->username);
+            }
+        }
+
+        return $this->userProfile;
+    }
+
+    /**
+     * Returns the last poster's profile object.
+     *
+     * @return  UserProfile
+     */
+    public function getLastPosterProfile()
+    {
+        if ($this->lastPosterProfile === null) {
+            if ($this->lastPosterID) {
+                $this->lastPosterProfile = UserProfileRuntimeCache::getInstance()->getObject($this->lastPosterID);
+            } else {
+                $this->lastPosterProfile = UserProfile::getGuestUserProfile($this->lastPoster);
+            }
+        }
+
+        return $this->lastPosterProfile;
+    }
+
+    /**
+     * Returns the number of pages in this conversation.
+     *
+     * @return  integer
+     */
+    public function getPages()
+    {
+        /** @noinspection PhpUndefinedFieldInspection */
+        if (WCF::getUser()->conversationMessagesPerPage) {
+            /** @noinspection PhpUndefinedFieldInspection */
+            $messagesPerPage = WCF::getUser()->conversationMessagesPerPage;
+        } else {
+            $messagesPerPage = CONVERSATION_MESSAGES_PER_PAGE;
+        }
+
+        return \intval(\ceil(($this->replies + 1) / $messagesPerPage));
+    }
+
+    /**
+     * Returns a summary of the participants.
+     *
+     * @return  User[]
+     */
+    public function getParticipantSummary()
+    {
+        if ($this->__participantSummary === null) {
+            $this->__participantSummary = [];
+
+            if ($this->participantSummary) {
+                $data = \unserialize($this->participantSummary);
+                if ($data !== false) {
+                    foreach ($data as $userData) {
+                        $this->__participantSummary[] = new User(null, [
+                            'userID' => $userData['userID'],
+                            'username' => $userData['username'],
+                            'hideConversation' => $userData['hideConversation'],
+                        ]);
+                    }
+                }
+            }
+        }
+
+        return $this->__participantSummary;
+    }
+
+    /**
+     * Returns the other participant's profile object.
+     *
+     * @return  UserProfile
+     */
+    public function getOtherParticipantProfile()
+    {
+        if ($this->otherParticipantProfile === null) {
+            if ($this->otherParticipantID) {
+                $this->otherParticipantProfile = UserProfileRuntimeCache::getInstance()
+                    ->getObject($this->otherParticipantID);
+            } else {
+                $this->otherParticipantProfile = UserProfile::getGuestUserProfile($this->otherParticipant);
+            }
+        }
+
+        return $this->otherParticipantProfile;
+    }
+
+    /**
+     * Assigns a label.
+     *
+     * @param ConversationLabel $label
+     */
+    public function assignLabel(ConversationLabel $label)
+    {
+        $this->labels[$label->labelID] = $label;
+    }
+
+    /**
+     * Returns a list of assigned labels.
+     *
+     * @return  ConversationLabel[]
+     */
+    public function getAssignedLabels()
+    {
+        return $this->labels;
+    }
+
+    /**
+     * Converts a conversation into a viewable conversation.
+     *
+     * @param Conversation $conversation
+     * @param ConversationLabelList $labelList
+     * @return  ViewableConversation
+     */
+    public static function getViewableConversation(Conversation $conversation, ?ConversationLabelList $labelList = null)
+    {
+        $conversation = new self($conversation);
+
+        if ($labelList === null) {
+            $labelList = ConversationLabel::getLabelsByUser();
+        }
+
+        $labels = $labelList->getObjects();
+        if (!empty($labels)) {
+            $conditions = new PreparedStatementConditionBuilder();
+            $conditions->add("conversationID = ?", [$conversation->conversationID]);
+            $conditions->add("labelID IN (?)", [\array_keys($labels)]);
+
+            $sql = "SELECT     labelID
+                               FROM    wcf" . WCF_N . "_conversation_label_to_object
+                               " . $conditions;
+            $statement = WCF::getDB()->prepareStatement($sql);
+            $statement->execute($conditions->getParameters());
+            while ($row = $statement->fetchArray()) {
+                $conversation->assignLabel($labels[$row['labelID']]);
+            }
+        }
+
+        return $conversation;
+    }
 }
index fdc6ef87b37ee8f611dc9d31cb60af429ce6d881..75aa11c5fb9ea035baa97711b9d96c4a0ff4a60b 100644 (file)
@@ -1,62 +1,69 @@
 <?php
+
 namespace wcf\data\conversation\label;
+
 use wcf\data\DatabaseObject;
 use wcf\system\WCF;
 
 /**
  * Represents a conversation label.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\Data\Conversation\Label
- * 
- * @property-read      integer         $labelID        unique id of the conversation label
- * @property-read      integer         $userID         id of the user who created the conversation label
- * @property-read      string          $label          name of the conversation label
- * @property-read      string          $cssClassName   CSS class name of the conversation label handeling its appearance (color)
+ *
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Data\Conversation\Label
+ *
+ * @property-read   integer $labelID    unique id of the conversation label
+ * @property-read   integer $userID     id of the user who created the conversation label
+ * @property-read   string $label      name of the conversation label
+ * @property-read   string $cssClassName   CSS class name of the conversation label handeling its appearance (color)
  */
-class ConversationLabel extends DatabaseObject {
-       /**
-        * list of pre-defined css class names
-        * @var string[]
-        */
-       public static $availableCssClassNames = [
-               'yellow',
-               'orange',
-               'brown',
-               'red',
-               'pink',
-               'purple',
-               'blue',
-               'green',
-               'black',
-               
-               'none' /* not a real value */
-       ];
-       
-       /**
-        * Returns a list of conversation labels for given user id.
-        * 
-        * @param       integer         $userID
-        * @return      ConversationLabelList
-        */
-       public static function getLabelsByUser($userID = null) {
-               if ($userID === null) $userID = WCF::getUser()->userID;
-               
-               $labelList = new ConversationLabelList();
-               $labelList->getConditionBuilder()->add("conversation_label.userID = ?", [$userID]);
-               $labelList->readObjects();
-               
-               return $labelList;
-       }
-       
-       /**
-        * Returns a list of available CSS class names.
-        * 
-        * @return      string[]
-        */
-       public static function getLabelCssClassNames() {
-               return self::$availableCssClassNames;
-       }
+class ConversationLabel extends DatabaseObject
+{
+    /**
+     * list of pre-defined css class names
+     * @var string[]
+     */
+    public static $availableCssClassNames = [
+        'yellow',
+        'orange',
+        'brown',
+        'red',
+        'pink',
+        'purple',
+        'blue',
+        'green',
+        'black',
+
+        'none', /* not a real value */
+    ];
+
+    /**
+     * Returns a list of conversation labels for given user id.
+     *
+     * @param integer $userID
+     * @return  ConversationLabelList
+     */
+    public static function getLabelsByUser($userID = null)
+    {
+        if ($userID === null) {
+            $userID = WCF::getUser()->userID;
+        }
+
+        $labelList = new ConversationLabelList();
+        $labelList->getConditionBuilder()->add("conversation_label.userID = ?", [$userID]);
+        $labelList->readObjects();
+
+        return $labelList;
+    }
+
+    /**
+     * Returns a list of available CSS class names.
+     *
+     * @return  string[]
+     */
+    public static function getLabelCssClassNames()
+    {
+        return self::$availableCssClassNames;
+    }
 }
index 7e310c920726f1b8052aaf2c28a8ceec0c69be99..06348c20f84ae4ee51be347d85c9e69f0bc2edbf 100644 (file)
@@ -1,7 +1,9 @@
 <?php
+
 namespace wcf\data\conversation\label;
-use wcf\data\conversation\Conversation;
+
 use wcf\data\AbstractDatabaseObjectAction;
+use wcf\data\conversation\Conversation;
 use wcf\system\clipboard\ClipboardHandler;
 use wcf\system\database\util\PreparedStatementConditionBuilder;
 use wcf\system\exception\IllegalLinkException;
@@ -13,267 +15,281 @@ use wcf\util\StringUtil;
 
 /**
  * Executes label-related actions.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\Data\Conversation\Label
- * 
- * @method     ConversationLabel               create()
- * @method     ConversationLabelEditor[]       getObjects()
- * @method     ConversationLabelEditor         getSingleObject()
+ *
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Data\Conversation\Label
+ *
+ * @method  ConversationLabel       create()
+ * @method  ConversationLabelEditor[]   getObjects()
+ * @method  ConversationLabelEditor     getSingleObject()
  */
-class ConversationLabelAction extends AbstractDatabaseObjectAction {
-       /**
-        * @inheritDoc
-        */
-       protected $className = ConversationLabelEditor::class;
-       
-       /**
-        * @inheritDoc
-        */
-       protected $permissionsDelete = ['user.conversation.canUseConversation'];
-       
-       /**
-        * @inheritDoc
-        */
-       protected $permissionsUpdate = ['user.conversation.canUseConversation'];
-       
-       /**
-        * conversation object
-        * @var Conversation
-        */
-       public $conversation;
-       
-       /**
-        * conversation label list object
-        * @var ConversationLabelList
-        */
-       public $labelList;
-       
-       /**
-        * @inheritDoc
-        */
-       public function validateUpdate() {
-               parent::validateUpdate();
-               
-               if (count($this->objects) != 1) {
-                       throw new UserInputException('objectID');
-               }
-               
-               $label = current($this->objects);
-               if ($label->userID != WCF::getUser()->userID) {
-                       throw new PermissionDeniedException();
-               }
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function validateDelete() {
-               parent::validateDelete();
-               
-               if (count($this->objects) != 1) {
-                       throw new UserInputException('objectID');
-               }
-               
-               $label = current($this->objects);
-               if ($label->userID != WCF::getUser()->userID) {
-                       throw new PermissionDeniedException();
-               }
-       }
-       
-       /**
-        * Validates parameters to add a new label.
-        * 
-        * @throws      PermissionDeniedException
-        * @throws      UserInputException
-        */
-       public function validateAdd() {
-               if (!WCF::getSession()->getPermission('user.conversation.canUseConversation')) {
-                       throw new PermissionDeniedException();
-               }
-               
-               // check if user has already created maximum number of labels
-               if (count(ConversationLabel::getLabelsByUser()) >= WCF::getSession()->getPermission('user.conversation.maxLabels')) {
-                       throw new PermissionDeniedException();
-               }
-               
-               $this->readString('labelName', false, 'data');
-               $this->readString('cssClassName', false, 'data');
-               if (!in_array($this->parameters['data']['cssClassName'], ConversationLabel::getLabelCssClassNames())) {
-                       throw new UserInputException('cssClassName');
-               }
-               
-               // 'none' is a pseudo value
-               if ($this->parameters['data']['cssClassName'] == 'none') $this->parameters['data']['cssClassName'] = '';
-       }
-       
-       /**
-        * Adds a new user-specific label.
-        * 
-        * @return      array
-        */
-       public function add() {
-               $label = ConversationLabelEditor::create([
-                       'userID' => WCF::getUser()->userID,
-                       'label' => $this->parameters['data']['labelName'],
-                       'cssClassName' => $this->parameters['data']['cssClassName']
-               ]);
-               
-               return [
-                       'actionName' => 'add',
-                       'cssClassName' => $label->cssClassName,
-                       'label' => StringUtil::encodeHTML($label->label),
-                       'labelID' => $label->labelID
-               ];
-       }
-       
-       /**
-        * Validates parameters for label assignment form.
-        * 
-        * @throws      IllegalLinkException
-        * @throws      PermissionDeniedException
-        * @throws      UserInputException
-        */
-       public function validateGetLabelForm() {
-               if (!WCF::getSession()->getPermission('user.conversation.canUseConversation')) {
-                       throw new PermissionDeniedException();
-               }
-               
-               // validate conversation id
-               $this->parameters['conversationIDs'] = isset($this->parameters['conversationIDs']) ? ArrayUtil::toIntegerArray($this->parameters['conversationIDs']) : [];
-               if (empty($this->parameters['conversationIDs'])) {
-                       throw new UserInputException('conversationID');
-               }
-               
-               if (!Conversation::isParticipant($this->parameters['conversationIDs'])) {
-                       throw new PermissionDeniedException();
-               }
-               
-               // validate available labels
-               $this->labelList = ConversationLabel::getLabelsByUser();
-               if (!count($this->labelList)) {
-                       throw new IllegalLinkException();
-               }
-       }
-       
-       /**
-        * Returns the label assignment form.
-        * 
-        * @return      array
-        */
-       public function getLabelForm() {
-               // read assigned labels
-               $labelIDs = [];
-               foreach ($this->labelList as $label) {
-                       $labelIDs[] = $label->labelID;
-               }
-               
-               $assignedLabels = [];
-               // read assigned labels if editing single conversation
-               if (count($this->parameters['conversationIDs']) == 1) {
-                       $conversationID = current($this->parameters['conversationIDs']);
-                       
-                       $conditions = new PreparedStatementConditionBuilder();
-                       $conditions->add("conversationID = ?", [$conversationID]);
-                       $conditions->add("labelID IN (?)", [$labelIDs]);
-                       
-                       $sql = "SELECT  labelID
-                               FROM    wcf".WCF_N."_conversation_label_to_object
-                               ".$conditions;
-                       $statement = WCF::getDB()->prepareStatement($sql);
-                       $statement->execute($conditions->getParameters());
-                       $assignedLabels = $statement->fetchAll(\PDO::FETCH_COLUMN);
-               }
-               
-               WCF::getTPL()->assign([
-                       'assignedLabels' => $assignedLabels,
-                       'conversation' => $this->conversation,
-                       'labelList' => $this->labelList
-               ]);
-               
-               return [
-                       'actionName' => 'getLabelForm',
-                       'template' => WCF::getTPL()->fetch('conversationLabelAssignment')
-               ];
-       }
-       
-       /**
-        * Validates parameters to assign labels for a conversation.
-        * 
-        * @throws      UserInputException
-        */
-       public function validateAssignLabel() {
-               $this->validateGetLabelForm();
-               
-               // validate given labels
-               $this->parameters['labelIDs'] = (isset($this->parameters['labelIDs']) && is_array($this->parameters['labelIDs'])) ? ArrayUtil::toIntegerArray($this->parameters['labelIDs']) : [];
-               if (!empty($this->parameters['labelIDs'])) {
-                       foreach ($this->parameters['labelIDs'] as $labelID) {
-                               $isValid = false;
-                               
-                               foreach ($this->labelList as $label) {
-                                       if ($labelID == $label->labelID) {
-                                               $isValid = true;
-                                               break;
-                                       }
-                               }
-                               
-                               if (!$isValid) {
-                                       throw new UserInputException('labelIDs');
-                               }
-                       }
-               }
-       }
-       
-       /**
-        * Assigns labels to a conversation.
-        * 
-        * @return      array
-        */
-       public function assignLabel() {
-               // remove previous labels (if any)
-               $labelIDs = [];
-               foreach ($this->labelList as $label) {
-                       $labelIDs[] = $label->labelID;
-               }
-               
-               $conditions = new PreparedStatementConditionBuilder();
-               $conditions->add("conversationID IN (?)", [$this->parameters['conversationIDs']]);
-               $conditions->add("labelID IN (?)", [$labelIDs]);
-               
-               $sql = "DELETE FROM     wcf".WCF_N."_conversation_label_to_object
-                       ".$conditions;
-               $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute($conditions->getParameters());
-               
-               // assign label ids
-               if (!empty($this->parameters['labelIDs'])) {
-                       $sql = "INSERT INTO     wcf".WCF_N."_conversation_label_to_object
+class ConversationLabelAction extends AbstractDatabaseObjectAction
+{
+    /**
+     * @inheritDoc
+     */
+    protected $className = ConversationLabelEditor::class;
+
+    /**
+     * @inheritDoc
+     */
+    protected $permissionsDelete = ['user.conversation.canUseConversation'];
+
+    /**
+     * @inheritDoc
+     */
+    protected $permissionsUpdate = ['user.conversation.canUseConversation'];
+
+    /**
+     * conversation object
+     * @var Conversation
+     */
+    public $conversation;
+
+    /**
+     * conversation label list object
+     * @var ConversationLabelList
+     */
+    public $labelList;
+
+    /**
+     * @inheritDoc
+     */
+    public function validateUpdate()
+    {
+        parent::validateUpdate();
+
+        if (\count($this->objects) != 1) {
+            throw new UserInputException('objectID');
+        }
+
+        $label = \current($this->objects);
+        if ($label->userID != WCF::getUser()->userID) {
+            throw new PermissionDeniedException();
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function validateDelete()
+    {
+        parent::validateDelete();
+
+        if (\count($this->objects) != 1) {
+            throw new UserInputException('objectID');
+        }
+
+        $label = \current($this->objects);
+        if ($label->userID != WCF::getUser()->userID) {
+            throw new PermissionDeniedException();
+        }
+    }
+
+    /**
+     * Validates parameters to add a new label.
+     *
+     * @throws  PermissionDeniedException
+     * @throws  UserInputException
+     */
+    public function validateAdd()
+    {
+        if (!WCF::getSession()->getPermission('user.conversation.canUseConversation')) {
+            throw new PermissionDeniedException();
+        }
+
+        // check if user has already created maximum number of labels
+        if (\count(ConversationLabel::getLabelsByUser()) >= WCF::getSession()->getPermission('user.conversation.maxLabels')) {
+            throw new PermissionDeniedException();
+        }
+
+        $this->readString('labelName', false, 'data');
+        $this->readString('cssClassName', false, 'data');
+        if (!\in_array($this->parameters['data']['cssClassName'], ConversationLabel::getLabelCssClassNames())) {
+            throw new UserInputException('cssClassName');
+        }
+
+        // 'none' is a pseudo value
+        if ($this->parameters['data']['cssClassName'] == 'none') {
+            $this->parameters['data']['cssClassName'] = '';
+        }
+    }
+
+    /**
+     * Adds a new user-specific label.
+     *
+     * @return  array
+     */
+    public function add()
+    {
+        $label = ConversationLabelEditor::create([
+            'userID' => WCF::getUser()->userID,
+            'label' => $this->parameters['data']['labelName'],
+            'cssClassName' => $this->parameters['data']['cssClassName'],
+        ]);
+
+        return [
+            'actionName' => 'add',
+            'cssClassName' => $label->cssClassName,
+            'label' => StringUtil::encodeHTML($label->label),
+            'labelID' => $label->labelID,
+        ];
+    }
+
+    /**
+     * Validates parameters for label assignment form.
+     *
+     * @throws  IllegalLinkException
+     * @throws  PermissionDeniedException
+     * @throws  UserInputException
+     */
+    public function validateGetLabelForm()
+    {
+        if (!WCF::getSession()->getPermission('user.conversation.canUseConversation')) {
+            throw new PermissionDeniedException();
+        }
+
+        // validate conversation id
+        $this->parameters['conversationIDs'] = isset($this->parameters['conversationIDs']) ? ArrayUtil::toIntegerArray($this->parameters['conversationIDs']) : [];
+        if (empty($this->parameters['conversationIDs'])) {
+            throw new UserInputException('conversationID');
+        }
+
+        if (!Conversation::isParticipant($this->parameters['conversationIDs'])) {
+            throw new PermissionDeniedException();
+        }
+
+        // validate available labels
+        $this->labelList = ConversationLabel::getLabelsByUser();
+        if (!\count($this->labelList)) {
+            throw new IllegalLinkException();
+        }
+    }
+
+    /**
+     * Returns the label assignment form.
+     *
+     * @return  array
+     */
+    public function getLabelForm()
+    {
+        // read assigned labels
+        $labelIDs = [];
+        foreach ($this->labelList as $label) {
+            $labelIDs[] = $label->labelID;
+        }
+
+        $assignedLabels = [];
+        // read assigned labels if editing single conversation
+        if (\count($this->parameters['conversationIDs']) == 1) {
+            $conversationID = \current($this->parameters['conversationIDs']);
+
+            $conditions = new PreparedStatementConditionBuilder();
+            $conditions->add("conversationID = ?", [$conversationID]);
+            $conditions->add("labelID IN (?)", [$labelIDs]);
+
+            $sql = "SELECT     labelID
+                               FROM    wcf" . WCF_N . "_conversation_label_to_object
+                               " . $conditions;
+            $statement = WCF::getDB()->prepareStatement($sql);
+            $statement->execute($conditions->getParameters());
+            $assignedLabels = $statement->fetchAll(\PDO::FETCH_COLUMN);
+        }
+
+        WCF::getTPL()->assign([
+            'assignedLabels' => $assignedLabels,
+            'conversation' => $this->conversation,
+            'labelList' => $this->labelList,
+        ]);
+
+        return [
+            'actionName' => 'getLabelForm',
+            'template' => WCF::getTPL()->fetch('conversationLabelAssignment'),
+        ];
+    }
+
+    /**
+     * Validates parameters to assign labels for a conversation.
+     *
+     * @throws  UserInputException
+     */
+    public function validateAssignLabel()
+    {
+        $this->validateGetLabelForm();
+
+        // validate given labels
+        $this->parameters['labelIDs'] = (isset($this->parameters['labelIDs']) && \is_array($this->parameters['labelIDs'])) ? ArrayUtil::toIntegerArray($this->parameters['labelIDs']) : [];
+        if (!empty($this->parameters['labelIDs'])) {
+            foreach ($this->parameters['labelIDs'] as $labelID) {
+                $isValid = false;
+
+                foreach ($this->labelList as $label) {
+                    if ($labelID == $label->labelID) {
+                        $isValid = true;
+                        break;
+                    }
+                }
+
+                if (!$isValid) {
+                    throw new UserInputException('labelIDs');
+                }
+            }
+        }
+    }
+
+    /**
+     * Assigns labels to a conversation.
+     *
+     * @return  array
+     */
+    public function assignLabel()
+    {
+        // remove previous labels (if any)
+        $labelIDs = [];
+        foreach ($this->labelList as $label) {
+            $labelIDs[] = $label->labelID;
+        }
+
+        $conditions = new PreparedStatementConditionBuilder();
+        $conditions->add("conversationID IN (?)", [$this->parameters['conversationIDs']]);
+        $conditions->add("labelID IN (?)", [$labelIDs]);
+
+        $sql = "DELETE FROM    wcf" . WCF_N . "_conversation_label_to_object
+                       " . $conditions;
+        $statement = WCF::getDB()->prepareStatement($sql);
+        $statement->execute($conditions->getParameters());
+
+        // assign label ids
+        if (!empty($this->parameters['labelIDs'])) {
+            $sql = "INSERT INTO        wcf" . WCF_N . "_conversation_label_to_object
                                                (labelID, conversationID)
                                VALUES          (?, ?)";
-                       $statement = WCF::getDB()->prepareStatement($sql);
-                       
-                       WCF::getDB()->beginTransaction();
-                       foreach ($this->parameters['labelIDs'] as $labelID) {
-                               foreach ($this->parameters['conversationIDs'] as $conversationID) {
-                                       $statement->execute([
-                                               $labelID,
-                                               $conversationID
-                                       ]);
-                               }
-                       }
-                       WCF::getDB()->commitTransaction();
-                       
-                       if (!empty($this->parameters['conversationIDs'])) {
-                               ClipboardHandler::getInstance()->unmark($this->parameters['conversationIDs'], ClipboardHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.conversation.conversation'));
-                       }
-               }
-               
-               return [
-                       'actionName' => 'assignLabel',
-                       'labelIDs' => $this->parameters['labelIDs']
-               ];
-       }
+            $statement = WCF::getDB()->prepareStatement($sql);
+
+            WCF::getDB()->beginTransaction();
+            foreach ($this->parameters['labelIDs'] as $labelID) {
+                foreach ($this->parameters['conversationIDs'] as $conversationID) {
+                    $statement->execute([
+                        $labelID,
+                        $conversationID,
+                    ]);
+                }
+            }
+            WCF::getDB()->commitTransaction();
+
+            if (!empty($this->parameters['conversationIDs'])) {
+                ClipboardHandler::getInstance()->unmark(
+                    $this->parameters['conversationIDs'],
+                    ClipboardHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.conversation.conversation')
+                );
+            }
+        }
+
+        return [
+            'actionName' => 'assignLabel',
+            'labelIDs' => $this->parameters['labelIDs'],
+        ];
+    }
 }
index ee834edf2e49fcf659d836f2c8a0952b92f2914e..525c8c2861843a5fe5f5c1b53e6b36b6d8ba1476 100644 (file)
@@ -1,22 +1,25 @@
 <?php
+
 namespace wcf\data\conversation\label;
+
 use wcf\data\DatabaseObjectEditor;
 
 /**
  * Extends the label object with functions to create, update and delete labels.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\Data\Conversation\Label
- * 
- * @method static      ConversationLabel       create(array $parameters = [])
- * @method             ConversationLabel       getDecoratedObject()
- * @mixin              ConversationLabel
+ *
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Data\Conversation\Label
+ *
+ * @method static ConversationLabel   create(array $parameters = [])
+ * @method      ConversationLabel   getDecoratedObject()
+ * @mixin       ConversationLabel
  */
-class ConversationLabelEditor extends DatabaseObjectEditor {
-       /**
-        * @inheritDoc
-        */
-       protected static $baseClass = ConversationLabel::class;
+class ConversationLabelEditor extends DatabaseObjectEditor
+{
+    /**
+     * @inheritDoc
+     */
+    protected static $baseClass = ConversationLabel::class;
 }
index 51a09accea779bc43f061868247adbc2470beb8b..be9596e5892271fd19b2d3947ef68d91ef78c11a 100644 (file)
@@ -1,23 +1,26 @@
 <?php
+
 namespace wcf\data\conversation\label;
+
 use wcf\data\DatabaseObjectList;
 
 /**
  * Represents a list of conversation labels.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\Data\Conversation\Label
- * 
- * @method     ConversationLabel               current()
- * @method     ConversationLabel[]             getObjects()
- * @method     ConversationLabel|null          search($objectID)
- * @property   ConversationLabel[]             $objects
+ *
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Data\Conversation\Label
+ *
+ * @method  ConversationLabel       current()
+ * @method  ConversationLabel[]     getObjects()
+ * @method  ConversationLabel|null      search($objectID)
+ * @property    ConversationLabel[] $objects
  */
-class ConversationLabelList extends DatabaseObjectList {
-       /**
-        * @inheritDoc
-        */
-       public $className = ConversationLabel::class;
+class ConversationLabelList extends DatabaseObjectList
+{
+    /**
+     * @inheritDoc
+     */
+    public $className = ConversationLabel::class;
 }
index 22a0d36c2b51b7701924b2377b03909846507373..9ec09f3ea1b8e0cd31b61e3e7e38d6b186709f57 100644 (file)
@@ -1,5 +1,7 @@
 <?php
+
 namespace wcf\data\conversation\message;
+
 use wcf\data\attachment\GroupedAttachmentList;
 use wcf\data\conversation\Conversation;
 use wcf\data\DatabaseObject;
@@ -12,186 +14,201 @@ use wcf\util\StringUtil;
 
 /**
  * Represents a conversation message.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\Data\Conversation\Message
  *
- * @property-read      integer         $messageID              unique id of the conversation message
- * @property-read      integer         $conversationID         id of the conversation the conversation message belongs to
- * @property-read      integer|null    $userID                 id of the user who wrote the conversation message or `null` if the user does not exist anymore
- * @property-read      string          $username               name of the user who wrote the conversation message
- * @property-read      string          $message                text of the conversation message
- * @property-read      integer         $time                   timestamp at which the conversation message has been written
- * @property-read      integer         $attachments            number of attachments
- * @property-read      integer         $enableHtml             is `1` if the conversation message's format has been converted to html, otherwise `0`
- * @property-read      string          $ipAddress              ip address of the user who wrote the conversation message at time of writing or empty if no ip addresses are logged
- * @property-read      integer         $lastEditTime           timestamp at which the conversation message has been edited the last time
- * @property-read      integer         $editCount              number of times the conversation message has been edited
- * @property-read      integer         $hasEmbeddedObjects     number of embedded objects in the conversation message
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Data\Conversation\Message
+ *
+ * @property-read   integer $messageID      unique id of the conversation message
+ * @property-read   integer $conversationID     id of the conversation the conversation message belongs to
+ * @property-read   integer|null $userID         id of the user who wrote the conversation message or `null` if the user does not exist anymore
+ * @property-read   string $username       name of the user who wrote the conversation message
+ * @property-read   string $message        text of the conversation message
+ * @property-read   integer $time           timestamp at which the conversation message has been written
+ * @property-read   integer $attachments        number of attachments
+ * @property-read   integer $enableHtml     is `1` if the conversation message's format has been converted to html, otherwise `0`
+ * @property-read   string $ipAddress      ip address of the user who wrote the conversation message at time of writing or empty if no ip addresses are logged
+ * @property-read   integer $lastEditTime       timestamp at which the conversation message has been edited the last time
+ * @property-read   integer $editCount      number of times the conversation message has been edited
+ * @property-read   integer $hasEmbeddedObjects number of embedded objects in the conversation message
  */
-class ConversationMessage extends DatabaseObject implements IMessage {
-       use TUserContent;
-       
-       /**
-        * conversation object
-        * @var Conversation
-        */
-       protected $conversation;
-       
-       /**
-        * @inheritDoc
-        */
-       public function getFormattedMessage() {
-               $processor = new HtmlOutputProcessor();
-               $processor->process($this->message, 'com.woltlab.wcf.conversation.message', $this->messageID);
-               
-               return $processor->getHtml();
-       }
-       
-       /**
-        * Returns a simplified version of the formatted message.
-        * 
-        * @return      string
-        */
-       public function getSimplifiedFormattedMessage() {
-               $processor = new HtmlOutputProcessor();
-               $processor->setOutputType('text/simplified-html');
-               $processor->process($this->message, 'com.woltlab.wcf.conversation.message', $this->messageID);
-               
-               return $processor->getHtml();
-       }
-       
-       /**
-        * Assigns and returns the embedded attachments.
-        * 
-        * @param       boolean         $ignoreCache
-        * @return      GroupedAttachmentList
-        */
-       public function getAttachments($ignoreCache = false) {
-               if ($this->attachments || $ignoreCache) {
-                       $attachmentList = new GroupedAttachmentList('com.woltlab.wcf.conversation.message');
-                       $attachmentList->getConditionBuilder()->add('attachment.objectID IN (?)', [$this->messageID]);
-                       $attachmentList->readObjects();
-                       $attachmentList->setPermissions([
-                               'canDownload' => true,
-                               'canViewPreview' => true
-                       ]);
-                       
-                       if ($ignoreCache && !count($attachmentList)) {
-                               return null;
-                       }
-                       
-                       return $attachmentList;
-               }
-               
-               return null;
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getExcerpt($maxLength = 255) {
-               return StringUtil::truncateHTML($this->getSimplifiedFormattedMessage(), $maxLength);
-       }
-       
-       /**
-        * Returns a version of this message optimized for use in emails.
-        * 
-        * @param       string  $mimeType       Either 'text/plain' or 'text/html'
-        * @return      string
-        */
-       public function getMailText($mimeType = 'text/plain') {
-               switch ($mimeType) {
-                       case 'text/plain':
-                               $processor = new HtmlOutputProcessor();
-                               $processor->setOutputType('text/plain');
-                               $processor->process($this->message, 'com.woltlab.wcf.conversation.message', $this->messageID);
-                               
-                               return $processor->getHtml();
-                       case 'text/html':
-                               return $this->getSimplifiedFormattedMessage();
-               }
-               
-               throw new \LogicException('Unreachable');
-       }
-       
-       /**
-        * Returns the conversation of this message.
-        * 
-        * @return      Conversation
-        */
-       public function getConversation() {
-               if ($this->conversation === null) {
-                       $this->conversation = Conversation::getUserConversation($this->conversationID, WCF::getUser()->userID);
-               }
-               
-               return $this->conversation;
-       }
-       
-       /**
-        * Sets the conversation of this message.
-        * 
-        * @param       Conversation    $conversation
-        */
-       public function setConversation(Conversation $conversation) {
-               if ($this->conversationID == $conversation->conversationID) {
-                       $this->conversation = $conversation;
-               }
-       }
-       
-       /**
-        * Returns true if current user may edit this message.
-        * 
-        * @return      boolean
-        */
-       public function canEdit() {
-               return  WCF::getUser()->userID == $this->userID
-                       && ($this->getConversation()->isDraft || WCF::getSession()->getPermission('user.conversation.canEditMessage')) 
-                       && $this->getConversation()->canReply();
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getMessage() {
-               return $this->message;
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getLink() {
-               return LinkHandler::getInstance()->getLink('Conversation', [
-                       'object' => $this->getConversation(),
-                       'messageID' => $this->messageID,
-                       'forceFrontend' => true
-               ], '#message'.$this->messageID);
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getTitle() {
-               if ($this->messageID == $this->getConversation()->firstMessageID) {
-                       return $this->getConversation()->subject;
-               }
-               
-               return 'RE: '.$this->getConversation()->subject;
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function isVisible() {
-               return true;
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function __toString() {
-               return $this->getFormattedMessage();
-       }
+class ConversationMessage extends DatabaseObject implements IMessage
+{
+    use TUserContent;
+
+    /**
+     * conversation object
+     * @var Conversation
+     */
+    protected $conversation;
+
+    /**
+     * @inheritDoc
+     */
+    public function getFormattedMessage()
+    {
+        $processor = new HtmlOutputProcessor();
+        $processor->process($this->message, 'com.woltlab.wcf.conversation.message', $this->messageID);
+
+        return $processor->getHtml();
+    }
+
+    /**
+     * Returns a simplified version of the formatted message.
+     *
+     * @return  string
+     */
+    public function getSimplifiedFormattedMessage()
+    {
+        $processor = new HtmlOutputProcessor();
+        $processor->setOutputType('text/simplified-html');
+        $processor->process($this->message, 'com.woltlab.wcf.conversation.message', $this->messageID);
+
+        return $processor->getHtml();
+    }
+
+    /**
+     * Assigns and returns the embedded attachments.
+     *
+     * @param boolean $ignoreCache
+     * @return  GroupedAttachmentList
+     */
+    public function getAttachments($ignoreCache = false)
+    {
+        if ($this->attachments || $ignoreCache) {
+            $attachmentList = new GroupedAttachmentList('com.woltlab.wcf.conversation.message');
+            $attachmentList->getConditionBuilder()->add('attachment.objectID IN (?)', [$this->messageID]);
+            $attachmentList->readObjects();
+            $attachmentList->setPermissions([
+                'canDownload' => true,
+                'canViewPreview' => true,
+            ]);
+
+            if ($ignoreCache && !\count($attachmentList)) {
+                return;
+            }
+
+            return $attachmentList;
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getExcerpt($maxLength = 255)
+    {
+        return StringUtil::truncateHTML($this->getSimplifiedFormattedMessage(), $maxLength);
+    }
+
+    /**
+     * Returns a version of this message optimized for use in emails.
+     *
+     * @param string $mimeType Either 'text/plain' or 'text/html'
+     * @return  string
+     */
+    public function getMailText($mimeType = 'text/plain')
+    {
+        switch ($mimeType) {
+            case 'text/plain':
+                $processor = new HtmlOutputProcessor();
+                $processor->setOutputType('text/plain');
+                $processor->process($this->message, 'com.woltlab.wcf.conversation.message', $this->messageID);
+
+                return $processor->getHtml();
+            case 'text/html':
+                return $this->getSimplifiedFormattedMessage();
+        }
+
+        throw new \LogicException('Unreachable');
+    }
+
+    /**
+     * Returns the conversation of this message.
+     *
+     * @return  Conversation
+     */
+    public function getConversation()
+    {
+        if ($this->conversation === null) {
+            $this->conversation = Conversation::getUserConversation($this->conversationID, WCF::getUser()->userID);
+        }
+
+        return $this->conversation;
+    }
+
+    /**
+     * Sets the conversation of this message.
+     *
+     * @param Conversation $conversation
+     */
+    public function setConversation(Conversation $conversation)
+    {
+        if ($this->conversationID == $conversation->conversationID) {
+            $this->conversation = $conversation;
+        }
+    }
+
+    /**
+     * Returns true if current user may edit this message.
+     *
+     * @return  boolean
+     */
+    public function canEdit()
+    {
+        return WCF::getUser()->userID == $this->userID
+            && (
+                $this->getConversation()->isDraft
+                || WCF::getSession()->getPermission('user.conversation.canEditMessage')
+            )
+            && $this->getConversation()->canReply();
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getMessage()
+    {
+        return $this->message;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getLink()
+    {
+        return LinkHandler::getInstance()->getLink('Conversation', [
+            'object' => $this->getConversation(),
+            'messageID' => $this->messageID,
+            'forceFrontend' => true,
+        ], '#message' . $this->messageID);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getTitle()
+    {
+        if ($this->messageID == $this->getConversation()->firstMessageID) {
+            return $this->getConversation()->subject;
+        }
+
+        return 'RE: ' . $this->getConversation()->subject;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function isVisible()
+    {
+        return true;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function __toString()
+    {
+        return $this->getFormattedMessage();
+    }
 }
index bed559f6dcfc79660c29255c1529d4b21a921e1c..d1a89be0a632b09c25c6edfa9ac5cb1a91581565 100644 (file)
@@ -1,14 +1,16 @@
 <?php
+
 namespace wcf\data\conversation\message;
+
+use wcf\data\AbstractDatabaseObjectAction;
 use wcf\data\conversation\Conversation;
 use wcf\data\conversation\ConversationAction;
 use wcf\data\conversation\ConversationEditor;
-use wcf\data\smiley\SmileyCache;
-use wcf\data\AbstractDatabaseObjectAction;
 use wcf\data\DatabaseObject;
 use wcf\data\IAttachmentMessageQuickReplyAction;
 use wcf\data\IMessageInlineEditorAction;
 use wcf\data\IMessageQuoteAction;
+use wcf\data\smiley\SmileyCache;
 use wcf\system\attachment\AttachmentHandler;
 use wcf\system\bbcode\BBCodeHandler;
 use wcf\system\exception\PermissionDeniedException;
@@ -16,8 +18,8 @@ use wcf\system\exception\UserInputException;
 use wcf\system\html\input\HtmlInputProcessor;
 use wcf\system\message\censorship\Censorship;
 use wcf\system\message\embedded\object\MessageEmbeddedObjectManager;
-use wcf\system\message\quote\MessageQuoteManager;
 use wcf\system\message\QuickReplyManager;
+use wcf\system\message\quote\MessageQuoteManager;
 use wcf\system\moderation\queue\ModerationQueueManager;
 use wcf\system\request\LinkHandler;
 use wcf\system\search\SearchIndexManager;
@@ -29,657 +31,741 @@ use wcf\util\StringUtil;
 
 /**
  * Executes conversation message-related actions.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\Data\Conversation\Message
- * 
- * @method     ConversationMessageEditor[]     getObjects()
- * @method     ConversationMessageEditor       getSingleObject()
+ *
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Data\Conversation\Message
+ *
+ * @method  ConversationMessageEditor[] getObjects()
+ * @method  ConversationMessageEditor   getSingleObject()
  */
-class ConversationMessageAction extends AbstractDatabaseObjectAction implements IAttachmentMessageQuickReplyAction, IMessageInlineEditorAction, IMessageQuoteAction {
-       /**
-        * @inheritDoc
-        */
-       protected $className = ConversationMessageEditor::class;
-       
-       /**
-        * conversation object
-        * @var Conversation
-        */
-       public $conversation;
-       
-       /**
-        * @var HtmlInputProcessor
-        */
-       public $htmlInputProcessor;
-       
-       /**
-        * conversation message object
-        * @var ConversationMessage
-        */
-       public $message;
-       
-       /**
-        * @inheritDoc
-        * @return      ConversationMessage
-        */
-       public function create() {
-               if (!isset($this->parameters['data']['enableHtml'])) $this->parameters['data']['enableHtml'] = 1;
-               
-               // count attachments
-               if (isset($this->parameters['attachmentHandler']) && $this->parameters['attachmentHandler'] !== null) {
-                       $this->parameters['data']['attachments'] = count($this->parameters['attachmentHandler']);
-               }
-               
-               if (LOG_IP_ADDRESS) {
-                       // add ip address
-                       if (!isset($this->parameters['data']['ipAddress'])) {
-                               $this->parameters['data']['ipAddress'] = WCF::getSession()->ipAddress;
-                       }
-               }
-               else {
-                       // do not track ip address
-                       if (isset($this->parameters['data']['ipAddress'])) {
-                               unset($this->parameters['data']['ipAddress']);
-                       }
-               }
-               
-               if (!empty($this->parameters['htmlInputProcessor'])) {
-                       /** @noinspection PhpUndefinedMethodInspection */
-                       $this->parameters['data']['message'] = $this->parameters['htmlInputProcessor']->getHtml();
-               }
-               
-               // create message
-               /** @var ConversationMessage $message */
-               $message = parent::create();
-               $messageEditor = new ConversationMessageEditor($message);
-               
-               // get conversation
-               $conversation = (isset($this->parameters['conversation']) ? $this->parameters['conversation'] : new Conversation($message->conversationID));
-               $conversationEditor = new ConversationEditor($conversation);
-               
-               if (empty($this->parameters['isFirstPost'])) {
-                       // update last message
-                       $conversationEditor->addMessage($message);
-                       
-                       // fire notification event
-                       if (!$conversation->isDraft) {
-                               $notificationRecipients = array_diff($conversation->getParticipantIDs(true), [$message->userID]); // don't notify message author
-                               if (!empty($notificationRecipients)) {
-                                       UserNotificationHandler::getInstance()->fireEvent(
-                                               'conversationMessage',
-                                               'com.woltlab.wcf.conversation.message.notification',
-                                               new ConversationMessageUserNotificationObject($message),
-                                               $notificationRecipients
-                                       );
-                               }
-                       }
-                       
-                       $userConversation = Conversation::getUserConversation($conversation->conversationID, $message->userID);
-                       if ($userConversation !== null && $userConversation->isInvisible) {
-                               // make invisible participant visible
-                               $sql = "UPDATE  wcf".WCF_N."_conversation_to_user
+class ConversationMessageAction extends AbstractDatabaseObjectAction implements
+    IAttachmentMessageQuickReplyAction,
+    IMessageInlineEditorAction,
+    IMessageQuoteAction
+{
+    /**
+     * @inheritDoc
+     */
+    protected $className = ConversationMessageEditor::class;
+
+    /**
+     * conversation object
+     * @var Conversation
+     */
+    public $conversation;
+
+    /**
+     * @var HtmlInputProcessor
+     */
+    public $htmlInputProcessor;
+
+    /**
+     * conversation message object
+     * @var ConversationMessage
+     */
+    public $message;
+
+    /**
+     * @inheritDoc
+     * @return  ConversationMessage
+     */
+    public function create()
+    {
+        if (!isset($this->parameters['data']['enableHtml'])) {
+            $this->parameters['data']['enableHtml'] = 1;
+        }
+
+        // count attachments
+        if (isset($this->parameters['attachmentHandler']) && $this->parameters['attachmentHandler'] !== null) {
+            $this->parameters['data']['attachments'] = \count($this->parameters['attachmentHandler']);
+        }
+
+        if (LOG_IP_ADDRESS) {
+            // add ip address
+            if (!isset($this->parameters['data']['ipAddress'])) {
+                $this->parameters['data']['ipAddress'] = WCF::getSession()->ipAddress;
+            }
+        } else {
+            // do not track ip address
+            if (isset($this->parameters['data']['ipAddress'])) {
+                unset($this->parameters['data']['ipAddress']);
+            }
+        }
+
+        if (!empty($this->parameters['htmlInputProcessor'])) {
+            /** @noinspection PhpUndefinedMethodInspection */
+            $this->parameters['data']['message'] = $this->parameters['htmlInputProcessor']->getHtml();
+        }
+
+        // create message
+        /** @var ConversationMessage $message */
+        $message = parent::create();
+        $messageEditor = new ConversationMessageEditor($message);
+
+        // get conversation
+        $conversation = ($this->parameters['conversation'] ?? new Conversation($message->conversationID));
+        $conversationEditor = new ConversationEditor($conversation);
+
+        if (empty($this->parameters['isFirstPost'])) {
+            // update last message
+            $conversationEditor->addMessage($message);
+
+            // fire notification event
+            if (!$conversation->isDraft) {
+                // don't notify message author
+                $notificationRecipients = \array_diff($conversation->getParticipantIDs(true), [$message->userID]);
+                if (!empty($notificationRecipients)) {
+                    UserNotificationHandler::getInstance()->fireEvent(
+                        'conversationMessage',
+                        'com.woltlab.wcf.conversation.message.notification',
+                        new ConversationMessageUserNotificationObject($message),
+                        $notificationRecipients
+                    );
+                }
+            }
+
+            $userConversation = Conversation::getUserConversation($conversation->conversationID, $message->userID);
+            if ($userConversation !== null && $userConversation->isInvisible) {
+                // make invisible participant visible
+                $sql = "UPDATE wcf" . WCF_N . "_conversation_to_user
                                        SET     isInvisible = 0
                                        WHERE   participantID = ?
                                                AND conversationID = ?";
-                               $statement = WCF::getDB()->prepareStatement($sql);
-                               $statement->execute([$message->userID, $conversation->conversationID]);
-                               
-                               $conversationEditor->updateParticipantSummary();
-                               $conversationEditor->updateParticipantCount();
-                       }
-                       
-                       // reset visibility if it was hidden but not left
-                       $sql = "UPDATE  wcf".WCF_N."_conversation_to_user
+                $statement = WCF::getDB()->prepareStatement($sql);
+                $statement->execute([$message->userID, $conversation->conversationID]);
+
+                $conversationEditor->updateParticipantSummary();
+                $conversationEditor->updateParticipantCount();
+            }
+
+            // reset visibility if it was hidden but not left
+            $sql = "UPDATE     wcf" . WCF_N . "_conversation_to_user
                                SET     hideConversation = ?
                                WHERE   conversationID = ?
                                        AND hideConversation = ?";
-                       $statement = WCF::getDB()->prepareStatement($sql);
-                       $statement->execute([
-                               Conversation::STATE_DEFAULT,
-                               $conversation->conversationID,
-                               Conversation::STATE_HIDDEN
-                       ]);
-               }
-               
-               // reset storage
-               UserStorageHandler::getInstance()->reset($conversation->getParticipantIDs(), 'unreadConversationCount');
-               
-               // update search index
-               SearchIndexManager::getInstance()->set(
-                       'com.woltlab.wcf.conversation.message',
-                       $message->messageID,
-                       $message->message,
-                       !empty($this->parameters['isFirstPost']) ? $conversation->subject : '',
-                       $message->time,
-                       $message->userID,
-                       $message->username
-               );
-               
-               // update attachments
-               if (isset($this->parameters['attachmentHandler']) && $this->parameters['attachmentHandler'] !== null) {
-                       /** @noinspection PhpUndefinedMethodInspection */
-                       $this->parameters['attachmentHandler']->updateObjectID($message->messageID);
-               }
-               
-               // save embedded objects
-               if (!empty($this->parameters['htmlInputProcessor'])) {
-                       /** @noinspection PhpUndefinedMethodInspection */
-                       $this->parameters['htmlInputProcessor']->setObjectID($message->messageID);
-                       
-                       if (MessageEmbeddedObjectManager::getInstance()->registerObjects($this->parameters['htmlInputProcessor'])) {
-                               $messageEditor->update(['hasEmbeddedObjects' => 1]);
-                       }
-               }
-               
-               // clear quotes
-               if (isset($this->parameters['removeQuoteIDs']) && !empty($this->parameters['removeQuoteIDs'])) {
-                       MessageQuoteManager::getInstance()->markQuotesForRemoval($this->parameters['removeQuoteIDs']);
-               }
-               MessageQuoteManager::getInstance()->removeMarkedQuotes();
-               
-               // return new message
-               return $message;
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function update() {
-               // count attachments
-               if (isset($this->parameters['attachmentHandler']) && $this->parameters['attachmentHandler'] !== null) {
-                       $this->parameters['data']['attachments'] = count($this->parameters['attachmentHandler']);
-               }
-               
-               if (!empty($this->parameters['htmlInputProcessor'])) {
-                       /** @noinspection PhpUndefinedMethodInspection */
-                       $this->parameters['data']['message'] = $this->parameters['htmlInputProcessor']->getHtml();
-               }
-               
-               parent::update();
-               
-               // update search index / embedded objects
-               if (isset($this->parameters['data']) && isset($this->parameters['data']['message'])) {
-                       foreach ($this->getObjects() as $message) {
-                               $conversation = $message->getConversation();
-                               SearchIndexManager::getInstance()->set(
-                                       'com.woltlab.wcf.conversation.message',
-                                       $message->messageID,
-                                       $this->parameters['data']['message'],
-                                       $conversation->firstMessageID == $message->messageID ? $conversation->subject : '',
-                                       $message->time,
-                                       $message->userID,
-                                       $message->username
-                               );
-                               
-                               if (!empty($this->parameters['htmlInputProcessor'])) {
-                                       /** @noinspection PhpUndefinedMethodInspection */
-                                       $this->parameters['htmlInputProcessor']->setObjectID($message->messageID);
-                                       
-                                       if ($message->hasEmbeddedObjects != MessageEmbeddedObjectManager::getInstance()->registerObjects($this->parameters['htmlInputProcessor'])) {
-                                               $message->update(['hasEmbeddedObjects' => $message->hasEmbeddedObjects ? 0 : 1]);
-                                       }
-                               }
-                       }
-               }
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function delete() {
-               $count = parent::delete();
-               
-               $attachmentMessageIDs = $conversationIDs = [];
-               foreach ($this->getObjects() as $message) {
-                       if (!in_array($message->conversationID, $conversationIDs)) {
-                               $conversationIDs[] = $message->conversationID;
-                       }
-                       
-                       if ($message->attachments) {
-                               $attachmentMessageIDs[] = $message->messageID;
-                       }
-               }
-               
-               // rebuild conversations
-               if (!empty($conversationIDs)) {
-                       $conversationAction = new ConversationAction($conversationIDs, 'rebuild');
-                       $conversationAction->executeAction();
-               }
-               
-               if (!empty($this->objectIDs)) {
-                       // delete notifications
-                       UserNotificationHandler::getInstance()->removeNotifications('com.woltlab.wcf.conversation.message.notification', $this->objectIDs);
-                       
-                       // update search index
-                       SearchIndexManager::getInstance()->delete('com.woltlab.wcf.conversation.message', $this->objectIDs);
-                       
-                       // update embedded objects
-                       MessageEmbeddedObjectManager::getInstance()->removeObjects('com.woltlab.wcf.conversation.message', $this->objectIDs);
-
-                       // remove moderation queues
-                       ModerationQueueManager::getInstance()->removeQueues('com.woltlab.wcf.conversation.message', $this->objectIDs);
-               }
-               
-               // remove attachments
-               if (!empty($attachmentMessageIDs)) {
-                       AttachmentHandler::removeAttachments('com.woltlab.wcf.conversation.message', $attachmentMessageIDs);
-               }
-               
-               return $count;
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function validateQuickReply() {
-               QuickReplyManager::getInstance()->setDisallowedBBCodes(explode(',', WCF::getSession()->getPermission('user.message.disallowedBBCodes')));
-               QuickReplyManager::getInstance()->validateParameters($this, $this->parameters, Conversation::class);
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function quickReply() {
-               return QuickReplyManager::getInstance()->createMessage(
-                       $this,
-                       $this->parameters,
-                       ConversationAction::class,
-                       CONVERSATION_LIST_DEFAULT_SORT_ORDER,
-                       'conversationMessageList'
-               );
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function validateJumpToExtended() {
-               $this->readInteger('containerID');
-               $this->readString('message', true);
-               $this->readString('tmpHash', true);
-               
-               $this->conversation = new Conversation($this->parameters['containerID']);
-               if (!$this->conversation->conversationID) {
-                       throw new UserInputException('containerID');
-               }
-               else if ($this->conversation->isClosed || !Conversation::isParticipant([$this->conversation->conversationID])) {
-                       throw new PermissionDeniedException();
-               }
-               
-               // editing existing message
-               if (isset($this->parameters['messageID'])) {
-                       $this->message = new ConversationMessage(intval($this->parameters['messageID']));
-                       if (!$this->message->messageID || ($this->message->conversationID != $this->conversation->conversationID)) {
-                               throw new UserInputException('messageID');
-                       }
-                       
-                       if (!$this->message->canEdit()) {
-                               throw new PermissionDeniedException();
-                       }
-               }
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function jumpToExtended() {
-               // quick reply
-               if ($this->message === null) {
-                       QuickReplyManager::getInstance()->setMessage('conversation', $this->conversation->conversationID, $this->parameters['message']);
-                       $url = LinkHandler::getInstance()->getLink('ConversationMessageAdd', ['id' => $this->conversation->conversationID]);
-               }
-               else {
-                       // editing message
-                       QuickReplyManager::getInstance()->setMessage('conversationMessage', $this->message->messageID, $this->parameters['message']);
-                       $url = LinkHandler::getInstance()->getLink('ConversationMessageEdit', ['id' => $this->message->messageID]);
-               }
-               
-               if (!empty($this->parameters['tmpHash'])) {
-                       QuickReplyManager::getInstance()->setTmpHash($this->parameters['tmpHash']);
-               }
-               
-               // redirect
-               return [
-                       'url' => $url
-               ];
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function validateBeginEdit() {
-               $this->readInteger('containerID');
-               $this->readInteger('objectID');
-               
-               $this->conversation = new Conversation($this->parameters['containerID']);
-               if (!$this->conversation->conversationID) {
-                       throw new UserInputException('containerID');
-               }
-               
-               if ($this->conversation->isClosed || !Conversation::isParticipant([$this->conversation->conversationID])) {
-                       throw new PermissionDeniedException();
-               }
-               
-               $this->message = new ConversationMessage($this->parameters['objectID']);
-               if (!$this->message->messageID) {
-                       throw new UserInputException('objectID');
-               }
-               
-               if (!$this->message->canEdit()) {
-                       throw new PermissionDeniedException();
-               }
-               
-               BBCodeHandler::getInstance()->setDisallowedBBCodes(explode(',', WCF::getSession()->getPermission('user.message.disallowedBBCodes')));
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function beginEdit() {
-               WCF::getTPL()->assign([
-                       'defaultSmilies' => SmileyCache::getInstance()->getCategorySmilies(),
-                       'message' => $this->message,
-                       'permissionCanUseSmilies' => 'user.message.canUseSmilies',
-                       'wysiwygSelector' => 'messageEditor'.$this->message->messageID,
-               ]);
-               
-               $tmpHash = StringUtil::getRandomID();
-               $attachmentHandler = new AttachmentHandler('com.woltlab.wcf.conversation.message', $this->message->messageID, $tmpHash);
-               $attachmentList = $attachmentHandler->getAttachmentList();
-               
-               WCF::getTPL()->assign([
-                       'attachmentHandler' => $attachmentHandler,
-                       'attachmentList' => $attachmentList->getObjects(),
-                       'attachmentObjectID' => $this->message->messageID,
-                       'attachmentObjectType' => 'com.woltlab.wcf.conversation.message',
-                       'attachmentParentObjectID' => 0,
-                       'tmpHash' => $tmpHash,
-               ]);
-               
-               return [
-                       'actionName' => 'beginEdit',
-                       'template' => WCF::getTPL()->fetch('conversationMessageInlineEditor'),
-               ];
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function validateSave() {
-               $this->readString('message', true, 'data');
-               
-               if (empty($this->parameters['data']['message'])) {
-                       throw new UserInputException('message', WCF::getLanguage()->getDynamicVariable('wcf.global.form.error.empty'));
-               }
-               
-               $this->validateBeginEdit();
-               
-               $this->validateMessage($this->conversation, $this->getHtmlInputProcessor($this->parameters['data']['message'], $this->message->messageID));
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function save() {
-               $data = [];
-               
-               if (!$this->message->getConversation()->isDraft) {
-                       $data['lastEditTime'] = TIME_NOW;
-                       $data['editCount'] = $this->message->editCount + 1;
-               }
-               // execute update action
-               $action = new ConversationMessageAction([$this->message], 'update', [
-                       'data' => $data,
-                       'htmlInputProcessor' => $this->getHtmlInputProcessor()
-               ]);
-               $action->executeAction();
-               
-               // load new message
-               $this->message = new ConversationMessage($this->message->messageID);
-               $this->message->getAttachments();
-               
-               $attachmentList = $this->message->getAttachments(true);
-               $count = 0;
-               if ($attachmentList !== null) {
-                       // set permissions
-                       $attachmentList->setPermissions([
-                               'canDownload' => true,
-                               'canViewPreview' => true,
-                       ]);
-                       
-                       $count = count($attachmentList);
-               }
-               
-               // update count to reflect number of attachments after edit
-               if ($count != $this->message->attachments) {
-                       $messageEditor = new ConversationMessageEditor($this->message);
-                       $messageEditor->update(['attachments' => $count]);
-               }
-               
-               // load embedded objects
-               MessageEmbeddedObjectManager::getInstance()->loadObjects('com.woltlab.wcf.conversation.message', [$this->message->messageID]);
-               
-               $data = [
-                       'actionName' => 'save',
-                       'message' => $this->message->getFormattedMessage()
-               ];
-               
-               WCF::getTPL()->assign([
-                       'attachmentList' => $attachmentList,
-                       'objectID' => $this->message->messageID
-               ]);
-               $data['attachmentList'] = WCF::getTPL()->fetch('attachments');
-               
-               return $data;
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function validateContainer(DatabaseObject $conversation) {
-               /** @var Conversation $conversation */
-               
-               if (!$conversation->conversationID) {
-                       throw new UserInputException('objectID');
-               }
-               if ($conversation->isClosed) {
-                       throw new PermissionDeniedException();
-               }
-               $conversation->loadUserParticipation();
-               if (!$conversation->canRead() || !$conversation->canReply()) {
-                       throw new PermissionDeniedException();
-               }
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function validateMessage(DatabaseObject $container, HtmlInputProcessor $htmlInputProcessor) {
-               $message = $htmlInputProcessor->getTextContent();
-               if (mb_strlen($message) > WCF::getSession()->getPermission('user.conversation.maxLength')) {
-                       throw new UserInputException('message', WCF::getLanguage()->getDynamicVariable('wcf.message.error.tooLong', ['maxTextLength' => WCF::getSession()->getPermission('user.conversation.maxLength')]));
-               }
-               
-               // search for disallowed bbcodes
-               $disallowedBBCodes = $htmlInputProcessor->validate();
-               if (!empty($disallowedBBCodes)) {
-                       throw new UserInputException('text', WCF::getLanguage()->getDynamicVariable('wcf.message.error.disallowedBBCodes', ['disallowedBBCodes' => $disallowedBBCodes]));
-               }
-               
-               // search for censored words
-               if (ENABLE_CENSORSHIP) {
-                       $result = Censorship::getInstance()->test($message);
-                       if ($result) {
-                               throw new UserInputException('message', WCF::getLanguage()->getDynamicVariable('wcf.message.error.censoredWordsFound', ['censoredWords' => $result]));
-                       }
-               }
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getMessageList(DatabaseObject $conversation, $lastMessageTime) {
-               /** @var Conversation $conversation */
-               
-               $messageList = new ViewableConversationMessageList();
-               $messageList->setConversation($conversation);
-               $messageList->getConditionBuilder()->add("conversation_message.conversationID = ?", [$conversation->conversationID]);
-               $messageList->getConditionBuilder()->add("conversation_message.time > ?", [$lastMessageTime]);
-               $messageList->sqlOrderBy = "conversation_message.time ".CONVERSATION_LIST_DEFAULT_SORT_ORDER;
-               $messageList->readObjects();
-               
-               return $messageList;
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getPageNo(DatabaseObject $conversation) {
-               /** @var Conversation $conversation */
-               
-               $sql = "SELECT  COUNT(*) AS count
-                       FROM    wcf".WCF_N."_conversation_message
+            $statement = WCF::getDB()->prepareStatement($sql);
+            $statement->execute([
+                Conversation::STATE_DEFAULT,
+                $conversation->conversationID,
+                Conversation::STATE_HIDDEN,
+            ]);
+        }
+
+        // reset storage
+        UserStorageHandler::getInstance()->reset($conversation->getParticipantIDs(), 'unreadConversationCount');
+
+        // update search index
+        SearchIndexManager::getInstance()->set(
+            'com.woltlab.wcf.conversation.message',
+            $message->messageID,
+            $message->message,
+            !empty($this->parameters['isFirstPost']) ? $conversation->subject : '',
+            $message->time,
+            $message->userID,
+            $message->username
+        );
+
+        // update attachments
+        if (isset($this->parameters['attachmentHandler']) && $this->parameters['attachmentHandler'] !== null) {
+            /** @noinspection PhpUndefinedMethodInspection */
+            $this->parameters['attachmentHandler']->updateObjectID($message->messageID);
+        }
+
+        // save embedded objects
+        if (!empty($this->parameters['htmlInputProcessor'])) {
+            /** @noinspection PhpUndefinedMethodInspection */
+            $this->parameters['htmlInputProcessor']->setObjectID($message->messageID);
+
+            if (MessageEmbeddedObjectManager::getInstance()->registerObjects($this->parameters['htmlInputProcessor'])) {
+                $messageEditor->update(['hasEmbeddedObjects' => 1]);
+            }
+        }
+
+        // clear quotes
+        if (isset($this->parameters['removeQuoteIDs']) && !empty($this->parameters['removeQuoteIDs'])) {
+            MessageQuoteManager::getInstance()->markQuotesForRemoval($this->parameters['removeQuoteIDs']);
+        }
+        MessageQuoteManager::getInstance()->removeMarkedQuotes();
+
+        // return new message
+        return $message;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function update()
+    {
+        // count attachments
+        if (isset($this->parameters['attachmentHandler']) && $this->parameters['attachmentHandler'] !== null) {
+            $this->parameters['data']['attachments'] = \count($this->parameters['attachmentHandler']);
+        }
+
+        if (!empty($this->parameters['htmlInputProcessor'])) {
+            /** @noinspection PhpUndefinedMethodInspection */
+            $this->parameters['data']['message'] = $this->parameters['htmlInputProcessor']->getHtml();
+        }
+
+        parent::update();
+
+        // update search index / embedded objects
+        if (isset($this->parameters['data']) && isset($this->parameters['data']['message'])) {
+            foreach ($this->getObjects() as $message) {
+                $conversation = $message->getConversation();
+                SearchIndexManager::getInstance()->set(
+                    'com.woltlab.wcf.conversation.message',
+                    $message->messageID,
+                    $this->parameters['data']['message'],
+                    $conversation->firstMessageID == $message->messageID ? $conversation->subject : '',
+                    $message->time,
+                    $message->userID,
+                    $message->username
+                );
+
+                if (!empty($this->parameters['htmlInputProcessor'])) {
+                    /** @noinspection PhpUndefinedMethodInspection */
+                    $this->parameters['htmlInputProcessor']->setObjectID($message->messageID);
+
+                    if ($message->hasEmbeddedObjects != MessageEmbeddedObjectManager::getInstance()->registerObjects($this->parameters['htmlInputProcessor'])) {
+                        $message->update(['hasEmbeddedObjects' => $message->hasEmbeddedObjects ? 0 : 1]);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function delete()
+    {
+        $count = parent::delete();
+
+        $attachmentMessageIDs = $conversationIDs = [];
+        foreach ($this->getObjects() as $message) {
+            if (!\in_array($message->conversationID, $conversationIDs)) {
+                $conversationIDs[] = $message->conversationID;
+            }
+
+            if ($message->attachments) {
+                $attachmentMessageIDs[] = $message->messageID;
+            }
+        }
+
+        // rebuild conversations
+        if (!empty($conversationIDs)) {
+            $conversationAction = new ConversationAction($conversationIDs, 'rebuild');
+            $conversationAction->executeAction();
+        }
+
+        if (!empty($this->objectIDs)) {
+            // delete notifications
+            UserNotificationHandler::getInstance()
+                ->removeNotifications('com.woltlab.wcf.conversation.message.notification', $this->objectIDs);
+
+            // update search index
+            SearchIndexManager::getInstance()->delete('com.woltlab.wcf.conversation.message', $this->objectIDs);
+
+            // update embedded objects
+            MessageEmbeddedObjectManager::getInstance()
+                ->removeObjects('com.woltlab.wcf.conversation.message', $this->objectIDs);
+
+            // remove moderation queues
+            ModerationQueueManager::getInstance()
+                ->removeQueues('com.woltlab.wcf.conversation.message', $this->objectIDs);
+        }
+
+        // remove attachments
+        if (!empty($attachmentMessageIDs)) {
+            AttachmentHandler::removeAttachments('com.woltlab.wcf.conversation.message', $attachmentMessageIDs);
+        }
+
+        return $count;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function validateQuickReply()
+    {
+        QuickReplyManager::getInstance()->setDisallowedBBCodes(\explode(
+            ',',
+            WCF::getSession()->getPermission('user.message.disallowedBBCodes')
+        ));
+        QuickReplyManager::getInstance()->validateParameters($this, $this->parameters, Conversation::class);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function quickReply()
+    {
+        return QuickReplyManager::getInstance()->createMessage(
+            $this,
+            $this->parameters,
+            ConversationAction::class,
+            CONVERSATION_LIST_DEFAULT_SORT_ORDER,
+            'conversationMessageList'
+        );
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function validateJumpToExtended()
+    {
+        $this->readInteger('containerID');
+        $this->readString('message', true);
+        $this->readString('tmpHash', true);
+
+        $this->conversation = new Conversation($this->parameters['containerID']);
+        if (!$this->conversation->conversationID) {
+            throw new UserInputException('containerID');
+        } elseif (
+            $this->conversation->isClosed
+            || !Conversation::isParticipant([$this->conversation->conversationID])
+        ) {
+            throw new PermissionDeniedException();
+        }
+
+        // editing existing message
+        if (isset($this->parameters['messageID'])) {
+            $this->message = new ConversationMessage(\intval($this->parameters['messageID']));
+            if (!$this->message->messageID || ($this->message->conversationID != $this->conversation->conversationID)) {
+                throw new UserInputException('messageID');
+            }
+
+            if (!$this->message->canEdit()) {
+                throw new PermissionDeniedException();
+            }
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function jumpToExtended()
+    {
+        // quick reply
+        if ($this->message === null) {
+            QuickReplyManager::getInstance()
+                ->setMessage('conversation', $this->conversation->conversationID, $this->parameters['message']);
+            $url = LinkHandler::getInstance()->getLink(
+                'ConversationMessageAdd',
+                ['id' => $this->conversation->conversationID]
+            );
+        } else {
+            // editing message
+            QuickReplyManager::getInstance()
+                ->setMessage('conversationMessage', $this->message->messageID, $this->parameters['message']);
+            $url = LinkHandler::getInstance()->getLink(
+                'ConversationMessageEdit',
+                ['id' => $this->message->messageID]
+            );
+        }
+
+        if (!empty($this->parameters['tmpHash'])) {
+            QuickReplyManager::getInstance()->setTmpHash($this->parameters['tmpHash']);
+        }
+
+        // redirect
+        return [
+            'url' => $url,
+        ];
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function validateBeginEdit()
+    {
+        $this->readInteger('containerID');
+        $this->readInteger('objectID');
+
+        $this->conversation = new Conversation($this->parameters['containerID']);
+        if (!$this->conversation->conversationID) {
+            throw new UserInputException('containerID');
+        }
+
+        if ($this->conversation->isClosed || !Conversation::isParticipant([$this->conversation->conversationID])) {
+            throw new PermissionDeniedException();
+        }
+
+        $this->message = new ConversationMessage($this->parameters['objectID']);
+        if (!$this->message->messageID) {
+            throw new UserInputException('objectID');
+        }
+
+        if (!$this->message->canEdit()) {
+            throw new PermissionDeniedException();
+        }
+
+        BBCodeHandler::getInstance()->setDisallowedBBCodes(\explode(
+            ',',
+            WCF::getSession()->getPermission('user.message.disallowedBBCodes')
+        ));
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function beginEdit()
+    {
+        WCF::getTPL()->assign([
+            'defaultSmilies' => SmileyCache::getInstance()->getCategorySmilies(),
+            'message' => $this->message,
+            'permissionCanUseSmilies' => 'user.message.canUseSmilies',
+            'wysiwygSelector' => 'messageEditor' . $this->message->messageID,
+        ]);
+
+        $tmpHash = StringUtil::getRandomID();
+        $attachmentHandler = new AttachmentHandler(
+            'com.woltlab.wcf.conversation.message',
+            $this->message->messageID,
+            $tmpHash
+        );
+        $attachmentList = $attachmentHandler->getAttachmentList();
+
+        WCF::getTPL()->assign([
+            'attachmentHandler' => $attachmentHandler,
+            'attachmentList' => $attachmentList->getObjects(),
+            'attachmentObjectID' => $this->message->messageID,
+            'attachmentObjectType' => 'com.woltlab.wcf.conversation.message',
+            'attachmentParentObjectID' => 0,
+            'tmpHash' => $tmpHash,
+        ]);
+
+        return [
+            'actionName' => 'beginEdit',
+            'template' => WCF::getTPL()->fetch('conversationMessageInlineEditor'),
+        ];
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function validateSave()
+    {
+        $this->readString('message', true, 'data');
+
+        if (empty($this->parameters['data']['message'])) {
+            throw new UserInputException(
+                'message',
+                WCF::getLanguage()->getDynamicVariable('wcf.global.form.error.empty')
+            );
+        }
+
+        $this->validateBeginEdit();
+
+        $this->validateMessage(
+            $this->conversation,
+            $this->getHtmlInputProcessor($this->parameters['data']['message'], $this->message->messageID)
+        );
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function save()
+    {
+        $data = [];
+
+        if (!$this->message->getConversation()->isDraft) {
+            $data['lastEditTime'] = TIME_NOW;
+            $data['editCount'] = $this->message->editCount + 1;
+        }
+        // execute update action
+        $action = new self([$this->message], 'update', [
+            'data' => $data,
+            'htmlInputProcessor' => $this->getHtmlInputProcessor(),
+        ]);
+        $action->executeAction();
+
+        // load new message
+        $this->message = new ConversationMessage($this->message->messageID);
+        $this->message->getAttachments();
+
+        $attachmentList = $this->message->getAttachments(true);
+        $count = 0;
+        if ($attachmentList !== null) {
+            // set permissions
+            $attachmentList->setPermissions([
+                'canDownload' => true,
+                'canViewPreview' => true,
+            ]);
+
+            $count = \count($attachmentList);
+        }
+
+        // update count to reflect number of attachments after edit
+        if ($count != $this->message->attachments) {
+            $messageEditor = new ConversationMessageEditor($this->message);
+            $messageEditor->update(['attachments' => $count]);
+        }
+
+        // load embedded objects
+        MessageEmbeddedObjectManager::getInstance()
+            ->loadObjects('com.woltlab.wcf.conversation.message', [$this->message->messageID]);
+
+        $data = [
+            'actionName' => 'save',
+            'message' => $this->message->getFormattedMessage(),
+        ];
+
+        WCF::getTPL()->assign([
+            'attachmentList' => $attachmentList,
+            'objectID' => $this->message->messageID,
+        ]);
+        $data['attachmentList'] = WCF::getTPL()->fetch('attachments');
+
+        return $data;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function validateContainer(DatabaseObject $conversation)
+    {
+        /** @var Conversation $conversation */
+
+        if (!$conversation->conversationID) {
+            throw new UserInputException('objectID');
+        }
+        if ($conversation->isClosed) {
+            throw new PermissionDeniedException();
+        }
+        $conversation->loadUserParticipation();
+        if (!$conversation->canRead() || !$conversation->canReply()) {
+            throw new PermissionDeniedException();
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function validateMessage(DatabaseObject $container, HtmlInputProcessor $htmlInputProcessor)
+    {
+        $message = $htmlInputProcessor->getTextContent();
+        if (\mb_strlen($message) > WCF::getSession()->getPermission('user.conversation.maxLength')) {
+            throw new UserInputException(
+                'message',
+                WCF::getLanguage()->getDynamicVariable(
+                    'wcf.message.error.tooLong',
+                    ['maxTextLength' => WCF::getSession()->getPermission('user.conversation.maxLength')]
+                )
+            );
+        }
+
+        // search for disallowed bbcodes
+        $disallowedBBCodes = $htmlInputProcessor->validate();
+        if (!empty($disallowedBBCodes)) {
+            throw new UserInputException(
+                'text',
+                WCF::getLanguage()->getDynamicVariable(
+                    'wcf.message.error.disallowedBBCodes',
+                    ['disallowedBBCodes' => $disallowedBBCodes]
+                )
+            );
+        }
+
+        // search for censored words
+        if (ENABLE_CENSORSHIP) {
+            $result = Censorship::getInstance()->test($message);
+            if ($result) {
+                throw new UserInputException(
+                    'message',
+                    WCF::getLanguage()->getDynamicVariable(
+                        'wcf.message.error.censoredWordsFound',
+                        ['censoredWords' => $result]
+                    )
+                );
+            }
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getMessageList(DatabaseObject $conversation, $lastMessageTime)
+    {
+        /** @var Conversation $conversation */
+
+        $messageList = new ViewableConversationMessageList();
+        $messageList->setConversation($conversation);
+        $messageList->getConditionBuilder()
+            ->add("conversation_message.conversationID = ?", [$conversation->conversationID]);
+        $messageList->getConditionBuilder()
+            ->add("conversation_message.time > ?", [$lastMessageTime]);
+        $messageList->sqlOrderBy = "conversation_message.time " . CONVERSATION_LIST_DEFAULT_SORT_ORDER;
+        $messageList->readObjects();
+
+        return $messageList;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getPageNo(DatabaseObject $conversation)
+    {
+        /** @var Conversation $conversation */
+
+        $sql = "SELECT COUNT(*) AS count
+                       FROM    wcf" . WCF_N . "_conversation_message
                        WHERE   conversationID = ?";
-               $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute([$conversation->conversationID]);
-               $count = $statement->fetchArray();
-               
-               return [intval(ceil($count['count'] / CONVERSATION_MESSAGES_PER_PAGE)), $count['count']];
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getRedirectUrl(DatabaseObject $conversation, DatabaseObject $message) {
-               /** @var ConversationMessage $message */
-               return $message->getLink();
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function validateSaveFullQuote() {
-               $this->message = $this->getSingleObject();
-               
-               if (!Conversation::isParticipant([$this->message->conversationID])) {
-                       throw new PermissionDeniedException();
-               }
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function saveFullQuote() {
-               $quoteID = MessageQuoteManager::getInstance()->addQuote(
-                       'com.woltlab.wcf.conversation.message',
-                       $this->message->conversationID,
-                       $this->message->messageID,
-                       $this->message->getExcerpt(),
-                       $this->message->getMessage()
-               );
-               
-               if ($quoteID === false) {
-                       $removeQuoteID = MessageQuoteManager::getInstance()->getQuoteID(
-                               'com.woltlab.wcf.conversation.message',
-                               $this->message->messageID,
-                               $this->message->getExcerpt(),
-                               $this->message->getMessage()
-                       );
-                       MessageQuoteManager::getInstance()->removeQuote($removeQuoteID);
-               }
-               
-               $returnValues = [
-                       'count' => MessageQuoteManager::getInstance()->countQuotes(),
-                       'fullQuoteMessageIDs' => MessageQuoteManager::getInstance()->getFullQuoteObjectIDs(['com.woltlab.wcf.conversation.message'])
-               ];
-               
-               if ($quoteID) {
-                       $returnValues['renderedQuote'] = MessageQuoteManager::getInstance()->getQuoteComponents($quoteID);
-               }
-               
-               return $returnValues;
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function validateSaveQuote() {
-               $this->readString('message');
-               $this->readBoolean('renderQuote', true);
-               $this->message = $this->getSingleObject();
-               
-               if (!Conversation::isParticipant([$this->message->conversationID])) {
-                       throw new PermissionDeniedException();
-               }
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function saveQuote() {
-               $quoteID = MessageQuoteManager::getInstance()->addQuote(
-                       'com.woltlab.wcf.conversation.message',
-                       $this->message->conversationID,
-                       $this->message->messageID,
-                       $this->parameters['message'],
-                       false
-               );
-               
-               $returnValues = [
-                       'count' => MessageQuoteManager::getInstance()->countQuotes(),
-                       'fullQuoteMessageIDs' => MessageQuoteManager::getInstance()->getFullQuoteObjectIDs(['com.woltlab.wcf.conversation.message'])
-               ];
-               
-               if ($this->parameters['renderQuote']) {
-                       $returnValues['renderedQuote'] = MessageQuoteManager::getInstance()->getQuoteComponents($quoteID);
-               }
-               
-               return $returnValues;
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function validateGetRenderedQuotes() {
-               $this->readInteger('parentObjectID');
-               
-               $this->conversation = new Conversation($this->parameters['parentObjectID']);
-               if (!$this->conversation->conversationID) {
-                       throw new UserInputException('parentObjectID');
-               }
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getRenderedQuotes() {
-               $quotes = MessageQuoteManager::getInstance()->getQuotesByParentObjectID('com.woltlab.wcf.conversation.message', $this->conversation->conversationID);
-               
-               return [
-                       'template' => implode("\n\n", $quotes)
-               ];
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getAttachmentHandler(DatabaseObject $conversation) {
-               return new AttachmentHandler('com.woltlab.wcf.conversation.message', 0, $this->parameters['tmpHash']);
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getHtmlInputProcessor($message = null, $objectID = 0) {
-               if ($message === null) {
-                       return $this->htmlInputProcessor;
-               }
-               
-               $this->htmlInputProcessor = new HtmlInputProcessor();
-               $this->htmlInputProcessor->process($message, 'com.woltlab.wcf.conversation.message', $objectID);
-               
-               return $this->htmlInputProcessor;
-       }
+        $statement = WCF::getDB()->prepareStatement($sql);
+        $statement->execute([$conversation->conversationID]);
+        $count = $statement->fetchArray();
+
+        return [\intval(\ceil($count['count'] / CONVERSATION_MESSAGES_PER_PAGE)), $count['count']];
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getRedirectUrl(DatabaseObject $conversation, DatabaseObject $message)
+    {
+        /** @var ConversationMessage $message */
+        return $message->getLink();
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function validateSaveFullQuote()
+    {
+        $this->message = $this->getSingleObject();
+
+        if (!Conversation::isParticipant([$this->message->conversationID])) {
+            throw new PermissionDeniedException();
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function saveFullQuote()
+    {
+        $quoteID = MessageQuoteManager::getInstance()->addQuote(
+            'com.woltlab.wcf.conversation.message',
+            $this->message->conversationID,
+            $this->message->messageID,
+            $this->message->getExcerpt(),
+            $this->message->getMessage()
+        );
+
+        if ($quoteID === false) {
+            $removeQuoteID = MessageQuoteManager::getInstance()->getQuoteID(
+                'com.woltlab.wcf.conversation.message',
+                $this->message->messageID,
+                $this->message->getExcerpt(),
+                $this->message->getMessage()
+            );
+            MessageQuoteManager::getInstance()->removeQuote($removeQuoteID);
+        }
+
+        $returnValues = [
+            'count' => MessageQuoteManager::getInstance()->countQuotes(),
+            'fullQuoteMessageIDs' => MessageQuoteManager::getInstance()->getFullQuoteObjectIDs(
+                ['com.woltlab.wcf.conversation.message']
+            ),
+        ];
+
+        if ($quoteID) {
+            $returnValues['renderedQuote'] = MessageQuoteManager::getInstance()->getQuoteComponents($quoteID);
+        }
+
+        return $returnValues;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function validateSaveQuote()
+    {
+        $this->readString('message');
+        $this->readBoolean('renderQuote', true);
+        $this->message = $this->getSingleObject();
+
+        if (!Conversation::isParticipant([$this->message->conversationID])) {
+            throw new PermissionDeniedException();
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function saveQuote()
+    {
+        $quoteID = MessageQuoteManager::getInstance()->addQuote(
+            'com.woltlab.wcf.conversation.message',
+            $this->message->conversationID,
+            $this->message->messageID,
+            $this->parameters['message'],
+            false
+        );
+
+        $returnValues = [
+            'count' => MessageQuoteManager::getInstance()->countQuotes(),
+            'fullQuoteMessageIDs' => MessageQuoteManager::getInstance()->getFullQuoteObjectIDs(
+                ['com.woltlab.wcf.conversation.message']
+            ),
+        ];
+
+        if ($this->parameters['renderQuote']) {
+            $returnValues['renderedQuote'] = MessageQuoteManager::getInstance()->getQuoteComponents($quoteID);
+        }
+
+        return $returnValues;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function validateGetRenderedQuotes()
+    {
+        $this->readInteger('parentObjectID');
+
+        $this->conversation = new Conversation($this->parameters['parentObjectID']);
+        if (!$this->conversation->conversationID) {
+            throw new UserInputException('parentObjectID');
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getRenderedQuotes()
+    {
+        $quotes = MessageQuoteManager::getInstance()
+            ->getQuotesByParentObjectID('com.woltlab.wcf.conversation.message', $this->conversation->conversationID);
+
+        return [
+            'template' => \implode("\n\n", $quotes),
+        ];
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getAttachmentHandler(DatabaseObject $conversation)
+    {
+        return new AttachmentHandler('com.woltlab.wcf.conversation.message', 0, $this->parameters['tmpHash']);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getHtmlInputProcessor($message = null, $objectID = 0)
+    {
+        if ($message === null) {
+            return $this->htmlInputProcessor;
+        }
+
+        $this->htmlInputProcessor = new HtmlInputProcessor();
+        $this->htmlInputProcessor->process($message, 'com.woltlab.wcf.conversation.message', $objectID);
+
+        return $this->htmlInputProcessor;
+    }
 }
index cf31ba22134bcabb90a7b79441850131740e5d20..694118b0d819ad9659a2684da6810de8e8197f6c 100644 (file)
@@ -1,22 +1,25 @@
 <?php
+
 namespace wcf\data\conversation\message;
+
 use wcf\data\DatabaseObjectEditor;
 
 /**
  * Extends the message object with functions to create, update and delete messages.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\Data\Conversation\Message
- * 
- * @method static      ConversationMessage     create(array $parameters = [])
- * @method             ConversationMessage     getDecoratedObject()
- * @mixin              ConversationMessage
+ *
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Data\Conversation\Message
+ *
+ * @method static ConversationMessage create(array $parameters = [])
+ * @method      ConversationMessage getDecoratedObject()
+ * @mixin       ConversationMessage
  */
-class ConversationMessageEditor extends DatabaseObjectEditor {
-       /**
-        * @inheritDoc
-        */
-       protected static $baseClass = ConversationMessage::class;
+class ConversationMessageEditor extends DatabaseObjectEditor
+{
+    /**
+     * @inheritDoc
+     */
+    protected static $baseClass = ConversationMessage::class;
 }
index a22dc4f7e2489186fe234e370dcd208d8acbe781..e4845bc95a09d6bd0868bd2577d199346d6771e2 100644 (file)
@@ -1,23 +1,26 @@
 <?php
+
 namespace wcf\data\conversation\message;
+
 use wcf\data\DatabaseObjectList;
 
 /**
  * Represents a list of conversation messages.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\Data\Conversation\Message
  *
- * @method     ConversationMessage             current()
- * @method     ConversationMessage[]           getObjects()
- * @method     ConversationMessage|null        search($objectID)
- * @property   ConversationMessage[]           $objects
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Data\Conversation\Message
+ *
+ * @method  ConversationMessage     current()
+ * @method  ConversationMessage[]       getObjects()
+ * @method  ConversationMessage|null    search($objectID)
+ * @property    ConversationMessage[] $objects
  */
-class ConversationMessageList extends DatabaseObjectList {
-       /**
-        * @inheritDoc
-        */
-       public $className = ConversationMessage::class;
+class ConversationMessageList extends DatabaseObjectList
+{
+    /**
+     * @inheritDoc
+     */
+    public $className = ConversationMessage::class;
 }
index 03dcfba677eca1214095bbb8b5ef633984076ddf..2582acea3a130d2cacfa7f79eae2f8ef6d1710a7 100644 (file)
@@ -1,5 +1,7 @@
 <?php
+
 namespace wcf\data\conversation\message;
+
 use wcf\data\conversation\Conversation;
 use wcf\data\search\ISearchResultObject;
 use wcf\system\request\LinkHandler;
@@ -7,94 +9,108 @@ use wcf\system\search\SearchResultTextParser;
 
 /**
  * Represents a list of search result.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\Data\Conversation\Message
- * 
- * @property-read      string|null     $subject
+ *
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Data\Conversation\Message
+ *
+ * @property-read   string|null $subject
  */
-class SearchResultConversationMessage extends ViewableConversationMessage implements ISearchResultObject {
-       /**
-        * conversation object
-        * @var Conversation
-        */
-       public $conversation;
-       
-       /** @noinspection PhpMissingParentCallCommonInspection */
-       /**
-        * Returns the conversation object.
-        * 
-        * @return      Conversation
-        */
-       public function getConversation() {
-               if ($this->conversation === null) {
-                       $this->conversation = new Conversation(null, [
-                               'conversationID' => $this->conversationID,
-                               'subject' => $this->subject
-                       ]);
-               }
-               
-               return $this->conversation;
-       }
-       
-       /** @noinspection PhpMissingParentCallCommonInspection */
-       /**
-        * @inheritDoc
-        */
-       public function getFormattedMessage() {
-               return SearchResultTextParser::getInstance()->parse($this->getDecoratedObject()->getSimplifiedFormattedMessage());
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getSubject() {
-               return $this->subject;
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getLink($query = '') {
-               if ($query) {
-                       return LinkHandler::getInstance()->getLink('Conversation', [
-                               'object' => $this->getConversation(),
-                               'messageID' => $this->messageID,
-                               'highlight' => urlencode($query)
-                       ], '#message'.$this->messageID);
-               }
-               
-               return $this->getDecoratedObject()->getLink();
-       }
-       
-       /** @noinspection PhpMissingParentCallCommonInspection */
-       /**
-        * @inheritDoc
-        */
-       public function getTime() {
-               return $this->time;
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getObjectTypeName() {
-               return 'com.woltlab.wcf.conversation.message';
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getContainerTitle() {
-               return '';
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getContainerLink() {
-               return '';
-       }
+class SearchResultConversationMessage extends ViewableConversationMessage implements ISearchResultObject
+{
+    /**
+     * conversation object
+     * @var Conversation
+     */
+    public $conversation;
+
+    /** @noinspection PhpMissingParentCallCommonInspection */
+
+    /**
+     * Returns the conversation object.
+     *
+     * @return  Conversation
+     */
+    public function getConversation()
+    {
+        if ($this->conversation === null) {
+            $this->conversation = new Conversation(null, [
+                'conversationID' => $this->conversationID,
+                'subject' => $this->subject,
+            ]);
+        }
+
+        return $this->conversation;
+    }
+
+    /** @noinspection PhpMissingParentCallCommonInspection */
+
+    /**
+     * @inheritDoc
+     */
+    public function getFormattedMessage()
+    {
+        return SearchResultTextParser::getInstance()->parse(
+            $this->getDecoratedObject()->getSimplifiedFormattedMessage()
+        );
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getSubject()
+    {
+        return $this->subject;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getLink($query = '')
+    {
+        if ($query) {
+            return LinkHandler::getInstance()->getLink('Conversation', [
+                'object' => $this->getConversation(),
+                'messageID' => $this->messageID,
+                'highlight' => \urlencode($query),
+            ], '#message' . $this->messageID);
+        }
+
+        return $this->getDecoratedObject()->getLink();
+    }
+
+    /** @noinspection PhpMissingParentCallCommonInspection */
+
+    /**
+     * @inheritDoc
+     */
+    public function getTime()
+    {
+        return $this->time;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getObjectTypeName()
+    {
+        return 'com.woltlab.wcf.conversation.message';
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getContainerTitle()
+    {
+        return '';
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getContainerLink()
+    {
+        return '';
+    }
 }
index e06bd8934dfb26db4223aadda830c68504a35038..db24c7e082304fe7bcfd09f0fc335e1ef61d2447 100644 (file)
@@ -1,33 +1,38 @@
 <?php
+
 namespace wcf\data\conversation\message;
 
 /**
  * Represents a list of search results.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\Data\Conversation\Message
  *
- * @method     SearchResultConversationMessage         current()
- * @method     SearchResultConversationMessage[]       getObjects()
- * @method     SearchResultConversationMessage|null    search($objectID)
- * @property   SearchResultConversationMessage[]       $objects
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Data\Conversation\Message
+ *
+ * @method  SearchResultConversationMessage     current()
+ * @method  SearchResultConversationMessage[]   getObjects()
+ * @method  SearchResultConversationMessage|null    search($objectID)
+ * @property    SearchResultConversationMessage[] $objects
  */
-class SearchResultConversationMessageList extends SimplifiedViewableConversationMessageList {
-       /**
-        * @inheritDoc
-        */
-       public $decoratorClassName = SearchResultConversationMessage::class;
-       
-       /**
-        * Creates a new SearchResultConversationMessageList object.
-        */
-       public function __construct() {
-               parent::__construct();
-               
-               if (!empty($this->sqlSelects)) $this->sqlSelects .= ',';
-               $this->sqlSelects .= 'conversation.subject';
-               $this->sqlJoins .= " LEFT JOIN wcf".WCF_N."_conversation conversation ON (conversation.conversationID = conversation_message.conversationID)";
-       }
+class SearchResultConversationMessageList extends SimplifiedViewableConversationMessageList
+{
+    /**
+     * @inheritDoc
+     */
+    public $decoratorClassName = SearchResultConversationMessage::class;
+
+    /**
+     * Creates a new SearchResultConversationMessageList object.
+     */
+    public function __construct()
+    {
+        parent::__construct();
+
+        if (!empty($this->sqlSelects)) {
+            $this->sqlSelects .= ',';
+        }
+        $this->sqlSelects .= 'conversation.subject';
+        $this->sqlJoins .= " LEFT JOIN wcf" . WCF_N . "_conversation conversation ON (conversation.conversationID = conversation_message.conversationID)";
+    }
 }
index 0dc22237c6166cbe889446ba856a89a2ee32e654..aa74800c5bd5d91c28c53bd22d3198344ad60d25 100644 (file)
@@ -1,23 +1,25 @@
 <?php
+
 namespace wcf\data\conversation\message;
 
 /**
  * Represents a simplified version of ViewableConversationMessageList.
  * Disables the loading of attachments and embedded objects by default.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\Data\Conversation\Message
+ *
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Data\Conversation\Message
  */
-class SimplifiedViewableConversationMessageList extends ViewableConversationMessageList {
-       /**
-        * @inheritDoc
-        */
-       protected $attachmentLoading = false;
-       
-       /**
-        * @inheritDoc
-        */
-       protected $embeddedObjectLoading = false;
+class SimplifiedViewableConversationMessageList extends ViewableConversationMessageList
+{
+    /**
+     * @inheritDoc
+     */
+    protected $attachmentLoading = false;
+
+    /**
+     * @inheritDoc
+     */
+    protected $embeddedObjectLoading = false;
 }
index f02e9a6ea194fe75faa258002034b875af014609..083f62171d17c46d82f721359f1f9bd203aaa8e4 100644 (file)
@@ -1,64 +1,68 @@
 <?php
+
 namespace wcf\data\conversation\message;
-use wcf\data\user\UserProfile;
+
 use wcf\data\DatabaseObjectDecorator;
 use wcf\data\TLegacyUserPropertyAccess;
+use wcf\data\user\UserProfile;
 use wcf\system\cache\runtime\UserProfileRuntimeCache;
 
 /**
  * Represents a viewable conversation message.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\Data\Conversation\Message
- * 
- * @method     ConversationMessage     getDecoratedObject()
- * @mixin      ConversationMessage
+ *
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Data\Conversation\Message
+ *
+ * @method  ConversationMessage getDecoratedObject()
+ * @mixin   ConversationMessage
  */
-class ViewableConversationMessage extends DatabaseObjectDecorator {
-       use TLegacyUserPropertyAccess;
-       
-       /**
-        * @inheritDoc
-        */
-       protected static $baseClass = ConversationMessage::class;
-       
-       /**
-        * user profile object
-        * @var UserProfile
-        */
-       protected $userProfile;
-       
-       /**
-        * Returns the user profile object.
-        * 
-        * @return      UserProfile
-        */
-       public function getUserProfile() {
-               if ($this->userProfile === null) {
-                       if ($this->userID) {
-                               $this->userProfile = UserProfileRuntimeCache::getInstance()->getObject($this->userID);
-                       }
-                       else {
-                               $this->userProfile = UserProfile::getGuestUserProfile($this->username);
-                       }
-               }
-               
-               return $this->userProfile;
-       }
-       
-       /**
-        * Returns the viewable conversation message with the given id.
-        * 
-        * @param       integer         $messageID
-        * @return      ViewableConversationMessage
-        */
-       public static function getViewableConversationMessage($messageID) {
-               $messageList = new ViewableConversationMessageList();
-               $messageList->setObjectIDs([$messageID]);
-               $messageList->readObjects();
-               
-               return $messageList->search($messageID);
-       }
+class ViewableConversationMessage extends DatabaseObjectDecorator
+{
+    use TLegacyUserPropertyAccess;
+
+    /**
+     * @inheritDoc
+     */
+    protected static $baseClass = ConversationMessage::class;
+
+    /**
+     * user profile object
+     * @var UserProfile
+     */
+    protected $userProfile;
+
+    /**
+     * Returns the user profile object.
+     *
+     * @return  UserProfile
+     */
+    public function getUserProfile()
+    {
+        if ($this->userProfile === null) {
+            if ($this->userID) {
+                $this->userProfile = UserProfileRuntimeCache::getInstance()->getObject($this->userID);
+            } else {
+                $this->userProfile = UserProfile::getGuestUserProfile($this->username);
+            }
+        }
+
+        return $this->userProfile;
+    }
+
+    /**
+     * Returns the viewable conversation message with the given id.
+     *
+     * @param integer $messageID
+     * @return  ViewableConversationMessage
+     */
+    public static function getViewableConversationMessage($messageID)
+    {
+        $messageList = new ViewableConversationMessageList();
+        $messageList->setObjectIDs([$messageID]);
+        $messageList->readObjects();
+
+        return $messageList->search($messageID);
+    }
 }
index 41ed1404acf698249f095bd653575f002f1ea567..0588e905034516d4172792f2c5423a2d0cf8c684 100644 (file)
@@ -1,5 +1,7 @@
 <?php
+
 namespace wcf\data\conversation\message;
+
 use wcf\data\attachment\GroupedAttachmentList;
 use wcf\data\conversation\Conversation;
 use wcf\data\object\type\ObjectTypeCache;
@@ -8,179 +10,193 @@ use wcf\system\message\embedded\object\MessageEmbeddedObjectManager;
 
 /**
  * Represents a list of viewable conversation messages.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\Data\Conversation\Message
  *
- * @method     ViewableConversationMessage             current()
- * @method     ViewableConversationMessage[]           getObjects()
- * @method     ViewableConversationMessage|null        search($objectID)
- * @property   ViewableConversationMessage[]           $objects
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Data\Conversation\Message
+ *
+ * @method  ViewableConversationMessage     current()
+ * @method  ViewableConversationMessage[]       getObjects()
+ * @method  ViewableConversationMessage|null    search($objectID)
+ * @property    ViewableConversationMessage[] $objects
  */
-class ViewableConversationMessageList extends ConversationMessageList {
-       /**
-        * @inheritDoc
-        */
-       public $sqlOrderBy = 'conversation_message.time';
-       
-       /**
-        * @inheritDoc
-        */
-       public $decoratorClassName = ViewableConversationMessage::class;
-       
-       /**
-        * attachment object ids
-        * @var integer[]
-        */
-       public $attachmentObjectIDs = [];
-       
-       /**
-        * ids of the messages with embedded objects
-        * @var integer[]
-        */
-       public $embeddedObjectMessageIDs = [];
-       
-       /**
-        * attachment list
-        * @var GroupedAttachmentList
-        */
-       protected $attachmentList;
-       
-       /**
-        * max post time
-        * @var integer
-        */
-       protected $maxPostTime = 0;
-       
-       /**
-        * enables/disables the loading of attachments
-        * @var boolean
-        */
-       protected $attachmentLoading = true;
-       
-       /**
-        * enables/disables the loading of embedded objects
-        * @var boolean
-        */
-       protected $embeddedObjectLoading = true;
-       
-       /**
-        * conversation object
-        * @var Conversation
-        */
-       protected $conversation;
-       
-       /**
-        * @inheritDoc
-        */
-       public function readObjects() {
-               if ($this->objectIDs === null) {
-                       $this->readObjectIDs();
-               }
-               
-               parent::readObjects();
-               
-               $userIDs = [];
-               foreach ($this->objects as $message) {
-                       if ($message->time > $this->maxPostTime) {
-                               $this->maxPostTime = $message->time;
-                       }
-                       if ($this->conversation !== null) {
-                               $message->setConversation($this->conversation);
-                       }
-                       
-                       if ($message->attachments) {
-                               $this->attachmentObjectIDs[] = $message->messageID;
-                       }
-                       
-                       if ($message->hasEmbeddedObjects) {
-                               $this->embeddedObjectMessageIDs[] = $message->messageID;
-                       }
-                       if ($message->userID) {
-                               $userIDs[] = $message->userID;
-                       }
-               }
-               
-               if (!empty($userIDs)) {
-                       UserProfileRuntimeCache::getInstance()->cacheObjectIDs($userIDs);
-               }
-               
-               if ($this->embeddedObjectLoading) {
-                       $this->readEmbeddedObjects();
-               }
-               if ($this->attachmentLoading) {
-                       $this->readAttachments();
-               }
-       }
-       
-       /**
-        * Reads the embedded objects of the messages in the list.
-        */
-       public function readEmbeddedObjects() {
-               if (!empty($this->embeddedObjectMessageIDs)) {
-                       // add message objects to attachment object cache to save SQL queries
-                       ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.attachment.objectType', 'com.woltlab.wcf.conversation.message')->getProcessor()->setCachedObjects($this->objects);
-                       
-                       // load embedded objects
-                       MessageEmbeddedObjectManager::getInstance()->loadObjects('com.woltlab.wcf.conversation.message', $this->embeddedObjectMessageIDs);
-               }
-       }
-       
-       /**
-        * Reads the list of attachments.
-        */
-       public function readAttachments() {
-               if (!empty($this->attachmentObjectIDs)) {
-                       $this->attachmentList = new GroupedAttachmentList('com.woltlab.wcf.conversation.message');
-                       $this->attachmentList->getConditionBuilder()->add('attachment.objectID IN (?)', [$this->attachmentObjectIDs]);
-                       $this->attachmentList->readObjects();
-               }
-       }
-       
-       /**
-        * Returns the max post time.
-        * 
-        * @return      integer
-        */
-       public function getMaxPostTime() {
-               return $this->maxPostTime;
-       }
-       
-       /**
-        * Returns the list of attachments.
-        * 
-        * @return      GroupedAttachmentList
-        */
-       public function getAttachmentList() {
-               return $this->attachmentList;
-       }
-       
-       /**
-        * Enables/disables the loading of attachments.
-        * 
-        * @param       boolean         $enable
-        */
-       public function enableAttachmentLoading($enable = true) {
-               $this->attachmentLoading = $enable;
-       }
-       
-       /**
-        * Enables/disables the loading of embedded objects.
-        * 
-        * @param       boolean         $enable
-        */
-       public function enableEmbeddedObjectLoading($enable = true) {
-               $this->embeddedObjectLoading = $enable;
-       }
-       
-       /**
-        * Sets active conversation.
-        * 
-        * @param       Conversation            $conversation
-        */
-       public function setConversation(Conversation $conversation) {
-               $this->conversation = $conversation;
-       }
+class ViewableConversationMessageList extends ConversationMessageList
+{
+    /**
+     * @inheritDoc
+     */
+    public $sqlOrderBy = 'conversation_message.time';
+
+    /**
+     * @inheritDoc
+     */
+    public $decoratorClassName = ViewableConversationMessage::class;
+
+    /**
+     * attachment object ids
+     * @var integer[]
+     */
+    public $attachmentObjectIDs = [];
+
+    /**
+     * ids of the messages with embedded objects
+     * @var integer[]
+     */
+    public $embeddedObjectMessageIDs = [];
+
+    /**
+     * attachment list
+     * @var GroupedAttachmentList
+     */
+    protected $attachmentList;
+
+    /**
+     * max post time
+     * @var integer
+     */
+    protected $maxPostTime = 0;
+
+    /**
+     * enables/disables the loading of attachments
+     * @var boolean
+     */
+    protected $attachmentLoading = true;
+
+    /**
+     * enables/disables the loading of embedded objects
+     * @var boolean
+     */
+    protected $embeddedObjectLoading = true;
+
+    /**
+     * conversation object
+     * @var Conversation
+     */
+    protected $conversation;
+
+    /**
+     * @inheritDoc
+     */
+    public function readObjects()
+    {
+        if ($this->objectIDs === null) {
+            $this->readObjectIDs();
+        }
+
+        parent::readObjects();
+
+        $userIDs = [];
+        foreach ($this->objects as $message) {
+            if ($message->time > $this->maxPostTime) {
+                $this->maxPostTime = $message->time;
+            }
+            if ($this->conversation !== null) {
+                $message->setConversation($this->conversation);
+            }
+
+            if ($message->attachments) {
+                $this->attachmentObjectIDs[] = $message->messageID;
+            }
+
+            if ($message->hasEmbeddedObjects) {
+                $this->embeddedObjectMessageIDs[] = $message->messageID;
+            }
+            if ($message->userID) {
+                $userIDs[] = $message->userID;
+            }
+        }
+
+        if (!empty($userIDs)) {
+            UserProfileRuntimeCache::getInstance()->cacheObjectIDs($userIDs);
+        }
+
+        if ($this->embeddedObjectLoading) {
+            $this->readEmbeddedObjects();
+        }
+        if ($this->attachmentLoading) {
+            $this->readAttachments();
+        }
+    }
+
+    /**
+     * Reads the embedded objects of the messages in the list.
+     */
+    public function readEmbeddedObjects()
+    {
+        if (!empty($this->embeddedObjectMessageIDs)) {
+            // add message objects to attachment object cache to save SQL queries
+            ObjectTypeCache::getInstance()
+                ->getObjectTypeByName('com.woltlab.wcf.attachment.objectType', 'com.woltlab.wcf.conversation.message')
+                ->getProcessor()
+                ->setCachedObjects($this->objects);
+
+            // load embedded objects
+            MessageEmbeddedObjectManager::getInstance()
+                ->loadObjects('com.woltlab.wcf.conversation.message', $this->embeddedObjectMessageIDs);
+        }
+    }
+
+    /**
+     * Reads the list of attachments.
+     */
+    public function readAttachments()
+    {
+        if (!empty($this->attachmentObjectIDs)) {
+            $this->attachmentList = new GroupedAttachmentList('com.woltlab.wcf.conversation.message');
+            $this->attachmentList->getConditionBuilder()
+                ->add('attachment.objectID IN (?)', [$this->attachmentObjectIDs]);
+            $this->attachmentList->readObjects();
+        }
+    }
+
+    /**
+     * Returns the max post time.
+     *
+     * @return  integer
+     */
+    public function getMaxPostTime()
+    {
+        return $this->maxPostTime;
+    }
+
+    /**
+     * Returns the list of attachments.
+     *
+     * @return  GroupedAttachmentList
+     */
+    public function getAttachmentList()
+    {
+        return $this->attachmentList;
+    }
+
+    /**
+     * Enables/disables the loading of attachments.
+     *
+     * @param boolean $enable
+     */
+    public function enableAttachmentLoading($enable = true)
+    {
+        $this->attachmentLoading = $enable;
+    }
+
+    /**
+     * Enables/disables the loading of embedded objects.
+     *
+     * @param boolean $enable
+     */
+    public function enableEmbeddedObjectLoading($enable = true)
+    {
+        $this->embeddedObjectLoading = $enable;
+    }
+
+    /**
+     * Sets active conversation.
+     *
+     * @param Conversation $conversation
+     */
+    public function setConversation(Conversation $conversation)
+    {
+        $this->conversation = $conversation;
+    }
 }
index 6c7c4f38fd4edacab11e3395e277461679c3e674..ad750aeb11c12f07dccbf76cab62cfe3e036c957 100644 (file)
 <?php
+
 namespace wcf\data\modification\log;
+
 use wcf\system\cache\runtime\UserProfileRuntimeCache;
 use wcf\system\log\modification\ConversationModificationLogHandler;
 use wcf\system\WCF;
 
 /**
  * Represents a list of modification logs for conversation log page.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\Data\Modification\Log
  *
- * @method     ViewableConversationModificationLog             current()
- * @method     ViewableConversationModificationLog[]           getObjects()
- * @method     ViewableConversationModificationLog|null        search($objectID)
- * @property   ViewableConversationModificationLog[]           $objects
+ * @author  Alexander Ebert
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Data\Modification\Log
+ *
+ * @method  ViewableConversationModificationLog     current()
+ * @method  ViewableConversationModificationLog[]       getObjects()
+ * @method  ViewableConversationModificationLog|null    search($objectID)
+ * @property    ViewableConversationModificationLog[] $objects
  */
-class ConversationLogModificationLogList extends ModificationLogList {
-       /**
-        * @inheritDoc
-        */
-       public function __construct($conversationID) {
-               parent::__construct();
-               
-               // set conditions
-               $this->getConditionBuilder()->add('modification_log.objectTypeID = ?', [ConversationModificationLogHandler::getInstance()->getObjectType('com.woltlab.wcf.conversation.conversation')->objectTypeID]);
-               $this->getConditionBuilder()->add('modification_log.objectID = ?', [$conversationID]);
-       }
-       
-       /** @noinspection PhpMissingParentCallCommonInspection */
-       /**
-        * @inheritDoc
-        */
-       public function readObjects() {
-               $sql = "SELECT  modification_log.*
-                       FROM    wcf".WCF_N."_modification_log modification_log
-                       ".$this->getConditionBuilder()."
-                       ".(!empty($this->sqlOrderBy) ? "ORDER BY ".$this->sqlOrderBy : '');
-               $statement = WCF::getDB()->prepareStatement($sql, $this->sqlLimit, $this->sqlOffset);
-               $statement->execute($this->getConditionBuilder()->getParameters());
-               $this->objects = $statement->fetchObjects(($this->objectClassName ?: $this->className));
-               
-               // use table index as array index
-               $objects = $userIDs = [];
-               foreach ($this->objects as $object) {
-                       $objectID = $object->{$this->getDatabaseTableIndexName()};
-                       $objects[$objectID] = $object;
-                       
-                       $this->indexToObject[] = $objectID;
-                       
-                       if ($object->userID) {
-                               $userIDs[] = $object->userID;
-                       }
-               }
-               $this->objectIDs = $this->indexToObject;
-               $this->objects = $objects;
-               
-               if (!empty($userIDs)) {
-                       UserProfileRuntimeCache::getInstance()->cacheObjectIDs($userIDs);
-               }
-               
-               foreach ($this->objects as &$object) {
-                       $object = new ViewableConversationModificationLog($object);
-               }
-               unset($object);
-       }
-       
-       /**
-        * Returns all log entries created before given point of time. Applicable entries
-        * will be returned and removed from collection.
-        * 
-        * @param       integer         $time
-        * @return      ViewableConversationModificationLog[]
-        */
-       public function getEntriesUntil($time) {
-               $entries = [];
-               foreach ($this->objects as $index => $entry) {
-                       if ($entry->time < $time) {
-                               $entries[] = $entry;
-                               unset($this->objects[$index]);
-                       }
-               }
-               
-               if (!empty($entries)) {
-                       $this->indexToObject = array_keys($this->objects);
-               }
-               
-               return $entries;
-       }
+class ConversationLogModificationLogList extends ModificationLogList
+{
+    /**
+     * @inheritDoc
+     */
+    public function __construct($conversationID)
+    {
+        parent::__construct();
+
+        // set conditions
+        $this->getConditionBuilder()->add(
+            'modification_log.objectTypeID = ?',
+            [ConversationModificationLogHandler::getInstance()->getObjectType('com.woltlab.wcf.conversation.conversation')->objectTypeID]
+        );
+        $this->getConditionBuilder()->add(
+            'modification_log.objectID = ?',
+            [$conversationID]
+        );
+    }
+
+    /** @noinspection PhpMissingParentCallCommonInspection */
+
+    /**
+     * @inheritDoc
+     */
+    public function readObjects()
+    {
+        $sql = "SELECT modification_log.*
+                       FROM    wcf" . WCF_N . "_modification_log modification_log
+                       " . $this->getConditionBuilder() . "
+                       " . (!empty($this->sqlOrderBy) ? "ORDER BY " . $this->sqlOrderBy : '');
+        $statement = WCF::getDB()->prepareStatement($sql, $this->sqlLimit, $this->sqlOffset);
+        $statement->execute($this->getConditionBuilder()->getParameters());
+        $this->objects = $statement->fetchObjects(($this->objectClassName ?: $this->className));
+
+        // use table index as array index
+        $objects = $userIDs = [];
+        foreach ($this->objects as $object) {
+            $objectID = $object->{$this->getDatabaseTableIndexName()};
+            $objects[$objectID] = $object;
+
+            $this->indexToObject[] = $objectID;
+
+            if ($object->userID) {
+                $userIDs[] = $object->userID;
+            }
+        }
+        $this->objectIDs = $this->indexToObject;
+        $this->objects = $objects;
+
+        if (!empty($userIDs)) {
+            UserProfileRuntimeCache::getInstance()->cacheObjectIDs($userIDs);
+        }
+
+        foreach ($this->objects as &$object) {
+            $object = new ViewableConversationModificationLog($object);
+        }
+        unset($object);
+    }
+
+    /**
+     * Returns all log entries created before given point of time. Applicable entries
+     * will be returned and removed from collection.
+     *
+     * @param integer $time
+     * @return  ViewableConversationModificationLog[]
+     */
+    public function getEntriesUntil($time)
+    {
+        $entries = [];
+        foreach ($this->objects as $index => $entry) {
+            if ($entry->time < $time) {
+                $entries[] = $entry;
+                unset($this->objects[$index]);
+            }
+        }
+
+        if (!empty($entries)) {
+            $this->indexToObject = \array_keys($this->objects);
+        }
+
+        return $entries;
+    }
 }
index c28aea784a841fa5e2de84cb60227c19cddbffcc..e74541247b349eed00ef49279a13f1581cc960b5 100644 (file)
@@ -1,60 +1,67 @@
 <?php
+
 namespace wcf\data\modification\log;
-use wcf\data\user\UserProfile;
+
 use wcf\data\DatabaseObjectDecorator;
 use wcf\data\TLegacyUserPropertyAccess;
+use wcf\data\user\UserProfile;
 use wcf\system\cache\runtime\UserProfileRuntimeCache;
 use wcf\system\WCF;
 
 /**
  * Provides a viewable conversation modification log.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\Data\Modification\Log
  *
- * @method     ModificationLog         getDecoratedObject()
- * @mixin      ModificationLog
+ * @author  Alexander Ebert
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Data\Modification\Log
+ *
+ * @method  ModificationLog     getDecoratedObject()
+ * @mixin   ModificationLog
  */
-class ViewableConversationModificationLog extends DatabaseObjectDecorator {
-       use TLegacyUserPropertyAccess;
-       
-       /**
-        * @inheritDoc
-        */
-       protected static $baseClass = ModificationLog::class;
-       
-       /**
-        * user profile object
-        * @var UserProfile
-        */
-       protected $userProfile;
-       
-       /**
-        * Returns readable representation of current log entry.
-        * 
-        * @return      string
-        */
-       public function __toString() {
-               return WCF::getLanguage()->getDynamicVariable('wcf.conversation.log.conversation.'.$this->action, ['additionalData' => $this->additionalData]);
-       }
-       
-       /**
-        * Returns the profile object of the user who created the modification entry.
-        * 
-        * @return      UserProfile
-        */
-       public function getUserProfile() {
-               if ($this->userProfile === null) {
-                       if ($this->userID) {
-                               $this->userProfile = UserProfileRuntimeCache::getInstance()->getObject($this->userID);
-                       }
-                       else {
-                               $this->userProfile = UserProfile::getGuestUserProfile($this->username);
-                       }
-               }
-               
-               return $this->userProfile;
-       }
+class ViewableConversationModificationLog extends DatabaseObjectDecorator
+{
+    use TLegacyUserPropertyAccess;
+
+    /**
+     * @inheritDoc
+     */
+    protected static $baseClass = ModificationLog::class;
+
+    /**
+     * user profile object
+     * @var UserProfile
+     */
+    protected $userProfile;
+
+    /**
+     * Returns readable representation of current log entry.
+     *
+     * @return  string
+     */
+    public function __toString()
+    {
+        return WCF::getLanguage()->getDynamicVariable(
+            'wcf.conversation.log.conversation.' . $this->action,
+            ['additionalData' => $this->additionalData]
+        );
+    }
+
+    /**
+     * Returns the profile object of the user who created the modification entry.
+     *
+     * @return  UserProfile
+     */
+    public function getUserProfile()
+    {
+        if ($this->userProfile === null) {
+            if ($this->userID) {
+                $this->userProfile = UserProfileRuntimeCache::getInstance()->getObject($this->userID);
+            } else {
+                $this->userProfile = UserProfile::getGuestUserProfile($this->username);
+            }
+        }
+
+        return $this->userProfile;
+    }
 }
index 916e5c647e45024ae5c00949e98a2303e8fb17a7..b6a18fe9bc389dac502a89e7763a7e9e312920d3 100644 (file)
@@ -1,5 +1,7 @@
 <?php
+
 namespace wcf\form;
+
 use wcf\data\conversation\Conversation;
 use wcf\data\conversation\ConversationAction;
 use wcf\data\user\group\UserGroup;
@@ -20,321 +22,374 @@ use wcf\util\StringUtil;
 
 /**
  * Shows the conversation form.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\Form
+ *
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Form
  */
-class ConversationAddForm extends MessageForm {
-       /**
-        * @inheritDoc
-        */
-       public $attachmentObjectType = 'com.woltlab.wcf.conversation.message';
-       
-       /**
-        * @inheritDoc
-        */
-       public $loginRequired = true;
-       
-       /**
-        * @inheritDoc
-        */
-       public $messageObjectType = 'com.woltlab.wcf.conversation.message';
-       
-       /**
-        * @inheritDoc
-        */
-       public $neededModules = ['MODULE_CONVERSATION'];
-       
-       /**
-        * @inheritDoc
-        */
-       public $neededPermissions = ['user.conversation.canUseConversation'];
-       
-       /**
-        * participants (comma separated user names)
-        * @var string
-        */
-       public $participants = '';
-       
-       /**
-        * invisible participants (comma separated user names)
-        * @var string
-        */
-       public $invisibleParticipants = '';
-       
-       /**
-        * user group participants (comma separated ids)
-        * @var string
-        */
-       public $participantsGroupIDs = '';
-       
-       /**
-        * invisible user group participants (comma separated ids)
-        * @var string
-        */
-       public $invisibleParticipantsGroupIDs = '';
-       
-       /**
-        * draft status
-        * @var integer
-        */
-       public $draft = 0;
-       
-       /**
-        * true, if participants can add new participants
-        * @var integer
-        */
-       public $participantCanInvite = 0;
-       
-       /**
-        * participants (user ids)
-        * @var integer[]
-        */
-       public $participantIDs = [];
-       
-       /**
-        * invisible participants (user ids)
-        * @var integer[]
-        */
-       public $invisibleParticipantIDs = [];
-       
-       /**
-        * @inheritDoc
-        */
-       public function readParameters() {
-               parent::readParameters();
-               
-               if (!WCF::getUser()->userID) return;
-               
-               // check max pc permission
-               if (ConversationHandler::getInstance()->getConversationCount() >= WCF::getSession()->getPermission('user.conversation.maxConversations')) {
-                       throw new NamedUserException(WCF::getLanguage()->getDynamicVariable('wcf.conversation.error.mailboxIsFull'));
-               }
-               
-               ConversationHandler::getInstance()->enforceFloodControl();
-               
-               if (isset($_REQUEST['userID'])) {
-                       $userID = intval($_REQUEST['userID']);
-                       $user = UserProfileRuntimeCache::getInstance()->getObject($userID);
-                       if ($user === null || $user->userID == WCF::getUser()->userID) {
-                               throw new IllegalLinkException();
-                       }
-                       
-                       // validate user
-                       try {
-                               Conversation::validateParticipant($user);
-                       }
-                       catch (UserInputException $e) {
-                               throw new NamedUserException(WCF::getLanguage()->getDynamicVariable('wcf.conversation.participants.error.'.$e->getType(), ['errorData' => ['username' => $user->username]]));
-                       }
-                       
-                       $this->participants = $user->username;
-               }
-               
-               // get max text length
-               $this->maxTextLength = WCF::getSession()->getPermission('user.conversation.maxLength');
-               
-               // quotes
-               MessageQuoteManager::getInstance()->readParameters();
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function readFormParameters() {
-               parent::readFormParameters();
-               
-               if (isset($_POST['draft'])) $this->draft = (bool) $_POST['draft'];
-               if (isset($_POST['participantCanInvite'])) $this->participantCanInvite = (bool) $_POST['participantCanInvite'];
-               if (isset($_POST['participants'])) $this->participants = StringUtil::trim($_POST['participants']);
-               if (isset($_POST['invisibleParticipants'])) $this->invisibleParticipants = StringUtil::trim($_POST['invisibleParticipants']);
-               if (WCF::getSession()->getPermission('user.conversation.canAddGroupParticipants')) {
-                       if (isset($_POST['participantsGroupIDs'])) $this->participantsGroupIDs = StringUtil::trim($_POST['participantsGroupIDs']);
-                       if (isset($_POST['invisibleParticipantsGroupIDs'])) $this->invisibleParticipantsGroupIDs = StringUtil::trim($_POST['invisibleParticipantsGroupIDs']);
-               }
-               
-               // quotes
-               MessageQuoteManager::getInstance()->readFormParameters();
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function validate() {
-               if (empty($this->participants) && empty($this->invisibleParticipants) && empty($this->participantsGroupIDs) && empty($this->invisibleParticipantsGroupIDs) && !$this->draft) {
-                       throw new UserInputException('participants');
-               }
-               
-               // check, if user is allowed to set invisible participants
-               if (!WCF::getSession()->getPermission('user.conversation.canAddInvisibleParticipants') && (!empty($this->invisibleParticipants) || !empty($this->invisibleParticipantsGroupIDs))) {
-                       throw new UserInputException('participants', 'invisibleParticipantsNoPermission');
-               }
-               
-               // check, if user is allowed to set participantCanInvite
-               if (!WCF::getSession()->getPermission('user.conversation.canSetCanInvite') && $this->participantCanInvite) {
-                       throw new UserInputException('participantCanInvite', 'participantCanInviteNoPermission');
-               }
-               
-               $this->participantIDs = Conversation::validateParticipants($this->participants);
-               $this->invisibleParticipantIDs = Conversation::validateParticipants($this->invisibleParticipants, 'invisibleParticipants');
-               if (!empty($this->participantsGroupIDs)) {
-                       $validGroupParticipants = Conversation::validateGroupParticipants($this->participantsGroupIDs);
-                       $validGroupParticipants = array_diff($validGroupParticipants, $this->participantIDs);
-                       if (empty($validGroupParticipants)) {
-                               throw new UserInputException('participants', 'emptyGroup');
-                       }
-                       $this->participantIDs = array_merge($this->participantIDs, $validGroupParticipants);
-               }
-               if (!empty($this->invisibleParticipantsGroupIDs)) {
-                       $validGroupParticipants = Conversation::validateGroupParticipants($this->invisibleParticipantsGroupIDs, 'invisibleParticipants');
-                       $validGroupParticipants = array_diff($validGroupParticipants, $this->invisibleParticipantIDs);
-                       if (empty($validGroupParticipants)) {
-                               throw new UserInputException('invisibleParticipants', 'emptyGroup');
-                       }
-                       $this->invisibleParticipantIDs = array_merge($this->invisibleParticipantIDs, $validGroupParticipants);
-               }
-               
-               // remove duplicates
-               $intersection = array_intersect($this->participantIDs, $this->invisibleParticipantIDs);
-               if (!empty($intersection)) {
-                       $users = UserProfileRuntimeCache::getInstance()->getObjects(array_slice($intersection, 0, 10));
-                       throw new UserInputException('invisibleParticipants', array_map(function ($user) {
-                               return [
-                                       'type' => 'intersects',
-                                       'username' => $user->username,
-                               ];
-                       }, $users));
-               }
-               
-               if (empty($this->participantIDs) && empty($this->invisibleParticipantIDs) && !$this->draft) {
-                       throw new UserInputException('participants');
-               }
-               
-               // check number of participants
-               if (count($this->participantIDs) + count($this->invisibleParticipantIDs) > WCF::getSession()->getPermission('user.conversation.maxParticipants')) {
-                       throw new UserInputException('participants', 'tooManyParticipants');
-               }
-               
-               parent::validate();
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function save() {
-               parent::save();
-               
-               // save conversation
-               $data = array_merge($this->additionalFields, [
-                       'subject' => $this->subject,
-                       'time' => TIME_NOW,
-                       'userID' => WCF::getUser()->userID,
-                       'username' => WCF::getUser()->username,
-                       'isDraft' => $this->draft ? 1 : 0,
-                       'participantCanInvite' => $this->participantCanInvite
-               ]);
-               if ($this->draft) {
-                       $data['draftData'] = serialize([
-                               'participants' => $this->participantIDs,
-                               'invisibleParticipants' => $this->invisibleParticipantIDs
-                       ]);
-               }
-               
-               $conversationData = [
-                       'data' => $data,
-                       'attachmentHandler' => $this->attachmentHandler,
-                       'htmlInputProcessor' => $this->htmlInputProcessor,
-                       'messageData' => []
-               ];
-               if (!$this->draft) {
-                       $conversationData['participants'] = $this->participantIDs;
-                       $conversationData['invisibleParticipants'] = $this->invisibleParticipantIDs;
-               }
-               
-               $this->objectAction = new ConversationAction([], 'create', $conversationData);
-               /** @var Conversation $conversation */
-               $conversation = $this->objectAction->executeAction()['returnValues'];
-               
-               MessageQuoteManager::getInstance()->saved();
-               
-               FloodControl::getInstance()->registerContent('com.woltlab.wcf.conversation');
-               
-               $this->saved();
-               
-               // forward
-               HeaderUtil::redirect($conversation->getLink());
-               exit;
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function readData() {
-               parent::readData();
-               
-               // add breadcrumbs
-               PageLocationManager::getInstance()->addParentLocation('com.woltlab.wcf.conversation.ConversationList');
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function assignVariables() {
-               parent::assignVariables();
-               
-               MessageQuoteManager::getInstance()->assignVariables();
-               
-               $allowedUserGroupIDs = [];
-               foreach (UserGroupCacheBuilder::getInstance()->getData([], 'groups') as $group) {
-                       if ($group->canBeAddedAsConversationParticipant) $allowedUserGroupIDs[] = $group->groupID;
-               }
-               
-               WCF::getTPL()->assign([
-                       'participantCanInvite' => $this->participantCanInvite,
-                       'participants' => $this->participants,
-                       'participantsData' => $this->getParticipantsData(),
-                       'invisibleParticipants' => $this->invisibleParticipants,
-                       'invisibleParticipantsData' => $this->getParticipantsData(true),
-                       'action' => 'add',
-                       'allowedUserGroupIDs' => $allowedUserGroupIDs
-               ]);
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function show() {
-               if (!WCF::getSession()->getPermission('user.conversation.canStartConversation')) {
-                       throw new PermissionDeniedException();
-               }
-               
-               parent::show();
-       }
-       
-       private function getParticipantsData($invisible = false) {
-               $result = [];
-               $participants = ArrayUtil::trim(explode(',', ($invisible ? $this->invisibleParticipants : $this->participants)));
-               foreach ($participants as $username) {
-                       $result[] = [
-                               'objectId' => 0,
-                               'value' => $username,
-                               'type' => 'user'
-                       ];
-               }
-               
-               $participants = ArrayUtil::toIntegerArray(explode(',', ($invisible ? $this->invisibleParticipantsGroupIDs : $this->participantsGroupIDs)));
-               foreach ($participants as $groupID) {
-                       $group = UserGroup::getGroupByID($groupID);
-                       if (!$group) continue;
-                       $result[] = [
-                               'objectId' => $groupID,
-                               'value' => $group->getName(),
-                               'type' => 'group'
-                       ];
-               }
-               
-               return $result;
-       }
+class ConversationAddForm extends MessageForm
+{
+    /**
+     * @inheritDoc
+     */
+    public $attachmentObjectType = 'com.woltlab.wcf.conversation.message';
+
+    /**
+     * @inheritDoc
+     */
+    public $loginRequired = true;
+
+    /**
+     * @inheritDoc
+     */
+    public $messageObjectType = 'com.woltlab.wcf.conversation.message';
+
+    /**
+     * @inheritDoc
+     */
+    public $neededModules = ['MODULE_CONVERSATION'];
+
+    /**
+     * @inheritDoc
+     */
+    public $neededPermissions = ['user.conversation.canUseConversation'];
+
+    /**
+     * participants (comma separated user names)
+     * @var string
+     */
+    public $participants = '';
+
+    /**
+     * invisible participants (comma separated user names)
+     * @var string
+     */
+    public $invisibleParticipants = '';
+
+    /**
+     * user group participants (comma separated ids)
+     * @var string
+     */
+    public $participantsGroupIDs = '';
+
+    /**
+     * invisible user group participants (comma separated ids)
+     * @var string
+     */
+    public $invisibleParticipantsGroupIDs = '';
+
+    /**
+     * draft status
+     * @var integer
+     */
+    public $draft = 0;
+
+    /**
+     * true, if participants can add new participants
+     * @var integer
+     */
+    public $participantCanInvite = 0;
+
+    /**
+     * participants (user ids)
+     * @var integer[]
+     */
+    public $participantIDs = [];
+
+    /**
+     * invisible participants (user ids)
+     * @var integer[]
+     */
+    public $invisibleParticipantIDs = [];
+
+    /**
+     * @inheritDoc
+     */
+    public function readParameters()
+    {
+        parent::readParameters();
+
+        if (!WCF::getUser()->userID) {
+            return;
+        }
+
+        // check max pc permission
+        if (ConversationHandler::getInstance()->getConversationCount() >= WCF::getSession()->getPermission('user.conversation.maxConversations')) {
+            throw new NamedUserException(WCF::getLanguage()->getDynamicVariable(
+                'wcf.conversation.error.mailboxIsFull'
+            ));
+        }
+
+        ConversationHandler::getInstance()->enforceFloodControl();
+
+        if (isset($_REQUEST['userID'])) {
+            $userID = \intval($_REQUEST['userID']);
+            $user = UserProfileRuntimeCache::getInstance()->getObject($userID);
+            if ($user === null || $user->userID == WCF::getUser()->userID) {
+                throw new IllegalLinkException();
+            }
+
+            // validate user
+            try {
+                Conversation::validateParticipant($user);
+            } catch (UserInputException $e) {
+                throw new NamedUserException(WCF::getLanguage()->getDynamicVariable(
+                    'wcf.conversation.participants.error.' . $e->getType(),
+                    ['errorData' => ['username' => $user->username]]
+                ));
+            }
+
+            $this->participants = $user->username;
+        }
+
+        // get max text length
+        $this->maxTextLength = WCF::getSession()->getPermission('user.conversation.maxLength');
+
+        // quotes
+        MessageQuoteManager::getInstance()->readParameters();
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function readFormParameters()
+    {
+        parent::readFormParameters();
+
+        if (isset($_POST['draft'])) {
+            $this->draft = (bool)$_POST['draft'];
+        }
+        if (isset($_POST['participantCanInvite'])) {
+            $this->participantCanInvite = (bool)$_POST['participantCanInvite'];
+        }
+        if (isset($_POST['participants'])) {
+            $this->participants = StringUtil::trim($_POST['participants']);
+        }
+        if (isset($_POST['invisibleParticipants'])) {
+            $this->invisibleParticipants = StringUtil::trim($_POST['invisibleParticipants']);
+        }
+        if (WCF::getSession()->getPermission('user.conversation.canAddGroupParticipants')) {
+            if (isset($_POST['participantsGroupIDs'])) {
+                $this->participantsGroupIDs = StringUtil::trim($_POST['participantsGroupIDs']);
+            }
+            if (isset($_POST['invisibleParticipantsGroupIDs'])) {
+                $this->invisibleParticipantsGroupIDs = StringUtil::trim($_POST['invisibleParticipantsGroupIDs']);
+            }
+        }
+
+        // quotes
+        MessageQuoteManager::getInstance()->readFormParameters();
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function validate()
+    {
+        if (
+            empty($this->participants)
+            && empty($this->invisibleParticipants)
+            && empty($this->participantsGroupIDs)
+            && empty($this->invisibleParticipantsGroupIDs)
+            && !$this->draft
+        ) {
+            throw new UserInputException('participants');
+        }
+
+        // check, if user is allowed to set invisible participants
+        if (
+            !WCF::getSession()->getPermission('user.conversation.canAddInvisibleParticipants')
+            && (!empty($this->invisibleParticipants) || !empty($this->invisibleParticipantsGroupIDs))
+        ) {
+            throw new UserInputException('participants', 'invisibleParticipantsNoPermission');
+        }
+
+        // check, if user is allowed to set participantCanInvite
+        if (!WCF::getSession()->getPermission('user.conversation.canSetCanInvite') && $this->participantCanInvite) {
+            throw new UserInputException('participantCanInvite', 'participantCanInviteNoPermission');
+        }
+
+        $this->participantIDs = Conversation::validateParticipants($this->participants);
+        $this->invisibleParticipantIDs = Conversation::validateParticipants(
+            $this->invisibleParticipants,
+            'invisibleParticipants'
+        );
+        if (!empty($this->participantsGroupIDs)) {
+            $validGroupParticipants = Conversation::validateGroupParticipants($this->participantsGroupIDs);
+            $validGroupParticipants = \array_diff($validGroupParticipants, $this->participantIDs);
+            if (empty($validGroupParticipants)) {
+                throw new UserInputException('participants', 'emptyGroup');
+            }
+            $this->participantIDs = \array_merge($this->participantIDs, $validGroupParticipants);
+        }
+        if (!empty($this->invisibleParticipantsGroupIDs)) {
+            $validGroupParticipants = Conversation::validateGroupParticipants(
+                $this->invisibleParticipantsGroupIDs,
+                'invisibleParticipants'
+            );
+            $validGroupParticipants = \array_diff($validGroupParticipants, $this->invisibleParticipantIDs);
+            if (empty($validGroupParticipants)) {
+                throw new UserInputException('invisibleParticipants', 'emptyGroup');
+            }
+            $this->invisibleParticipantIDs = \array_merge($this->invisibleParticipantIDs, $validGroupParticipants);
+        }
+
+        // remove duplicates
+        $intersection = \array_intersect($this->participantIDs, $this->invisibleParticipantIDs);
+        if (!empty($intersection)) {
+            $users = UserProfileRuntimeCache::getInstance()->getObjects(\array_slice($intersection, 0, 10));
+            throw new UserInputException('invisibleParticipants', \array_map(static function ($user) {
+                return [
+                    'type' => 'intersects',
+                    'username' => $user->username,
+                ];
+            }, $users));
+        }
+
+        if (empty($this->participantIDs) && empty($this->invisibleParticipantIDs) && !$this->draft) {
+            throw new UserInputException('participants');
+        }
+
+        // check number of participants
+        if (\count($this->participantIDs) + \count($this->invisibleParticipantIDs) > WCF::getSession()->getPermission('user.conversation.maxParticipants')) {
+            throw new UserInputException('participants', 'tooManyParticipants');
+        }
+
+        parent::validate();
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function save()
+    {
+        parent::save();
+
+        // save conversation
+        $data = \array_merge($this->additionalFields, [
+            'subject' => $this->subject,
+            'time' => TIME_NOW,
+            'userID' => WCF::getUser()->userID,
+            'username' => WCF::getUser()->username,
+            'isDraft' => $this->draft ? 1 : 0,
+            'participantCanInvite' => $this->participantCanInvite,
+        ]);
+        if ($this->draft) {
+            $data['draftData'] = \serialize([
+                'participants' => $this->participantIDs,
+                'invisibleParticipants' => $this->invisibleParticipantIDs,
+            ]);
+        }
+
+        $conversationData = [
+            'data' => $data,
+            'attachmentHandler' => $this->attachmentHandler,
+            'htmlInputProcessor' => $this->htmlInputProcessor,
+            'messageData' => [],
+        ];
+        if (!$this->draft) {
+            $conversationData['participants'] = $this->participantIDs;
+            $conversationData['invisibleParticipants'] = $this->invisibleParticipantIDs;
+        }
+
+        $this->objectAction = new ConversationAction([], 'create', $conversationData);
+        /** @var Conversation $conversation */
+        $conversation = $this->objectAction->executeAction()['returnValues'];
+
+        MessageQuoteManager::getInstance()->saved();
+
+        FloodControl::getInstance()->registerContent('com.woltlab.wcf.conversation');
+
+        $this->saved();
+
+        // forward
+        HeaderUtil::redirect($conversation->getLink());
+
+        exit;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function readData()
+    {
+        parent::readData();
+
+        // add breadcrumbs
+        PageLocationManager::getInstance()->addParentLocation('com.woltlab.wcf.conversation.ConversationList');
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function assignVariables()
+    {
+        parent::assignVariables();
+
+        MessageQuoteManager::getInstance()->assignVariables();
+
+        $allowedUserGroupIDs = [];
+        foreach (UserGroupCacheBuilder::getInstance()->getData([], 'groups') as $group) {
+            if ($group->canBeAddedAsConversationParticipant) {
+                $allowedUserGroupIDs[] = $group->groupID;
+            }
+        }
+
+        WCF::getTPL()->assign([
+            'participantCanInvite' => $this->participantCanInvite,
+            'participants' => $this->participants,
+            'participantsData' => $this->getParticipantsData(),
+            'invisibleParticipants' => $this->invisibleParticipants,
+            'invisibleParticipantsData' => $this->getParticipantsData(true),
+            'action' => 'add',
+            'allowedUserGroupIDs' => $allowedUserGroupIDs,
+        ]);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function show()
+    {
+        if (!WCF::getSession()->getPermission('user.conversation.canStartConversation')) {
+            throw new PermissionDeniedException();
+        }
+
+        parent::show();
+    }
+
+    private function getParticipantsData($invisible = false)
+    {
+        $result = [];
+        $participants = ArrayUtil::trim(\explode(
+            ',',
+            ($invisible ? $this->invisibleParticipants : $this->participants)
+        ));
+        foreach ($participants as $username) {
+            $result[] = [
+                'objectId' => 0,
+                'value' => $username,
+                'type' => 'user',
+            ];
+        }
+
+        $participants = ArrayUtil::toIntegerArray(\explode(
+            ',',
+            ($invisible ? $this->invisibleParticipantsGroupIDs : $this->participantsGroupIDs)
+        ));
+        foreach ($participants as $groupID) {
+            $group = UserGroup::getGroupByID($groupID);
+            if (!$group) {
+                continue;
+            }
+            $result[] = [
+                'objectId' => $groupID,
+                'value' => $group->getName(),
+                'type' => 'group',
+            ];
+        }
+
+        return $result;
+    }
 }
index cc0e2e5798f8cff93eeda9313ae8ead8d0f0eb77..f2fa89bcf553baba27c8743ab385d08bece64596 100644 (file)
@@ -1,8 +1,10 @@
 <?php
+
 namespace wcf\form;
-use wcf\data\conversation\message\ConversationMessageAction;
+
 use wcf\data\conversation\Conversation;
 use wcf\data\conversation\ConversationAction;
+use wcf\data\conversation\message\ConversationMessageAction;
 use wcf\data\user\UserProfile;
 use wcf\system\exception\IllegalLinkException;
 use wcf\system\message\quote\MessageQuoteManager;
@@ -12,134 +14,150 @@ use wcf\util\HeaderUtil;
 /**
  * Allows the editing of conversation drafts.
  *
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\Form
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Form
  */
-class ConversationDraftEditForm extends ConversationAddForm {
-       /**
-        * @inheritDoc
-        */
-       public $templateName = 'conversationAdd';
-               
-       /**
-        * conversation id
-        * @var integer
-        */
-       public $conversationID = 0;
-       
-       /**
-        * conversation
-        * @var Conversation
-        */
-       public $conversation;
-       
-       /**
-        * @inheritDoc
-        */
-       public function readParameters() {
-               parent::readParameters();
-               
-               if (isset($_REQUEST['id'])) $this->conversationID = intval($_REQUEST['id']);
-               $this->conversation = new Conversation($this->conversationID);
-               if ($this->conversation->userID != WCF::getUser()->userID || !$this->conversation->isDraft) {
-                       throw new IllegalLinkException();
-               }
-               
-               $this->attachmentObjectID = $this->conversation->getFirstMessage()->messageID;
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function save() {
-               MessageForm::save();
-               
-               // save message
-               $messageData = [
-                       'data' => [],
-                       'attachmentHandler' => $this->attachmentHandler,
-                       'htmlInputProcessor' => $this->htmlInputProcessor,
-               ];
-               if (!$this->draft) {
-                       // update timestamp
-                       $messageData['data']['time'] = TIME_NOW;
-               }
-               $messageAction = new ConversationMessageAction([$this->conversation->getFirstMessage()], 'update', $messageData);
-               $messageAction->executeAction();
-               
-               // save conversation
-               $data = array_merge($this->additionalFields, [
-                       'subject' => $this->subject,
-                       'isDraft' => $this->draft ? 1 : 0,
-                       'participantCanInvite' => $this->participantCanInvite
-               ]);
-               if ($this->draft) {
-                       $data['draftData'] = serialize([
-                               'participants' => $this->participantIDs,
-                               'invisibleParticipants' => $this->invisibleParticipantIDs
-                       ]);
-               }
-               $conversationData = [
-                       'data' => $data,
-               ];
-               if (!$this->draft) {
-                       $conversationData['participants'] = $this->participantIDs;
-                       $conversationData['invisibleParticipants'] = $this->invisibleParticipantIDs;
-                       // update timestamp
-                       $conversationData['data']['time'] = $conversationData['data']['lastPostTime'] = TIME_NOW;
-               }
-               $this->objectAction = new ConversationAction([$this->conversation], 'update', $conversationData);
-               $this->objectAction->executeAction();
-               
-               MessageQuoteManager::getInstance()->saved();
-               $this->saved();
-               
-               // forward
-               HeaderUtil::redirect($this->conversation->getLink());
-               exit;
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function readData() {
-               parent::readData();
-               
-               if (empty($_POST)) {
-                       $this->text = $this->conversation->getFirstMessage()->message;
-                       $this->participantCanInvite = $this->conversation->participantCanInvite;
-                       $this->subject = $this->conversation->subject;
-                       
-                       if ($this->conversation->draftData) {
-                               $draftData = @unserialize($this->conversation->draftData);
-                               if (!empty($draftData['participants'])) {
-                                       foreach (UserProfile::getUserProfiles($draftData['participants']) as $user) {
-                                               if (!empty($this->participants)) $this->participants .= ', ';
-                                               $this->participants .= $user->username;
-                                       }
-                               }
-                               if (!empty($draftData['invisibleParticipants'])) {
-                                       foreach (UserProfile::getUserProfiles($draftData['invisibleParticipants']) as $user) {
-                                               if (!empty($this->invisibleParticipants)) $this->invisibleParticipants .= ', ';
-                                               $this->invisibleParticipants .= $user->username;
-                                       }
-                               }
-                       }
-               }
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function assignVariables() {
-               parent::assignVariables();
-               
-               WCF::getTPL()->assign([
-                       'conversationID' => $this->conversationID,
-                       'conversation' => $this->conversation,
-                       'action' => 'edit'
-               ]);
-       }
+class ConversationDraftEditForm extends ConversationAddForm
+{
+    /**
+     * @inheritDoc
+     */
+    public $templateName = 'conversationAdd';
+
+    /**
+     * conversation id
+     * @var integer
+     */
+    public $conversationID = 0;
+
+    /**
+     * conversation
+     * @var Conversation
+     */
+    public $conversation;
+
+    /**
+     * @inheritDoc
+     */
+    public function readParameters()
+    {
+        parent::readParameters();
+
+        if (isset($_REQUEST['id'])) {
+            $this->conversationID = \intval($_REQUEST['id']);
+        }
+        $this->conversation = new Conversation($this->conversationID);
+        if ($this->conversation->userID != WCF::getUser()->userID || !$this->conversation->isDraft) {
+            throw new IllegalLinkException();
+        }
+
+        $this->attachmentObjectID = $this->conversation->getFirstMessage()->messageID;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function save()
+    {
+        MessageForm::save();
+
+        // save message
+        $messageData = [
+            'data' => [],
+            'attachmentHandler' => $this->attachmentHandler,
+            'htmlInputProcessor' => $this->htmlInputProcessor,
+        ];
+        if (!$this->draft) {
+            // update timestamp
+            $messageData['data']['time'] = TIME_NOW;
+        }
+        $messageAction = new ConversationMessageAction(
+            [$this->conversation->getFirstMessage()],
+            'update',
+            $messageData
+        );
+        $messageAction->executeAction();
+
+        // save conversation
+        $data = \array_merge($this->additionalFields, [
+            'subject' => $this->subject,
+            'isDraft' => $this->draft ? 1 : 0,
+            'participantCanInvite' => $this->participantCanInvite,
+        ]);
+        if ($this->draft) {
+            $data['draftData'] = \serialize([
+                'participants' => $this->participantIDs,
+                'invisibleParticipants' => $this->invisibleParticipantIDs,
+            ]);
+        }
+        $conversationData = [
+            'data' => $data,
+        ];
+        if (!$this->draft) {
+            $conversationData['participants'] = $this->participantIDs;
+            $conversationData['invisibleParticipants'] = $this->invisibleParticipantIDs;
+            // update timestamp
+            $conversationData['data']['time'] = $conversationData['data']['lastPostTime'] = TIME_NOW;
+        }
+        $this->objectAction = new ConversationAction([$this->conversation], 'update', $conversationData);
+        $this->objectAction->executeAction();
+
+        MessageQuoteManager::getInstance()->saved();
+        $this->saved();
+
+        // forward
+        HeaderUtil::redirect($this->conversation->getLink());
+
+        exit;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function readData()
+    {
+        parent::readData();
+
+        if (empty($_POST)) {
+            $this->text = $this->conversation->getFirstMessage()->message;
+            $this->participantCanInvite = $this->conversation->participantCanInvite;
+            $this->subject = $this->conversation->subject;
+
+            if ($this->conversation->draftData) {
+                $draftData = @\unserialize($this->conversation->draftData);
+                if (!empty($draftData['participants'])) {
+                    foreach (UserProfile::getUserProfiles($draftData['participants']) as $user) {
+                        if (!empty($this->participants)) {
+                            $this->participants .= ', ';
+                        }
+                        $this->participants .= $user->username;
+                    }
+                }
+                if (!empty($draftData['invisibleParticipants'])) {
+                    foreach (UserProfile::getUserProfiles($draftData['invisibleParticipants']) as $user) {
+                        if (!empty($this->invisibleParticipants)) {
+                            $this->invisibleParticipants .= ', ';
+                        }
+                        $this->invisibleParticipants .= $user->username;
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function assignVariables()
+    {
+        parent::assignVariables();
+
+        WCF::getTPL()->assign([
+            'conversationID' => $this->conversationID,
+            'conversation' => $this->conversation,
+            'action' => 'edit',
+        ]);
+    }
 }
index 39c0d4c98558c219d3f4048324490ba1bbc2dd1d..1dde9a2e3960c88032043adb819e035a3bd30806 100644 (file)
@@ -1,35 +1,39 @@
 <?php
+
 namespace wcf\page;
+
 use wcf\data\conversation\FeedConversationList;
 use wcf\system\WCF;
 
 /**
  * Shows most recent conversations.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\Page
+ *
+ * @author  Alexander Ebert
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Page
  */
-class ConversationFeedPage extends AbstractFeedPage {
-       /**
-        * @inheritDoc
-        */
-       public $loginRequired = true;
-       
-       /**
-        * @inheritDoc
-        */
-       public function readData() {
-               parent::readData();
-               
-               $this->items = new FeedConversationList();
-               $this->items->getConditionBuilder()->add('conversation_to_user.participantID = ?', [WCF::getUser()->userID]);
-               $this->items->getConditionBuilder()->add('conversation_to_user.hideConversation = ?', [0]);
-               $this->items->sqlConditionJoins = "LEFT JOIN wcf".WCF_N."_conversation conversation ON (conversation.conversationID = conversation_to_user.conversationID)";
-               $this->items->sqlLimit = 20;
-               $this->items->readObjects();
-               
-               $this->title = WCF::getLanguage()->get('wcf.conversation.conversations');
-       }
+class ConversationFeedPage extends AbstractFeedPage
+{
+    /**
+     * @inheritDoc
+     */
+    public $loginRequired = true;
+
+    /**
+     * @inheritDoc
+     */
+    public function readData()
+    {
+        parent::readData();
+
+        $this->items = new FeedConversationList();
+        $this->items->getConditionBuilder()->add('conversation_to_user.participantID = ?', [WCF::getUser()->userID]);
+        $this->items->getConditionBuilder()->add('conversation_to_user.hideConversation = ?', [0]);
+        $this->items->sqlConditionJoins = "LEFT JOIN wcf" . WCF_N . "_conversation conversation ON (conversation.conversationID = conversation_to_user.conversationID)";
+        $this->items->sqlLimit = 20;
+        $this->items->readObjects();
+
+        $this->title = WCF::getLanguage()->get('wcf.conversation.conversations');
+    }
 }
index 59f7eb5e2a1d6a7c26c1850e2948890850e01a4d..a8713a95ef1f3d16ba492ce576701bf8d60bd506 100644 (file)
@@ -1,5 +1,7 @@
 <?php
+
 namespace wcf\page;
+
 use wcf\data\conversation\label\ConversationLabel;
 use wcf\data\conversation\label\ConversationLabelList;
 use wcf\data\conversation\UserConversationList;
@@ -12,176 +14,186 @@ use wcf\util\ArrayUtil;
 
 /**
  * Shows a list of conversations.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\Page
- * 
- * @property   UserConversationList    $objectList
+ *
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Page
+ *
+ * @property    UserConversationList $objectList
  */
-class ConversationListPage extends SortablePage {
-       /**
-        * @inheritDoc
-        */
-       public $defaultSortField = CONVERSATION_LIST_DEFAULT_SORT_FIELD;
-       
-       /**
-        * @inheritDoc
-        */
-       public $defaultSortOrder = CONVERSATION_LIST_DEFAULT_SORT_ORDER;
-       
-       /**
-        * @inheritDoc
-        */
-       public $validSortFields = ['subject', 'time', 'username', 'lastPostTime', 'replies', 'participants'];
-       
-       /**
-        * @inheritDoc
-        */
-       public $itemsPerPage = CONVERSATIONS_PER_PAGE;
-       
-       /**
-        * @inheritDoc
-        */
-       public $loginRequired = true;
-       
-       /**
-        * @inheritDoc
-        */
-       public $neededModules = ['MODULE_CONVERSATION'];
-       
-       /**
-        * @inheritDoc
-        */
-       public $neededPermissions = ['user.conversation.canUseConversation'];
-       
-       /**
-        * list filter
-        * @var string
-        */
-       public $filter = '';
-       
-       /**
-        * label id
-        * @var integer
-        */
-       public $labelID = 0;
-       
-       /**
-        * label list object
-        * @var ConversationLabelList
-        */
-       public $labelList;
-       
-       /**
-        * number of conversations (no filter)
-        * @var integer
-        */
-       public $conversationCount = 0;
-       
-       /**
-        * number of drafts
-        * @var integer
-        */
-       public $draftCount = 0;
-       
-       /**
-        * number of hidden conversations
-        * @var integer
-        */
-       public $hiddenCount = 0;
-       
-       /**
-        * number of sent conversations
-        * @var integer
-        */
-       public $outboxCount = 0;
-       
-       /**
-        * participant that
-        * @var string[]
-        */
-       public $participants = [];
-       
-       /**
-        * @inheritDoc
-        */
-       public function readParameters() {
-               parent::readParameters();
-               
-               if (isset($_REQUEST['filter'])) $this->filter = $_REQUEST['filter'];
-               if (!in_array($this->filter, UserConversationList::$availableFilters)) $this->filter = '';
-               
-               // user settings
-               /** @noinspection PhpUndefinedFieldInspection */
-               if (WCF::getUser()->conversationsPerPage) {
-                       /** @noinspection PhpUndefinedFieldInspection */
-                       $this->itemsPerPage = WCF::getUser()->conversationsPerPage;
-               }
-               
-               // labels
-               $this->labelList = ConversationLabel::getLabelsByUser();
-               if (isset($_REQUEST['labelID'])) {
-                       $this->labelID = intval($_REQUEST['labelID']);
-                       
-                       $validLabel = false;
-                       foreach ($this->labelList as $label) {
-                               if ($label->labelID == $this->labelID) {
-                                       $validLabel = true;
-                                       break;
-                               }
-                       }
-                       
-                       if (!$validLabel) {
-                               throw new IllegalLinkException();
-                       }
-               }
-               
-               if (isset($_REQUEST['participants'])) $this->participants = array_slice(ArrayUtil::trim(explode(',', $_REQUEST['participants'])), 0, 20);
-       }
-       
-       /** @noinspection PhpMissingParentCallCommonInspection */
-       /**
-        * @inheritDoc
-        */
-       protected function initObjectList() {
-               $this->objectList = new UserConversationList(WCF::getUser()->userID, $this->filter, $this->labelID);
-               $this->objectList->setLabelList($this->labelList);
-               
-               if (!empty($this->participants)) {
-                       // The column `conversation_to_user.username` has no index, causing full table scans when
-                       // trying to filter by it, therefore we'll read the user ids in advance.
-                       $conditions = new PreparedStatementConditionBuilder();
-                       $conditions->add('username IN (?)', [$this->participants]);
-                       $sql = "SELECT  userID
-                               FROM    wcf".WCF_N."_user
-                               ".$conditions;
-                       $statement = WCF::getDB()->prepareStatement($sql);
-                       $statement->execute($conditions->getParameters());
-                       $userIDs = [];
-                       while ($userID = $statement->fetchColumn()) {
-                               $userIDs[] = $userID;
-                       }
-                       
-                       if (!empty($userIDs)) {
-                               // The condition is split into two branches in order to account for invisible participants.
-                               // Invisible participants are only visible to the conversation starter and remain invisible
-                               // until the write their first message.
-                               //
-                               // We need to protect these users from being exposed as participants by including them for
-                               // any conversation that the current user has started. For all other conversations, users
-                               // flagged with `isInvisible = 0` must be excluded.
-                               //
-                               // See https://github.com/WoltLab/com.woltlab.wcf.conversation/issues/131
-                               $this->objectList->getConditionBuilder()->add('
+class ConversationListPage extends SortablePage
+{
+    /**
+     * @inheritDoc
+     */
+    public $defaultSortField = CONVERSATION_LIST_DEFAULT_SORT_FIELD;
+
+    /**
+     * @inheritDoc
+     */
+    public $defaultSortOrder = CONVERSATION_LIST_DEFAULT_SORT_ORDER;
+
+    /**
+     * @inheritDoc
+     */
+    public $validSortFields = ['subject', 'time', 'username', 'lastPostTime', 'replies', 'participants'];
+
+    /**
+     * @inheritDoc
+     */
+    public $itemsPerPage = CONVERSATIONS_PER_PAGE;
+
+    /**
+     * @inheritDoc
+     */
+    public $loginRequired = true;
+
+    /**
+     * @inheritDoc
+     */
+    public $neededModules = ['MODULE_CONVERSATION'];
+
+    /**
+     * @inheritDoc
+     */
+    public $neededPermissions = ['user.conversation.canUseConversation'];
+
+    /**
+     * list filter
+     * @var string
+     */
+    public $filter = '';
+
+    /**
+     * label id
+     * @var integer
+     */
+    public $labelID = 0;
+
+    /**
+     * label list object
+     * @var ConversationLabelList
+     */
+    public $labelList;
+
+    /**
+     * number of conversations (no filter)
+     * @var integer
+     */
+    public $conversationCount = 0;
+
+    /**
+     * number of drafts
+     * @var integer
+     */
+    public $draftCount = 0;
+
+    /**
+     * number of hidden conversations
+     * @var integer
+     */
+    public $hiddenCount = 0;
+
+    /**
+     * number of sent conversations
+     * @var integer
+     */
+    public $outboxCount = 0;
+
+    /**
+     * participant that
+     * @var string[]
+     */
+    public $participants = [];
+
+    /**
+     * @inheritDoc
+     */
+    public function readParameters()
+    {
+        parent::readParameters();
+
+        if (isset($_REQUEST['filter'])) {
+            $this->filter = $_REQUEST['filter'];
+        }
+        if (!\in_array($this->filter, UserConversationList::$availableFilters)) {
+            $this->filter = '';
+        }
+
+        // user settings
+        /** @noinspection PhpUndefinedFieldInspection */
+        if (WCF::getUser()->conversationsPerPage) {
+            /** @noinspection PhpUndefinedFieldInspection */
+            $this->itemsPerPage = WCF::getUser()->conversationsPerPage;
+        }
+
+        // labels
+        $this->labelList = ConversationLabel::getLabelsByUser();
+        if (isset($_REQUEST['labelID'])) {
+            $this->labelID = \intval($_REQUEST['labelID']);
+
+            $validLabel = false;
+            foreach ($this->labelList as $label) {
+                if ($label->labelID == $this->labelID) {
+                    $validLabel = true;
+                    break;
+                }
+            }
+
+            if (!$validLabel) {
+                throw new IllegalLinkException();
+            }
+        }
+
+        if (isset($_REQUEST['participants'])) {
+            $this->participants = \array_slice(ArrayUtil::trim(\explode(',', $_REQUEST['participants'])), 0, 20);
+        }
+    }
+
+    /** @noinspection PhpMissingParentCallCommonInspection */
+
+    /**
+     * @inheritDoc
+     */
+    protected function initObjectList()
+    {
+        $this->objectList = new UserConversationList(WCF::getUser()->userID, $this->filter, $this->labelID);
+        $this->objectList->setLabelList($this->labelList);
+
+        if (!empty($this->participants)) {
+            // The column `conversation_to_user.username` has no index, causing full table scans when
+            // trying to filter by it, therefore we'll read the user ids in advance.
+            $conditions = new PreparedStatementConditionBuilder();
+            $conditions->add('username IN (?)', [$this->participants]);
+            $sql = "SELECT  userID
+                               FROM    wcf" . WCF_N . "_user
+                               " . $conditions;
+            $statement = WCF::getDB()->prepareStatement($sql);
+            $statement->execute($conditions->getParameters());
+            $userIDs = [];
+            while ($userID = $statement->fetchColumn()) {
+                $userIDs[] = $userID;
+            }
+
+            if (!empty($userIDs)) {
+                // The condition is split into two branches in order to account for invisible participants.
+                // Invisible participants are only visible to the conversation starter and remain invisible
+                // until the write their first message.
+                //
+                // We need to protect these users from being exposed as participants by including them for
+                // any conversation that the current user has started. For all other conversations, users
+                // flagged with `isInvisible = 0` must be excluded.
+                //
+                // See https://github.com/WoltLab/com.woltlab.wcf.conversation/issues/131
+                $this->objectList->getConditionBuilder()->add('
                                        (
                                                (
                                                        conversation.userID = ?
                                                        AND
                                                        conversation.conversationID IN (
                                                                SELECT          conversationID
-                                                               FROM            wcf'.WCF_N.'_conversation_to_user
+                                                               FROM            wcf' . WCF_N . '_conversation_to_user
                                                                WHERE           participantID IN (?)
                                                                GROUP BY        conversationID
                                                                HAVING          COUNT(conversationID) = ?
@@ -193,7 +205,7 @@ class ConversationListPage extends SortablePage {
                                                        AND
                                                        conversation.conversationID IN (
                                                                SELECT          conversationID
-                                                               FROM            wcf'.WCF_N.'_conversation_to_user
+                                                               FROM            wcf' . WCF_N . '_conversation_to_user
                                                                WHERE           participantID IN (?)
                                                                                AND isInvisible = ?
                                                                GROUP BY        conversationID
@@ -201,99 +213,103 @@ class ConversationListPage extends SortablePage {
                                                        )
                                                )
                                        )', [
-                                       // Parameters for the first condition.
-                                       WCF::getUser()->userID,
-                                       $userIDs,
-                                       count($userIDs),
-                                       
-                                       // Parameters for the second condition.
-                                       WCF::getUser()->userID,
-                                       $userIDs,
-                                       0,
-                                       count($userIDs),
-                               ]);
-                       }
-               }
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function readData() {
-               // if sort field is `username`, `conversation.` has to prepended because `username`
-               // alone is ambiguous
-               if ($this->sortField === 'username') {
-                       $this->sortField = 'conversation.username';
-               }
-               
-               parent::readData();
-               
-               // change back to old value
-               if ($this->sortField === 'conversation.username') {
-                       $this->sortField = 'username';
-               }
-               
-               if ($this->filter != '') {
-                       // `-1` = pseudo object id to have to pages with identifier `com.woltlab.wcf.conversation.ConversationList`
-                       PageLocationManager::getInstance()->addParentLocation('com.woltlab.wcf.conversation.ConversationList', -1);
-               }
-               
-               // read stats
-               if (!$this->labelID && empty($this->participants)) {
-                       switch ($this->filter) {
-                               case '':
-                                       $this->conversationCount = $this->items;
-                               break;
-                               
-                               case 'draft':
-                                       $this->draftCount = $this->items;
-                               break;
-                               
-                               case 'hidden':
-                                       $this->hiddenCount = $this->items;
-                               break;
-                               
-                               case 'outbox':
-                                       $this->outboxCount = $this->items;
-                               break;
-                       }
-               }
-               
-               if ($this->filter != '' || $this->labelID || !empty($this->participants)) {
-                       $conversationList = new UserConversationList(WCF::getUser()->userID, '');
-                       $this->conversationCount = $conversationList->countObjects();
-               }
-               if ($this->filter != 'draft' || $this->labelID || !empty($this->participants)) {
-                       $conversationList = new UserConversationList(WCF::getUser()->userID, 'draft');
-                       $this->draftCount = $conversationList->countObjects();
-               }
-               if ($this->filter != 'hidden' || $this->labelID || !empty($this->participants)) {
-                       $conversationList = new UserConversationList(WCF::getUser()->userID, 'hidden');
-                       $this->hiddenCount = $conversationList->countObjects();
-               }
-               if ($this->filter != 'outbox' || $this->labelID || !empty($this->participants)) {
-                       $conversationList = new UserConversationList(WCF::getUser()->userID, 'outbox');
-                       $this->outboxCount = $conversationList->countObjects();
-               }
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function assignVariables() {
-               parent::assignVariables();
-               
-               WCF::getTPL()->assign([
-                       'filter' => $this->filter,
-                       'hasMarkedItems' => ClipboardHandler::getInstance()->hasMarkedItems(ClipboardHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.conversation.conversation')),
-                       'labelID' => $this->labelID,
-                       'labelList' => $this->labelList,
-                       'conversationCount' => $this->conversationCount,
-                       'draftCount' => $this->draftCount,
-                       'hiddenCount' => $this->hiddenCount,
-                       'outboxCount' => $this->outboxCount,
-                       'participants' => $this->participants,
-                       'validSortFields' => $this->validSortFields,
-               ]);
-       }
+                    // Parameters for the first condition.
+                    WCF::getUser()->userID,
+                    $userIDs,
+                    \count($userIDs),
+
+                    // Parameters for the second condition.
+                    WCF::getUser()->userID,
+                    $userIDs,
+                    0,
+                    \count($userIDs),
+                ]);
+            }
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function readData()
+    {
+        // if sort field is `username`, `conversation.` has to prepended because `username`
+        // alone is ambiguous
+        if ($this->sortField === 'username') {
+            $this->sortField = 'conversation.username';
+        }
+
+        parent::readData();
+
+        // change back to old value
+        if ($this->sortField === 'conversation.username') {
+            $this->sortField = 'username';
+        }
+
+        if ($this->filter != '') {
+            // `-1` = pseudo object id to have to pages with identifier `com.woltlab.wcf.conversation.ConversationList`
+            PageLocationManager::getInstance()->addParentLocation('com.woltlab.wcf.conversation.ConversationList', -1);
+        }
+
+        // read stats
+        if (!$this->labelID && empty($this->participants)) {
+            switch ($this->filter) {
+                case '':
+                    $this->conversationCount = $this->items;
+                    break;
+
+                case 'draft':
+                    $this->draftCount = $this->items;
+                    break;
+
+                case 'hidden':
+                    $this->hiddenCount = $this->items;
+                    break;
+
+                case 'outbox':
+                    $this->outboxCount = $this->items;
+                    break;
+            }
+        }
+
+        if ($this->filter != '' || $this->labelID || !empty($this->participants)) {
+            $conversationList = new UserConversationList(WCF::getUser()->userID, '');
+            $this->conversationCount = $conversationList->countObjects();
+        }
+        if ($this->filter != 'draft' || $this->labelID || !empty($this->participants)) {
+            $conversationList = new UserConversationList(WCF::getUser()->userID, 'draft');
+            $this->draftCount = $conversationList->countObjects();
+        }
+        if ($this->filter != 'hidden' || $this->labelID || !empty($this->participants)) {
+            $conversationList = new UserConversationList(WCF::getUser()->userID, 'hidden');
+            $this->hiddenCount = $conversationList->countObjects();
+        }
+        if ($this->filter != 'outbox' || $this->labelID || !empty($this->participants)) {
+            $conversationList = new UserConversationList(WCF::getUser()->userID, 'outbox');
+            $this->outboxCount = $conversationList->countObjects();
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function assignVariables()
+    {
+        parent::assignVariables();
+
+        WCF::getTPL()->assign([
+            'filter' => $this->filter,
+            'hasMarkedItems' => ClipboardHandler::getInstance()->hasMarkedItems(
+                ClipboardHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.conversation.conversation')
+            ),
+            'labelID' => $this->labelID,
+            'labelList' => $this->labelList,
+            'conversationCount' => $this->conversationCount,
+            'draftCount' => $this->draftCount,
+            'hiddenCount' => $this->hiddenCount,
+            'outboxCount' => $this->outboxCount,
+            'participants' => $this->participants,
+            'validSortFields' => $this->validSortFields,
+        ]);
+    }
 }
index e0a73afad8622d46f60cb4aba05daa13215b8a78..62d0ae8adee8b35f4abb3fb443a8813811e5903f 100644 (file)
@@ -1,12 +1,14 @@
 <?php
+
 namespace wcf\page;
+
+use wcf\data\conversation\Conversation;
+use wcf\data\conversation\ConversationAction;
+use wcf\data\conversation\ConversationParticipantList;
 use wcf\data\conversation\label\ConversationLabel;
 use wcf\data\conversation\label\ConversationLabelList;
 use wcf\data\conversation\message\ConversationMessage;
 use wcf\data\conversation\message\ViewableConversationMessageList;
-use wcf\data\conversation\Conversation;
-use wcf\data\conversation\ConversationAction;
-use wcf\data\conversation\ConversationParticipantList;
 use wcf\data\conversation\ViewableConversation;
 use wcf\data\modification\log\ConversationLogModificationLogList;
 use wcf\data\smiley\SmileyCache;
@@ -26,346 +28,403 @@ use wcf\util\StringUtil;
 
 /**
  * Shows a conversation.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\Page
- * 
- * @property   ViewableConversationMessageList         $objectList
+ *
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Page
+ *
+ * @property    ViewableConversationMessageList $objectList
  */
-class ConversationPage extends MultipleLinkPage {
-       /**
-        * @inheritDoc
-        */
-       public $itemsPerPage = CONVERSATION_MESSAGES_PER_PAGE;
-       
-       /**
-        * @inheritDoc
-        */
-       public $sortOrder = 'ASC';
-       
-       /**
-        * @inheritDoc
-        */
-       public $objectListClassName = ViewableConversationMessageList::class;
-       
-       /**
-        * @inheritDoc
-        */
-       public $loginRequired = true;
-       
-       /**
-        * @inheritDoc
-        */
-       public $neededModules = ['MODULE_CONVERSATION'];
-       
-       /**
-        * @inheritDoc
-        */
-       public $neededPermissions = ['user.conversation.canUseConversation'];
-       
-       /**
-        * conversation id
-        * @var integer
-        */
-       public $conversationID = 0;
-       
-       /**
-        * viewable conversation object
-        * @var ViewableConversation
-        */
-       public $conversation;
-       
-       /**
-        * conversation label list
-        * @var ConversationLabelList
-        */
-       public $labelList;
-       
-       /**
-        * message id
-        * @var integer
-        */
-       public $messageID = 0;
-       
-       /**
-        * conversation message object
-        * @var ConversationMessage
-        */
-       public $message;
-       
-       /**
-        * modification log list object
-        * @var ConversationLogModificationLogList
-        */
-       public $modificationLogList;
-       
-       /**
-        * list of participants
-        * @var ConversationParticipantList
-        */
-       public $participantList;
-       
-       /**
-        * @inheritDoc
-        */
-       public function readParameters() {
-               parent::readParameters();
-               
-               if (isset($_REQUEST['id'])) $this->conversationID = intval($_REQUEST['id']);
-               if (isset($_REQUEST['messageID'])) $this->messageID = intval($_REQUEST['messageID']);
-               if ($this->messageID) {
-                       $this->message = new ConversationMessage($this->messageID);
-                       if (!$this->message->messageID) {
-                               throw new IllegalLinkException();
-                       }
-                       $this->conversationID = $this->message->conversationID;
-               }
-               
-               $this->conversation = Conversation::getUserConversation($this->conversationID, WCF::getUser()->userID);
-               if ($this->conversation === null) {
-                       throw new IllegalLinkException();
-               }
-               if (!$this->conversation->canRead()) {
-                       throw new PermissionDeniedException();
-               }
-               
-               // load labels
-               $this->labelList = ConversationLabel::getLabelsByUser();
-               $this->conversation = ViewableConversation::getViewableConversation($this->conversation, $this->labelList);
-               
-               // messages per page
-               /** @noinspection PhpUndefinedFieldInspection */
-               if (WCF::getUser()->conversationMessagesPerPage) {
-                       /** @noinspection PhpUndefinedFieldInspection */
-                       $this->itemsPerPage = WCF::getUser()->conversationMessagesPerPage;
-               }
-               
-               $this->canonicalURL = LinkHandler::getInstance()->getLink('Conversation', [
-                       'object' => $this->conversation
-               ], ($this->pageNo ? 'pageNo=' . $this->pageNo : ''));
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       protected function initObjectList() {
-               parent::initObjectList();
-               
-               $this->objectList->getConditionBuilder()->add('conversation_message.conversationID = ?', [$this->conversation->conversationID]);
-               $this->objectList->setConversation($this->conversation->getDecoratedObject());
-               
-               // handle visibility filter
-               if ($this->conversation->joinedAt > 0) $this->objectList->getConditionBuilder()->add('conversation_message.time >= ?', [$this->conversation->joinedAt]);
-               if ($this->conversation->leftAt > 0) $this->objectList->getConditionBuilder()->add('conversation_message.time <= ?', [$this->conversation->leftAt]);
-               
-               // handle jump to
-               if ($this->action == 'lastPost') $this->goToLastPost();
-               if ($this->action == 'firstNew') $this->goToFirstNewPost();
-               if ($this->messageID) $this->goToPost();
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function readData() {
-               parent::readData();
-               
-               // add breadcrumbs
-               if ($this->conversation->isDraft) {
-                       // `-1` = pseudo object id to have to pages with identifier `com.woltlab.wcf.conversation.ConversationList`
-                       PageLocationManager::getInstance()->addParentLocation('com.woltlab.wcf.conversation.ConversationList', -1, new ParentPageLocation(
-                               WCF::getLanguage()->get('wcf.conversation.folder.draft'),
-                               LinkHandler::getInstance()->getLink('ConversationList', ['filter' => 'draft'])
-                       ));
-               }
-               PageLocationManager::getInstance()->addParentLocation('com.woltlab.wcf.conversation.ConversationList');
-               
-               // update last visit time count
-               if (
-                       $this->conversation->isNew()
-                       && (
-                               $this->objectList->getMaxPostTime() > $this->conversation->lastVisitTime
-                               || ($this->conversation->joinedAt && !count($this->objectList))
-                       )
-               ) {
-                       $visitTime = $this->objectList->getMaxPostTime();
-                       if ($visitTime == $this->conversation->lastPostTime) $visitTime = TIME_NOW;
-                       $conversationAction = new ConversationAction([$this->conversation->getDecoratedObject()], 'markAsRead', ['visitTime' => $visitTime]);
-                       $conversationAction->executeAction();
-               }
-               
-               // get participants
-               $this->participantList = new ConversationParticipantList($this->conversationID, WCF::getUser()->userID, $this->conversation->userID == WCF::getUser()->userID);
-               $this->participantList->readObjects();
-               
-               // init quote objects
-               $messageIDs = [];
-               foreach ($this->objectList as $message) {
-                       $messageIDs[] = $message->messageID;
-               }
-               MessageQuoteManager::getInstance()->initObjects('com.woltlab.wcf.conversation.message', $messageIDs);
-               
-               $userIDs = [];
-               foreach ($this->objectList as $message) {
-                       if ($message->userID) {
-                               $userIDs[] = $message->userID;
-                       }
-               }
-               
-               // fetch special trophies
-               if (MODULE_TROPHY) {
-                       if (!empty($userIDs)) {
-                               UserProfile::prepareSpecialTrophies(array_unique($userIDs));
-                       }
-               }
-               
-               if (MODULE_USER_SIGNATURE) {
-                       if (!empty($userIDs)) {
-                               SignatureCache::getInstance()->cacheUserSignature($userIDs);
-                       }
-               }
-               
-               // set attachment permissions
-               if ($this->objectList->getAttachmentList() !== null) {
-                       $this->objectList->getAttachmentList()->setPermissions([
-                               'canDownload' => true,
-                               'canViewPreview' => true
-                       ]);
-               }
-               
-               // get timeframe for modifications
-               $this->objectList->rewind();
-               $startTime = ($this->conversation->joinedAt ?: $this->objectList->current()->time);
-               $endTime = ($this->conversation->leftAt ?: TIME_NOW);
-               
-               $count = count($this->objectList);
-               if ($count > 1) {
-                       $this->objectList->seek($count - 1);
-                       if ($this->objectList->current()->time < $this->conversation->lastPostTime) {
-                               $sql = "SELECT          time
-                                       FROM            wcf".WCF_N."_conversation_message
+class ConversationPage extends MultipleLinkPage
+{
+    /**
+     * @inheritDoc
+     */
+    public $itemsPerPage = CONVERSATION_MESSAGES_PER_PAGE;
+
+    /**
+     * @inheritDoc
+     */
+    public $sortOrder = 'ASC';
+
+    /**
+     * @inheritDoc
+     */
+    public $objectListClassName = ViewableConversationMessageList::class;
+
+    /**
+     * @inheritDoc
+     */
+    public $loginRequired = true;
+
+    /**
+     * @inheritDoc
+     */
+    public $neededModules = ['MODULE_CONVERSATION'];
+
+    /**
+     * @inheritDoc
+     */
+    public $neededPermissions = ['user.conversation.canUseConversation'];
+
+    /**
+     * conversation id
+     * @var integer
+     */
+    public $conversationID = 0;
+
+    /**
+     * viewable conversation object
+     * @var ViewableConversation
+     */
+    public $conversation;
+
+    /**
+     * conversation label list
+     * @var ConversationLabelList
+     */
+    public $labelList;
+
+    /**
+     * message id
+     * @var integer
+     */
+    public $messageID = 0;
+
+    /**
+     * conversation message object
+     * @var ConversationMessage
+     */
+    public $message;
+
+    /**
+     * modification log list object
+     * @var ConversationLogModificationLogList
+     */
+    public $modificationLogList;
+
+    /**
+     * list of participants
+     * @var ConversationParticipantList
+     */
+    public $participantList;
+
+    /**
+     * @inheritDoc
+     */
+    public function readParameters()
+    {
+        parent::readParameters();
+
+        if (isset($_REQUEST['id'])) {
+            $this->conversationID = \intval($_REQUEST['id']);
+        }
+        if (isset($_REQUEST['messageID'])) {
+            $this->messageID = \intval($_REQUEST['messageID']);
+        }
+        if ($this->messageID) {
+            $this->message = new ConversationMessage($this->messageID);
+            if (!$this->message->messageID) {
+                throw new IllegalLinkException();
+            }
+            $this->conversationID = $this->message->conversationID;
+        }
+
+        $this->conversation = Conversation::getUserConversation($this->conversationID, WCF::getUser()->userID);
+        if ($this->conversation === null) {
+            throw new IllegalLinkException();
+        }
+        if (!$this->conversation->canRead()) {
+            throw new PermissionDeniedException();
+        }
+
+        // load labels
+        $this->labelList = ConversationLabel::getLabelsByUser();
+        $this->conversation = ViewableConversation::getViewableConversation($this->conversation, $this->labelList);
+
+        // messages per page
+        /** @noinspection PhpUndefinedFieldInspection */
+        if (WCF::getUser()->conversationMessagesPerPage) {
+            /** @noinspection PhpUndefinedFieldInspection */
+            $this->itemsPerPage = WCF::getUser()->conversationMessagesPerPage;
+        }
+
+        $this->canonicalURL = LinkHandler::getInstance()->getLink('Conversation', [
+            'object' => $this->conversation,
+        ], ($this->pageNo ? 'pageNo=' . $this->pageNo : ''));
+    }
+
+    /**
+     * @inheritDoc
+     */
+    protected function initObjectList()
+    {
+        parent::initObjectList();
+
+        $this->objectList->getConditionBuilder()
+            ->add('conversation_message.conversationID = ?', [$this->conversation->conversationID]);
+        $this->objectList->setConversation($this->conversation->getDecoratedObject());
+
+        // handle visibility filter
+        if ($this->conversation->joinedAt > 0) {
+            $this->objectList->getConditionBuilder()
+                ->add('conversation_message.time >= ?', [$this->conversation->joinedAt]);
+        }
+        if ($this->conversation->leftAt > 0) {
+            $this->objectList->getConditionBuilder()
+                ->add('conversation_message.time <= ?', [$this->conversation->leftAt]);
+        }
+
+        // handle jump to
+        if ($this->action == 'lastPost') {
+            $this->goToLastPost();
+        }
+        if ($this->action == 'firstNew') {
+            $this->goToFirstNewPost();
+        }
+        if ($this->messageID) {
+            $this->goToPost();
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function readData()
+    {
+        parent::readData();
+
+        // add breadcrumbs
+        if ($this->conversation->isDraft) {
+            // `-1` = pseudo object id to have to pages with identifier `com.woltlab.wcf.conversation.ConversationList`
+            PageLocationManager::getInstance()->addParentLocation(
+                'com.woltlab.wcf.conversation.ConversationList',
+                -1,
+                new ParentPageLocation(
+                    WCF::getLanguage()->get('wcf.conversation.folder.draft'),
+                    LinkHandler::getInstance()->getLink('ConversationList', ['filter' => 'draft'])
+                )
+            );
+        }
+        PageLocationManager::getInstance()->addParentLocation('com.woltlab.wcf.conversation.ConversationList');
+
+        // update last visit time count
+        if (
+            $this->conversation->isNew()
+            && (
+                $this->objectList->getMaxPostTime() > $this->conversation->lastVisitTime
+                || ($this->conversation->joinedAt && !\count($this->objectList))
+            )
+        ) {
+            $visitTime = $this->objectList->getMaxPostTime();
+            if ($visitTime == $this->conversation->lastPostTime) {
+                $visitTime = TIME_NOW;
+            }
+            $conversationAction = new ConversationAction(
+                [$this->conversation->getDecoratedObject()],
+                'markAsRead',
+                ['visitTime' => $visitTime]
+            );
+            $conversationAction->executeAction();
+        }
+
+        // get participants
+        $this->participantList = new ConversationParticipantList(
+            $this->conversationID,
+            WCF::getUser()->userID,
+            $this->conversation->userID == WCF::getUser()->userID
+        );
+        $this->participantList->readObjects();
+
+        // init quote objects
+        $messageIDs = [];
+        foreach ($this->objectList as $message) {
+            $messageIDs[] = $message->messageID;
+        }
+        MessageQuoteManager::getInstance()->initObjects('com.woltlab.wcf.conversation.message', $messageIDs);
+
+        $userIDs = [];
+        foreach ($this->objectList as $message) {
+            if ($message->userID) {
+                $userIDs[] = $message->userID;
+            }
+        }
+
+        // fetch special trophies
+        if (MODULE_TROPHY) {
+            if (!empty($userIDs)) {
+                UserProfile::prepareSpecialTrophies(\array_unique($userIDs));
+            }
+        }
+
+        if (MODULE_USER_SIGNATURE) {
+            if (!empty($userIDs)) {
+                SignatureCache::getInstance()->cacheUserSignature($userIDs);
+            }
+        }
+
+        // set attachment permissions
+        if ($this->objectList->getAttachmentList() !== null) {
+            $this->objectList->getAttachmentList()->setPermissions([
+                'canDownload' => true,
+                'canViewPreview' => true,
+            ]);
+        }
+
+        // get timeframe for modifications
+        $this->objectList->rewind();
+        $startTime = ($this->conversation->joinedAt ?: $this->objectList->current()->time);
+        $endTime = ($this->conversation->leftAt ?: TIME_NOW);
+
+        $count = \count($this->objectList);
+        if ($count > 1) {
+            $this->objectList->seek($count - 1);
+            if ($this->objectList->current()->time < $this->conversation->lastPostTime) {
+                $sql = "SELECT          time
+                                       FROM            wcf" . WCF_N . "_conversation_message
                                        WHERE           conversationID = ?
                                                        AND time > ?
                                        ORDER BY        time";
-                               $statement = WCF::getDB()->prepareStatement($sql, 1);
-                               $statement->execute([$this->conversationID, $this->objectList->current()->time]);
-                               $endTime = $statement->fetchSingleColumn() - 1;
-                       }
-               }
-               $this->objectList->rewind();
-               
-               // get invisible participants
-               $invisibleParticipantIDs = [];
-               if (WCF::getUser()->userID != $this->conversation->userID) {
-                       foreach ($this->participantList as $participant) {
-                               if ($participant->isInvisible) {
-                                       $invisibleParticipantIDs[] = $participant->userID;
-                               }
-                       }
-               }
-               
-               // load modification log entries
-               $this->modificationLogList = new ConversationLogModificationLogList($this->conversation->conversationID);
-               $this->modificationLogList->getConditionBuilder()->add("modification_log.time BETWEEN ? AND ?", [$startTime, $endTime]);
-               
-               if (!empty($invisibleParticipantIDs)) {
-                       $this->modificationLogList->getConditionBuilder()->add("(modification_log.action <> ? OR modification_log.userID NOT IN (?))", ['leave', $invisibleParticipantIDs]);
-               }
-               
-               $this->modificationLogList->readObjects();
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function assignVariables() {
-               parent::assignVariables();
-               
-               MessageQuoteManager::getInstance()->assignVariables();
-               
-               $tmpHash = StringUtil::getRandomID();
-               $attachmentHandler = new AttachmentHandler('com.woltlab.wcf.conversation.message', 0, $tmpHash, 0);
-               
-               WCF::getTPL()->assign([
-                       'attachmentHandler' => $attachmentHandler,
-                       'attachmentObjectID' => 0,
-                       'attachmentObjectType' => 'com.woltlab.wcf.conversation.message',
-                       'attachmentParentObjectID' => 0,
-                       'tmpHash' => $tmpHash,
-                       'attachmentList' => $this->objectList->getAttachmentList(),
-                       'labelList' => $this->labelList,
-                       'modificationLogList' => $this->modificationLogList,
-                       'sortOrder' => $this->sortOrder,
-                       'conversation' => $this->conversation,
-                       'conversationID' => $this->conversationID,
-                       'participants' => $this->participantList->getObjects(),
-                       'defaultSmilies' => SmileyCache::getInstance()->getCategorySmilies()
-               ]);
-               
-               BBCodeHandler::getInstance()->setDisallowedBBCodes(explode(',', WCF::getSession()->getPermission('user.message.disallowedBBCodes')));
-       }
-       
-       /**
-        * Calculates the position of a specific post in this conversation.
-        */
-       protected function goToPost() {
-               $conditionBuilder = clone $this->objectList->getConditionBuilder();
-               $conditionBuilder->add('time '.($this->sortOrder == 'ASC' ? '<=' : '>=').' ?', [$this->message->time]);
-               
-               $sql = "SELECT  COUNT(*) AS messages
-                       FROM    wcf".WCF_N."_conversation_message conversation_message
-                       ".$conditionBuilder;
-               $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute($conditionBuilder->getParameters());
-               $row = $statement->fetchArray();
-               $this->pageNo = intval(ceil($row['messages'] / $this->itemsPerPage));
-       }
-       
-       /**
-        * Gets the id of the last post in this conversation and forwards the user to this post.
-        */
-       protected function goToLastPost() {
-               $sql = "SELECT          conversation_message.messageID
-                       FROM            wcf".WCF_N."_conversation_message conversation_message
-                       ".$this->objectList->getConditionBuilder()."
-                       ORDER BY        time ".($this->sortOrder == 'ASC' ? 'DESC' : 'ASC');
-               $statement = WCF::getDB()->prepareStatement($sql, 1);
-               $statement->execute($this->objectList->getConditionBuilder()->getParameters());
-               $row = $statement->fetchArray();
-               HeaderUtil::redirect(LinkHandler::getInstance()->getLink('Conversation', [
-                       'encodeTitle' => true,
-                       'object' => $this->conversation,
-                       'messageID' => $row['messageID']
-               ]).'#message'.$row['messageID']);
-               exit;
-       }
-       
-       /**
-        * Forwards the user to the first new message in this conversation.
-        */
-       protected function goToFirstNewPost() {
-               $conditionBuilder = clone $this->objectList->getConditionBuilder();
-               $conditionBuilder->add('time > ?', [$this->conversation->lastVisitTime]);
-               
-               $sql = "SELECT          conversation_message.messageID
-                       FROM            wcf".WCF_N."_conversation_message conversation_message
-                       ".$conditionBuilder."
+                $statement = WCF::getDB()->prepareStatement($sql, 1);
+                $statement->execute([$this->conversationID, $this->objectList->current()->time]);
+                $endTime = $statement->fetchSingleColumn() - 1;
+            }
+        }
+        $this->objectList->rewind();
+
+        // get invisible participants
+        $invisibleParticipantIDs = [];
+        if (WCF::getUser()->userID != $this->conversation->userID) {
+            foreach ($this->participantList as $participant) {
+                if ($participant->isInvisible) {
+                    $invisibleParticipantIDs[] = $participant->userID;
+                }
+            }
+        }
+
+        // load modification log entries
+        $this->modificationLogList = new ConversationLogModificationLogList($this->conversation->conversationID);
+        $this->modificationLogList->getConditionBuilder()
+            ->add("modification_log.time BETWEEN ? AND ?", [$startTime, $endTime]);
+
+        if (!empty($invisibleParticipantIDs)) {
+            $this->modificationLogList->getConditionBuilder()->add(
+                "(modification_log.action <> ? OR modification_log.userID NOT IN (?))",
+                ['leave', $invisibleParticipantIDs]
+            );
+        }
+
+        $this->modificationLogList->readObjects();
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function assignVariables()
+    {
+        parent::assignVariables();
+
+        MessageQuoteManager::getInstance()->assignVariables();
+
+        $tmpHash = StringUtil::getRandomID();
+        $attachmentHandler = new AttachmentHandler('com.woltlab.wcf.conversation.message', 0, $tmpHash, 0);
+
+        WCF::getTPL()->assign([
+            'attachmentHandler' => $attachmentHandler,
+            'attachmentObjectID' => 0,
+            'attachmentObjectType' => 'com.woltlab.wcf.conversation.message',
+            'attachmentParentObjectID' => 0,
+            'tmpHash' => $tmpHash,
+            'attachmentList' => $this->objectList->getAttachmentList(),
+            'labelList' => $this->labelList,
+            'modificationLogList' => $this->modificationLogList,
+            'sortOrder' => $this->sortOrder,
+            'conversation' => $this->conversation,
+            'conversationID' => $this->conversationID,
+            'participants' => $this->participantList->getObjects(),
+            'defaultSmilies' => SmileyCache::getInstance()->getCategorySmilies(),
+        ]);
+
+        BBCodeHandler::getInstance()->setDisallowedBBCodes(\explode(
+            ',',
+            WCF::getSession()->getPermission('user.message.disallowedBBCodes')
+        ));
+    }
+
+    /**
+     * Calculates the position of a specific post in this conversation.
+     */
+    protected function goToPost()
+    {
+        $conditionBuilder = clone $this->objectList->getConditionBuilder();
+        $conditionBuilder->add('time ' . ($this->sortOrder == 'ASC' ? '<=' : '>=') . ' ?', [$this->message->time]);
+
+        $sql = "SELECT COUNT(*) AS messages
+                       FROM    wcf" . WCF_N . "_conversation_message conversation_message
+                       " . $conditionBuilder;
+        $statement = WCF::getDB()->prepareStatement($sql);
+        $statement->execute($conditionBuilder->getParameters());
+        $row = $statement->fetchArray();
+        $this->pageNo = \intval(\ceil($row['messages'] / $this->itemsPerPage));
+    }
+
+    /**
+     * Gets the id of the last post in this conversation and forwards the user to this post.
+     */
+    protected function goToLastPost()
+    {
+        $sql = "SELECT         conversation_message.messageID
+                       FROM            wcf" . WCF_N . "_conversation_message conversation_message
+                       " . $this->objectList->getConditionBuilder() . "
+                       ORDER BY        time " . ($this->sortOrder == 'ASC' ? 'DESC' : 'ASC');
+        $statement = WCF::getDB()->prepareStatement($sql, 1);
+        $statement->execute($this->objectList->getConditionBuilder()->getParameters());
+        $row = $statement->fetchArray();
+        HeaderUtil::redirect(
+            LinkHandler::getInstance()->getLink(
+                'Conversation',
+                [
+                    'encodeTitle' => true,
+                    'object' => $this->conversation,
+                    'messageID' => $row['messageID'],
+                ]
+            ) . '#message' . $row['messageID']
+        );
+
+        exit;
+    }
+
+    /**
+     * Forwards the user to the first new message in this conversation.
+     */
+    protected function goToFirstNewPost()
+    {
+        $conditionBuilder = clone $this->objectList->getConditionBuilder();
+        $conditionBuilder->add('time > ?', [$this->conversation->lastVisitTime]);
+
+        $sql = "SELECT         conversation_message.messageID
+                       FROM            wcf" . WCF_N . "_conversation_message conversation_message
+                       " . $conditionBuilder . "
                        ORDER BY        time ASC";
-               $statement = WCF::getDB()->prepareStatement($sql, 1);
-               $statement->execute($conditionBuilder->getParameters());
-               $row = $statement->fetchArray();
-               if ($row !== false) {
-                       HeaderUtil::redirect(LinkHandler::getInstance()->getLink('Conversation', [
-                               'encodeTitle' => true,
-                               'object' => $this->conversation,
-                               'messageID' => $row['messageID']
-                       ]).'#message'.$row['messageID']);
-                       exit;
-               }
-               else {
-                       $this->goToLastPost();
-               }
-       }
+        $statement = WCF::getDB()->prepareStatement($sql, 1);
+        $statement->execute($conditionBuilder->getParameters());
+        $row = $statement->fetchArray();
+        if ($row !== false) {
+            HeaderUtil::redirect(
+                LinkHandler::getInstance()->getLink(
+                    'Conversation',
+                    [
+                        'encodeTitle' => true,
+                        'object' => $this->conversation,
+                        'messageID' => $row['messageID'],
+                    ]
+                ) . '#message' . $row['messageID']
+            );
+
+            exit;
+        } else {
+            $this->goToLastPost();
+        }
+    }
 }
index 6bdaec1ea61f8aa4b6bed6ba86670387a6bd3ccf..99cf9d07d87805b683c3affd79f2192e93f00031 100644 (file)
 <?php
+
 namespace wcf\system\attachment;
+
+use wcf\data\conversation\Conversation;
 use wcf\data\conversation\message\ConversationMessage;
 use wcf\data\conversation\message\ConversationMessageList;
-use wcf\data\conversation\Conversation;
 use wcf\system\WCF;
 use wcf\util\ArrayUtil;
 
 /**
  * Attachment object type implementation for conversations.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\System\Attachment
- * 
- * @method     ConversationMessage     getObject($objectID)
+ *
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Attachment
+ *
+ * @method  ConversationMessage getObject($objectID)
  */
-class ConversationMessageAttachmentObjectType extends AbstractAttachmentObjectType {
-       /** @noinspection PhpMissingParentCallCommonInspection */
-       /**
-        * @inheritDoc
-        */
-       public function getMaxSize() {
-               return WCF::getSession()->getPermission('user.conversation.maxAttachmentSize');
-       }
-       
-       /** @noinspection PhpMissingParentCallCommonInspection */
-       /**
-        * @inheritDoc
-        */
-       public function getAllowedExtensions() {
-               return ArrayUtil::trim(explode("\n", WCF::getSession()->getPermission('user.conversation.allowedAttachmentExtensions')));
-       }
-       
-       /** @noinspection PhpMissingParentCallCommonInspection */
-       /**
-        * @inheritDoc
-        */
-       public function getMaxCount() {
-               return WCF::getSession()->getPermission('user.conversation.maxAttachmentCount');
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function canDownload($objectID) {
-               if ($objectID) {
-                       $message = new ConversationMessage($objectID);
-                       $conversation = Conversation::getUserConversation($message->conversationID, WCF::getUser()->userID);
-                       if ($conversation->canRead()) return true;
-               }
-               
-               return false;
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function canUpload($objectID, $parentObjectID = 0) {
-               if (!WCF::getSession()->getPermission('user.conversation.canUploadAttachment')) {
-                       return false;
-               }
-               
-               if ($objectID) {
-                       $message = new ConversationMessage($objectID);
-                       if ($message->userID == WCF::getUser()->userID) return true;
-                       return false;
-               }
-               
-               return true;
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function canDelete($objectID) {
-               if ($objectID) {
-                       $message = new ConversationMessage($objectID);
-                       if ($message->userID == WCF::getUser()->userID) return true;
-               }
-               
-               return false;
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function cacheObjects(array $objectIDs) {
-               $messageList = new ConversationMessageList();
-               $messageList->setObjectIDs($objectIDs);
-               $messageList->readObjects();
-               $conversationIDs = [];
-               foreach ($messageList as $message) {
-                       $conversationIDs[] = $message->conversationID;
-               }
-               if (!empty($conversationIDs)) {
-                       $conversations = Conversation::getUserConversations($conversationIDs, WCF::getUser()->userID);
-                       foreach ($messageList as $message) {
-                               if (isset($conversations[$message->conversationID])) $message->setConversation($conversations[$message->conversationID]);
-                       }
-               }
-               
-               foreach ($messageList->getObjects() as $objectID => $object) {
-                       $this->cachedObjects[$objectID] = $object;
-               }
-       }
-       
-       /** @noinspection PhpMissingParentCallCommonInspection */
-       /**
-        * @inheritDoc
-        */
-       public function setPermissions(array $attachments) {
-               $messageIDs = [];
-               foreach ($attachments as $attachment) {
-                       // set default permissions
-                       $attachment->setPermissions([
-                               'canDownload' => false,
-                               'canViewPreview' => false
-                       ]);
-                       
-                       if ($this->getObject($attachment->objectID) === null) {
-                               $messageIDs[] = $attachment->objectID;
-                       }
-               }
-               
-               if (!empty($messageIDs)) {
-                       $this->cacheObjects($messageIDs);
-               }
-               
-               foreach ($attachments as $attachment) {
-                       if (($message = $this->getObject($attachment->objectID)) !== null) {
-                               if (!$message->getConversation()->canRead()) continue;
-                               
-                               $attachment->setPermissions([
-                                       'canDownload' => true,
-                                       'canViewPreview' => true
-                               ]);
-                       }
-                       else if ($attachment->tmpHash != '' && $attachment->userID == WCF::getUser()->userID) {
-                               $attachment->setPermissions([
-                                       'canDownload' => true,
-                                       'canViewPreview' => true
-                               ]);
-                       }
-               }
-       }
+class ConversationMessageAttachmentObjectType extends AbstractAttachmentObjectType
+{
+    /** @noinspection PhpMissingParentCallCommonInspection */
+
+    /**
+     * @inheritDoc
+     */
+    public function getMaxSize()
+    {
+        return WCF::getSession()->getPermission('user.conversation.maxAttachmentSize');
+    }
+
+    /** @noinspection PhpMissingParentCallCommonInspection */
+
+    /**
+     * @inheritDoc
+     */
+    public function getAllowedExtensions()
+    {
+        return ArrayUtil::trim(\explode(
+            "\n",
+            WCF::getSession()->getPermission('user.conversation.allowedAttachmentExtensions')
+        ));
+    }
+
+    /** @noinspection PhpMissingParentCallCommonInspection */
+
+    /**
+     * @inheritDoc
+     */
+    public function getMaxCount()
+    {
+        return WCF::getSession()->getPermission('user.conversation.maxAttachmentCount');
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function canDownload($objectID)
+    {
+        if ($objectID) {
+            $message = new ConversationMessage($objectID);
+            $conversation = Conversation::getUserConversation($message->conversationID, WCF::getUser()->userID);
+            if ($conversation->canRead()) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function canUpload($objectID, $parentObjectID = 0)
+    {
+        if (!WCF::getSession()->getPermission('user.conversation.canUploadAttachment')) {
+            return false;
+        }
+
+        if ($objectID) {
+            $message = new ConversationMessage($objectID);
+            if ($message->userID == WCF::getUser()->userID) {
+                return true;
+            }
+
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function canDelete($objectID)
+    {
+        if ($objectID) {
+            $message = new ConversationMessage($objectID);
+            if ($message->userID == WCF::getUser()->userID) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function cacheObjects(array $objectIDs)
+    {
+        $messageList = new ConversationMessageList();
+        $messageList->setObjectIDs($objectIDs);
+        $messageList->readObjects();
+        $conversationIDs = [];
+        foreach ($messageList as $message) {
+            $conversationIDs[] = $message->conversationID;
+        }
+        if (!empty($conversationIDs)) {
+            $conversations = Conversation::getUserConversations($conversationIDs, WCF::getUser()->userID);
+            foreach ($messageList as $message) {
+                if (isset($conversations[$message->conversationID])) {
+                    $message->setConversation($conversations[$message->conversationID]);
+                }
+            }
+        }
+
+        foreach ($messageList->getObjects() as $objectID => $object) {
+            $this->cachedObjects[$objectID] = $object;
+        }
+    }
+
+    /** @noinspection PhpMissingParentCallCommonInspection */
+
+    /**
+     * @inheritDoc
+     */
+    public function setPermissions(array $attachments)
+    {
+        $messageIDs = [];
+        foreach ($attachments as $attachment) {
+            // set default permissions
+            $attachment->setPermissions([
+                'canDownload' => false,
+                'canViewPreview' => false,
+            ]);
+
+            if ($this->getObject($attachment->objectID) === null) {
+                $messageIDs[] = $attachment->objectID;
+            }
+        }
+
+        if (!empty($messageIDs)) {
+            $this->cacheObjects($messageIDs);
+        }
+
+        foreach ($attachments as $attachment) {
+            if (($message = $this->getObject($attachment->objectID)) !== null) {
+                if (!$message->getConversation()->canRead()) {
+                    continue;
+                }
+
+                $attachment->setPermissions([
+                    'canDownload' => true,
+                    'canViewPreview' => true,
+                ]);
+            } elseif ($attachment->tmpHash != '' && $attachment->userID == WCF::getUser()->userID) {
+                $attachment->setPermissions([
+                    'canDownload' => true,
+                    'canViewPreview' => true,
+                ]);
+            }
+        }
+    }
 }
index ac0ebacaea77223646413b4af7c863652682ea50..91a3e8cad2b592afe9f3c10126037fb130c2dc6c 100644 (file)
@@ -1,24 +1,27 @@
 <?php
+
 namespace wcf\system\cache\runtime;
+
 use wcf\data\conversation\Conversation;
 use wcf\data\conversation\ConversationList;
 
 /**
  * Runtime cache implementation for conversations.
  *
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\System\Cache\Runtime
- * @since      3.0
- * 
- * @method     Conversation[]          getCachedObjects()
- * @method     Conversation            getObject($objectID)
- * @method     Conversation[]          getObjects(array $objectIDs)
+ * @author  Matthias Schmidt
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Cache\Runtime
+ * @since   3.0
+ *
+ * @method  Conversation[]      getCachedObjects()
+ * @method  Conversation        getObject($objectID)
+ * @method  Conversation[]      getObjects(array $objectIDs)
  */
-class ConversationRuntimeCache extends AbstractRuntimeCache {
-       /**
-        * @inheritDoc
-        */
-       protected $listClassName = ConversationList::class;
+class ConversationRuntimeCache extends AbstractRuntimeCache
+{
+    /**
+     * @inheritDoc
+     */
+    protected $listClassName = ConversationList::class;
 }
index 85fe28b4be072af7a17792d445aaad11d228e355..690a66d5f042370e570fab4f61a4639f34bcde68 100644 (file)
@@ -1,5 +1,7 @@
 <?php
+
 namespace wcf\system\cache\runtime;
+
 use wcf\data\conversation\Conversation;
 use wcf\data\conversation\UserConversationList;
 use wcf\system\WCF;
@@ -7,27 +9,30 @@ use wcf\system\WCF;
 /**
  * Runtime cache implementation for conversation fetched using UserConversationList.
  *
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\System\Cache\Runtime
- * @since      3.0
- * 
- * @method     Conversation[]          getCachedObjects()
- * @method     Conversation            getObject($objectID)
- * @method     Conversation[]          getObjects(array $objectIDs)
+ * @author  Matthias Schmidt
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Cache\Runtime
+ * @since   3.0
+ *
+ * @method  Conversation[]      getCachedObjects()
+ * @method  Conversation        getObject($objectID)
+ * @method  Conversation[]      getObjects(array $objectIDs)
  */
-class UserConversationRuntimeCache extends AbstractRuntimeCache {
-       /**
-        * @inheritDoc
-        */
-       protected $listClassName = UserConversationList::class;
-       
-       /** @noinspection PhpMissingParentCallCommonInspection */
-       /**
-        * @inheritDoc
-        */
-       protected function getObjectList() {
-               return new UserConversationList(WCF::getUser()->userID);
-       }
+class UserConversationRuntimeCache extends AbstractRuntimeCache
+{
+    /**
+     * @inheritDoc
+     */
+    protected $listClassName = UserConversationList::class;
+
+    /** @noinspection PhpMissingParentCallCommonInspection */
+
+    /**
+     * @inheritDoc
+     */
+    protected function getObjectList()
+    {
+        return new UserConversationList(WCF::getUser()->userID);
+    }
 }
index 31719040054b3643c9e09daff1eddd1bf05c8523..a6674d482193238fe36dd178ae4a30704e1e47bd 100644 (file)
@@ -1,5 +1,7 @@
 <?php
+
 namespace wcf\system\clipboard\action;
+
 use wcf\data\clipboard\action\ClipboardAction;
 use wcf\data\conversation\Conversation;
 use wcf\data\conversation\ConversationAction;
@@ -8,269 +10,296 @@ use wcf\system\WCF;
 
 /**
  * Prepares clipboard editor items for conversations.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\System\Clipboard\Action
+ *
+ * @author  Alexander Ebert
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Clipboard\Action
  */
-class ConversationClipboardAction extends AbstractClipboardAction {
-       /**
-        * @inheritDoc
-        */
-       protected $actionClassActions = ['close', 'markAsRead', 'open'];
-       
-       /**
-        * list of conversations
-        * @var Conversation[]
-        */
-       public $conversations;
-       
-       /**
-        * @inheritDoc
-        */
-       protected $supportedActions = ['assignLabel', 'close', 'leave', 'leavePermanently', 'markAsRead', 'open', 'restore'];
-       
-       /**
-        * @inheritDoc
-        */
-       public function execute(array $objects, ClipboardAction $action) {
-               if ($this->conversations === null) {
-                       // validate conversations
-                       $this->validateParticipation($objects);
-               }
-               
-               // check if no conversation was accessible
-               if (empty($this->conversations)) {
-                       return null;
-               }
-               
-               $item = parent::execute($objects, $action);
-               
-               if ($item === null) {
-                       return null;
-               }
-               
-               switch ($action->actionName) {
-                       case 'assignLabel':
-                               // check if user has labels
-                               $sql = "SELECT  COUNT(*) AS count
-                                       FROM    wcf".WCF_N."_conversation_label
+class ConversationClipboardAction extends AbstractClipboardAction
+{
+    /**
+     * @inheritDoc
+     */
+    protected $actionClassActions = ['close', 'markAsRead', 'open'];
+
+    /**
+     * list of conversations
+     * @var Conversation[]
+     */
+    public $conversations;
+
+    /**
+     * @inheritDoc
+     */
+    protected $supportedActions = [
+        'assignLabel',
+        'close',
+        'leave',
+        'leavePermanently',
+        'markAsRead',
+        'open',
+        'restore',
+    ];
+
+    /**
+     * @inheritDoc
+     */
+    public function execute(array $objects, ClipboardAction $action)
+    {
+        if ($this->conversations === null) {
+            // validate conversations
+            $this->validateParticipation($objects);
+        }
+
+        // check if no conversation was accessible
+        if (empty($this->conversations)) {
+            return;
+        }
+
+        $item = parent::execute($objects, $action);
+
+        if ($item === null) {
+            return;
+        }
+
+        switch ($action->actionName) {
+            case 'assignLabel':
+                // check if user has labels
+                $sql = "SELECT COUNT(*) AS count
+                                       FROM    wcf" . WCF_N . "_conversation_label
                                        WHERE   userID = ?";
-                               $statement = WCF::getDB()->prepareStatement($sql);
-                               $statement->execute([WCF::getUser()->userID]);
-                               $row = $statement->fetchArray();
-                               if ($row['count'] == 0) {
-                                       return null;
-                               }
-                               
-                               $item->addParameter('objectIDs', array_keys($this->conversations));
-                       break;
-                       
-                       case 'leave':
-                               $item->addInternalData('parameters', ['hideConversation' => 1]);
-                               $item->addParameter('actionName', 'hideConversation');
-                               $item->addParameter('className', $this->getClassName());
-                       break;
-                       
-                       case 'leavePermanently':
-                               $item->addParameter('objectIDs', array_keys($this->conversations));
-                               $item->addInternalData('parameters', ['hideConversation' => 2]);
-                               $item->addParameter('actionName', 'hideConversation');
-                               $item->addParameter('className', $this->getClassName());
-                       break;
-                       
-                       case 'markAsRead':
-                               $item->addParameter('objectIDs', array_keys($this->conversations));
-                               $item->addParameter('actionName', 'markAsRead');
-                               $item->addParameter('className', $this->getClassName());
-                               $item->addInternalData('confirmMessage', WCF::getLanguage()->getDynamicVariable('wcf.clipboard.item.com.woltlab.wcf.conversation.conversation.markAsRead.confirmMessage', [
-                                       'count' => $item->getCount()
-                               ]));
-                       break;
-                       
-                       case 'restore':
-                               $item->addInternalData('parameters', ['hideConversation' => 0]);
-                               $item->addParameter('actionName', 'hideConversation');
-                               $item->addParameter('className', $this->getClassName());
-                       break;
-               }
-               
-               return $item;
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getClassName() {
-               return ConversationAction::class;
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getTypeName() {
-               return 'com.woltlab.wcf.conversation.conversation';
-       }
-       
-       /**
-        * Returns a list of conversations with user participation.
-        * 
-        * @param       Conversation[]          $conversations
-        */
-       protected function validateParticipation(array $conversations) {
-               $conversationIDs = [];
-               
-               // validate ownership
-               foreach ($conversations as $conversation) {
-                       if ($conversation->userID != WCF::getUser()->userID) {
-                               $conversationIDs[] = $conversation->conversationID;
-                       }
-               }
-               
-               // validate participation as non-owner
-               if (!empty($conversationIDs)) {
-                       $conditions = new PreparedStatementConditionBuilder();
-                       $conditions->add("conversationID IN (?)", [$conversationIDs]);
-                       $conditions->add("participantID = ?", [WCF::getUser()->userID]);
-                       
-                       $sql = "SELECT  conversationID
-                               FROM    wcf".WCF_N."_conversation_to_user
-                               ".$conditions;
-                       $statement = WCF::getDB()->prepareStatement($sql);
-                       $statement->execute($conditions->getParameters());
-                       while ($row = $statement->fetchArray()) {
-                               $index = array_search($row['conversationID'], $conversationIDs);
-                               unset($conversationIDs[$index]);
-                       }
-                       
-                       // remove unaccessible conversations
-                       if (!empty($conversationIDs)) {
-                               foreach ($conversations as $index => $conversation) {
-                                       if (in_array($conversation->conversationID, $conversationIDs)) {
-                                               unset($conversations[$index]);
-                                       }
-                               }
-                       }
-               }
-               
-               foreach ($conversations as $conversation) {
-                       $this->conversations[$conversation->conversationID] = $conversation;
-               }
-       }
-       
-       /**
-        * Validates if user may close the given conversations.
-        * 
-        * @return      integer[]
-        */
-       protected function validateClose() {
-               $conversationIDs = [];
-               
-               foreach ($this->conversations as $conversation) {
-                       if (!$conversation->isClosed && $conversation->userID == WCF::getUser()->userID) {
-                               $conversationIDs[] = $conversation->conversationID;
-                       }
-               }
-               
-               return $conversationIDs;
-       }
-       
-       /**
-        * Validates conversations available for leaving.
-        * 
-        * @return      integer[]
-        */
-       public function validateLeave() {
-               $tmpIDs = [];
-               foreach ($this->conversations as $conversation) {
-                       $tmpIDs[] = $conversation->conversationID;
-               }
-               
-               $conditions = new PreparedStatementConditionBuilder();
-               $conditions->add("conversationID IN (?)", [$tmpIDs]);
-               $conditions->add("participantID = ?", [WCF::getUser()->userID]);
-               $conditions->add("hideConversation <> ?", [1]);
-               
-               $sql = "SELECT  conversationID
-                       FROM    wcf".WCF_N."_conversation_to_user
-                       ".$conditions;
-               $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute($conditions->getParameters());
-               
-               return $statement->fetchAll(\PDO::FETCH_COLUMN);
-       }
-       
-       /**
-        * Validates conversations applicable for mark as read.
-        * 
-        * @return      integer[]
-        */
-       public function validateMarkAsRead() {
-               $conversationIDs = [];
-               
-               $conditions = new PreparedStatementConditionBuilder();
-               $conditions->add("conversationID IN (?)", [array_keys($this->conversations)]);
-               $conditions->add("participantID = ?", [WCF::getUser()->userID]);
-               
-               $sql = "SELECT  conversationID, lastVisitTime
-                       FROM    wcf".WCF_N."_conversation_to_user
-                       ".$conditions;
-               $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute($conditions->getParameters());
-               $lastVisitTime = [];
-               while ($row = $statement->fetchArray()) {
-                       $lastVisitTime[$row['conversationID']] = $row['lastVisitTime'];
-               }
-               
-               foreach ($this->conversations as $conversation) {
-                       if (isset($lastVisitTime[$conversation->conversationID]) && $lastVisitTime[$conversation->conversationID] < $conversation->lastPostTime) {
-                               $conversationIDs[] = $conversation->conversationID;
-                       }
-               }
-               
-               return $conversationIDs;
-       }
-       
-       /**
-        * Validates if user may open the given conversations.
-        * 
-        * @return      integer[]
-        */
-       protected function validateOpen() {
-               $conversationIDs = [];
-               
-               foreach ($this->conversations as $conversation) {
-                       if ($conversation->isClosed && $conversation->userID == WCF::getUser()->userID) {
-                               $conversationIDs[] = $conversation->conversationID;
-                       }
-               }
-               
-               return $conversationIDs;
-       }
-       
-       /**
-        * Validates conversations available for restore.
-        * 
-        * @return      integer[]
-        */
-       public function validateRestore() {
-               $tmpIDs = [];
-               foreach ($this->conversations as $conversation) {
-                       $tmpIDs[] = $conversation->conversationID;
-               }
-               
-               $conditions = new PreparedStatementConditionBuilder();
-               $conditions->add("conversationID IN (?)", [$tmpIDs]);
-               $conditions->add("participantID = ?", [WCF::getUser()->userID]);
-               $conditions->add("hideConversation <> ?", [0]);
-               
-               $sql = "SELECT  conversationID
-                       FROM    wcf".WCF_N."_conversation_to_user
-                       ".$conditions;
-               $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute($conditions->getParameters());
-               
-               return $statement->fetchAll(\PDO::FETCH_COLUMN);
-       }
+                $statement = WCF::getDB()->prepareStatement($sql);
+                $statement->execute([WCF::getUser()->userID]);
+                $row = $statement->fetchArray();
+                if ($row['count'] == 0) {
+                    return;
+                }
+
+                $item->addParameter('objectIDs', \array_keys($this->conversations));
+                break;
+
+            case 'leave':
+                $item->addInternalData('parameters', ['hideConversation' => 1]);
+                $item->addParameter('actionName', 'hideConversation');
+                $item->addParameter('className', $this->getClassName());
+                break;
+
+            case 'leavePermanently':
+                $item->addParameter('objectIDs', \array_keys($this->conversations));
+                $item->addInternalData('parameters', ['hideConversation' => 2]);
+                $item->addParameter('actionName', 'hideConversation');
+                $item->addParameter('className', $this->getClassName());
+                break;
+
+            case 'markAsRead':
+                $item->addParameter('objectIDs', \array_keys($this->conversations));
+                $item->addParameter('actionName', 'markAsRead');
+                $item->addParameter('className', $this->getClassName());
+                $item->addInternalData(
+                    'confirmMessage',
+                    WCF::getLanguage()->getDynamicVariable(
+                        'wcf.clipboard.item.com.woltlab.wcf.conversation.conversation.markAsRead.confirmMessage',
+                        [
+                            'count' => $item->getCount(),
+                        ]
+                    )
+                );
+                break;
+
+            case 'restore':
+                $item->addInternalData('parameters', ['hideConversation' => 0]);
+                $item->addParameter('actionName', 'hideConversation');
+                $item->addParameter('className', $this->getClassName());
+                break;
+        }
+
+        return $item;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getClassName()
+    {
+        return ConversationAction::class;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getTypeName()
+    {
+        return 'com.woltlab.wcf.conversation.conversation';
+    }
+
+    /**
+     * Returns a list of conversations with user participation.
+     *
+     * @param Conversation[] $conversations
+     */
+    protected function validateParticipation(array $conversations)
+    {
+        $conversationIDs = [];
+
+        // validate ownership
+        foreach ($conversations as $conversation) {
+            if ($conversation->userID != WCF::getUser()->userID) {
+                $conversationIDs[] = $conversation->conversationID;
+            }
+        }
+
+        // validate participation as non-owner
+        if (!empty($conversationIDs)) {
+            $conditions = new PreparedStatementConditionBuilder();
+            $conditions->add("conversationID IN (?)", [$conversationIDs]);
+            $conditions->add("participantID = ?", [WCF::getUser()->userID]);
+
+            $sql = "SELECT     conversationID
+                               FROM    wcf" . WCF_N . "_conversation_to_user
+                               " . $conditions;
+            $statement = WCF::getDB()->prepareStatement($sql);
+            $statement->execute($conditions->getParameters());
+            while ($row = $statement->fetchArray()) {
+                $index = \array_search($row['conversationID'], $conversationIDs);
+                unset($conversationIDs[$index]);
+            }
+
+            // remove unaccessible conversations
+            if (!empty($conversationIDs)) {
+                foreach ($conversations as $index => $conversation) {
+                    if (\in_array($conversation->conversationID, $conversationIDs)) {
+                        unset($conversations[$index]);
+                    }
+                }
+            }
+        }
+
+        foreach ($conversations as $conversation) {
+            $this->conversations[$conversation->conversationID] = $conversation;
+        }
+    }
+
+    /**
+     * Validates if user may close the given conversations.
+     *
+     * @return  integer[]
+     */
+    protected function validateClose()
+    {
+        $conversationIDs = [];
+
+        foreach ($this->conversations as $conversation) {
+            if (!$conversation->isClosed && $conversation->userID == WCF::getUser()->userID) {
+                $conversationIDs[] = $conversation->conversationID;
+            }
+        }
+
+        return $conversationIDs;
+    }
+
+    /**
+     * Validates conversations available for leaving.
+     *
+     * @return  integer[]
+     */
+    public function validateLeave()
+    {
+        $tmpIDs = [];
+        foreach ($this->conversations as $conversation) {
+            $tmpIDs[] = $conversation->conversationID;
+        }
+
+        $conditions = new PreparedStatementConditionBuilder();
+        $conditions->add("conversationID IN (?)", [$tmpIDs]);
+        $conditions->add("participantID = ?", [WCF::getUser()->userID]);
+        $conditions->add("hideConversation <> ?", [1]);
+
+        $sql = "SELECT conversationID
+                       FROM    wcf" . WCF_N . "_conversation_to_user
+                       " . $conditions;
+        $statement = WCF::getDB()->prepareStatement($sql);
+        $statement->execute($conditions->getParameters());
+
+        return $statement->fetchAll(\PDO::FETCH_COLUMN);
+    }
+
+    /**
+     * Validates conversations applicable for mark as read.
+     *
+     * @return  integer[]
+     */
+    public function validateMarkAsRead()
+    {
+        $conversationIDs = [];
+
+        $conditions = new PreparedStatementConditionBuilder();
+        $conditions->add("conversationID IN (?)", [\array_keys($this->conversations)]);
+        $conditions->add("participantID = ?", [WCF::getUser()->userID]);
+
+        $sql = "SELECT conversationID, lastVisitTime
+                       FROM    wcf" . WCF_N . "_conversation_to_user
+                       " . $conditions;
+        $statement = WCF::getDB()->prepareStatement($sql);
+        $statement->execute($conditions->getParameters());
+        $lastVisitTime = [];
+        while ($row = $statement->fetchArray()) {
+            $lastVisitTime[$row['conversationID']] = $row['lastVisitTime'];
+        }
+
+        foreach ($this->conversations as $conversation) {
+            if (
+                isset($lastVisitTime[$conversation->conversationID])
+                && $lastVisitTime[$conversation->conversationID] < $conversation->lastPostTime
+            ) {
+                $conversationIDs[] = $conversation->conversationID;
+            }
+        }
+
+        return $conversationIDs;
+    }
+
+    /**
+     * Validates if user may open the given conversations.
+     *
+     * @return  integer[]
+     */
+    protected function validateOpen()
+    {
+        $conversationIDs = [];
+
+        foreach ($this->conversations as $conversation) {
+            if ($conversation->isClosed && $conversation->userID == WCF::getUser()->userID) {
+                $conversationIDs[] = $conversation->conversationID;
+            }
+        }
+
+        return $conversationIDs;
+    }
+
+    /**
+     * Validates conversations available for restore.
+     *
+     * @return  integer[]
+     */
+    public function validateRestore()
+    {
+        $tmpIDs = [];
+        foreach ($this->conversations as $conversation) {
+            $tmpIDs[] = $conversation->conversationID;
+        }
+
+        $conditions = new PreparedStatementConditionBuilder();
+        $conditions->add("conversationID IN (?)", [$tmpIDs]);
+        $conditions->add("participantID = ?", [WCF::getUser()->userID]);
+        $conditions->add("hideConversation <> ?", [0]);
+
+        $sql = "SELECT conversationID
+                       FROM    wcf" . WCF_N . "_conversation_to_user
+                       " . $conditions;
+        $statement = WCF::getDB()->prepareStatement($sql);
+        $statement->execute($conditions->getParameters());
+
+        return $statement->fetchAll(\PDO::FETCH_COLUMN);
+    }
 }
index 837e15053aa3ca5869920003b9dbf9d3037d7dd5..37162d51ff2de36782ee258c5690eced8def84bd 100644 (file)
 <?php
+
 namespace wcf\system\conversation;
+
 use wcf\system\database\util\PreparedStatementConditionBuilder;
 use wcf\system\exception\NamedUserException;
 use wcf\system\exception\PermissionDeniedException;
 use wcf\system\flood\FloodControl;
-use wcf\system\user\storage\UserStorageHandler;
 use wcf\system\SingletonFactory;
+use wcf\system\user\storage\UserStorageHandler;
 use wcf\system\WCF;
 
 /**
  * Handles the number of conversations and unread conversations of the active user.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\System\Conversation
+ *
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Conversation
  */
-class ConversationHandler extends SingletonFactory {
-       /**
-        * number of unread conversations
-        * @var integer[]
-        */
-       protected $unreadConversationCount = [];
-       
-       /**
-        * number of conversations
-        * @var integer[]
-        */
-       protected $conversationCount = [];
-       
-       /**
-        * Returns the number of unread conversations for given user.
-        * 
-        * @param       integer         $userID
-        * @param       boolean         $skipCache
-        * @return      integer
-        */
-       public function getUnreadConversationCount($userID = null, $skipCache = false) {
-               if ($userID === null) $userID = WCF::getUser()->userID;
-               
-               if (!isset($this->unreadConversationCount[$userID]) || $skipCache) {
-                       $this->unreadConversationCount[$userID] = 0;
-                       
-                       // load storage data
-                       UserStorageHandler::getInstance()->loadStorage([$userID]);
-                       
-                       // get ids
-                       $data = UserStorageHandler::getInstance()->getStorage([$userID], 'unreadConversationCount');
-                       
-                       // cache does not exist or is outdated
-                       if ($data[$userID] === null || $skipCache) {
-                               $conditionBuilder = new PreparedStatementConditionBuilder();
-                               $conditionBuilder->add('conversation.conversationID = conversation_to_user.conversationID');
-                               $conditionBuilder->add('conversation_to_user.participantID = ?', [$userID]);
-                               $conditionBuilder->add('conversation_to_user.hideConversation = 0');
-                               $conditionBuilder->add('conversation_to_user.lastVisitTime < conversation.lastPostTime');
-                               $conditionBuilder->add('conversation_to_user.leftAt = 0');
-                               
-                               $sql = "SELECT  COUNT(*) AS count
-                                       FROM    wcf".WCF_N."_conversation_to_user conversation_to_user,
-                                               wcf".WCF_N."_conversation conversation
-                                       ".$conditionBuilder;
-                               $statement = WCF::getDB()->prepareStatement($sql);
-                               $statement->execute($conditionBuilder->getParameters());
-                               $row = $statement->fetchArray();
-                               $this->unreadConversationCount[$userID] = $row['count'];
-                               
-                               // update storage data
-                               UserStorageHandler::getInstance()->update($userID, 'unreadConversationCount', serialize($this->unreadConversationCount[$userID]));
-                       }
-                       else {
-                               $this->unreadConversationCount[$userID] = unserialize($data[$userID]);
-                       }
-               }
-               
-               return $this->unreadConversationCount[$userID];
-       }
-       
-       /**
-        * Returns the number of conversations for given user.
-        * 
-        * @param       integer         $userID
-        * @return      integer
-        */
-       public function getConversationCount($userID = null) {
-               if ($userID === null) $userID = WCF::getUser()->userID;
-               
-               if (!isset($this->conversationCount[$userID])) {
-                       $this->conversationCount[$userID] = 0;
-                       
-                       // load storage data
-                       UserStorageHandler::getInstance()->loadStorage([$userID]);
-                       
-                       // get ids
-                       $data = UserStorageHandler::getInstance()->getStorage([$userID], 'conversationCount');
-                       
-                       // cache does not exist or is outdated
-                       if ($data[$userID] === null) {
-                               $conditionBuilder1 = new PreparedStatementConditionBuilder();
-                               $conditionBuilder1->add('conversation_to_user.participantID = ?', [$userID]);
-                               $conditionBuilder1->add('conversation_to_user.hideConversation IN (0,1)');
-                               $conditionBuilder2 = new PreparedStatementConditionBuilder();
-                               $conditionBuilder2->add('conversation.userID = ?', [$userID]);
-                               $conditionBuilder2->add('conversation.isDraft = 1');
-                               
-                               $sql = "SELECT (SELECT  COUNT(*)
-                                               FROM    wcf".WCF_N."_conversation_to_user conversation_to_user
-                                               ".$conditionBuilder1->__toString().")
+class ConversationHandler extends SingletonFactory
+{
+    /**
+     * number of unread conversations
+     * @var integer[]
+     */
+    protected $unreadConversationCount = [];
+
+    /**
+     * number of conversations
+     * @var integer[]
+     */
+    protected $conversationCount = [];
+
+    /**
+     * Returns the number of unread conversations for given user.
+     *
+     * @param integer $userID
+     * @param boolean $skipCache
+     * @return  integer
+     */
+    public function getUnreadConversationCount($userID = null, $skipCache = false)
+    {
+        if ($userID === null) {
+            $userID = WCF::getUser()->userID;
+        }
+
+        if (!isset($this->unreadConversationCount[$userID]) || $skipCache) {
+            $this->unreadConversationCount[$userID] = 0;
+
+            // load storage data
+            UserStorageHandler::getInstance()->loadStorage([$userID]);
+
+            // get ids
+            $data = UserStorageHandler::getInstance()->getStorage([$userID], 'unreadConversationCount');
+
+            // cache does not exist or is outdated
+            if ($data[$userID] === null || $skipCache) {
+                $conditionBuilder = new PreparedStatementConditionBuilder();
+                $conditionBuilder->add('conversation.conversationID = conversation_to_user.conversationID');
+                $conditionBuilder->add('conversation_to_user.participantID = ?', [$userID]);
+                $conditionBuilder->add('conversation_to_user.hideConversation = 0');
+                $conditionBuilder->add('conversation_to_user.lastVisitTime < conversation.lastPostTime');
+                $conditionBuilder->add('conversation_to_user.leftAt = 0');
+
+                $sql = "SELECT COUNT(*) AS count
+                                       FROM    wcf" . WCF_N . "_conversation_to_user conversation_to_user,
+                                               wcf" . WCF_N . "_conversation conversation
+                                       " . $conditionBuilder;
+                $statement = WCF::getDB()->prepareStatement($sql);
+                $statement->execute($conditionBuilder->getParameters());
+                $row = $statement->fetchArray();
+                $this->unreadConversationCount[$userID] = $row['count'];
+
+                // update storage data
+                UserStorageHandler::getInstance()->update(
+                    $userID,
+                    'unreadConversationCount',
+                    \serialize($this->unreadConversationCount[$userID])
+                );
+            } else {
+                $this->unreadConversationCount[$userID] = \unserialize($data[$userID]);
+            }
+        }
+
+        return $this->unreadConversationCount[$userID];
+    }
+
+    /**
+     * Returns the number of conversations for given user.
+     *
+     * @param integer $userID
+     * @return  integer
+     */
+    public function getConversationCount($userID = null)
+    {
+        if ($userID === null) {
+            $userID = WCF::getUser()->userID;
+        }
+
+        if (!isset($this->conversationCount[$userID])) {
+            $this->conversationCount[$userID] = 0;
+
+            // load storage data
+            UserStorageHandler::getInstance()->loadStorage([$userID]);
+
+            // get ids
+            $data = UserStorageHandler::getInstance()->getStorage([$userID], 'conversationCount');
+
+            // cache does not exist or is outdated
+            if ($data[$userID] === null) {
+                $conditionBuilder1 = new PreparedStatementConditionBuilder();
+                $conditionBuilder1->add('conversation_to_user.participantID = ?', [$userID]);
+                $conditionBuilder1->add('conversation_to_user.hideConversation IN (0,1)');
+                $conditionBuilder2 = new PreparedStatementConditionBuilder();
+                $conditionBuilder2->add('conversation.userID = ?', [$userID]);
+                $conditionBuilder2->add('conversation.isDraft = 1');
+
+                $sql = "SELECT (SELECT COUNT(*)
+                                               FROM    wcf" . WCF_N . "_conversation_to_user conversation_to_user
+                                               " . $conditionBuilder1->__toString() . ")
                                                +
                                                (SELECT COUNT(*)
-                                               FROM    wcf".WCF_N."_conversation conversation
-                                               ".$conditionBuilder2->__toString().") AS count";
-                               $statement = WCF::getDB()->prepareStatement($sql);
-                               $statement->execute(array_merge($conditionBuilder1->getParameters(), $conditionBuilder2->getParameters()));
-                               $row = $statement->fetchArray();
-                               $this->conversationCount[$userID] = $row['count'];
-                               
-                               // update storage data
-                               UserStorageHandler::getInstance()->update($userID, 'conversationCount', serialize($this->conversationCount[$userID]));
-                       }
-                       else {
-                               $this->conversationCount[$userID] = unserialize($data[$userID]);
-                       }
-               }
-               
-               return $this->conversationCount[$userID];
-       }
-       
-       /**
-        * Enforces the flood control.
-        */
-       public function enforceFloodControl() {
-               $limit = WCF::getSession()->getPermission('user.conversation.maxStartedConversationsPer24Hours');
-               if ($limit == -1) {
-                       return;
-               }
-               else if ($limit == 0) {
-                       // `0` is not a valid value, but the interface logic does not permit and exclusion
-                       // while also allowing the special value `-1`. Therefore, `0` behaves like the
-                       // 'canStartConversation' permission added in WoltLab Suite 5.2.
-                       throw new PermissionDeniedException();
-               }
-               
-               $count = FloodControl::getInstance()->countContent('com.woltlab.wcf.conversation', new \DateInterval('P1D'));
-               if ($count['count'] >= $limit) {
-                       throw new NamedUserException(WCF::getLanguage()->getDynamicVariable('wcf.conversation.error.floodControl', [
-                               'limit' => $count['count'],
-                               'notBefore' => $count['earliestTime'] + 86400,
-                       ]));
-               }
-       }
+                                               FROM    wcf" . WCF_N . "_conversation conversation
+                                               " . $conditionBuilder2->__toString() . ") AS count";
+                $statement = WCF::getDB()->prepareStatement($sql);
+                $statement->execute(\array_merge(
+                    $conditionBuilder1->getParameters(),
+                    $conditionBuilder2->getParameters()
+                ));
+                $row = $statement->fetchArray();
+                $this->conversationCount[$userID] = $row['count'];
+
+                // update storage data
+                UserStorageHandler::getInstance()->update(
+                    $userID,
+                    'conversationCount',
+                    \serialize($this->conversationCount[$userID])
+                );
+            } else {
+                $this->conversationCount[$userID] = \unserialize($data[$userID]);
+            }
+        }
+
+        return $this->conversationCount[$userID];
+    }
+
+    /**
+     * Enforces the flood control.
+     */
+    public function enforceFloodControl()
+    {
+        $limit = WCF::getSession()->getPermission('user.conversation.maxStartedConversationsPer24Hours');
+        if ($limit == -1) {
+            return;
+        } elseif ($limit == 0) {
+            // `0` is not a valid value, but the interface logic does not permit and exclusion
+            // while also allowing the special value `-1`. Therefore, `0` behaves like the
+            // 'canStartConversation' permission added in WoltLab Suite 5.2.
+            throw new PermissionDeniedException();
+        }
+
+        $count = FloodControl::getInstance()->countContent('com.woltlab.wcf.conversation', new \DateInterval('P1D'));
+        if ($count['count'] >= $limit) {
+            throw new NamedUserException(WCF::getLanguage()->getDynamicVariable('wcf.conversation.error.floodControl', [
+                'limit' => $count['count'],
+                'notBefore' => $count['earliestTime'] + 86400,
+            ]));
+        }
+    }
 }
index eb18eb0feeda0199b7c35aa44075a0a159f2f672..a35df3e90620fcd1436e442b8bbd29c31fc44f50 100644 (file)
@@ -1,22 +1,26 @@
 <?php
+
 namespace wcf\system\event\listener;
+
 use wcf\system\cronjob\PruneIpAddressesCronjob;
 
 /**
  * Prunes the stored ip addresses.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\System\Event\Listener
+ *
+ * @author  Alexander Ebert
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Event\Listener
  * @since       5.2
  */
-class ConversationPruneIpAddressesCronjobListener implements IParameterizedEventListener {
-       /**
-        * @inheritDoc
-        */
-       public function execute($eventObj, $className, $eventName, array &$parameters) {
-               /** @var PruneIpAddressesCronjob $eventObj */
-               $eventObj->columns['wcf'.WCF_N.'_conversation_message']['ipAddress'] = 'time';
-       }
+class ConversationPruneIpAddressesCronjobListener implements IParameterizedEventListener
+{
+    /**
+     * @inheritDoc
+     */
+    public function execute($eventObj, $className, $eventName, array &$parameters)
+    {
+        /** @var PruneIpAddressesCronjob $eventObj */
+        $eventObj->columns['wcf' . WCF_N . '_conversation_message']['ipAddress'] = 'time';
+    }
 }
index debf3bd6aa4f1e52f27c8c8ef0a0000430c77223..3eb89219cf049ac652f80f6d16ff62cfe9c5b177 100644 (file)
@@ -1,29 +1,31 @@
 <?php
+
 namespace wcf\system\event\listener;
 
 /**
  * Updates the stored username during user rename.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\System\Event\Listener
+ *
+ * @author  Alexander Ebert
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Event\Listener
  */
-class ConversationUserActionRenameListener extends AbstractUserActionRenameListener {
-       /**
-        * @inheritDoc
-        */
-       protected $databaseTables = [
-               'wcf{WCF_N}_conversation',
-               'wcf{WCF_N}_conversation_message',
-               [
-                       'name' => 'wcf{WCF_N}_conversation',
-                       'userID' => 'lastPosterID',
-                       'username' => 'lastPoster'
-               ],
-               [
-                       'name' => 'wcf{WCF_N}_conversation_to_user',
-                       'userID' => 'participantID'
-               ]
-       ];
+class ConversationUserActionRenameListener extends AbstractUserActionRenameListener
+{
+    /**
+     * @inheritDoc
+     */
+    protected $databaseTables = [
+        'wcf{WCF_N}_conversation',
+        'wcf{WCF_N}_conversation_message',
+        [
+            'name' => 'wcf{WCF_N}_conversation',
+            'userID' => 'lastPosterID',
+            'username' => 'lastPoster',
+        ],
+        [
+            'name' => 'wcf{WCF_N}_conversation_to_user',
+            'userID' => 'participantID',
+        ],
+    ];
 }
index a0d7fd89e7e3a35e765dba392b6be7eab7b7d5cb..7cb15ccd08f9ed5ed7e83661c658a46da43717e3 100644 (file)
@@ -1,29 +1,31 @@
 <?php
+
 namespace wcf\system\event\listener;
 
 /**
  * Merges user conversations.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\System\Event\Listener
+ *
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Event\Listener
  */
-class ConversationUserMergeListener extends AbstractUserMergeListener {
-       /**
-        * @inheritDoc
-        */
-       protected $databaseTables = [
-               'wcf{WCF_N}_conversation',
-               'wcf{WCF_N}_conversation_message',
-               [
-                       'name' => 'wcf{WCF_N}_conversation_label',
-                       'username' => null
-               ],
-               [
-                       'name' => 'wcf{WCF_N}_conversation_to_user',
-                       'userID' => 'participantID',
-                       'ignore' => true
-               ]
-       ];
+class ConversationUserMergeListener extends AbstractUserMergeListener
+{
+    /**
+     * @inheritDoc
+     */
+    protected $databaseTables = [
+        'wcf{WCF_N}_conversation',
+        'wcf{WCF_N}_conversation_message',
+        [
+            'name' => 'wcf{WCF_N}_conversation_label',
+            'username' => null,
+        ],
+        [
+            'name' => 'wcf{WCF_N}_conversation_to_user',
+            'userID' => 'participantID',
+            'ignore' => true,
+        ],
+    ];
 }
index 88ddad10b92196749f5c74ddc8496bc45d9141eb..6def4804c1c42a41178bfb1e7adf09ae7607889a 100644 (file)
@@ -1,5 +1,7 @@
 <?php
+
 namespace wcf\system\event\listener;
+
 use wcf\acp\form\UserGroupAddForm;
 use wcf\acp\form\UserGroupEditForm;
 use wcf\data\user\group\UserGroup;
@@ -8,74 +10,82 @@ use wcf\system\WCF;
 /**
  * Handles 'canBeAddedAsConversationParticipant' setting.
  *
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\System\Event\Listener
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Event\Listener
  */
-class UserGroupAddCanBeAddedAsConversationParticipantListener implements IParameterizedEventListener {
-       /**
-        * instance of UserGroupAddForm
-        * @var UserGroupAddForm|UserGroupEditForm
-        */
-       protected $eventObj;
-       
-       /**
-        * true if group can be added as participant
-        * @var boolean
-        */
-       protected $canBeAddedAsConversationParticipant = 0;
-       
-       /**
-        * @inheritDoc
-        */
-       public function execute($eventObj, $className, $eventName, array &$parameters) {
-               $this->eventObj = $eventObj;
-               
-               if ($this->eventObj instanceof UserGroupEditForm && is_object($this->eventObj->group)) {
-                       switch ($this->eventObj->group->groupType) {
-                               case UserGroup::EVERYONE:
-                               case UserGroup::GUESTS:
-                               case UserGroup::USERS:
-                                       return;
-                       }
-               }
-               
-               $this->$eventName();
-       }
-       
-       /**
-        * Handles the assignVariables event.
-        */
-       protected function assignVariables() {
-               WCF::getTPL()->assign([
-                       'canBeAddedAsConversationParticipant' => $this->canBeAddedAsConversationParticipant
-               ]);
-       }
-       
-       /**
-        * Handles the readData event.
-        * This is only called in UserGroupEditForm.
-        */
-       protected function readData() {
-               if (empty($_POST)) {
-                       $this->canBeAddedAsConversationParticipant = $this->eventObj->group->canBeAddedAsConversationParticipant;
-               }
-       }
-       
-       /**
-        * Handles the readFormParameters event.
-        */
-       protected function readFormParameters() {
-               if (isset($_POST['canBeAddedAsConversationParticipant'])) $this->canBeAddedAsConversationParticipant = intval($_POST['canBeAddedAsConversationParticipant']);
-       }
-       
-       /**
-        * Handles the save event.
-        */
-       protected function save() {
-               $this->eventObj->additionalFields = array_merge($this->eventObj->additionalFields, [
-                       'canBeAddedAsConversationParticipant' => $this->canBeAddedAsConversationParticipant
-               ]);
-       }
+class UserGroupAddCanBeAddedAsConversationParticipantListener implements IParameterizedEventListener
+{
+    /**
+     * instance of UserGroupAddForm
+     * @var UserGroupAddForm|UserGroupEditForm
+     */
+    protected $eventObj;
+
+    /**
+     * true if group can be added as participant
+     * @var boolean
+     */
+    protected $canBeAddedAsConversationParticipant = 0;
+
+    /**
+     * @inheritDoc
+     */
+    public function execute($eventObj, $className, $eventName, array &$parameters)
+    {
+        $this->eventObj = $eventObj;
+
+        if ($this->eventObj instanceof UserGroupEditForm && \is_object($this->eventObj->group)) {
+            switch ($this->eventObj->group->groupType) {
+                case UserGroup::EVERYONE:
+                case UserGroup::GUESTS:
+                case UserGroup::USERS:
+                    return;
+            }
+        }
+
+        $this->{$eventName}();
+    }
+
+    /**
+     * Handles the assignVariables event.
+     */
+    protected function assignVariables()
+    {
+        WCF::getTPL()->assign([
+            'canBeAddedAsConversationParticipant' => $this->canBeAddedAsConversationParticipant,
+        ]);
+    }
+
+    /**
+     * Handles the readData event.
+     * This is only called in UserGroupEditForm.
+     */
+    protected function readData()
+    {
+        if (empty($_POST)) {
+            $this->canBeAddedAsConversationParticipant = $this->eventObj->group->canBeAddedAsConversationParticipant;
+        }
+    }
+
+    /**
+     * Handles the readFormParameters event.
+     */
+    protected function readFormParameters()
+    {
+        if (isset($_POST['canBeAddedAsConversationParticipant'])) {
+            $this->canBeAddedAsConversationParticipant = \intval($_POST['canBeAddedAsConversationParticipant']);
+        }
+    }
+
+    /**
+     * Handles the save event.
+     */
+    protected function save()
+    {
+        $this->eventObj->additionalFields = \array_merge($this->eventObj->additionalFields, [
+            'canBeAddedAsConversationParticipant' => $this->canBeAddedAsConversationParticipant,
+        ]);
+    }
 }
index cdd783195c8fd86f513c217cb8bbe3ba72c235de..eb1e6035515b3092a795d541618a769785a58eb1 100644 (file)
@@ -1,46 +1,55 @@
 <?php
+
 namespace wcf\system\importer;
+
 use wcf\data\conversation\message\ConversationMessage;
 use wcf\data\conversation\message\ConversationMessageEditor;
 use wcf\data\object\type\ObjectTypeCache;
 
 /**
  * Imports conversation attachments.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\System\Importer
+ *
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Importer
  */
-class ConversationAttachmentImporter extends AbstractAttachmentImporter {
-       /**
-        * Creates a new ConversationAttachmentImporter object.
-        */
-       public function __construct() {
-               $objectType = ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.attachment.objectType', 'com.woltlab.wcf.conversation.message');
-               $this->objectTypeID = $objectType->objectTypeID;
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function import($oldID, array $data, array $additionalData = []) {
-               $data['objectID'] = ImportHandler::getInstance()->getNewID('com.woltlab.wcf.conversation.message', $data['objectID']);
-               if (!$data['objectID']) return 0;
-               
-               $attachmentID = parent::import($oldID, $data, $additionalData);
-               if ($attachmentID && $attachmentID != $oldID) {
-                       // fix embedded attachments
-                       $messageObj = new ConversationMessage($data['objectID']);
-                       
-                       if (($newMessage = $this->fixEmbeddedAttachments($messageObj->message, $oldID, $attachmentID)) !== false) {
-                               $editor = new ConversationMessageEditor($messageObj);
-                               $editor->update([
-                                       'message' => $newMessage
-                               ]);
-                       }
-               }
-               
-               return $attachmentID;
-       }
+class ConversationAttachmentImporter extends AbstractAttachmentImporter
+{
+    /**
+     * Creates a new ConversationAttachmentImporter object.
+     */
+    public function __construct()
+    {
+        $objectType = ObjectTypeCache::getInstance()
+            ->getObjectTypeByName('com.woltlab.wcf.attachment.objectType', 'com.woltlab.wcf.conversation.message');
+        $this->objectTypeID = $objectType->objectTypeID;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function import($oldID, array $data, array $additionalData = [])
+    {
+        $data['objectID'] = ImportHandler::getInstance()
+            ->getNewID('com.woltlab.wcf.conversation.message', $data['objectID']);
+        if (!$data['objectID']) {
+            return 0;
+        }
+
+        $attachmentID = parent::import($oldID, $data, $additionalData);
+        if ($attachmentID && $attachmentID != $oldID) {
+            // fix embedded attachments
+            $messageObj = new ConversationMessage($data['objectID']);
+
+            if (($newMessage = $this->fixEmbeddedAttachments($messageObj->message, $oldID, $attachmentID)) !== false) {
+                $editor = new ConversationMessageEditor($messageObj);
+                $editor->update([
+                    'message' => $newMessage,
+                ]);
+            }
+        }
+
+        return $attachmentID;
+    }
 }
index c0ae2c78d3bea78d84aa1aae6d59ff9bae4e0067..4db0a301a7d07eb161f1e017c941374546aa01e8 100644 (file)
@@ -1,51 +1,57 @@
 <?php
+
 namespace wcf\system\importer;
+
 use wcf\data\conversation\Conversation;
 use wcf\data\conversation\ConversationEditor;
 
 /**
  * Imports conversations.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\System\Importer
+ *
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Importer
  */
-class ConversationImporter extends AbstractImporter {
-       /**
-        * @inheritDoc
-        */
-       protected $className = Conversation::class;
-       
-       /**
-        * @inheritDoc
-        */
-       public function import($oldID, array $data, array $additionalData = []) {
-               $oldUserID = $data['userID'];
-               $data['userID'] = ImportHandler::getInstance()->getNewID('com.woltlab.wcf.user', $data['userID']);
-               
-               // check existing conversation
-               if (ctype_digit((string)$oldID)) {
-                       $existingConversation = new Conversation($oldID);
-                       if (!$existingConversation->conversationID) $data['conversationID'] = $oldID;
-               }
-               
-               $conversation = ConversationEditor::create($data);
-               
-               ImportHandler::getInstance()->saveNewID('com.woltlab.wcf.conversation', $oldID, $conversation->conversationID);
-               
-               // add author
-               if (empty($data['isDraft'])) {
-                       ImportHandler::getInstance()->getImporter('com.woltlab.wcf.conversation.user')->import(0, [
-                               'conversationID' => $oldID,
-                               'participantID' => $oldUserID,
-                               'username' => $data['username'],
-                               'hideConversation' => 0,
-                               'isInvisible' => 0,
-                               'lastVisitTime' => $data['time']
-                       ], ['labelIDs' => []]);
-               }
-               
-               return $conversation->conversationID;
-       }
+class ConversationImporter extends AbstractImporter
+{
+    /**
+     * @inheritDoc
+     */
+    protected $className = Conversation::class;
+
+    /**
+     * @inheritDoc
+     */
+    public function import($oldID, array $data, array $additionalData = [])
+    {
+        $oldUserID = $data['userID'];
+        $data['userID'] = ImportHandler::getInstance()->getNewID('com.woltlab.wcf.user', $data['userID']);
+
+        // check existing conversation
+        if (\ctype_digit((string)$oldID)) {
+            $existingConversation = new Conversation($oldID);
+            if (!$existingConversation->conversationID) {
+                $data['conversationID'] = $oldID;
+            }
+        }
+
+        $conversation = ConversationEditor::create($data);
+
+        ImportHandler::getInstance()->saveNewID('com.woltlab.wcf.conversation', $oldID, $conversation->conversationID);
+
+        // add author
+        if (empty($data['isDraft'])) {
+            ImportHandler::getInstance()->getImporter('com.woltlab.wcf.conversation.user')->import(0, [
+                'conversationID' => $oldID,
+                'participantID' => $oldUserID,
+                'username' => $data['username'],
+                'hideConversation' => 0,
+                'isInvisible' => 0,
+                'lastVisitTime' => $data['time'],
+            ], ['labelIDs' => []]);
+        }
+
+        return $conversation->conversationID;
+    }
 }
index 64fbac9f875cb083aa9be3986cb044f1f16459bf..ad7130b9ce605ab35c23c1d7307fe395e5ec67ec 100644 (file)
@@ -1,37 +1,43 @@
 <?php
+
 namespace wcf\system\importer;
+
 use wcf\data\conversation\label\ConversationLabel;
 use wcf\data\conversation\label\ConversationLabelAction;
 
 /**
  * Imports conversation labels.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\System\Importer
+ *
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Importer
  */
-class ConversationLabelImporter extends AbstractImporter {
-       /**
-        * @inheritDoc
-        */
-       protected $className = ConversationLabel::class;
-       
-       /**
-        * @inheritDoc
-        */
-       public function import($oldID, array $data, array $additionalData = []) {
-               $data['userID'] = ImportHandler::getInstance()->getNewID('com.woltlab.wcf.user', $data['userID']);
-               if (!$data['userID']) return 0;
-               
-               $action = new ConversationLabelAction([], 'create', [
-                       'data' => $data
-               ]);
-               $returnValues = $action->executeAction();
-               $newID = $returnValues['returnValues']->labelID;
-               
-               ImportHandler::getInstance()->saveNewID('com.woltlab.wcf.conversation.label', $oldID, $newID);
-               
-               return $newID;
-       }
+class ConversationLabelImporter extends AbstractImporter
+{
+    /**
+     * @inheritDoc
+     */
+    protected $className = ConversationLabel::class;
+
+    /**
+     * @inheritDoc
+     */
+    public function import($oldID, array $data, array $additionalData = [])
+    {
+        $data['userID'] = ImportHandler::getInstance()->getNewID('com.woltlab.wcf.user', $data['userID']);
+        if (!$data['userID']) {
+            return 0;
+        }
+
+        $action = new ConversationLabelAction([], 'create', [
+            'data' => $data,
+        ]);
+        $returnValues = $action->executeAction();
+        $newID = $returnValues['returnValues']->labelID;
+
+        ImportHandler::getInstance()->saveNewID('com.woltlab.wcf.conversation.label', $oldID, $newID);
+
+        return $newID;
+    }
 }
index 7bc0dda7fb8e9de6c8be2d8e61170008d7d5716b..11bec63269516a4f4532db25e8ed06e5af4bda7b 100644 (file)
@@ -1,40 +1,49 @@
 <?php
+
 namespace wcf\system\importer;
+
 use wcf\data\conversation\message\ConversationMessage;
 use wcf\data\conversation\message\ConversationMessageEditor;
 
 /**
  * Imports conversation messages.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\System\Importer
+ *
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Importer
  */
-class ConversationMessageImporter extends AbstractImporter {
-       /**
-        * @inheritDoc
-        */
-       protected $className = ConversationMessage::class;
-       
-       /**
-        * @inheritDoc
-        */
-       public function import($oldID, array $data, array $additionalData = []) {
-               $data['conversationID'] = ImportHandler::getInstance()->getNewID('com.woltlab.wcf.conversation', $data['conversationID']);
-               if (!$data['conversationID']) return 0;
-               $data['userID'] = ImportHandler::getInstance()->getNewID('com.woltlab.wcf.user', $data['userID']);
-               
-               // check existing message
-               if (ctype_digit((string)$oldID)) {
-                       $existingMessage = new ConversationMessage($oldID);
-                       if (!$existingMessage->messageID) $data['messageID'] = $oldID;
-               }
-               
-               $message = ConversationMessageEditor::create($data);
-               
-               ImportHandler::getInstance()->saveNewID('com.woltlab.wcf.conversation.message', $oldID, $message->messageID);
-               
-               return $message->messageID;
-       }
+class ConversationMessageImporter extends AbstractImporter
+{
+    /**
+     * @inheritDoc
+     */
+    protected $className = ConversationMessage::class;
+
+    /**
+     * @inheritDoc
+     */
+    public function import($oldID, array $data, array $additionalData = [])
+    {
+        $data['conversationID'] = ImportHandler::getInstance()
+            ->getNewID('com.woltlab.wcf.conversation', $data['conversationID']);
+        if (!$data['conversationID']) {
+            return 0;
+        }
+        $data['userID'] = ImportHandler::getInstance()->getNewID('com.woltlab.wcf.user', $data['userID']);
+
+        // check existing message
+        if (\ctype_digit((string)$oldID)) {
+            $existingMessage = new ConversationMessage($oldID);
+            if (!$existingMessage->messageID) {
+                $data['messageID'] = $oldID;
+            }
+        }
+
+        $message = ConversationMessageEditor::create($data);
+
+        ImportHandler::getInstance()->saveNewID('com.woltlab.wcf.conversation.message', $oldID, $message->messageID);
+
+        return $message->messageID;
+    }
 }
index 85b489492dcb2cba6d9f934535999bfaf0bb824a..4cd97ad873f54b5301119a6b3fe26a17d7e3f0bb 100644 (file)
@@ -1,52 +1,61 @@
 <?php
+
 namespace wcf\system\importer;
+
 use wcf\system\WCF;
 
 /**
  * Imports conversation users.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\System\Importer
+ *
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Importer
  */
-class ConversationUserImporter extends AbstractImporter {
-       /**
-        * @inheritDoc
-        */
-       public function import($oldID, array $data, array $additionalData = []) {
-               $data['conversationID'] = ImportHandler::getInstance()->getNewID('com.woltlab.wcf.conversation', $data['conversationID']);
-               if (!$data['conversationID']) return 0;
-               $data['participantID'] = ImportHandler::getInstance()->getNewID('com.woltlab.wcf.user', $data['participantID']);
-               
-               $sql = "INSERT INTO                     wcf".WCF_N."_conversation_to_user
+class ConversationUserImporter extends AbstractImporter
+{
+    /**
+     * @inheritDoc
+     */
+    public function import($oldID, array $data, array $additionalData = [])
+    {
+        $data['conversationID'] = ImportHandler::getInstance()
+            ->getNewID('com.woltlab.wcf.conversation', $data['conversationID']);
+        if (!$data['conversationID']) {
+            return 0;
+        }
+        $data['participantID'] = ImportHandler::getInstance()->getNewID('com.woltlab.wcf.user', $data['participantID']);
+
+        $sql = "INSERT INTO                    wcf" . WCF_N . "_conversation_to_user
                                                        (conversationID, participantID, username, hideConversation, isInvisible, lastVisitTime)
                        VALUES                          (?, ?, ?, ?, ?, ?)
                        ON DUPLICATE KEY UPDATE         hideConversation = IF(hideConversation > 0 AND hideConversation = VALUES(hideConversation),hideConversation,0),
                                                        isInvisible = IF(isInvisible AND VALUES(isInvisible),1,0),
                                                        lastVisitTime = GREATEST(lastVisitTime,VALUES(lastVisitTime))";
-               $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute([
-                       $data['conversationID'],
-                       $data['participantID'],
-                       $data['username'],
-                       $data['hideConversation'],
-                       $data['isInvisible'],
-                       $data['lastVisitTime']
-               ]);
-               
-               // save labels
-               if ($data['participantID'] && !empty($additionalData['labelIDs'])) {
-                       $sql = "INSERT IGNORE INTO              wcf".WCF_N."_conversation_label_to_object
+        $statement = WCF::getDB()->prepareStatement($sql);
+        $statement->execute([
+            $data['conversationID'],
+            $data['participantID'],
+            $data['username'],
+            $data['hideConversation'],
+            $data['isInvisible'],
+            $data['lastVisitTime'],
+        ]);
+
+        // save labels
+        if ($data['participantID'] && !empty($additionalData['labelIDs'])) {
+            $sql = "INSERT IGNORE INTO         wcf" . WCF_N . "_conversation_label_to_object
                                                                (labelID, conversationID)
                                VALUES                          (?, ?)";
-                       $statement = WCF::getDB()->prepareStatement($sql);
-                       foreach ($additionalData['labelIDs'] as $labelID) {
-                               $labelID = ImportHandler::getInstance()->getNewID('com.woltlab.wcf.conversation.label', $labelID);
-                               if ($labelID) $statement->execute([$labelID, $data['conversationID']]);
-                       }
-               }
-               
-               return 1;
-       }
+            $statement = WCF::getDB()->prepareStatement($sql);
+            foreach ($additionalData['labelIDs'] as $labelID) {
+                $labelID = ImportHandler::getInstance()->getNewID('com.woltlab.wcf.conversation.label', $labelID);
+                if ($labelID) {
+                    $statement->execute([$labelID, $data['conversationID']]);
+                }
+            }
+        }
+
+        return 1;
+    }
 }
index f59796ee86f17754e90a32026cd31ab538f534d7..a42e7a774ae5830d3dbfa9b8558b31e4adc0562c 100644 (file)
 <?php
+
 namespace wcf\system\log\modification;
+
 use wcf\data\conversation\Conversation;
 use wcf\data\user\User;
 use wcf\data\user\UserList;
 
 /**
  * Handles conversation modification logs.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\System\Log\Modification
+ *
+ * @author  Alexander Ebert
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Log\Modification
  */
-class ConversationModificationLogHandler extends VoidExtendedModificationLogHandler {
-       /**
-        * @inheritDoc
-        */
-       protected $objectTypeName = 'com.woltlab.wcf.conversation.conversation';
-       
-       /**
-        * Adds a log entry for newly added conversation participants.
-        * 
-        * @param       Conversation    $conversation
-        * @param       integer[]       $participantIDs
-        */
-       public function addParticipants(Conversation $conversation, array $participantIDs) {
-               $participants = [];
-               $userList = new UserList();
-               $userList->setObjectIDs($participantIDs);
-               $userList->readObjects();
-               foreach ($userList as $user) {
-                       $participants[] = [
-                               'userID' => $user->userID,
-                               'username' => $user->username
-                       ];
-               }
-               
-               $this->add($conversation, 'addParticipants', [
-                       'participants' => $participants
-               ]);
-       }
-       
-       /**
-        * Adds a log entry for conversation close.
-        * 
-        * @param       Conversation    $conversation
-        */
-       public function close(Conversation $conversation) {
-               $this->add($conversation, 'close');
-       }
-       
-       /**
-        * Adds a log entry for conversation open.
-        * 
-        * @param       Conversation    $conversation
-        */
-       public function open(Conversation $conversation) {
-               $this->add($conversation, 'open');
-       }
-       
-       /**
-        * Adds a log entry for conversation leave.
-        * 
-        * @param       Conversation    $conversation
-        */
-       public function leave(Conversation $conversation) {
-               $this->add($conversation, 'leave');
-       }
-       
-       /**
-        * Adds a log entry for a removed participant.
-        * 
-        * @param       Conversation    $conversation
-        * @param       integer         $userID
-        */
-       public function removeParticipant(Conversation $conversation, $userID) {
-               $user = new User($userID);
-               $this->add($conversation, 'removeParticipant', [
-                       'userID' => $userID,
-                       'username' => $user->username
-               ]);
-       }
-       
-       /**
-        * Adds a conversation modification log entry.
-        * 
-        * @param       Conversation    $conversation
-        * @param       string          $action
-        * @param       array           $additionalData
-        */
-       public function add(Conversation $conversation, $action, array $additionalData = []) {
-               $this->createLog($action, $conversation->conversationID, null, $additionalData);
-       }
-       
-       /**
-        * Removes the conversation log entries of the conversations with the given
-        * ids.
-        * 
-        * @param       integer[]       $objectIDs
-        * @deprecated  3.0, use deleteLogs()
-        */
-       public function remove(array $objectIDs) {
-               $this->deleteLogs($objectIDs);
-       }
+class ConversationModificationLogHandler extends VoidExtendedModificationLogHandler
+{
+    /**
+     * @inheritDoc
+     */
+    protected $objectTypeName = 'com.woltlab.wcf.conversation.conversation';
+
+    /**
+     * Adds a log entry for newly added conversation participants.
+     *
+     * @param Conversation $conversation
+     * @param integer[] $participantIDs
+     */
+    public function addParticipants(Conversation $conversation, array $participantIDs)
+    {
+        $participants = [];
+        $userList = new UserList();
+        $userList->setObjectIDs($participantIDs);
+        $userList->readObjects();
+        foreach ($userList as $user) {
+            $participants[] = [
+                'userID' => $user->userID,
+                'username' => $user->username,
+            ];
+        }
+
+        $this->add($conversation, 'addParticipants', [
+            'participants' => $participants,
+        ]);
+    }
+
+    /**
+     * Adds a log entry for conversation close.
+     *
+     * @param Conversation $conversation
+     */
+    public function close(Conversation $conversation)
+    {
+        $this->add($conversation, 'close');
+    }
+
+    /**
+     * Adds a log entry for conversation open.
+     *
+     * @param Conversation $conversation
+     */
+    public function open(Conversation $conversation)
+    {
+        $this->add($conversation, 'open');
+    }
+
+    /**
+     * Adds a log entry for conversation leave.
+     *
+     * @param Conversation $conversation
+     */
+    public function leave(Conversation $conversation)
+    {
+        $this->add($conversation, 'leave');
+    }
+
+    /**
+     * Adds a log entry for a removed participant.
+     *
+     * @param Conversation $conversation
+     * @param integer $userID
+     */
+    public function removeParticipant(Conversation $conversation, $userID)
+    {
+        $user = new User($userID);
+        $this->add($conversation, 'removeParticipant', [
+            'userID' => $userID,
+            'username' => $user->username,
+        ]);
+    }
+
+    /**
+     * Adds a conversation modification log entry.
+     *
+     * @param Conversation $conversation
+     * @param string $action
+     * @param array $additionalData
+     */
+    public function add(Conversation $conversation, $action, array $additionalData = [])
+    {
+        $this->createLog($action, $conversation->conversationID, null, $additionalData);
+    }
+
+    /**
+     * Removes the conversation log entries of the conversations with the given
+     * ids.
+     *
+     * @param integer[] $objectIDs
+     * @deprecated  3.0, use deleteLogs()
+     */
+    public function remove(array $objectIDs)
+    {
+        $this->deleteLogs($objectIDs);
+    }
 }
index fea113298cd4e89e42b37d01e4e96c7243e623f7..dfa7060076415ee8d6d73a6b3f91e8261e21a047 100644 (file)
@@ -1,72 +1,76 @@
 <?php
+
 namespace wcf\system\message\quote;
-use wcf\data\conversation\message\ConversationMessageList;
+
 use wcf\data\conversation\ConversationList;
+use wcf\data\conversation\message\ConversationMessageList;
 
 /**
  * IMessageQuoteHandler implementation for conversation messages.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\System\Message\Quote
+ *
+ * @author  Alexander Ebert
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Message\Quote
  */
-class ConversationMessageQuoteHandler extends AbstractMessageQuoteHandler {
-       /**
-        * @inheritDoc
-        */
-       protected function getMessages(array $data) {
-               // read messages
-               $messageList = new ConversationMessageList();
-               $messageList->setObjectIDs(array_keys($data));
-               $messageList->readObjects();
-               $messages = $messageList->getObjects();
-               
-               // read conversations
-               $conversationIDs = $validMessageIDs = [];
-               foreach ($messages as $message) {
-                       $conversationIDs[] = $message->conversationID;
-                       $validMessageIDs[] = $message->messageID;
-               }
-               
-               $quotedMessages = [];
-               if (!empty($conversationIDs)) {
-                       $conversationList = new ConversationList();
-                       $conversationList->setObjectIDs($conversationIDs);
-                       $conversationList->readObjects();
-                       $conversations = $conversationList->getObjects();
-                       
-                       // create QuotedMessage objects
-                       foreach ($messages as $conversationMessage) {
-                               $conversationMessage->setConversation($conversations[$conversationMessage->conversationID]);
-                               $message = new QuotedMessage($conversationMessage);
-                               
-                               foreach (array_keys($data[$conversationMessage->messageID]) as $quoteID) {
-                                       $message->addQuote(
-                                               $quoteID,
-                                               MessageQuoteManager::getInstance()->getQuote($quoteID, false),  // single quote or excerpt
-                                               MessageQuoteManager::getInstance()->getQuote($quoteID, true)    // same as above or full quote
-                                       );
-                               }
-                               
-                               $quotedMessages[] = $message;
-                       }
-               }
-               
-               // check for orphaned quotes
-               if (count($validMessageIDs) != count($data)) {
-                       $orphanedQuoteIDs = [];
-                       foreach ($data as $messageID => $quoteIDs) {
-                               if (!in_array($messageID, $validMessageIDs)) {
-                                       foreach (array_keys($quoteIDs) as $quoteID) {
-                                               $orphanedQuoteIDs[] = $quoteID;
-                                       }
-                               }
-                       }
-                       
-                       MessageQuoteManager::getInstance()->removeOrphanedQuotes($orphanedQuoteIDs);
-               }
-               
-               return $quotedMessages;
-       }
+class ConversationMessageQuoteHandler extends AbstractMessageQuoteHandler
+{
+    /**
+     * @inheritDoc
+     */
+    protected function getMessages(array $data)
+    {
+        // read messages
+        $messageList = new ConversationMessageList();
+        $messageList->setObjectIDs(\array_keys($data));
+        $messageList->readObjects();
+        $messages = $messageList->getObjects();
+
+        // read conversations
+        $conversationIDs = $validMessageIDs = [];
+        foreach ($messages as $message) {
+            $conversationIDs[] = $message->conversationID;
+            $validMessageIDs[] = $message->messageID;
+        }
+
+        $quotedMessages = [];
+        if (!empty($conversationIDs)) {
+            $conversationList = new ConversationList();
+            $conversationList->setObjectIDs($conversationIDs);
+            $conversationList->readObjects();
+            $conversations = $conversationList->getObjects();
+
+            // create QuotedMessage objects
+            foreach ($messages as $conversationMessage) {
+                $conversationMessage->setConversation($conversations[$conversationMessage->conversationID]);
+                $message = new QuotedMessage($conversationMessage);
+
+                foreach (\array_keys($data[$conversationMessage->messageID]) as $quoteID) {
+                    $message->addQuote(
+                        $quoteID,
+                        MessageQuoteManager::getInstance()->getQuote($quoteID, false),  // single quote or excerpt
+                        MessageQuoteManager::getInstance()->getQuote($quoteID, true)    // same as above or full quote
+                    );
+                }
+
+                $quotedMessages[] = $message;
+            }
+        }
+
+        // check for orphaned quotes
+        if (\count($validMessageIDs) != \count($data)) {
+            $orphanedQuoteIDs = [];
+            foreach ($data as $messageID => $quoteIDs) {
+                if (!\in_array($messageID, $validMessageIDs)) {
+                    foreach (\array_keys($quoteIDs) as $quoteID) {
+                        $orphanedQuoteIDs[] = $quoteID;
+                    }
+                }
+            }
+
+            MessageQuoteManager::getInstance()->removeOrphanedQuotes($orphanedQuoteIDs);
+        }
+
+        return $quotedMessages;
+    }
 }
index 33cb07fe3e997b3ebb2fc0c3a906be8a327a9932..2c30ac8695e33caf4cbc9bdec3a698d33d0feb7e 100644 (file)
@@ -1,11 +1,13 @@
 <?php
+
 namespace wcf\system\moderation\queue\report;
+
+use wcf\data\conversation\Conversation;
+use wcf\data\conversation\ConversationList;
 use wcf\data\conversation\message\ConversationMessage;
 use wcf\data\conversation\message\ConversationMessageAction;
 use wcf\data\conversation\message\ConversationMessageList;
 use wcf\data\conversation\message\ViewableConversationMessage;
-use wcf\data\conversation\Conversation;
-use wcf\data\conversation\ConversationList;
 use wcf\data\moderation\queue\ModerationQueue;
 use wcf\data\moderation\queue\ViewableModerationQueue;
 use wcf\system\moderation\queue\AbstractModerationQueueHandler;
@@ -14,181 +16,190 @@ use wcf\system\WCF;
 
 /**
  * An implementation of IModerationQueueReportHandler for conversation messages.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\System\Moderation\Queue
+ *
+ * @author  Alexander Ebert
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Moderation\Queue
  */
-class ConversationMessageModerationQueueReportHandler extends AbstractModerationQueueHandler implements IModerationQueueReportHandler {
-       /**
-        * @inheritDoc
-        */
-       protected $className = ConversationMessage::class;
-       
-       /**
-        * @inheritDoc
-        */
-       protected $definitionName = 'com.woltlab.wcf.moderation.report';
-       
-       /**
-        * @inheritDoc
-        */
-       protected $objectType = 'com.woltlab.wcf.conversation.message';
-       
-       /**
-        * list of conversation message
-        * @var ConversationMessage[]
-        */
-       protected static $messages = [];
-       
-       /**
-        * @inheritDoc
-        */
-       protected $requiredPermission = 'mod.conversation.canModerateConversation';
-       
-       /**
-        * @inheritDoc
-        */
-       public function assignQueues(array $queues) {
-               $assignments = [];
-               foreach ($queues as $queue) {
-                       $assignUser = false;
-                       if (WCF::getSession()->getPermission('mod.conversation.canModerateConversation')) {
-                               $assignUser = true;
-                       }
-                               
-                       $assignments[$queue->queueID] = $assignUser;
-               }
-               
-               ModerationQueueManager::getInstance()->setAssignment($assignments);
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function canReport($objectID) {
-               if (!$this->isValid($objectID)) {
-                       return false;
-               }
-               
-               if (!Conversation::isParticipant([$this->getMessage($objectID)->conversationID])) {
-                       return false;
-               }
-               
-               return true;
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getContainerID($objectID) {
-               return 0;
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getReportedContent(ViewableModerationQueue $queue) {
-               /** @noinspection PhpParamsInspection */
-               WCF::getTPL()->assign([
-                       'message' => new ViewableConversationMessage($queue->getAffectedObject())
-               ]);
-               
-               return WCF::getTPL()->fetch('moderationConversationMessage');
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getReportedObject($objectID) {
-               if ($this->isValid($objectID)) {
-                       return $this->getMessage($objectID);
-               }
-               
-               return null;
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function isValid($objectID) {
-               if ($this->getMessage($objectID) === null) {
-                       return false;
-               }
-               
-               return true;
-       }
-       
-       /**
-        * Returns a conversation message object by message id or null if message id is invalid.
-        * 
-        * @param       integer         $objectID
-        * @return      ConversationMessage
-        */
-       protected function getMessage($objectID) {
-               if (!array_key_exists($objectID, self::$messages)) {
-                       self::$messages[$objectID] = new ConversationMessage($objectID);
-                       if (!self::$messages[$objectID]->messageID) {
-                               self::$messages[$objectID] = null;
-                       }
-               }
-               
-               return self::$messages[$objectID];
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function populate(array $queues) {
-               $objectIDs = [];
-               foreach ($queues as $object) {
-                       $objectIDs[] = $object->objectID;
-               }
-               
-               // fetch messages
-               $messageList = new ConversationMessageList();
-               $messageList->setObjectIDs($objectIDs);
-               $messageList->readObjects();
-               $messages = $messageList->getObjects();
-               
-               // set orphaned queues
-               foreach ($queues as $queue) {
-                       if (!isset($messages[$queue->objectID])) {
-                               $queue->setIsOrphaned();
-                       }
-               }
-               
-               // fetch conversations
-               $conversationIDs = [];
-               foreach ($messages as $message) {
-                       $conversationIDs[] = $message->conversationID;
-               }
-               
-               if (!empty($conversationIDs)) {
-                       $conversationList = new ConversationList();
-                       $conversationList->setObjectIDs($conversationIDs);
-                       $conversationList->readObjects();
-                       $conversations = $conversationList->getObjects();
-                       
-                       foreach ($queues as $object) {
-                               if (isset($messages[$object->objectID])) {
-                                       $message = $messages[$object->objectID];
-                                       $message->setConversation($conversations[$message->conversationID]);
-                                       
-                                       $object->setAffectedObject($message);
-                               }
-                       }
-               }
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function removeContent(ModerationQueue $queue, $message) {
-               if ($this->isValid($queue->objectID)) {
-                       $messageAction = new ConversationMessageAction([$this->getMessage($queue->objectID)], 'delete');
-                       $messageAction->executeAction();
-               }
-       }
+class ConversationMessageModerationQueueReportHandler extends AbstractModerationQueueHandler implements
+    IModerationQueueReportHandler
+{
+    /**
+     * @inheritDoc
+     */
+    protected $className = ConversationMessage::class;
+
+    /**
+     * @inheritDoc
+     */
+    protected $definitionName = 'com.woltlab.wcf.moderation.report';
+
+    /**
+     * @inheritDoc
+     */
+    protected $objectType = 'com.woltlab.wcf.conversation.message';
+
+    /**
+     * list of conversation message
+     * @var ConversationMessage[]
+     */
+    protected static $messages = [];
+
+    /**
+     * @inheritDoc
+     */
+    protected $requiredPermission = 'mod.conversation.canModerateConversation';
+
+    /**
+     * @inheritDoc
+     */
+    public function assignQueues(array $queues)
+    {
+        $assignments = [];
+        foreach ($queues as $queue) {
+            $assignUser = false;
+            if (WCF::getSession()->getPermission('mod.conversation.canModerateConversation')) {
+                $assignUser = true;
+            }
+
+            $assignments[$queue->queueID] = $assignUser;
+        }
+
+        ModerationQueueManager::getInstance()->setAssignment($assignments);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function canReport($objectID)
+    {
+        if (!$this->isValid($objectID)) {
+            return false;
+        }
+
+        if (!Conversation::isParticipant([$this->getMessage($objectID)->conversationID])) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getContainerID($objectID)
+    {
+        return 0;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getReportedContent(ViewableModerationQueue $queue)
+    {
+        /** @noinspection PhpParamsInspection */
+        WCF::getTPL()->assign([
+            'message' => new ViewableConversationMessage($queue->getAffectedObject()),
+        ]);
+
+        return WCF::getTPL()->fetch('moderationConversationMessage');
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getReportedObject($objectID)
+    {
+        if ($this->isValid($objectID)) {
+            return $this->getMessage($objectID);
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function isValid($objectID)
+    {
+        if ($this->getMessage($objectID) === null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns a conversation message object by message id or null if message id is invalid.
+     *
+     * @param integer $objectID
+     * @return  ConversationMessage
+     */
+    protected function getMessage($objectID)
+    {
+        if (!\array_key_exists($objectID, self::$messages)) {
+            self::$messages[$objectID] = new ConversationMessage($objectID);
+            if (!self::$messages[$objectID]->messageID) {
+                self::$messages[$objectID] = null;
+            }
+        }
+
+        return self::$messages[$objectID];
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function populate(array $queues)
+    {
+        $objectIDs = [];
+        foreach ($queues as $object) {
+            $objectIDs[] = $object->objectID;
+        }
+
+        // fetch messages
+        $messageList = new ConversationMessageList();
+        $messageList->setObjectIDs($objectIDs);
+        $messageList->readObjects();
+        $messages = $messageList->getObjects();
+
+        // set orphaned queues
+        foreach ($queues as $queue) {
+            if (!isset($messages[$queue->objectID])) {
+                $queue->setIsOrphaned();
+            }
+        }
+
+        // fetch conversations
+        $conversationIDs = [];
+        foreach ($messages as $message) {
+            $conversationIDs[] = $message->conversationID;
+        }
+
+        if (!empty($conversationIDs)) {
+            $conversationList = new ConversationList();
+            $conversationList->setObjectIDs($conversationIDs);
+            $conversationList->readObjects();
+            $conversations = $conversationList->getObjects();
+
+            foreach ($queues as $object) {
+                if (isset($messages[$object->objectID])) {
+                    $message = $messages[$object->objectID];
+                    $message->setConversation($conversations[$message->conversationID]);
+
+                    $object->setAffectedObject($message);
+                }
+            }
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function removeContent(ModerationQueue $queue, $message)
+    {
+        if ($this->isValid($queue->objectID)) {
+            $messageAction = new ConversationMessageAction([$this->getMessage($queue->objectID)], 'delete');
+            $messageAction->executeAction();
+        }
+    }
 }
index c6580f62bf94679e6df98a68a2e2ccbcbbc3defa..bd2f5f82f3fc81f2b7f2bb9423b282afa15dc302 100644 (file)
@@ -1,31 +1,38 @@
 <?php
+
 namespace wcf\system\page\handler;
+
 use wcf\system\conversation\ConversationHandler;
 use wcf\system\WCF;
 
 /**
  * Page handler implementation for the conversation list.
  *
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\System\Page\Handler
- * @since      3.0
+ * @author  Matthias Schmidt
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Page\Handler
+ * @since   3.0
  */
-class ConversationListPageHandler extends AbstractMenuPageHandler {
-       /** @noinspection PhpMissingParentCallCommonInspection */
-       /**
-        * @inheritDoc
-        */
-       public function getOutstandingItemCount($objectID = null) {
-               return ConversationHandler::getInstance()->getUnreadConversationCount();
-       }
-       
-       /** @noinspection PhpMissingParentCallCommonInspection */
-       /**
-        * @inheritDoc
-        */
-       public function isVisible($objectID = null) {
-               return WCF::getUser()->userID != 0;
-       }
+class ConversationListPageHandler extends AbstractMenuPageHandler
+{
+    /** @noinspection PhpMissingParentCallCommonInspection */
+
+    /**
+     * @inheritDoc
+     */
+    public function getOutstandingItemCount($objectID = null)
+    {
+        return ConversationHandler::getInstance()->getUnreadConversationCount();
+    }
+
+    /** @noinspection PhpMissingParentCallCommonInspection */
+
+    /**
+     * @inheritDoc
+     */
+    public function isVisible($objectID = null)
+    {
+        return WCF::getUser()->userID != 0;
+    }
 }
index a2b59ccaf31419988402778896c1d06adb25c1e7..974f088ffc679a06815ea01865a94dac749d9f1e 100644 (file)
@@ -1,17 +1,19 @@
 <?php
+
 namespace wcf\system\page\handler;
 
 /**
  * Default implementation of a board-related page handler.
- * 
+ *
  * Only use this class when you need the online location handling for a board-related page.
  *
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\System\Page\Handler
- * @since      3.0
+ * @author  Matthias Schmidt
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Page\Handler
+ * @since   3.0
  */
-class DefaultConversationRelatedPageHandler extends AbstractMenuPageHandler implements IOnlineLocationPageHandler {
-       use TConversationOnlineLocationPageHandler;
+class DefaultConversationRelatedPageHandler extends AbstractMenuPageHandler implements IOnlineLocationPageHandler
+{
+    use TConversationOnlineLocationPageHandler;
 }
index 96731a11691725d8c1f19ca181e5ecf09fd6a30c..1d892b75a0a30d7a45c2bbb22bcd024deb8ed763 100644 (file)
@@ -1,5 +1,7 @@
 <?php
+
 namespace wcf\system\page\handler;
+
 use wcf\data\conversation\Conversation;
 use wcf\data\page\Page;
 use wcf\data\user\online\UserOnline;
@@ -8,59 +10,67 @@ use wcf\system\WCF;
 
 /**
  * Implementation of the online location-related page handler methods for conversations.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\System\Page\Handler
- * @since      3.0
+ *
+ * @author  Matthias Schmidt
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Page\Handler
+ * @since   3.0
  */
-trait TConversationOnlineLocationPageHandler {
-       use TOnlineLocationPageHandler;
-       
-       /**
-        * Returns the textual description if a user is currently online viewing this page.
-        *
-        * @see IOnlineLocationPageHandler::getOnlineLocation()
-        *
-        * @param       Page            $page           visited page
-        * @param       UserOnline      $user           user online object with request data
-        * @return      string
-        */
-       public function getOnlineLocation(Page $page, UserOnline $user) {
-               if ($user->pageObjectID === null) {
-                       return '';
-               }
-               
-               $conversation = UserConversationRuntimeCache::getInstance()->getObject($user->pageObjectID);
-               if ($conversation === null || !$conversation->canRead()) {
-                       return '';
-               }
-               
-               if ($conversation->userID != WCF::getUser()->userID && $user->userID != WCF::getUser()->userID) {
-                       // Make sure that requests from invisible participants are not listed
-                       // if the active user is not the author of the conversation.
-                       $userConversation = Conversation::getUserConversation($conversation->conversationID, $user->userID);
-                       if ($userConversation !== null && $userConversation->isInvisible) {
-                               return '';
-                       }
-               }
-               
-               return WCF::getLanguage()->getDynamicVariable('wcf.page.onlineLocation.'.$page->identifier, ['conversation' => $conversation]);
-       }
-       
-       /**
-        * Prepares fetching all necessary data for the textual description if a user is currently online
-        * viewing this page.
-        *
-        * @see IOnlineLocationPageHandler::prepareOnlineLocation()
-        *
-        * @param       Page            $page           visited page
-        * @param       UserOnline      $user           user online object with request data
-        */
-       public function prepareOnlineLocation(/** @noinspection PhpUnusedParameterInspection */Page $page, UserOnline $user) {
-               if ($user->pageObjectID !== null) {
-                       UserConversationRuntimeCache::getInstance()->cacheObjectID($user->pageObjectID);
-               }
-       }
+trait TConversationOnlineLocationPageHandler
+{
+    use TOnlineLocationPageHandler;
+
+    /**
+     * Returns the textual description if a user is currently online viewing this page.
+     *
+     * @param Page $page visited page
+     * @param UserOnline $user user online object with request data
+     * @return  string
+     * @see IOnlineLocationPageHandler::getOnlineLocation()
+     *
+     */
+    public function getOnlineLocation(Page $page, UserOnline $user)
+    {
+        if ($user->pageObjectID === null) {
+            return '';
+        }
+
+        $conversation = UserConversationRuntimeCache::getInstance()->getObject($user->pageObjectID);
+        if ($conversation === null || !$conversation->canRead()) {
+            return '';
+        }
+
+        if ($conversation->userID != WCF::getUser()->userID && $user->userID != WCF::getUser()->userID) {
+            // Make sure that requests from invisible participants are not listed
+            // if the active user is not the author of the conversation.
+            $userConversation = Conversation::getUserConversation($conversation->conversationID, $user->userID);
+            if ($userConversation !== null && $userConversation->isInvisible) {
+                return '';
+            }
+        }
+
+        return WCF::getLanguage()->getDynamicVariable(
+            'wcf.page.onlineLocation.' . $page->identifier,
+            ['conversation' => $conversation]
+        );
+    }
+
+    /**
+     * Prepares fetching all necessary data for the textual description if a user is currently online
+     * viewing this page.
+     *
+     * @param Page $page visited page
+     * @param UserOnline $user user online object with request data
+     * @see IOnlineLocationPageHandler::prepareOnlineLocation()
+     * @noinspection PhpUnusedParameterInspection
+     */
+    public function prepareOnlineLocation(
+        Page $page,
+        UserOnline $user
+    ) {
+        if ($user->pageObjectID !== null) {
+            UserConversationRuntimeCache::getInstance()->cacheObjectID($user->pageObjectID);
+        }
+    }
 }
index 132ee0bc797d749f7aac6ae759abcedb489a47b9..dbf6b80057a4d8b1a89597784b9bb095ffa34ff0 100644 (file)
@@ -1,8 +1,10 @@
 <?php
+
 namespace wcf\system\search;
+
+use wcf\data\conversation\Conversation;
 use wcf\data\conversation\message\SearchResultConversationMessage;
 use wcf\data\conversation\message\SearchResultConversationMessageList;
-use wcf\data\conversation\Conversation;
 use wcf\form\IForm;
 use wcf\form\SearchForm;
 use wcf\system\database\util\PreparedStatementConditionBuilder;
@@ -10,149 +12,176 @@ use wcf\system\WCF;
 
 /**
  * An implementation of ISearchableObjectType for searching in conversations.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\System\Search
+ *
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Search
  */
-class ConversationMessageSearch extends AbstractSearchableObjectType {
-       /**
-        * id of the searched conversation
-        * @var integer
-        */
-       public $conversationID = 0;
-       
-       /**
-        * searched conversation
-        * @var Conversation
-        */
-       public $conversation;
-       
-       /**
-        * message data cache
-        * @var SearchResultConversationMessage[]
-        */
-       public $messageCache = [];
-       
-       /**
-        * @inheritDoc
-        */
-       public function cacheObjects(array $objectIDs, array $additionalData = null) {
-               $messageList = new SearchResultConversationMessageList();
-               $messageList->setObjectIDs($objectIDs);
-               $messageList->readObjects();
-               foreach ($messageList->getObjects() as $message) {
-                       $this->messageCache[$message->messageID] = $message;
-               }
-       }
-       
-       /** @noinspection PhpMissingParentCallCommonInspection */
-       /**
-        * @inheritDoc
-        */
-       public function getAdditionalData() {
-               return [
-                       'conversationID' => $this->conversationID
-               ];
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getObject($objectID) {
-               if (isset($this->messageCache[$objectID])) return $this->messageCache[$objectID];
-               return null;
-       }
-       
-       /** @noinspection PhpMissingParentCallCommonInspection */
-       /**
-        * @inheritDoc
-        */
-       public function getJoins() {
-               return "JOIN wcf".WCF_N."_conversation_to_user conversation_to_user ON (conversation_to_user.participantID = ".WCF::getUser()->userID." AND conversation_to_user.conversationID = ".$this->getTableName().".conversationID)
-                       LEFT JOIN wcf".WCF_N."_conversation conversation ON (conversation.conversationID = ".$this->getTableName().".conversationID)";
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getTableName() {
-               return 'wcf'.WCF_N.'_conversation_message';
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getIDFieldName() {
-               return $this->getTableName().'.messageID';
-       }
-       
-       /** @noinspection PhpMissingParentCallCommonInspection */
-       /**
-        * @inheritDoc
-        */
-       public function getSubjectFieldName() {
-               return 'conversation.subject';
-       }
-       
-       /** @noinspection PhpMissingParentCallCommonInspection */
-       /**
-        * @inheritDoc
-        */
-       public function getConditions(IForm $form = null) {
-               $conditionBuilder = new PreparedStatementConditionBuilder();
-               $conditionBuilder->add('conversation_to_user.hideConversation IN (0,1)');
-               
-               if (isset($_POST['conversationID'])) {
-                       $this->conversationID = intval($_POST['conversationID']);
-                       
-                       $conditionBuilder->add('conversation.conversationID = ?', [$this->conversationID]);
-               }
-               
-               return $conditionBuilder;
-       }
-       
-       /** @noinspection PhpMissingParentCallCommonInspection */
-       /**
-        * @inheritDoc
-        */
-       public function isAccessible() {
-               return (WCF::getUser()->userID ? true : false) && MODULE_CONVERSATION && WCF::getSession()->getPermission('user.conversation.canUseConversation');
-       }
-       
-       /** @noinspection PhpMissingParentCallCommonInspection */
-       /**
-        * @inheritDoc
-        */
-       public function getFormTemplateName() {
-               if ($this->conversation) {
-                       return 'searchConversationMessage';
-               }
-               
-               return null;
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function show(IForm $form = null) {
-               /** @var SearchForm $form */
-               
-               // get existing values
-               if ($form !== null && isset($form->searchData['additionalData']['com.woltlab.wcf.conversation.message']['conversationID'])) {
-                       $this->conversationID = $form->searchData['additionalData']['com.woltlab.wcf.conversation.message']['conversationID'];
-                       
-                       if ($this->conversationID) {
-                               $this->conversation = Conversation::getUserConversation($this->conversationID, WCF::getUser()->userID);
-                               
-                               if ($this->conversation === null || !$this->conversation->canRead()) {
-                                       $this->conversationID = 0;
-                                       $this->conversation = null;
-                               }
-                       }
-               }
-               
-               WCF::getTPL()->assign('searchedConversation', $this->conversation);
-       }
+class ConversationMessageSearch extends AbstractSearchableObjectType
+{
+    /**
+     * id of the searched conversation
+     * @var integer
+     */
+    public $conversationID = 0;
+
+    /**
+     * searched conversation
+     * @var Conversation
+     */
+    public $conversation;
+
+    /**
+     * message data cache
+     * @var SearchResultConversationMessage[]
+     */
+    public $messageCache = [];
+
+    /**
+     * @inheritDoc
+     */
+    public function cacheObjects(array $objectIDs, ?array $additionalData = null)
+    {
+        $messageList = new SearchResultConversationMessageList();
+        $messageList->setObjectIDs($objectIDs);
+        $messageList->readObjects();
+        foreach ($messageList->getObjects() as $message) {
+            $this->messageCache[$message->messageID] = $message;
+        }
+    }
+
+    /** @noinspection PhpMissingParentCallCommonInspection */
+
+    /**
+     * @inheritDoc
+     */
+    public function getAdditionalData()
+    {
+        return [
+            'conversationID' => $this->conversationID,
+        ];
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getObject($objectID)
+    {
+        if (isset($this->messageCache[$objectID])) {
+            return $this->messageCache[$objectID];
+        }
+    }
+
+    /** @noinspection PhpMissingParentCallCommonInspection */
+
+    /**
+     * @inheritDoc
+     */
+    public function getJoins()
+    {
+        return "JOIN wcf" . WCF_N . "_conversation_to_user conversation_to_user ON (conversation_to_user.participantID = " . WCF::getUser()->userID . " AND conversation_to_user.conversationID = " . $this->getTableName() . ".conversationID)
+                       LEFT JOIN wcf" . WCF_N . "_conversation conversation ON (conversation.conversationID = " . $this->getTableName() . ".conversationID)";
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getTableName()
+    {
+        return 'wcf' . WCF_N . '_conversation_message';
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getIDFieldName()
+    {
+        return $this->getTableName() . '.messageID';
+    }
+
+    /** @noinspection PhpMissingParentCallCommonInspection */
+
+    /**
+     * @inheritDoc
+     */
+    public function getSubjectFieldName()
+    {
+        return 'conversation.subject';
+    }
+
+    /** @noinspection PhpMissingParentCallCommonInspection */
+
+    /**
+     * @inheritDoc
+     */
+    public function getConditions(?IForm $form = null)
+    {
+        $conditionBuilder = new PreparedStatementConditionBuilder();
+        $conditionBuilder->add('conversation_to_user.hideConversation IN (0,1)');
+
+        if (isset($_POST['conversationID'])) {
+            $this->conversationID = \intval($_POST['conversationID']);
+
+            $conditionBuilder->add('conversation.conversationID = ?', [$this->conversationID]);
+        }
+
+        return $conditionBuilder;
+    }
+
+    /** @noinspection PhpMissingParentCallCommonInspection */
+
+    /**
+     * @inheritDoc
+     */
+    public function isAccessible()
+    {
+        if (!WCF::getUser()->userID) {
+            return false;
+        }
+        if (!MODULE_CONVERSATION) {
+            return false;
+        }
+
+        return WCF::getSession()->getPermission('user.conversation.canUseConversation');
+    }
+
+    /** @noinspection PhpMissingParentCallCommonInspection */
+
+    /**
+     * @inheritDoc
+     */
+    public function getFormTemplateName()
+    {
+        if ($this->conversation) {
+            return 'searchConversationMessage';
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function show(?IForm $form = null)
+    {
+        /** @var SearchForm $form */
+
+        // get existing values
+        if (
+            $form !== null
+            && isset($form->searchData['additionalData']['com.woltlab.wcf.conversation.message']['conversationID'])
+        ) {
+            $this->conversationID = $form->searchData['additionalData']['com.woltlab.wcf.conversation.message']['conversationID'];
+
+            if ($this->conversationID) {
+                $this->conversation = Conversation::getUserConversation($this->conversationID, WCF::getUser()->userID);
+
+                if ($this->conversation === null || !$this->conversation->canRead()) {
+                    $this->conversationID = 0;
+                    $this->conversation = null;
+                }
+            }
+        }
+
+        WCF::getTPL()->assign('searchedConversation', $this->conversation);
+    }
 }
index 11d30576fb322aa43b6983aa892bc14f01bf0eb5..b2661c1ab11ccf5e02cebf049e60d1e02bb0a1af 100644 (file)
@@ -1,22 +1,25 @@
 <?php
+
 namespace wcf\system\stat;
 
 /**
  * Stat handler implementation for conversation messages.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\System\Stat
+ *
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Stat
  */
-class ConversationMessageStatDailyHandler extends AbstractStatDailyHandler {
-       /**
-        * @inheritDoc
-        */
-       public function getData($date) {
-               return [
-                       'counter' => $this->getCounter($date, 'wcf'.WCF_N.'_conversation_message', 'time'),
-                       'total' => $this->getTotal($date, 'wcf'.WCF_N.'_conversation_message', 'time')
-               ];
-       }
+class ConversationMessageStatDailyHandler extends AbstractStatDailyHandler
+{
+    /**
+     * @inheritDoc
+     */
+    public function getData($date)
+    {
+        return [
+            'counter' => $this->getCounter($date, 'wcf' . WCF_N . '_conversation_message', 'time'),
+            'total' => $this->getTotal($date, 'wcf' . WCF_N . '_conversation_message', 'time'),
+        ];
+    }
 }
index d96c93e3b5393fd7e7102bd1997d8b602bc11eb5..c3229d7a62c95578260c26f880005a3af9071be4 100644 (file)
@@ -1,22 +1,25 @@
 <?php
+
 namespace wcf\system\stat;
 
 /**
  * Stat handler implementation for conversations.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\System\Stat
+ *
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Stat
  */
-class ConversationStatDailyHandler extends AbstractStatDailyHandler {
-       /**
-        * @inheritDoc
-        */
-       public function getData($date) {
-               return [
-                       'counter' => $this->getCounter($date, 'wcf'.WCF_N.'_conversation', 'time'),
-                       'total' => $this->getTotal($date, 'wcf'.WCF_N.'_conversation', 'time')
-               ];
-       }
+class ConversationStatDailyHandler extends AbstractStatDailyHandler
+{
+    /**
+     * @inheritDoc
+     */
+    public function getData($date)
+    {
+        return [
+            'counter' => $this->getCounter($date, 'wcf' . WCF_N . '_conversation', 'time'),
+            'total' => $this->getTotal($date, 'wcf' . WCF_N . '_conversation', 'time'),
+        ];
+    }
 }
index 0797970382576f80d0fb65019ac9e346b5a86709..58829672c5239465562ae27bd62107078412470c 100644 (file)
@@ -1,21 +1,25 @@
 <?php
+
 namespace wcf\system\user\content\provider;
+
 use wcf\data\conversation\message\ConversationMessage;
 
 /**
  * User content provider for conversation messages.
  *
- * @author     Joshua Ruesweg
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\System\User\Content\Provider
- * @since      5.2
+ * @author  Joshua Ruesweg
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\User\Content\Provider
+ * @since   5.2
  */
-class ConversationMessageUserContentProvider extends AbstractDatabaseUserContentProvider {
-       /**
-        * @inheritdoc
-        */
-       public static function getDatabaseObjectClass() {
-               return ConversationMessage::class;
-       }
+class ConversationMessageUserContentProvider extends AbstractDatabaseUserContentProvider
+{
+    /**
+     * @inheritdoc
+     */
+    public static function getDatabaseObjectClass()
+    {
+        return ConversationMessage::class;
+    }
 }
index 322b5629ecccf218aec00433c595aa69ea1866db..ccc764acb6aaab57da8ca0af4e427b40b5a702da 100644 (file)
@@ -1,21 +1,25 @@
 <?php
+
 namespace wcf\system\user\content\provider;
+
 use wcf\data\conversation\Conversation;
 
 /**
  * User content provider for conversations.
  *
- * @author     Joshua Ruesweg
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\System\User\Content\Provider
- * @since      5.2
+ * @author  Joshua Ruesweg
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\User\Content\Provider
+ * @since   5.2
  */
-class ConversationUserContentProvider extends AbstractDatabaseUserContentProvider {
-       /**
-        * @inheritdoc
-        */
-       public static function getDatabaseObjectClass() {
-               return Conversation::class;
-       }
+class ConversationUserContentProvider extends AbstractDatabaseUserContentProvider
+{
+    /**
+     * @inheritdoc
+     */
+    public static function getDatabaseObjectClass()
+    {
+        return Conversation::class;
+    }
 }
index eda19b7425e20da909f5958d5be8f62481da21e7..9faf2b49a5d4f4b970645adde4e0d5ca1a024ebe 100644 (file)
 <?php
+
 namespace wcf\system\user\notification\event;
+
 use wcf\data\user\UserProfile;
 use wcf\system\email\Email;
 use wcf\system\user\notification\object\ConversationMessageUserNotificationObject;
 
 /**
  * User notification event for conversation messages.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\System\User\Notification\Event
- * 
- * @method     ConversationMessageUserNotificationObject       getUserNotificationObject()
+ *
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\User\Notification\Event
+ *
+ * @method  ConversationMessageUserNotificationObject   getUserNotificationObject()
  */
-class ConversationMessageUserNotificationEvent extends AbstractUserNotificationEvent implements ITestableUserNotificationEvent {
-       use TTestableConversationRelatedUserNotificationEvent;
-       use TTestableUserNotificationEvent;
-       
-       /**
-        * @inheritDoc
-        */
-       protected $stackable = true;
-       
-       /**
-        * @inheritDoc
-        */
-       public function getTitle() {
-               $count = count($this->getAuthors());
-               if ($count > 1) {
-                       return $this->getLanguage()->getDynamicVariable('wcf.user.notification.conversation.message.title.stacked', ['count' => $count]);
-               }
-               
-               return $this->getLanguage()->get('wcf.user.notification.conversation.message.title');
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getMessage() {
-               $authors = array_values($this->getAuthors());
-               $count = count($authors);
-               
-               if ($count > 1) {
-                       return $this->getLanguage()->getDynamicVariable('wcf.user.notification.conversation.message.message.stacked', [
-                               'author' => $this->author,
-                               'authors' => $authors,
-                               'count' => $count,
-                               'message' => $this->userNotificationObject,
-                               'others' => $count - 1
-                       ]);
-               }
-               
-               return $this->getLanguage()->getDynamicVariable('wcf.user.notification.conversation.message.message', [
-                       'author' => $this->author,
-                       'message' => $this->userNotificationObject
-               ]);
-       }
-       
-       /** @noinspection PhpMissingParentCallCommonInspection */
-       /**
-        * @inheritDoc
-        */
-       public function getEmailMessage($notificationType = 'instant') {
-               $messageID = '<com.woltlab.wcf.conversation.notification/'.$this->getUserNotificationObject()->getConversation()->conversationID.'@'.Email::getHost().'>';
-               
-               return [
-                       'template' => 'email_notification_conversationMessage',
-                       'application' => 'wcf',
-                       'in-reply-to' => [$messageID],
-                       'references' => [$messageID]
-               ];
-       }
-       
-       /**
-        * @inheritDoc
-        * @since       5.2
-        */
-       public function getEmailTitle() {
-               if (count($this->getAuthors()) > 1) {
-                       return parent::getEmailTitle();
-               }
-               
-               return $this->getLanguage()->getDynamicVariable('wcf.user.notification.conversation.message.mail.title', [
-                       'author' => $this->author,
-                       'message' => $this->userNotificationObject
-               ]);
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getLink() {
-               return $this->getUserNotificationObject()->getLink();
-       }
-       
-       /** @noinspection PhpMissingParentCallCommonInspection */
-       /**
-        * @inheritDoc
-        */
-       public function getEventHash() {
-               return sha1($this->eventID . '-' . $this->getUserNotificationObject()->conversationID);
-       }
-       
-       /** @noinspection PhpMissingParentCallCommonInspection */
-       /**
-        * @inheritDoc
-        */
-       public function checkAccess() {
-               return $this->getUserNotificationObject()->getConversation()->canRead();
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public static function getTestObjects(UserProfile $recipient, UserProfile $author) {
-               return [new ConversationMessageUserNotificationObject(self::createTestConversationMessage($recipient, $author))];
-       }
+class ConversationMessageUserNotificationEvent extends AbstractUserNotificationEvent implements
+    ITestableUserNotificationEvent
+{
+    use TTestableConversationRelatedUserNotificationEvent;
+    use TTestableUserNotificationEvent;
+
+    /**
+     * @inheritDoc
+     */
+    protected $stackable = true;
+
+    /**
+     * @inheritDoc
+     */
+    public function getTitle()
+    {
+        $count = \count($this->getAuthors());
+        if ($count > 1) {
+            return $this->getLanguage()->getDynamicVariable(
+                'wcf.user.notification.conversation.message.title.stacked',
+                ['count' => $count]
+            );
+        }
+
+        return $this->getLanguage()->get('wcf.user.notification.conversation.message.title');
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getMessage()
+    {
+        $authors = \array_values($this->getAuthors());
+        $count = \count($authors);
+
+        if ($count > 1) {
+            return $this->getLanguage()->getDynamicVariable(
+                'wcf.user.notification.conversation.message.message.stacked',
+                [
+                    'author' => $this->author,
+                    'authors' => $authors,
+                    'count' => $count,
+                    'message' => $this->userNotificationObject,
+                    'others' => $count - 1,
+                ]
+            );
+        }
+
+        return $this->getLanguage()->getDynamicVariable('wcf.user.notification.conversation.message.message', [
+            'author' => $this->author,
+            'message' => $this->userNotificationObject,
+        ]);
+    }
+
+    /** @noinspection PhpMissingParentCallCommonInspection */
+
+    /**
+     * @inheritDoc
+     */
+    public function getEmailMessage($notificationType = 'instant')
+    {
+        $messageID = '<com.woltlab.wcf.conversation.notification/' . $this->getUserNotificationObject()->getConversation()->conversationID . '@' . Email::getHost() . '>';
+
+        return [
+            'template' => 'email_notification_conversationMessage',
+            'application' => 'wcf',
+            'in-reply-to' => [$messageID],
+            'references' => [$messageID],
+        ];
+    }
+
+    /**
+     * @inheritDoc
+     * @since   5.2
+     */
+    public function getEmailTitle()
+    {
+        if (\count($this->getAuthors()) > 1) {
+            return parent::getEmailTitle();
+        }
+
+        return $this->getLanguage()->getDynamicVariable('wcf.user.notification.conversation.message.mail.title', [
+            'author' => $this->author,
+            'message' => $this->userNotificationObject,
+        ]);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getLink()
+    {
+        return $this->getUserNotificationObject()->getLink();
+    }
+
+    /** @noinspection PhpMissingParentCallCommonInspection */
+
+    /**
+     * @inheritDoc
+     */
+    public function getEventHash()
+    {
+        return \sha1($this->eventID . '-' . $this->getUserNotificationObject()->conversationID);
+    }
+
+    /** @noinspection PhpMissingParentCallCommonInspection */
+
+    /**
+     * @inheritDoc
+     */
+    public function checkAccess()
+    {
+        return $this->getUserNotificationObject()->getConversation()->canRead();
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public static function getTestObjects(UserProfile $recipient, UserProfile $author)
+    {
+        return [
+            new ConversationMessageUserNotificationObject(self::createTestConversationMessage($recipient, $author)),
+        ];
+    }
 }
index 4fe28b871b63e1ef59e0751336048f8b570c8435..839541f4761c16c16e002e2cda7c417cbc126290 100644 (file)
@@ -1,81 +1,93 @@
 <?php
+
 namespace wcf\system\user\notification\event;
+
 use wcf\data\user\UserProfile;
 use wcf\system\user\notification\object\ConversationUserNotificationObject;
 
 /**
  * User notification event for conversations.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\System\User\Notification\Event
- * 
- * @method     ConversationUserNotificationObject      getUserNotificationObject()
+ *
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\User\Notification\Event
+ *
+ * @method  ConversationUserNotificationObject  getUserNotificationObject()
  */
-class ConversationUserNotificationEvent extends AbstractUserNotificationEvent implements ITestableUserNotificationEvent {
-       use TTestableConversationRelatedUserNotificationEvent;
-       use TTestableUserNotificationEvent;
-       
-       /**
-        * @inheritDoc
-        */
-       public function getTitle() {
-               return $this->getLanguage()->get('wcf.user.notification.conversation.title');
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getMessage() {
-               return $this->getLanguage()->getDynamicVariable('wcf.user.notification.conversation.message', [
-                       'author' => $this->author,
-                       'conversation' => $this->userNotificationObject
-               ]);
-       }
-       
-       /** @noinspection PhpMissingParentCallCommonInspection */
-       /**
-        * @inheritDoc
-        */
-       public function getEmailMessage($notificationType = 'instant') {
-               return [
-                       'message-id' => 'com.woltlab.wcf.conversation.notification/'.$this->getUserNotificationObject()->conversationID,
-                       'template' => 'email_notification_conversation',
-                       'application' => 'wcf'
-               ];
-       }
-       
-       /**
-        * @inheritDoc
-        * @since       5.2
-        */
-       public function getEmailTitle() {
-               return $this->getLanguage()->getDynamicVariable('wcf.user.notification.conversation.mail.title', [
-                       'author' => $this->author,
-                       'conversation' => $this->userNotificationObject
-               ]);
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getLink() {
-               return $this->getUserNotificationObject()->getLink();
-       }
-       
-       /** @noinspection PhpMissingParentCallCommonInspection */
-       /**
-        * @inheritDoc
-        */
-       public function checkAccess() {
-               return $this->getUserNotificationObject()->canRead();
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public static function getTestObjects(UserProfile $recipient, UserProfile $author) {
-               return [new ConversationUserNotificationObject(self::createTestConversation($author, $recipient))];
-       }
+class ConversationUserNotificationEvent extends AbstractUserNotificationEvent implements ITestableUserNotificationEvent
+{
+    use TTestableConversationRelatedUserNotificationEvent;
+    use TTestableUserNotificationEvent;
+
+    /**
+     * @inheritDoc
+     */
+    public function getTitle()
+    {
+        return $this->getLanguage()->get('wcf.user.notification.conversation.title');
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getMessage()
+    {
+        return $this->getLanguage()->getDynamicVariable('wcf.user.notification.conversation.message', [
+            'author' => $this->author,
+            'conversation' => $this->userNotificationObject,
+        ]);
+    }
+
+    /** @noinspection PhpMissingParentCallCommonInspection */
+
+    /**
+     * @inheritDoc
+     */
+    public function getEmailMessage($notificationType = 'instant')
+    {
+        return [
+            'message-id' => 'com.woltlab.wcf.conversation.notification/' . $this->getUserNotificationObject()->conversationID,
+            'template' => 'email_notification_conversation',
+            'application' => 'wcf',
+        ];
+    }
+
+    /**
+     * @inheritDoc
+     * @since   5.2
+     */
+    public function getEmailTitle()
+    {
+        return $this->getLanguage()->getDynamicVariable('wcf.user.notification.conversation.mail.title', [
+            'author' => $this->author,
+            'conversation' => $this->userNotificationObject,
+        ]);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getLink()
+    {
+        return $this->getUserNotificationObject()->getLink();
+    }
+
+    /** @noinspection PhpMissingParentCallCommonInspection */
+
+    /**
+     * @inheritDoc
+     */
+    public function checkAccess()
+    {
+        return $this->getUserNotificationObject()->canRead();
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public static function getTestObjects(UserProfile $recipient, UserProfile $author)
+    {
+        return [new ConversationUserNotificationObject(self::createTestConversation($author, $recipient))];
+    }
 }
index e27da9e42ab9c4adb8a514802e4a325b8eddc7b0..77dcfb58e72ca03dc7da6019f958ce51c09eafa7 100644 (file)
@@ -1,63 +1,68 @@
 <?php
+
 namespace wcf\system\user\notification\event;
-use wcf\data\conversation\message\ConversationMessage;
-use wcf\data\conversation\message\ConversationMessageAction;
+
 use wcf\data\conversation\Conversation;
 use wcf\data\conversation\ConversationAction;
+use wcf\data\conversation\message\ConversationMessage;
+use wcf\data\conversation\message\ConversationMessageAction;
 use wcf\data\user\UserProfile;
 
 /**
  * Provides methods to create conversations and conversation messages for testing
  * user notification events.
  *
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\System\User\Notification\Event
- * @since      3.1
+ * @author  Matthias Schmidt
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\User\Notification\Event
+ * @since   3.1
  */
-trait TTestableConversationRelatedUserNotificationEvent {
-       /**
-        * Creates a conversation for testing.
-        * 
-        * @param       UserProfile     $conversationAuthor
-        * @param       UserProfile     $participant
-        * @return      Conversation
-        */
-       public static function createTestConversation(UserProfile $conversationAuthor, UserProfile $participant) {
-               return (new ConversationAction([], 'create', [
-                       'data' => [
-                               'subject' => 'Test Conversation Subject',
-                               'time' => TIME_NOW,
-                               'userID' => $conversationAuthor->userID,
-                               'username' => $conversationAuthor->username
-                       ],
-                       'messageData' => [
-                               'message' => 'Test Conversation Message'
-                       ],
-                       'participants' => [$participant->userID]
-               ]))->executeAction()['returnValues'];
-       }
-       
-       /**
-        * Creates a conversation message for testing.
-        * 
-        * @param       UserProfile     $conversationAuthor
-        * @param       UserProfile     $messageAuthor
-        * @return      ConversationMessage
-        */
-       public static function createTestConversationMessage(UserProfile $conversationAuthor, UserProfile $messageAuthor) {
-               $conversation = self::createTestConversation($conversationAuthor, $messageAuthor);
-               
-               return (new ConversationMessageAction([], 'create', [
-                       'data' => [
-                               'conversationID' => $conversation->conversationID,
-                               'message' => 'Test Conversation Message Message',
-                               'time' => TIME_NOW,
-                               'userID' => $messageAuthor->userID,
-                               'username' => $messageAuthor->username
-                       ],
-                       'conversation' => $conversation
-               ]))->executeAction()['returnValues'];
-       }
+trait TTestableConversationRelatedUserNotificationEvent
+{
+    /**
+     * Creates a conversation for testing.
+     *
+     * @param UserProfile $conversationAuthor
+     * @param UserProfile $participant
+     * @return  Conversation
+     */
+    public static function createTestConversation(UserProfile $conversationAuthor, UserProfile $participant)
+    {
+        return (new ConversationAction([], 'create', [
+            'data' => [
+                'subject' => 'Test Conversation Subject',
+                'time' => TIME_NOW,
+                'userID' => $conversationAuthor->userID,
+                'username' => $conversationAuthor->username,
+            ],
+            'messageData' => [
+                'message' => 'Test Conversation Message',
+            ],
+            'participants' => [$participant->userID],
+        ]))->executeAction()['returnValues'];
+    }
+
+    /**
+     * Creates a conversation message for testing.
+     *
+     * @param UserProfile $conversationAuthor
+     * @param UserProfile $messageAuthor
+     * @return  ConversationMessage
+     */
+    public static function createTestConversationMessage(UserProfile $conversationAuthor, UserProfile $messageAuthor)
+    {
+        $conversation = self::createTestConversation($conversationAuthor, $messageAuthor);
+
+        return (new ConversationMessageAction([], 'create', [
+            'data' => [
+                'conversationID' => $conversation->conversationID,
+                'message' => 'Test Conversation Message Message',
+                'time' => TIME_NOW,
+                'userID' => $messageAuthor->userID,
+                'username' => $messageAuthor->username,
+            ],
+            'conversation' => $conversation,
+        ]))->executeAction()['returnValues'];
+    }
 }
index 23b8d459a42bf1ece3bdad07b6140a6591d81005..da9ea3a6dfadd800a8b6be96cc38013d0c606878 100644 (file)
@@ -1,43 +1,49 @@
 <?php
+
 namespace wcf\system\user\notification\object;
+
 use wcf\data\conversation\message\ConversationMessage;
 use wcf\data\DatabaseObjectDecorator;
 
 /**
  * Notification object for conversations.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\System\User\Notification\Object
  *
- * @method     ConversationMessage     getDecoratedObject()
- * @mixin      ConversationMessage
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\User\Notification\Object
+ *
+ * @method  ConversationMessage getDecoratedObject()
+ * @mixin   ConversationMessage
  */
-class ConversationMessageUserNotificationObject extends DatabaseObjectDecorator implements IUserNotificationObject {
-       /**
-        * @inheritDoc
-        */
-       protected static $baseClass = ConversationMessage::class;
-       
-       /**
-        * @inheritDoc
-        */
-       public function getTitle() {
-               return $this->getConversation()->subject;
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getURL() {
-               return $this->getLink();
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getAuthorID() {
-               return $this->userID;
-       }
+class ConversationMessageUserNotificationObject extends DatabaseObjectDecorator implements IUserNotificationObject
+{
+    /**
+     * @inheritDoc
+     */
+    protected static $baseClass = ConversationMessage::class;
+
+    /**
+     * @inheritDoc
+     */
+    public function getTitle()
+    {
+        return $this->getConversation()->subject;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getURL()
+    {
+        return $this->getLink();
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getAuthorID()
+    {
+        return $this->userID;
+    }
 }
index ef9e069e996418182e7cc7c2b4645aa9582e5376..ab6e27dacf3e2f207418912ebbd21fb6e8ad129b 100644 (file)
@@ -1,43 +1,49 @@
 <?php
+
 namespace wcf\system\user\notification\object;
+
 use wcf\data\conversation\Conversation;
 use wcf\data\DatabaseObjectDecorator;
 
 /**
  * Notification object for conversations.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\System\User\Notification\Object
- * 
- * @method     Conversation    getDecoratedObject()
- * @mixin      Conversation
+ *
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\User\Notification\Object
+ *
+ * @method  Conversation    getDecoratedObject()
+ * @mixin   Conversation
  */
-class ConversationUserNotificationObject extends DatabaseObjectDecorator implements IUserNotificationObject {
-       /**
-        * @inheritDoc
-        */
-       protected static $baseClass = Conversation::class;
-       
-       /**
-        * @inheritDoc
-        */
-       public function getTitle() {
-               return $this->subject;
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getURL() {
-               return $this->getLink();
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getAuthorID() {
-               return $this->userID;
-       }
+class ConversationUserNotificationObject extends DatabaseObjectDecorator implements IUserNotificationObject
+{
+    /**
+     * @inheritDoc
+     */
+    protected static $baseClass = Conversation::class;
+
+    /**
+     * @inheritDoc
+     */
+    public function getTitle()
+    {
+        return $this->subject;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getURL()
+    {
+        return $this->getLink();
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getAuthorID()
+    {
+        return $this->userID;
+    }
 }
index 16c9e42e26720b2fc9c49eb03f764fe586bbf7a9..f55fca1d3a5a1825b8289fccc76e753246fe9594 100644 (file)
@@ -1,30 +1,33 @@
 <?php
+
 namespace wcf\system\user\notification\object\type;
+
 use wcf\data\conversation\message\ConversationMessage;
 use wcf\data\conversation\message\ConversationMessageList;
 use wcf\system\user\notification\object\ConversationMessageUserNotificationObject;
 
 /**
  * Represents a conversation message notification object type.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\System\User\Notification\Object\Type
+ *
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\User\Notification\Object\Type
  */
-class ConversationMessageNotificationObjectType extends AbstractUserNotificationObjectType {
-       /**
-        * @inheritDoc
-        */
-       protected static $decoratorClassName = ConversationMessageUserNotificationObject::class;
-       
-       /**
-        * @inheritDoc
-        */
-       protected static $objectClassName = ConversationMessage::class;
-       
-       /**
-        * @inheritDoc
-        */
-       protected static $objectListClassName = ConversationMessageList::class;
+class ConversationMessageNotificationObjectType extends AbstractUserNotificationObjectType
+{
+    /**
+     * @inheritDoc
+     */
+    protected static $decoratorClassName = ConversationMessageUserNotificationObject::class;
+
+    /**
+     * @inheritDoc
+     */
+    protected static $objectClassName = ConversationMessage::class;
+
+    /**
+     * @inheritDoc
+     */
+    protected static $objectListClassName = ConversationMessageList::class;
 }
index 90853b645626b091bacdb93d6a56e2253e58631e..f1b8af8900f65dc67fd3398a1b5867cf17703730 100644 (file)
@@ -1,5 +1,7 @@
 <?php
+
 namespace wcf\system\user\notification\object\type;
+
 use wcf\data\conversation\Conversation;
 use wcf\data\conversation\ConversationList;
 use wcf\system\user\notification\object\ConversationUserNotificationObject;
@@ -7,52 +9,55 @@ use wcf\system\WCF;
 
 /**
  * Represents a conversation notification object type.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\System\User\Notification\Object\Type
+ *
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\User\Notification\Object\Type
  */
-class ConversationNotificationObjectType extends AbstractUserNotificationObjectType {
-       /**
-        * @inheritDoc
-        */
-       protected static $decoratorClassName = ConversationUserNotificationObject::class;
-       
-       /**
-        * @inheritDoc
-        */
-       protected static $objectClassName = Conversation::class;
-       
-       /**
-        * @inheritDoc
-        */
-       protected static $objectListClassName = ConversationList::class;
-       
-       /** @noinspection PhpMissingParentCallCommonInspection */
-       /**
-        * @inheritDoc
-        */
-       public function getObjectsByIDs(array $objectIDs) {
-               $objects = Conversation::getUserConversations($objectIDs, WCF::getUser()->userID);
-               
-               foreach ($objects as $objectID => $conversation) {
-                       $objects[$objectID] = new static::$decoratorClassName($conversation);
-               }
-               
-               foreach ($objectIDs as $objectID) {
-                       // append empty objects for unknown ids
-                       if (!isset($objects[$objectID])) {
-                               // '__unknownNotificationObject' tells the notification API
-                               // that the object does not exist anymore so that the related
-                               // notification can be deleted automatically
-                               $objects[$objectID] = new static::$decoratorClassName(new static::$objectClassName(null, [
-                                       '__unknownNotificationObject' => true,
-                                       'conversationID' => $objectID
-                               ]));
-                       }
-               }
-               
-               return $objects;
-       }
+class ConversationNotificationObjectType extends AbstractUserNotificationObjectType
+{
+    /**
+     * @inheritDoc
+     */
+    protected static $decoratorClassName = ConversationUserNotificationObject::class;
+
+    /**
+     * @inheritDoc
+     */
+    protected static $objectClassName = Conversation::class;
+
+    /**
+     * @inheritDoc
+     */
+    protected static $objectListClassName = ConversationList::class;
+
+    /** @noinspection PhpMissingParentCallCommonInspection */
+
+    /**
+     * @inheritDoc
+     */
+    public function getObjectsByIDs(array $objectIDs)
+    {
+        $objects = Conversation::getUserConversations($objectIDs, WCF::getUser()->userID);
+
+        foreach ($objects as $objectID => $conversation) {
+            $objects[$objectID] = new static::$decoratorClassName($conversation);
+        }
+
+        foreach ($objectIDs as $objectID) {
+            // append empty objects for unknown ids
+            if (!isset($objects[$objectID])) {
+                // '__unknownNotificationObject' tells the notification API
+                // that the object does not exist anymore so that the related
+                // notification can be deleted automatically
+                $objects[$objectID] = new static::$decoratorClassName(new static::$objectClassName(null, [
+                    '__unknownNotificationObject' => true,
+                    'conversationID' => $objectID,
+                ]));
+            }
+        }
+
+        return $objects;
+    }
 }
index 0b85755a0446636fb00519d72d43d03c499a5661..92385415a5c4d9b535dfe1208f4324efc5f92a46 100644 (file)
@@ -1,5 +1,7 @@
 <?php
+
 namespace wcf\system\worker;
+
 use wcf\data\conversation\message\ConversationMessage;
 use wcf\data\conversation\message\ConversationMessageList;
 use wcf\data\object\type\ObjectTypeCache;
@@ -11,157 +13,180 @@ use wcf\system\WCF;
 
 /**
  * Worker implementation for updating conversation messages.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\System\Worker
- * 
- * @method     ConversationMessageList         getObjectList()
+ *
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Worker
+ *
+ * @method  ConversationMessageList     getObjectList()
  */
-class ConversationMessageRebuildDataWorker extends AbstractRebuildDataWorker {
-       /**
-        * @inheritDoc
-        */
-       protected $limit = 500;
-       
-       /**
-        * @var HtmlInputProcessor
-        */
-       protected $htmlInputProcessor;
-       
-       /** @noinspection PhpMissingParentCallCommonInspection */
-       /**
-        * @inheritDoc
-        */
-       public function countObjects() {
-               if ($this->count === null) {
-                       $this->count = 0;
-                       $sql = "SELECT  MAX(messageID) AS messageID
-                               FROM    wcf".WCF_N."_conversation_message";
-                       $statement = WCF::getDB()->prepareStatement($sql);
-                       $statement->execute();
-                       $row = $statement->fetchArray();
-                       if ($row !== false) $this->count = $row['messageID'];
-               }
-       }
-       
-       /** @noinspection PhpMissingParentCallCommonInspection */
-       /**
-        * @inheritDoc
-        */
-       protected function initObjectList() {
-               $this->objectList = new ConversationMessageList();
-               $this->objectList->sqlOrderBy = 'conversation_message.messageID';
-               $this->objectList->sqlSelects = '(SELECT subject FROM wcf'.WCF_N.'_conversation WHERE conversationID = conversation_message.conversationID) AS subject';
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function execute() {
-               $this->objectList->getConditionBuilder()->add('conversation_message.messageID BETWEEN ? AND ?', [$this->limit * $this->loopCount + 1, $this->limit * $this->loopCount + $this->limit]);
-               
-               parent::execute();
-               
-               if (!$this->loopCount) {
-                       // reset search index
-                       SearchIndexManager::getInstance()->reset('com.woltlab.wcf.conversation.message');
-               }
-               
-               if (!count($this->objectList)) {
-                       return;
-               }
-               
-               // prepare statements
-               $attachmentObjectType = ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.attachment.objectType', 'com.woltlab.wcf.conversation.message');
-               $sql = "SELECT          COUNT(*) AS attachments
-                       FROM            wcf".WCF_N."_attachment
+class ConversationMessageRebuildDataWorker extends AbstractRebuildDataWorker
+{
+    /**
+     * @inheritDoc
+     */
+    protected $limit = 500;
+
+    /**
+     * @var HtmlInputProcessor
+     */
+    protected $htmlInputProcessor;
+
+    /** @noinspection PhpMissingParentCallCommonInspection */
+
+    /**
+     * @inheritDoc
+     */
+    public function countObjects()
+    {
+        if ($this->count === null) {
+            $this->count = 0;
+            $sql = "SELECT     MAX(messageID) AS messageID
+                               FROM    wcf" . WCF_N . "_conversation_message";
+            $statement = WCF::getDB()->prepareStatement($sql);
+            $statement->execute();
+            $row = $statement->fetchArray();
+            if ($row !== false) {
+                $this->count = $row['messageID'];
+            }
+        }
+    }
+
+    /** @noinspection PhpMissingParentCallCommonInspection */
+
+    /**
+     * @inheritDoc
+     */
+    protected function initObjectList()
+    {
+        $this->objectList = new ConversationMessageList();
+        $this->objectList->sqlOrderBy = 'conversation_message.messageID';
+        $this->objectList->sqlSelects = '(SELECT subject FROM wcf' . WCF_N . '_conversation WHERE conversationID = conversation_message.conversationID) AS subject';
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function execute()
+    {
+        $this->objectList->getConditionBuilder()->add(
+            'conversation_message.messageID BETWEEN ? AND ?',
+            [$this->limit * $this->loopCount + 1, $this->limit * $this->loopCount + $this->limit]
+        );
+
+        parent::execute();
+
+        if (!$this->loopCount) {
+            // reset search index
+            SearchIndexManager::getInstance()->reset('com.woltlab.wcf.conversation.message');
+        }
+
+        if (!\count($this->objectList)) {
+            return;
+        }
+
+        // prepare statements
+        $attachmentObjectType = ObjectTypeCache::getInstance()
+            ->getObjectTypeByName('com.woltlab.wcf.attachment.objectType', 'com.woltlab.wcf.conversation.message');
+        $sql = "SELECT         COUNT(*) AS attachments
+                       FROM            wcf" . WCF_N . "_attachment
                        WHERE           objectTypeID = ?
                                        AND objectID = ?";
-               $attachmentStatement = WCF::getDB()->prepareStatement($sql);
-               
-               // retrieve permissions
-               $userIDs = [];
-               foreach ($this->objectList as $object) {
-                       // passing `0` is actually valid, because it won't yield any results when querying the group membership
-                       $userIDs[] = ($object->userID ?: 0);
-               }
-               $userPermissions = $this->getBulkUserPermissions($userIDs, ['user.message.disallowedBBCodes']);
-               
-               $updateData = [];
-               /** @var ConversationMessage $message */
-               foreach ($this->objectList as $message) {
-                       SearchIndexManager::getInstance()->set(
-                               'com.woltlab.wcf.conversation.message',
-                               $message->messageID,
-                               $message->message,
-                               $message->subject ?: '',
-                               $message->time,
-                               $message->userID,
-                               $message->username
-                       );
-                       
-                       $data = [];
-                       
-                       // count attachments
-                       $attachmentStatement->execute([$attachmentObjectType->objectTypeID, $message->messageID]);
-                       $data['attachments'] = $attachmentStatement->fetchSingleColumn();
-                       
-                       BBCodeHandler::getInstance()->setDisallowedBBCodes(explode(',', $this->getBulkUserPermissionValue($userPermissions, $message->userID, 'user.message.disallowedBBCodes')));
-                       
-                       // update message
-                       $data['enableHtml'] = 1;
-                       if (!$message->enableHtml) {
-                               $this->getHtmlInputProcessor()->process($message->message, 'com.woltlab.wcf.conversation.message', $message->messageID, true);
-                               $data['message'] = $this->getHtmlInputProcessor()->getHtml();
-                       }
-                       else {
-                               $this->getHtmlInputProcessor()->reprocess($message->message, 'com.woltlab.wcf.conversation.message', $message->messageID);
-                               $data['message'] = $this->getHtmlInputProcessor()->getHtml();
-                       }
-                       
-                       if (MessageEmbeddedObjectManager::getInstance()->registerObjects($this->getHtmlInputProcessor(), true)) {
-                               $data['hasEmbeddedObjects'] = 1;
-                       }
-                       else {
-                               $data['hasEmbeddedObjects'] = 0;
-                       }
-                       
-                       $updateData[$message->messageID] = $data;
-               }
-               
-               $sql = "UPDATE  wcf".WCF_N."_conversation_message
+        $attachmentStatement = WCF::getDB()->prepareStatement($sql);
+
+        // retrieve permissions
+        $userIDs = [];
+        foreach ($this->objectList as $object) {
+            // passing `0` is actually valid, because it won't yield any results when querying the group membership
+            $userIDs[] = ($object->userID ?: 0);
+        }
+        $userPermissions = $this->getBulkUserPermissions($userIDs, ['user.message.disallowedBBCodes']);
+
+        $updateData = [];
+        /** @var ConversationMessage $message */
+        foreach ($this->objectList as $message) {
+            SearchIndexManager::getInstance()->set(
+                'com.woltlab.wcf.conversation.message',
+                $message->messageID,
+                $message->message,
+                $message->subject ?: '',
+                $message->time,
+                $message->userID,
+                $message->username
+            );
+
+            $data = [];
+
+            // count attachments
+            $attachmentStatement->execute([$attachmentObjectType->objectTypeID, $message->messageID]);
+            $data['attachments'] = $attachmentStatement->fetchSingleColumn();
+
+            BBCodeHandler::getInstance()->setDisallowedBBCodes(\explode(
+                ',',
+                $this->getBulkUserPermissionValue($userPermissions, $message->userID, 'user.message.disallowedBBCodes')
+            ));
+
+            // update message
+            $data['enableHtml'] = 1;
+            if (!$message->enableHtml) {
+                $this->getHtmlInputProcessor()->process(
+                    $message->message,
+                    'com.woltlab.wcf.conversation.message',
+                    $message->messageID,
+                    true
+                );
+                $data['message'] = $this->getHtmlInputProcessor()->getHtml();
+            } else {
+                $this->getHtmlInputProcessor()->reprocess(
+                    $message->message,
+                    'com.woltlab.wcf.conversation.message',
+                    $message->messageID
+                );
+                $data['message'] = $this->getHtmlInputProcessor()->getHtml();
+            }
+
+            if (MessageEmbeddedObjectManager::getInstance()->registerObjects($this->getHtmlInputProcessor(), true)) {
+                $data['hasEmbeddedObjects'] = 1;
+            } else {
+                $data['hasEmbeddedObjects'] = 0;
+            }
+
+            $updateData[$message->messageID] = $data;
+        }
+
+        $sql = "UPDATE  wcf" . WCF_N . "_conversation_message
                        SET     attachments = ?,
                                message = ?,
                                enableHtml = ?,
                                hasEmbeddedObjects = ?
                        WHERE   messageID = ?";
-               $statement = WCF::getDB()->prepareStatement($sql);
-               
-               WCF::getDB()->beginTransaction();
-               foreach ($updateData as $messageID => $data) {
-                       $statement->execute([
-                               $data['attachments'],
-                               $data['message'],
-                               $data['enableHtml'],
-                               $data['hasEmbeddedObjects'],
-                               $messageID
-                       ]);
-               }
-               WCF::getDB()->commitTransaction();
-               
-               MessageEmbeddedObjectManager::getInstance()->commitBulkOperation();
-       }
-       
-       /**
-        * @return      HtmlInputProcessor
-        */
-       protected function getHtmlInputProcessor() {
-               if ($this->htmlInputProcessor === null) {
-                       $this->htmlInputProcessor = new HtmlInputProcessor();
-               }
-               
-               return $this->htmlInputProcessor;
-       }
+        $statement = WCF::getDB()->prepareStatement($sql);
+
+        WCF::getDB()->beginTransaction();
+        foreach ($updateData as $messageID => $data) {
+            $statement->execute([
+                $data['attachments'],
+                $data['message'],
+                $data['enableHtml'],
+                $data['hasEmbeddedObjects'],
+                $messageID,
+            ]);
+        }
+        WCF::getDB()->commitTransaction();
+
+        MessageEmbeddedObjectManager::getInstance()->commitBulkOperation();
+    }
+
+    /**
+     * @return  HtmlInputProcessor
+     */
+    protected function getHtmlInputProcessor()
+    {
+        if ($this->htmlInputProcessor === null) {
+            $this->htmlInputProcessor = new HtmlInputProcessor();
+        }
+
+        return $this->htmlInputProcessor;
+    }
 }
index 66615b6030c4b20749120ca1f76ab9af9e43354a..f8720fe09aee924d084eef538143f6721cffc54f 100644 (file)
@@ -1,5 +1,7 @@
 <?php
+
 namespace wcf\system\worker;
+
 use wcf\data\conversation\Conversation;
 use wcf\data\conversation\ConversationAction;
 use wcf\data\conversation\ConversationEditor;
@@ -8,161 +10,180 @@ use wcf\system\WCF;
 
 /**
  * Worker implementation for updating conversations.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\System\Worker
- * 
- * @method     ConversationList        getObjectList()
+ *
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Worker
+ *
+ * @method  ConversationList    getObjectList()
  */
-class ConversationRebuildDataWorker extends AbstractRebuildDataWorker {
-       /**
-        * @inheritDoc
-        */
-       protected $limit = 100;
-       
-       /** @noinspection PhpMissingParentCallCommonInspection */
-       /**
-        * @inheritDoc
-        */
-       public function countObjects() {
-               if ($this->count === null) {
-                       $this->count = 0;
-                       $sql = "SELECT  MAX(conversationID) AS conversationID
-                               FROM    wcf".WCF_N."_conversation";
-                       $statement = WCF::getDB()->prepareStatement($sql);
-                       $statement->execute();
-                       $row = $statement->fetchArray();
-                       if ($row !== false) $this->count = $row['conversationID'];
-               }
-       }
-       
-       /** @noinspection PhpMissingParentCallCommonInspection */
-       /**
-        * @inheritDoc
-        */
-       protected function initObjectList() {
-               $this->objectList = new ConversationList();
-               $this->objectList->sqlOrderBy = 'conversation.conversationID';
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function execute() {
-               $this->objectList->getConditionBuilder()->add('conversation.conversationID BETWEEN ? AND ?', [$this->limit * $this->loopCount + 1, $this->limit * $this->loopCount + $this->limit]);
-               
-               parent::execute();
-               
-               // prepare statements
-               $sql = "SELECT          messageID, time, userID, username
-                       FROM            wcf".WCF_N."_conversation_message
+class ConversationRebuildDataWorker extends AbstractRebuildDataWorker
+{
+    /**
+     * @inheritDoc
+     */
+    protected $limit = 100;
+
+    /** @noinspection PhpMissingParentCallCommonInspection */
+
+    /**
+     * @inheritDoc
+     */
+    public function countObjects()
+    {
+        if ($this->count === null) {
+            $this->count = 0;
+            $sql = "SELECT     MAX(conversationID) AS conversationID
+                               FROM    wcf" . WCF_N . "_conversation";
+            $statement = WCF::getDB()->prepareStatement($sql);
+            $statement->execute();
+            $row = $statement->fetchArray();
+            if ($row !== false) {
+                $this->count = $row['conversationID'];
+            }
+        }
+    }
+
+    /** @noinspection PhpMissingParentCallCommonInspection */
+
+    /**
+     * @inheritDoc
+     */
+    protected function initObjectList()
+    {
+        $this->objectList = new ConversationList();
+        $this->objectList->sqlOrderBy = 'conversation.conversationID';
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function execute()
+    {
+        $this->objectList->getConditionBuilder()->add(
+            'conversation.conversationID BETWEEN ? AND ?',
+            [$this->limit * $this->loopCount + 1, $this->limit * $this->loopCount + $this->limit]
+        );
+
+        parent::execute();
+
+        // prepare statements
+        $sql = "SELECT         messageID, time, userID, username
+                       FROM            wcf" . WCF_N . "_conversation_message
                        WHERE           conversationID = ?
                        ORDER BY        time";
-               $firstMessageStatement = WCF::getDB()->prepareStatement($sql, 1);
-               $sql = "SELECT          time, userID, username
-                       FROM            wcf".WCF_N."_conversation_message
+        $firstMessageStatement = WCF::getDB()->prepareStatement($sql, 1);
+        $sql = "SELECT         time, userID, username
+                       FROM            wcf" . WCF_N . "_conversation_message
                        WHERE           conversationID = ?
                        ORDER BY        time DESC";
-               $lastMessageStatement = WCF::getDB()->prepareStatement($sql, 1);
-               $sql = "SELECT  COUNT(*) AS messages,
+        $lastMessageStatement = WCF::getDB()->prepareStatement($sql, 1);
+        $sql = "SELECT COUNT(*) AS messages,
                                SUM(attachments) AS attachments
-                       FROM    wcf".WCF_N."_conversation_message
+                       FROM    wcf" . WCF_N . "_conversation_message
                        WHERE   conversationID = ?";
-               $statsStatement = WCF::getDB()->prepareStatement($sql);
-               $sql = "SELECT  COUNT(*) AS participants
-                       FROM    wcf".WCF_N."_conversation_to_user conversation_to_user
+        $statsStatement = WCF::getDB()->prepareStatement($sql);
+        $sql = "SELECT COUNT(*) AS participants
+                       FROM    wcf" . WCF_N . "_conversation_to_user conversation_to_user
                        WHERE   conversation_to_user.conversationID = ?
                                AND conversation_to_user.hideConversation <> ?
                                AND conversation_to_user.participantID <> ?
                                AND conversation_to_user.isInvisible = ?";
-               $participantCounterStatement = WCF::getDB()->prepareStatement($sql);
-               $sql = "SELECT          conversation_to_user.participantID AS userID, conversation_to_user.hideConversation, user_table.username
-                       FROM            wcf".WCF_N."_conversation_to_user conversation_to_user
-                       LEFT JOIN       wcf".WCF_N."_user user_table
+        $participantCounterStatement = WCF::getDB()->prepareStatement($sql);
+        $sql = "SELECT         conversation_to_user.participantID AS userID, conversation_to_user.hideConversation, user_table.username
+                       FROM            wcf" . WCF_N . "_conversation_to_user conversation_to_user
+                       LEFT JOIN       wcf" . WCF_N . "_user user_table
                        ON              (user_table.userID = conversation_to_user.participantID)
                        WHERE           conversation_to_user.conversationID = ?
                                        AND conversation_to_user.participantID <> ?
                                        AND conversation_to_user.isInvisible = ?
                        ORDER BY        user_table.username";
-               $participantStatement = WCF::getDB()->prepareStatement($sql, 5);
-               
-               $sql = "SELECT  COUNT(*) AS participants
-                       FROM    wcf".WCF_N."_conversation_to_user
+        $participantStatement = WCF::getDB()->prepareStatement($sql, 5);
+
+        $sql = "SELECT COUNT(*) AS participants
+                       FROM    wcf" . WCF_N . "_conversation_to_user
                        WHERE   conversationID = ?
                                AND hideConversation <> ?
                                AND participantID IS NOT NULL";
-               $existingParticipantStatement = WCF::getDB()->prepareStatement($sql);
-               
-               $obsoleteConversations = [];
-               $updateData = [];
-               /** @var Conversation $conversation */
-               foreach ($this->objectList as $conversation) {
-                       // check for obsolete conversations
-                       $obsolete = false;
-                       if ($conversation->isDraft) {
-                               if (!$conversation->userID) $obsolete = true;
-                       }
-                       else {
-                               $existingParticipantStatement->execute([$conversation->conversationID, Conversation::STATE_LEFT]);
-                               $row = $existingParticipantStatement->fetchSingleRow();
-                               if (!$row['participants']) $obsolete = true;
-                       }
-                       if ($obsolete) {
-                               $obsoleteConversations[] = new ConversationEditor($conversation);
-                               continue;
-                       }
-                       
-                       // update data
-                       $data = [
-                               'firstMessageID' => $conversation->firstMessageID,
-                               'lastPostTime' => $conversation->lastPostTime,
-                               'lastPosterID' => $conversation->lastPosterID,
-                               'lastPoster' => $conversation->lastPoster,
-                               'userID' => $conversation->userID,
-                               'username' => $conversation->username
-                       ];
-                       
-                       // get first post
-                       $firstMessageStatement->execute([$conversation->conversationID]);
-                       if (($row = $firstMessageStatement->fetchSingleRow()) !== false) {
-                               $data['firstMessageID'] = $row['messageID'];
-                               $data['lastPostTime'] = $data['time'] = $row['time'];
-                               $data['userID'] = $row['userID'];
-                               $data['username'] = $row['username'];
-                       }
-                       
-                       // get last post
-                       $lastMessageStatement->execute([$conversation->conversationID]);
-                       if (($row = $lastMessageStatement->fetchSingleRow()) !== false) {
-                               $data['lastPostTime'] = $row['time'];
-                               $data['lastPosterID'] = $row['userID'];
-                               $data['lastPoster'] = $row['username'];
-                       }
-                       
-                       // get stats
-                       $statsStatement->execute([$conversation->conversationID]);
-                       $row = $statsStatement->fetchSingleRow();
-                       $data['replies'] = ($row['messages'] ? $row['messages'] - 1 : 0);
-                       $data['attachments'] = ($row['attachments'] ?: 0);
-                       
-                       // get number of participants
-                       $participantCounterStatement->execute([$conversation->conversationID, Conversation::STATE_LEFT, $conversation->userID, 0]);
-                       $data['participants'] = $participantCounterStatement->fetchSingleColumn();
-                       
-                       // get participant summary
-                       $participantStatement->execute([$conversation->conversationID, $conversation->userID, 0]);
-                       $users = [];
-                       while ($row = $participantStatement->fetchArray()) {
-                               $users[] = $row;
-                       }
-                       $data['participantSummary'] = serialize($users);
-                       
-                       $updateData[$conversation->conversationID] = $data;
-               }
-               
-               $sql = "UPDATE  wcf".WCF_N."_conversation
+        $existingParticipantStatement = WCF::getDB()->prepareStatement($sql);
+
+        $obsoleteConversations = [];
+        $updateData = [];
+        /** @var Conversation $conversation */
+        foreach ($this->objectList as $conversation) {
+            // check for obsolete conversations
+            $obsolete = false;
+            if ($conversation->isDraft) {
+                if (!$conversation->userID) {
+                    $obsolete = true;
+                }
+            } else {
+                $existingParticipantStatement->execute([$conversation->conversationID, Conversation::STATE_LEFT]);
+                $row = $existingParticipantStatement->fetchSingleRow();
+                if (!$row['participants']) {
+                    $obsolete = true;
+                }
+            }
+            if ($obsolete) {
+                $obsoleteConversations[] = new ConversationEditor($conversation);
+                continue;
+            }
+
+            // update data
+            $data = [
+                'firstMessageID' => $conversation->firstMessageID,
+                'lastPostTime' => $conversation->lastPostTime,
+                'lastPosterID' => $conversation->lastPosterID,
+                'lastPoster' => $conversation->lastPoster,
+                'userID' => $conversation->userID,
+                'username' => $conversation->username,
+            ];
+
+            // get first post
+            $firstMessageStatement->execute([$conversation->conversationID]);
+            if (($row = $firstMessageStatement->fetchSingleRow()) !== false) {
+                $data['firstMessageID'] = $row['messageID'];
+                $data['lastPostTime'] = $data['time'] = $row['time'];
+                $data['userID'] = $row['userID'];
+                $data['username'] = $row['username'];
+            }
+
+            // get last post
+            $lastMessageStatement->execute([$conversation->conversationID]);
+            if (($row = $lastMessageStatement->fetchSingleRow()) !== false) {
+                $data['lastPostTime'] = $row['time'];
+                $data['lastPosterID'] = $row['userID'];
+                $data['lastPoster'] = $row['username'];
+            }
+
+            // get stats
+            $statsStatement->execute([$conversation->conversationID]);
+            $row = $statsStatement->fetchSingleRow();
+            $data['replies'] = ($row['messages'] ? $row['messages'] - 1 : 0);
+            $data['attachments'] = ($row['attachments'] ?: 0);
+
+            // get number of participants
+            $participantCounterStatement->execute([
+                $conversation->conversationID,
+                Conversation::STATE_LEFT,
+                $conversation->userID,
+                0,
+            ]);
+            $data['participants'] = $participantCounterStatement->fetchSingleColumn();
+
+            // get participant summary
+            $participantStatement->execute([$conversation->conversationID, $conversation->userID, 0]);
+            $users = [];
+            while ($row = $participantStatement->fetchArray()) {
+                $users[] = $row;
+            }
+            $data['participantSummary'] = \serialize($users);
+
+            $updateData[$conversation->conversationID] = $data;
+        }
+
+        $sql = "UPDATE  wcf" . WCF_N . "_conversation
                        SET     firstMessageID = ?,
                                lastPostTime = ?,
                                lastPosterID = ?,
@@ -174,30 +195,30 @@ class ConversationRebuildDataWorker extends AbstractRebuildDataWorker {
                                participants = ?,
                                participantSummary = ?
                        WHERE   conversationID = ?";
-               $statement = WCF::getDB()->prepareStatement($sql);
-               
-               WCF::getDB()->beginTransaction();
-               foreach ($updateData as $conversationID => $data) {
-                       $statement->execute([
-                               $data['firstMessageID'],
-                               $data['lastPostTime'],
-                               $data['lastPosterID'],
-                               $data['lastPoster'],
-                               $data['userID'],
-                               $data['username'],
-                               $data['replies'],
-                               $data['attachments'],
-                               $data['participants'],
-                               $data['participantSummary'],
-                               $conversationID
-                       ]);
-               }
-               WCF::getDB()->commitTransaction();
-               
-               // delete obsolete conversations
-               if (!empty($obsoleteConversations)) {
-                       $action = new ConversationAction($obsoleteConversations, 'delete');
-                       $action->executeAction();
-               }
-       }
+        $statement = WCF::getDB()->prepareStatement($sql);
+
+        WCF::getDB()->beginTransaction();
+        foreach ($updateData as $conversationID => $data) {
+            $statement->execute([
+                $data['firstMessageID'],
+                $data['lastPostTime'],
+                $data['lastPosterID'],
+                $data['lastPoster'],
+                $data['userID'],
+                $data['username'],
+                $data['replies'],
+                $data['attachments'],
+                $data['participants'],
+                $data['participantSummary'],
+                $conversationID,
+            ]);
+        }
+        WCF::getDB()->commitTransaction();
+
+        // delete obsolete conversations
+        if (!empty($obsoleteConversations)) {
+            $action = new ConversationAction($obsoleteConversations, 'delete');
+            $action->executeAction();
+        }
+    }
 }