--- /dev/null
+{
+ "problemMatcher": [
+ {
+ "owner": "node -c",
+ "pattern": [
+ {
+ "regexp": "^(./\\S+):(\\d+) - (.*)$",
+ "file": 1,
+ "line": 2,
+ "message": 3
+ }
+ ]
+ }
+ ]
+}
--- /dev/null
+{
+ "problemMatcher": [
+ {
+ "owner": "php -l",
+ "pattern": [
+ {
+ "regexp": "^\\s*(PHP\\s+)?([a-zA-Z\\s]+):\\s+(.*)\\s+in\\s+(\\S+)\\s+on\\s+line\\s+(\\d+)$",
+ "file": 4,
+ "line": 5,
+ "message": 3
+ }
+ ]
+ }
+ ]
+}
--- /dev/null
+name: Code Style
+
+on:
+ push:
+ branches:
+ - "5.2"
+ - "5.3"
+ - master
+ pull_request:
+
+jobs:
+ php:
+ name: PHP CodeSniffer
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - run: git clone --branch=5.2 --depth=1 --quiet git://github.com/WoltLab/WCF.git WCF
+ - uses: chekalsky/phpcs-action@e269c2f264f400adcda7c6b24c8550302350d495
--- /dev/null
+name: JavaScript
+
+on:
+ push:
+ branches:
+ - "5.2"
+ - "5.3"
+ - master
+ pull_request:
+
+jobs:
+ syntax:
+ name: "Check Syntax"
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ steps:
+ - name: Set up node.js
+ uses: actions/setup-node@v1
+ with:
+ node-version: "12"
+ - uses: actions/checkout@v2
+ - run: echo "::add-matcher::.github/javascript-syntax.json"
+ - name: Remove files to be ignored
+ run: |
+ true
+ - run: |
+ ! find . -type f -name '*.js' -exec node -c '{}' \; 2>&1 \
+ |awk 'BEGIN {m=0} /(.js):[0-9]+$/ {m=1; printf "%s - ",$0} m==1 && /^SyntaxError/ { m=0; print }' \
+ |sed "s@$(pwd)@.@" \
+ |grep '^'
--- /dev/null
+name: PHP
+
+on:
+ push:
+ branches:
+ - "5.2"
+ - "5.3"
+ - master
+ pull_request:
+
+jobs:
+ syntax:
+ name: "Check Syntax (${{ matrix.php }})"
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ php:
+ - '7.0'
+ - '7.1'
+ - '7.2'
+ - '7.3'
+ - '7.4'
+ - '8.0'
+ steps:
+ - name: Set up PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ - uses: actions/checkout@v2
+ - run: echo "::add-matcher::.github/php-syntax.json"
+ - name: Remove files to be ignored
+ run: |
+ true
+ - run: |
+ ! find . -type f -name '*.php' -exec php -l '{}' \; 2>&1 |grep -v '^No syntax errors detected'
--- /dev/null
+<?xml version="1.0"?>
+<ruleset>
+ <file>files/</file>
+ <arg name="extensions" value="php" />
+ <arg value="p"/>
+ <arg name="basepath" value="."/>
+
+ <rule ref="./WCF/CodeSniff/WCF/ruleset.xml"/>
+</ruleset>
+++ /dev/null
-language: php
-sudo: false
-dist: trusty
-php:
- - 7.1
- - 5.5
-before_install:
- - export PATH="$PATH:$(composer global config bin-dir --absolute)"
- - composer global require "squizlabs/php_codesniffer=3.*"
- - phpenv rehash
-before_script:
- - git clone --branch=master --depth=1 --quiet git://github.com/WoltLab/WCF.git WCF
-script:
- - find files -type f -name '*.php' |xargs -I file php -l file
- - phpcs -p --extensions=php --standard="`pwd`/WCF/CodeSniff/WCF" files
--- /dev/null
+{if MODULE_CONVERSATION && ($action == 'add' || $group->groupType > 3)}
+ <dl>
+ <dt></dt>
+ <dd>
+ <label><input type="checkbox" id="canBeAddedAsConversationParticipant" name="canBeAddedAsConversationParticipant" value="1"{if $canBeAddedAsConversationParticipant} checked{/if}> {lang}wcf.acp.group.canBeAddedAsConversationParticipant{/lang}</label>
+ </dd>
+ </dl>
+{/if}
<?xml version="1.0" encoding="UTF-8"?>
-<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/vortex/clipboardAction.xsd">
+<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/2019/clipboardAction.xsd">
<import>
<!-- conversation -->
<action name="assignLabel">
<page>wcf\page\ConversationListPage</page>
</pages>
</action>
- <!-- /conversation -->
</import>
</data>
* Defines constants for autocompletion in IDEs. This file is not meant to be actively used anywhere!
*
* @author Matthias Schmidt
- * @copyright 2001-2017 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core
*/
<?xml version="1.0" encoding="UTF-8"?>
-<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/vortex/coreObject.xsd">
+<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/2019/coreObject.xsd">
<import>
<coreobject>
<objectname>wcf\system\conversation\ConversationHandler</objectname>
<?xml version="1.0" encoding="UTF-8"?>
-<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/vortex/eventListener.xsd">
+<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/2019/eventListener.xsd">
<import>
<eventlistener name="userMerge">
<eventclassname>wcf\acp\form\UserMergeForm</eventclassname>
<listenerclassname>wcf\system\event\listener\ConversationUserMergeListener</listenerclassname>
<environment>admin</environment>
</eventlistener>
-
<eventlistener name="userRenameUser">
<eventclassname>wcf\data\user\UserAction</eventclassname>
<eventname>rename</eventname>
<listenerclassname>wcf\system\event\listener\ConversationUserActionRenameListener</listenerclassname>
- <environment>user</environment>
</eventlistener>
<eventlistener name="userRenameAdmin">
<eventclassname>wcf\data\user\UserAction</eventclassname>
<listenerclassname>wcf\system\event\listener\ConversationUserActionRenameListener</listenerclassname>
<environment>admin</environment>
</eventlistener>
- </import>
-
- <delete>
- <eventlistener>
- <eventclassname>wcf\acp\form\UserMergeForm</eventclassname>
- <eventname>save</eventname>
- <listenerclassname>wcf\system\event\listener\ConversationUserMergeListener</listenerclassname>
+ <eventlistener name="pruneIpAddresses">
+ <eventclassname>wcf\system\cronjob\PruneIpAddressesCronjob</eventclassname>
+ <eventname>execute</eventname>
+ <listenerclassname>wcf\system\event\listener\ConversationPruneIpAddressesCronjobListener</listenerclassname>
+ <environment>user</environment>
+ </eventlistener>
+ <eventlistener name="pruneIpAddressesAdmin">
+ <eventclassname>wcf\system\cronjob\PruneIpAddressesCronjob</eventclassname>
+ <eventname>execute</eventname>
+ <listenerclassname>wcf\system\event\listener\ConversationPruneIpAddressesCronjobListener</listenerclassname>
<environment>admin</environment>
</eventlistener>
-
- <eventlistener>
- <eventclassname>wcf\data\user\UserAction</eventclassname>
- <eventname>rename</eventname>
- <listenerclassname>wcf\system\event\listener\ConversationUserActionRenameListener</listenerclassname>
- <environment>user</environment>
+ <eventlistener name="userGroupAddCanBeAddedAsConversationParticipant">
+ <eventclassname>wcf\acp\form\UserGroupAddForm</eventclassname>
+ <eventname>assignVariables,readFormParameters,save</eventname>
+ <listenerclassname>wcf\system\event\listener\UserGroupAddCanBeAddedAsConversationParticipantListener</listenerclassname>
+ <environment>admin</environment>
+ <inherit>1</inherit>
</eventlistener>
- <eventlistener>
- <eventclassname>wcf\data\user\UserAction</eventclassname>
- <eventname>rename</eventname>
- <listenerclassname>wcf\system\event\listener\ConversationUserActionRenameListener</listenerclassname>
+ <eventlistener name="userGroupEditCanBeAddedAsConversationParticipant">
+ <eventclassname>wcf\acp\form\UserGroupEditForm</eventclassname>
+ <eventname>readData</eventname>
+ <listenerclassname>wcf\system\event\listener\UserGroupAddCanBeAddedAsConversationParticipantListener</listenerclassname>
<environment>admin</environment>
</eventlistener>
- </delete>
+ </import>
</data>
/**
* @author Matthias Schmidt
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
*/
// set default mod permissions
+++ /dev/null
-<?php
-use wcf\system\package\SplitNodeException;
-use wcf\system\WCF;
-use wcf\util\StringUtil;
-
-/**
- * Adds database columns, each row in the data section
- * below is executed in a separate request.
- *
- * @author Alexander Ebert
- * @copyright 2001-2018 WoltLab GmbH
- * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package WoltLabSuite\Core\Conversation
- */
-$data = <<<DATA
-ALTER TABLE wcf1_conversation_to_user ADD COLUMN joinedAt INT(10) NOT NULL DEFAULT 0, ADD COLUMN leftAt INT(10) NOT NULL DEFAULT 0, ADD COLUMN lastMessageID INT(10) NULL;
-DATA;
-
-$lines = explode("\n", StringUtil::trim($data));
-
-$rebuildData = WCF::getSession()->getVar('__wcfConversationUpdateAddColumns');
-if ($rebuildData === null) {
- $rebuildData = [
- 'i' => 0,
- 'max' => count($lines)
- ];
-}
-
-// MySQL adds a column by creating a new table in the
-// background and copying over all the data afterwards.
-//
-// Using a single `ALTER TABLE` to add multiple columns
-// results in the same runtime, because copying the table
-// is what actually takes ages.
-$statement = WCF::getDB()->prepareStatement(str_replace('wcf1_', 'wcf'.WCF_N.'_', $lines[$rebuildData['i']]));
-$statement->execute();
-
-$rebuildData['i']++;
-
-if ($rebuildData['i'] === $rebuildData['max']) {
- WCF::getSession()->unregister('__wcfConversationUpdateAddColumns');
-}
-else {
- WCF::getSession()->register('__wcfConversationUpdateAddColumns', $rebuildData);
-
- // call this script again
- throw new SplitNodeException();
-}
--- /dev/null
+<?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
+ * @copyright 2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ */
+
+$tables = [
+ 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())
+)->process();
* Namespace for conversations.
*
* @author Alexander Ebert
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
*/
WCF.Conversation = { };
-define("WoltLabSuite/Core/Conversation/Ui/Participant/Add",["Ajax","Language","Ui/Dialog","Ui/Notification","WoltLabSuite/Core/Ui/ItemList/User"],function(t,e,n,i,a){"use strict";function s(t){this.init(t)}return s.prototype={init:function(e){this._conversationId=e,t.api(this,{actionName:"getAddParticipantsForm"})},_ajaxSetup:function(){return{data:{className:"wcf\\data\\conversation\\ConversationAction",objectIDs:[this._conversationId]}}},_ajaxSuccess:function(t){switch(t.actionName){case"addParticipants":this._handleResponse(t);break;case"getAddParticipantsForm":this._render(t)}},_handleResponse:function(t){if(t.returnValues.errorMessage){var e=elCreate("small");e.className="innerError",e.textContent=t.returnValues.errorMessage;var a=elById("participantsInput").closest(".inputItemList");a.parentNode.insertBefore(e,a.nextSibling);var s=e.nextElementSibling;return void(s&&s.classList.contains("innerError")&&elRemove(s))}t.returnValues.count&&i.show(t.returnValues.successMessage,window.location.reload.bind(window.location)),n.close(this)},_render:function(t){n.open(this,t.returnValues.template);var e=elById("addParticipants");e.disabled=!0,a.init("participantsInput",{callbackChange:function(t,n){e.disabled=0===n.length},excludedSearchValues:t.returnValues.excludedSearchValues,maxItems:t.returnValues.maxItems}),e.addEventListener("click",this._submit.bind(this))},_submit:function(){for(var e=a.getValues("participantsInput"),i=[],s=0,o=e.length;s<o;s++)i.push(e[s].value);var r={participants:i},c=elBySel('input[name="messageVisibility"]:checked, input[name="messageVisibility"][type="hidden"]',n.getDialog(this).content);c&&(r.visibility=c.value),t.api(this,{actionName:"addParticipants",parameters:r})},_dialogSetup:function(){return{id:"conversationAddParticipants",options:{title:e.get("wcf.conversation.edit.addParticipants")},source:null}}},s}),define("WoltLabSuite/Core/Conversation/Ui/Subject/Editor",["Ajax","EventKey","Language","Ui/Dialog","Ui/Notification"],function(t,e,n,i,a){"use strict";var s=0,o=null;return{beginEdit:function(t){s=t,i.open(this)},_saveEdit:function(e){e.preventDefault();var i=o.nextElementSibling;i&&i.classList.contains("innerError")&&elRemove(i);var a=o.value.trim();""===a?(i=elCreate("small"),i.className="innerError",i.textContent=n.get("wcf.global.form.error.empty"),o.parentNode.insertBefore(i,o.nextElementSibling)):t.api(this,{parameters:{subject:a},objectIDs:[s]})},_getCurrentValue:function(){var t="";return elBySelAll('.jsConversationSubject[data-conversation-id="'+s+'"], .conversationLink[data-conversation-id="'+s+'"]',void 0,function(e){t=e.textContent}),t},_ajaxSuccess:function(t){i.close(this),elBySelAll('.jsConversationSubject[data-conversation-id="'+s+'"], .conversationLink[data-conversation-id="'+s+'"]',void 0,function(e){e.textContent=t.returnValues.subject}),a.show()},_dialogSetup:function(){return{id:"dialogConversationSubjectEditor",options:{onSetup:function(t){o=elById("jsConversationSubject"),o.addEventListener("keyup",function(t){e.Enter(t)&&this._saveEdit(t)}.bind(this)),elBySel(".jsButtonSave",t).addEventListener(WCF_CLICK_EVENT,this._saveEdit.bind(this))}.bind(this),onShow:function(){o.value=this._getCurrentValue()}.bind(this),title:n.get("wcf.conversation.edit.subject")},source:'<dl><dt><label for="jsConversationSubject">'+n.get("wcf.global.subject")+'</label></dt><dd><input type="text" id="jsConversationSubject" class="long" maxlength="255"></dd></dl><div class="formSubmit"><button class="buttonPrimary jsButtonSave">'+n.get("wcf.global.button.save")+"</button></div>"}},_ajaxSetup:function(){return{data:{actionName:"editSubject",className:"wcf\\data\\conversation\\ConversationAction"}}}}});
\ No newline at end of file
+define("WoltLabSuite/Core/Conversation/Ui/Participant/Add",["Ajax","Language","Ui/Dialog","Ui/Notification","WoltLabSuite/Core/Ui/ItemList/User"],function(t,e,n,i,a){"use strict";function s(t){this.init(t)}return s.prototype={init:function(e){this._conversationId=e,t.api(this,{actionName:"getAddParticipantsForm"})},_ajaxSetup:function(){return{data:{className:"wcf\\data\\conversation\\ConversationAction",objectIDs:[this._conversationId]}}},_ajaxSuccess:function(t){switch(t.actionName){case"addParticipants":this._handleResponse(t);break;case"getAddParticipantsForm":this._render(t)}},_handleResponse:function(t){if(t.returnValues.errorMessage){var e=elCreate("small");e.className="innerError",e.textContent=t.returnValues.errorMessage;var a=elById("participantsInput").closest(".inputItemList");a.parentNode.insertBefore(e,a.nextSibling);var s=e.nextElementSibling;return void(s&&s.classList.contains("innerError")&&elRemove(s))}t.returnValues.count&&i.show(t.returnValues.successMessage,window.location.reload.bind(window.location)),n.close(this)},_render:function(t){n.open(this,t.returnValues.template);var e=elById("addParticipants");e.disabled=!0,a.init("participantsInput",{callbackChange:function(t,n){e.disabled=0===n.length},excludedSearchValues:t.returnValues.excludedSearchValues,maxItems:t.returnValues.maxItems,includeUserGroups:t.returnValues.canAddGroupParticipants&&t.returnValues.restrictUserGroupIDs.length>0,restrictUserGroupIDs:t.returnValues.restrictUserGroupIDs,csvPerType:!0}),e.addEventListener("click",this._submit.bind(this))},_submit:function(){for(var e=a.getValues("participantsInput"),i=[],s=[],r=0,o=e.length;r<o;r++)"group"===e[r].type?s.push(e[r].objectId):i.push(e[r].value);var c={participants:i,participantsGroupIDs:s},u=elBySel('input[name="messageVisibility"]:checked, input[name="messageVisibility"][type="hidden"]',n.getDialog(this).content);u&&(c.visibility=u.value),t.api(this,{actionName:"addParticipants",parameters:c})},_dialogSetup:function(){return{id:"conversationAddParticipants",options:{title:e.get("wcf.conversation.edit.addParticipants")},source:null}}},s}),define("WoltLabSuite/Core/Conversation/Ui/Subject/Editor",["Ajax","EventKey","Language","Ui/Dialog","Ui/Notification"],function(t,e,n,i,a){"use strict";var s=0,r=null;return{beginEdit:function(t){s=t,i.open(this)},_saveEdit:function(e){e.preventDefault();var i=r.nextElementSibling;i&&i.classList.contains("innerError")&&elRemove(i);var a=r.value.trim();""===a?(i=elCreate("small"),i.className="innerError",i.textContent=n.get("wcf.global.form.error.empty"),r.parentNode.insertBefore(i,r.nextElementSibling)):t.api(this,{parameters:{subject:a},objectIDs:[s]})},_getCurrentValue:function(){var t="";return elBySelAll('.jsConversationSubject[data-conversation-id="'+s+'"], .conversationLink[data-conversation-id="'+s+'"]',void 0,function(e){t=e.textContent}),t},_ajaxSuccess:function(t){i.close(this),elBySelAll('.jsConversationSubject[data-conversation-id="'+s+'"], .conversationLink[data-conversation-id="'+s+'"]',void 0,function(e){e.textContent=t.returnValues.subject}),a.show()},_dialogSetup:function(){return{id:"dialogConversationSubjectEditor",options:{onSetup:function(t){r=elById("jsConversationSubject"),r.addEventListener("keyup",function(t){e.Enter(t)&&this._saveEdit(t)}.bind(this)),elBySel(".jsButtonSave",t).addEventListener(WCF_CLICK_EVENT,this._saveEdit.bind(this))}.bind(this),onShow:function(){r.value=this._getCurrentValue()}.bind(this),title:n.get("wcf.conversation.edit.subject")},source:'<dl><dt><label for="jsConversationSubject">'+n.get("wcf.global.subject")+'</label></dt><dd><input type="text" id="jsConversationSubject" class="long" maxlength="255"></dd></dl><div class="formSubmit"><button class="buttonPrimary jsButtonSave">'+n.get("wcf.global.button.save")+"</button></div>"}},_ajaxSetup:function(){return{data:{actionName:"editSubject",className:"wcf\\data\\conversation\\ConversationAction"}}}}});
\ No newline at end of file
-define("WoltLabSuite/Core/Conversation/Ui/Participant/Add",["Ajax","Language","Ui/Dialog","Ui/Notification","WoltLabSuite/Core/Ui/ItemList/User"],function(t,e,n,i,a){"use strict";function s(t){this.init(t)}return s.prototype={init:function(e){this._conversationId=e,t.api(this,{actionName:"getAddParticipantsForm"})},_ajaxSetup:function(){return{data:{className:"wcf\\data\\conversation\\ConversationAction",objectIDs:[this._conversationId]}}},_ajaxSuccess:function(t){switch(t.actionName){case"addParticipants":this._handleResponse(t);break;case"getAddParticipantsForm":this._render(t)}},_handleResponse:function(t){if(t.returnValues.errorMessage){var e=elCreate("small");e.className="innerError",e.textContent=t.returnValues.errorMessage;var a=elById("participantsInput").closest(".inputItemList");a.parentNode.insertBefore(e,a.nextSibling);var s=e.nextElementSibling;return void(s&&s.classList.contains("innerError")&&elRemove(s))}t.returnValues.count&&i.show(t.returnValues.successMessage,window.location.reload.bind(window.location)),n.close(this)},_render:function(t){n.open(this,t.returnValues.template);var e=elById("addParticipants");e.disabled=!0,a.init("participantsInput",{callbackChange:function(t,n){e.disabled=0===n.length},excludedSearchValues:t.returnValues.excludedSearchValues,maxItems:t.returnValues.maxItems}),e.addEventListener("click",this._submit.bind(this))},_submit:function(){for(var e=a.getValues("participantsInput"),i=[],s=0,o=e.length;s<o;s++)i.push(e[s].value);var r={participants:i},c=elBySel('input[name="messageVisibility"]:checked, input[name="messageVisibility"][type="hidden"]',n.getDialog(this).content);c&&(r.visibility=c.value),t.api(this,{actionName:"addParticipants",parameters:r})},_dialogSetup:function(){return{id:"conversationAddParticipants",options:{title:e.get("wcf.conversation.edit.addParticipants")},source:null}}},s}),define("WoltLabSuite/Core/Conversation/Ui/Subject/Editor",["Ajax","EventKey","Language","Ui/Dialog","Ui/Notification"],function(t,e,n,i,a){"use strict";var s=0,o=null;return{beginEdit:function(t){s=t,i.open(this)},_saveEdit:function(e){e.preventDefault();var i=o.nextElementSibling;i&&i.classList.contains("innerError")&&elRemove(i);var a=o.value.trim();""===a?(i=elCreate("small"),i.className="innerError",i.textContent=n.get("wcf.global.form.error.empty"),o.parentNode.insertBefore(i,o.nextElementSibling)):t.api(this,{parameters:{subject:a},objectIDs:[s]})},_getCurrentValue:function(){var t="";return elBySelAll('.jsConversationSubject[data-conversation-id="'+s+'"], .conversationLink[data-conversation-id="'+s+'"]',void 0,function(e){t=e.textContent}),t},_ajaxSuccess:function(t){i.close(this),elBySelAll('.jsConversationSubject[data-conversation-id="'+s+'"], .conversationLink[data-conversation-id="'+s+'"]',void 0,function(e){e.textContent=t.returnValues.subject}),a.show()},_dialogSetup:function(){return{id:"dialogConversationSubjectEditor",options:{onSetup:function(t){o=elById("jsConversationSubject"),o.addEventListener("keyup",function(t){e.Enter(t)&&this._saveEdit(t)}.bind(this)),elBySel(".jsButtonSave",t).addEventListener(WCF_CLICK_EVENT,this._saveEdit.bind(this))}.bind(this),onShow:function(){o.value=this._getCurrentValue()}.bind(this),title:n.get("wcf.conversation.edit.subject")},source:'<dl><dt><label for="jsConversationSubject">'+n.get("wcf.global.subject")+'</label></dt><dd><input type="text" id="jsConversationSubject" class="long" maxlength="255"></dd></dl><div class="formSubmit"><button class="buttonPrimary jsButtonSave">'+n.get("wcf.global.button.save")+"</button></div>"}},_ajaxSetup:function(){return{data:{actionName:"editSubject",className:"wcf\\data\\conversation\\ConversationAction"}}}}});
\ No newline at end of file
+define("WoltLabSuite/Core/Conversation/Ui/Participant/Add",["Ajax","Language","Ui/Dialog","Ui/Notification","WoltLabSuite/Core/Ui/ItemList/User"],function(t,e,n,i,a){"use strict";function s(t){this.init(t)}return s.prototype={init:function(e){this._conversationId=e,t.api(this,{actionName:"getAddParticipantsForm"})},_ajaxSetup:function(){return{data:{className:"wcf\\data\\conversation\\ConversationAction",objectIDs:[this._conversationId]}}},_ajaxSuccess:function(t){switch(t.actionName){case"addParticipants":this._handleResponse(t);break;case"getAddParticipantsForm":this._render(t)}},_handleResponse:function(t){if(t.returnValues.errorMessage){var e=elCreate("small");e.className="innerError",e.textContent=t.returnValues.errorMessage;var a=elById("participantsInput").closest(".inputItemList");a.parentNode.insertBefore(e,a.nextSibling);var s=e.nextElementSibling;return void(s&&s.classList.contains("innerError")&&elRemove(s))}t.returnValues.count&&i.show(t.returnValues.successMessage,window.location.reload.bind(window.location)),n.close(this)},_render:function(t){n.open(this,t.returnValues.template);var e=elById("addParticipants");e.disabled=!0,a.init("participantsInput",{callbackChange:function(t,n){e.disabled=0===n.length},excludedSearchValues:t.returnValues.excludedSearchValues,maxItems:t.returnValues.maxItems,includeUserGroups:t.returnValues.canAddGroupParticipants&&t.returnValues.restrictUserGroupIDs.length>0,restrictUserGroupIDs:t.returnValues.restrictUserGroupIDs,csvPerType:!0}),e.addEventListener("click",this._submit.bind(this))},_submit:function(){for(var e=a.getValues("participantsInput"),i=[],s=[],r=0,o=e.length;r<o;r++)"group"===e[r].type?s.push(e[r].objectId):i.push(e[r].value);var c={participants:i,participantsGroupIDs:s},u=elBySel('input[name="messageVisibility"]:checked, input[name="messageVisibility"][type="hidden"]',n.getDialog(this).content);u&&(c.visibility=u.value),t.api(this,{actionName:"addParticipants",parameters:c})},_dialogSetup:function(){return{id:"conversationAddParticipants",options:{title:e.get("wcf.conversation.edit.addParticipants")},source:null}}},s}),define("WoltLabSuite/Core/Conversation/Ui/Subject/Editor",["Ajax","EventKey","Language","Ui/Dialog","Ui/Notification"],function(t,e,n,i,a){"use strict";var s=0,r=null;return{beginEdit:function(t){s=t,i.open(this)},_saveEdit:function(e){e.preventDefault();var i=r.nextElementSibling;i&&i.classList.contains("innerError")&&elRemove(i);var a=r.value.trim();""===a?(i=elCreate("small"),i.className="innerError",i.textContent=n.get("wcf.global.form.error.empty"),r.parentNode.insertBefore(i,r.nextElementSibling)):t.api(this,{parameters:{subject:a},objectIDs:[s]})},_getCurrentValue:function(){var t="";return elBySelAll('.jsConversationSubject[data-conversation-id="'+s+'"], .conversationLink[data-conversation-id="'+s+'"]',void 0,function(e){t=e.textContent}),t},_ajaxSuccess:function(t){i.close(this),elBySelAll('.jsConversationSubject[data-conversation-id="'+s+'"], .conversationLink[data-conversation-id="'+s+'"]',void 0,function(e){e.textContent=t.returnValues.subject}),a.show()},_dialogSetup:function(){return{id:"dialogConversationSubjectEditor",options:{onSetup:function(t){r=elById("jsConversationSubject"),r.addEventListener("keyup",function(t){e.Enter(t)&&this._saveEdit(t)}.bind(this)),elBySel(".jsButtonSave",t).addEventListener(WCF_CLICK_EVENT,this._saveEdit.bind(this))}.bind(this),onShow:function(){r.value=this._getCurrentValue()}.bind(this),title:n.get("wcf.conversation.edit.subject")},source:'<dl><dt><label for="jsConversationSubject">'+n.get("wcf.global.subject")+'</label></dt><dd><input type="text" id="jsConversationSubject" class="long" maxlength="255"></dd></dl><div class="formSubmit"><button class="buttonPrimary jsButtonSave">'+n.get("wcf.global.button.save")+"</button></div>"}},_ajaxSetup:function(){return{data:{actionName:"editSubject",className:"wcf\\data\\conversation\\ConversationAction"}}}}});
\ No newline at end of file
* Adds participants to an existing conversation.
*
* @author Alexander Ebert
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @module WoltLabSuite/Core/Conversation/Ui/Participant/Add
*/
UiItemListUser.init('participantsInput', {
callbackChange: function(elementId, values) { buttonSubmit.disabled = (values.length === 0); },
excludedSearchValues: data.returnValues.excludedSearchValues,
- maxItems: data.returnValues.maxItems
+ maxItems: data.returnValues.maxItems,
+ includeUserGroups: data.returnValues.canAddGroupParticipants && data.returnValues.restrictUserGroupIDs.length > 0,
+ restrictUserGroupIDs: data.returnValues.restrictUserGroupIDs,
+ csvPerType: true
});
buttonSubmit.addEventListener('click', this._submit.bind(this));
* Sends a request to add participants.
*/
_submit: function() {
- var values = UiItemListUser.getValues('participantsInput'), participants = [];
+ var values = UiItemListUser.getValues('participantsInput'), participants = [], participantsGroupIDs = [];
for (var i = 0, length = values.length; i < length; i++) {
- participants.push(values[i].value);
+ if (values[i].type === 'group') participantsGroupIDs.push(values[i].objectId);
+ else participants.push(values[i].value);
}
var parameters = {
- participants: participants
+ participants: participants,
+ participantsGroupIDs: participantsGroupIDs
};
var visibility = elBySel('input[name="messageVisibility"]:checked, input[name="messageVisibility"][type="hidden"]', UiDialog.getDialog(this).content);
* Provides the editor for conversation subjects.
*
* @author Alexander Ebert
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @module WoltLabSuite/Core/Conversation/Ui/Subject/Editor
*/
<?php
namespace wcf\data\conversation;
use wcf\data\conversation\message\ConversationMessage;
+use wcf\data\user\group\UserGroup;
use wcf\data\user\UserProfile;
use wcf\data\DatabaseObject;
use wcf\data\ITitledLinkObject;
+use wcf\system\cache\runtime\UserProfileRuntimeCache;
use wcf\system\conversation\ConversationHandler;
use wcf\system\database\util\PreparedStatementConditionBuilder;
use wcf\system\exception\UserInputException;
* Represents a conversation.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\Data\Conversation
*
* Returns a list of the usernames of all participants.
*
* @param boolean $excludeSelf
+ * @param boolean $leftByOwnChoice
* @return string[]
*/
- public function getParticipantNames($excludeSelf = false) {
+ 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
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.
*
use wcf\data\AbstractDatabaseObjectAction;
use wcf\data\IClipboardAction;
use wcf\data\IVisitableObjectAction;
+use wcf\data\user\group\UserGroup;
use wcf\system\clipboard\ClipboardHandler;
use wcf\system\conversation\ConversationHandler;
use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\event\EventHandler;
use wcf\system\exception\PermissionDeniedException;
use wcf\system\exception\UserInputException;
use wcf\system\log\modification\ConversationModificationLogHandler;
* Executes conversation-related actions.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\Data\Conversation
*
* conversation object
* @var ConversationEditor
*/
- protected $conversation;
+ public $conversation;
/**
* list of conversation data modifications
}
$returnValues = [
- 'totalCount' => ConversationHandler::getInstance()->getUnreadConversationCount(null, true)
+ 'totalCount' => ConversationHandler::getInstance()->getUnreadConversationCount($this->parameters['userID'], true)
];
if (count($conversationIDs) == 1) {
* @return array
*/
public function getAddParticipantsForm() {
+ $restrictUserGroupIDs = [];
+ foreach (UserGroup::getAllGroups() as $group) {
+ if ($group->canBeAddedAsConversationParticipant) {
+ $restrictUserGroupIDs[] = $group->groupID;
+ }
+ }
+
return [
- 'excludedSearchValues' => $this->conversation->getParticipantNames(),
+ 'excludedSearchValues' => $this->conversation->getParticipantNames(false, true),
'maxItems' => WCF::getSession()->getPermission('user.conversation.maxParticipants') - $this->conversation->participants,
- 'template' => WCF::getTPL()->fetch('conversationAddParticipants', 'wcf', ['conversation' => $this->conversation])
+ 'canAddGroupParticipants' => WCF::getSession()->getPermission('user.conversation.canAddGroupParticipants'),
+ 'template' => WCF::getTPL()->fetch('conversationAddParticipants', 'wcf', ['conversation' => $this->conversation]),
+ 'restrictUserGroupIDs' => $restrictUserGroupIDs,
];
}
$this->validateGetAddParticipantsForm();
// validate participants
- $this->readStringArray('participants');
+ $this->readStringArray('participants', true);
+ $this->readIntegerArray('participantsGroupIDs', true);
if (!$this->conversation->getDecoratedObject()->isDraft) {
$this->readString('visibility');
public function addParticipants() {
try {
$participantIDs = Conversation::validateParticipants($this->parameters['participants'], 'participants', $this->conversation->getParticipantIDs(true));
+ if (!empty($this->parameters['participantsGroupIDs']) && WCF::getSession()->getPermission('user.conversation.canAddGroupParticipants')) {
+ $participantIDs = array_merge($participantIDs, Conversation::validateGroupParticipants($this->parameters['participantsGroupIDs'], 'participants', $this->conversation->getParticipantIDs(true)));
+ $participantIDs = array_unique($participantIDs);
+ }
+
+ $parameters = [
+ 'participantIDs' => $participantIDs,
+ ];
+ EventHandler::getInstance()->fireAction($this, 'addParticipants_validateParticipants', $parameters);
+ $participantIDs = $parameters['participantIDs'];
}
catch (UserInputException $e) {
$errorMessage = '';
* Extends the conversation object with functions to create, update and delete conversations.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\Data\Conversation
*
(conversationID, participantID, username, isInvisible, joinedAt)
VALUES (?, ?, ?, ?, ?)
ON DUPLICATE KEY
- UPDATE hideConversation = 0";
+ UPDATE hideConversation = 0, leftAt = 0, leftByOwnChoice = 1";
$statement = WCF::getDB()->prepareStatement($sql);
foreach ($participantIDs as $userID) {
$sql = "UPDATE wcf".WCF_N."_conversation_to_user
SET leftAt = ?,
- lastMessageID = ?
+ lastMessageID = ?,
+ leftByOwnChoice = ?
WHERE conversationID = ?
AND participantID = ?";
$statement = WCF::getDB()->prepareStatement($sql);
$statement->execute([
TIME_NOW,
$lastMessageID ?: null,
+ 0,
$this->conversationID,
$userID
]);
* Represents a list of conversations.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\Data\Conversation
*
* Represents a list of conversation participants.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\Data\Conversation
*/
* Represents a conversation for RSS feeds.
*
* @author Alexander Ebert
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\Data\Conversation
*
* Represents a list of conversations for RSS feeds.
*
* @author Alexander Ebert
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\Data\Conversation
*
* Represents a list of conversations.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\Data\Conversation
*
* Represents a viewable conversation.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\Data\Conversation
*
* Represents a conversation label.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\Data\Conversation\Label
*
* Executes label-related actions.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\Data\Conversation\Label
*
* Extends the label object with functions to create, update and delete labels.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\Data\Conversation\Label
*
* Represents a list of conversation labels.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\Data\Conversation\Label
*
* Represents a conversation message.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\Data\Conversation\Message
*
* Executes conversation message-related actions.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\Data\Conversation\Message
*
* Extends the message object with functions to create, update and delete messages.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\Data\Conversation\Message
*
* Represents a list of conversation messages.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\Data\Conversation\Message
*
* Represents a list of search result.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\Data\Conversation\Message
*
* Represents a list of search results.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\Data\Conversation\Message
*
* Disables the loading of attachments and embedded objects by default.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\Data\Conversation\Message
*/
* Represents a viewable conversation message.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\Data\Conversation\Message
*
* Represents a list of viewable conversation messages.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\Data\Conversation\Message
*
* Represents a list of modification logs for conversation log page.
*
* @author Alexander Ebert
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\Data\Modification\Log
*
* Provides a viewable conversation modification log.
*
* @author Alexander Ebert
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\Data\Modification\Log
*
namespace wcf\form;
use wcf\data\conversation\Conversation;
use wcf\data\conversation\ConversationAction;
+use wcf\data\user\group\UserGroup;
+use wcf\system\cache\builder\UserGroupCacheBuilder;
use wcf\system\cache\runtime\UserProfileRuntimeCache;
use wcf\system\conversation\ConversationHandler;
use wcf\system\exception\IllegalLinkException;
use wcf\system\page\PageLocationManager;
use wcf\system\request\LinkHandler;
use wcf\system\WCF;
+use wcf\util\ArrayUtil;
use wcf\util\HeaderUtil;
use wcf\util\StringUtil;
* Shows the conversation form.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\Form
*/
*/
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
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) && !$this->draft) {
+ 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)) {
+ if (!WCF::getSession()->getPermission('user.conversation.canAddInvisibleParticipants') && (!empty($this->invisibleParticipants) || !empty($this->invisibleParticipantsGroupIDs))) {
throw new UserInputException('participants', 'invisibleParticipantsNoPermission');
}
$this->participantIDs = Conversation::validateParticipants($this->participants);
$this->invisibleParticipantIDs = Conversation::validateParticipants($this->invisibleParticipants, 'invisibleParticipants');
+ if (!empty($this->participantsGroupIDs)) {
+ $this->participantIDs = array_merge($this->participantIDs, Conversation::validateGroupParticipants($this->participantsGroupIDs));
+ $this->participantIDs = array_unique($this->participantIDs);
+ }
+ if (!empty($this->invisibleParticipantsGroupIDs)) {
+ $this->invisibleParticipantIDs = array_merge($this->invisibleParticipantIDs, Conversation::validateGroupParticipants($this->invisibleParticipantsGroupIDs, 'invisibleParticipants'));
+ $this->invisibleParticipantIDs = array_unique($this->invisibleParticipantIDs);
+ }
// remove duplicates
$intersection = array_intersect($this->participantIDs, $this->invisibleParticipantIDs);
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,
- 'action' => 'add'
+ 'invisibleParticipantsData' => $this->getParticipantsData(true),
+ 'action' => 'add',
+ 'allowedUserGroupIDs' => $allowedUserGroupIDs
]);
}
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;
+ }
}
* Allows the editing of conversation drafts.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\Form
*/
* Shows most recent conversations.
*
* @author Alexander Ebert
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\Page
*/
* Shows a list of conversations.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\Page
*
'draftCount' => $this->draftCount,
'hiddenCount' => $this->hiddenCount,
'outboxCount' => $this->outboxCount,
- 'participants' => $this->participants
+ 'participants' => $this->participants,
+ 'validSortFields' => $this->validSortFields,
]);
}
}
use wcf\data\conversation\ViewableConversation;
use wcf\data\modification\log\ConversationLogModificationLogList;
use wcf\data\smiley\SmileyCache;
+use wcf\data\user\UserProfile;
use wcf\system\attachment\AttachmentHandler;
use wcf\system\bbcode\BBCodeHandler;
use wcf\system\exception\IllegalLinkException;
use wcf\system\page\PageLocationManager;
use wcf\system\page\ParentPageLocation;
use wcf\system\request\LinkHandler;
+use wcf\system\user\signature\SignatureCache;
use wcf\system\WCF;
use wcf\util\HeaderUtil;
use wcf\util\StringUtil;
* Shows a conversation.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\Page
*
PageLocationManager::getInstance()->addParentLocation('com.woltlab.wcf.conversation.ConversationList');
// update last visit time count
- if ($this->conversation->isNew() && $this->objectList->getMaxPostTime() > $this->conversation->lastVisitTime) {
+ 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]);
}
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([
* Attachment object type implementation for conversations.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\System\Attachment
*
* Runtime cache implementation for conversations.
*
* @author Matthias Schmidt
- * @copyright 2001-2018 WoltLab GmbH
+ * @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
* Runtime cache implementation for conversation fetched using UserConversationList.
*
* @author Matthias Schmidt
- * @copyright 2001-2018 WoltLab GmbH
+ * @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
* Prepares clipboard editor items for conversations.
*
* @author Alexander Ebert
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\System\Clipboard\Action
*/
* Handles the number of conversations and unread conversations of the active user.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\System\Conversation
*/
--- /dev/null
+<?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
+ * @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';
+ }
+}
* Updates the stored username during user rename.
*
* @author Alexander Ebert
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\System\Event\Listener
*/
* Merges user conversations.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\System\Event\Listener
*/
--- /dev/null
+<?php
+namespace wcf\system\event\listener;
+use wcf\acp\form\UserGroupAddForm;
+use wcf\acp\form\UserGroupEditForm;
+use wcf\data\user\group\UserGroup;
+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
+ */
+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
+ ]);
+ }
+}
* Imports conversation attachments.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\System\Importer
*/
* Imports conversations.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\System\Importer
*/
* Imports conversation labels.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\System\Importer
*/
* Imports conversation messages.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\System\Importer
*/
* Imports conversation users.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\System\Importer
*/
* Handles conversation modification logs.
*
* @author Alexander Ebert
- * @copyright 2001-2018 WoltLab GmbH
+ * @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 AbstractModificationLogHandler {
+class ConversationModificationLogHandler extends VoidExtendedModificationLogHandler {
/**
* @inheritDoc
*/
* IMessageQuoteHandler implementation for conversation messages.
*
* @author Alexander Ebert
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\System\Message\Quote
*/
* An implementation of IModerationQueueReportHandler for conversation messages.
*
* @author Alexander Ebert
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\System\Moderation\Queue
*/
* Page handler implementation for the conversation list.
*
* @author Matthias Schmidt
- * @copyright 2001-2018 WoltLab GmbH
+ * @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
* Only use this class when you need the online location handling for a board-related page.
*
* @author Matthias Schmidt
- * @copyright 2001-2018 WoltLab GmbH
+ * @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
* Implementation of the online location-related page handler methods for conversations.
*
* @author Matthias Schmidt
- * @copyright 2001-2018 WoltLab GmbH
+ * @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
* An implementation of ISearchableObjectType for searching in conversations.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\System\Search
*/
* Stat handler implementation for conversation messages.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\System\Stat
*/
* Stat handler implementation for conversations.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\System\Stat
*/
--- /dev/null
+<?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
+ */
+class ConversationMessageUserContentProvider extends AbstractDatabaseUserContentProvider {
+ /**
+ * @inheritdoc
+ */
+ public static function getDatabaseObjectClass() {
+ return ConversationMessage::class;
+ }
+}
--- /dev/null
+<?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
+ */
+class ConversationUserContentProvider extends AbstractDatabaseUserContentProvider {
+ /**
+ * @inheritdoc
+ */
+ public static function getDatabaseObjectClass() {
+ return Conversation::class;
+ }
+}
* User notification event for conversation messages.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @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
*
];
}
+ /**
+ * @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
*/
* User notification event for conversations.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @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
*
];
}
+ /**
+ * @inheritDoc
+ * @since 5.2
+ */
+ public function getEmailTitle() {
+ return $this->getLanguage()->getDynamicVariable('wcf.user.notification.conversation.mail.title', [
+ 'author' => $this->author,
+ 'conversation' => $this->userNotificationObject
+ ]);
+ }
+
/**
* @inheritDoc
*/
* user notification events.
*
* @author Matthias Schmidt
- * @copyright 2001-2018 WoltLab GmbH
+ * @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
* Notification object for conversations.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @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
*
* Notification object for conversations.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @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
*
* Represents a conversation message notification object type.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @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
*/
* Represents a conversation notification object type.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @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
*/
* Worker implementation for updating conversation messages.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\System\Worker
*
* Worker implementation for updating conversations.
*
* @author Marcel Werk
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\System\Worker
*
joinedAt INT(10) NOT NULL DEFAULT 0,
leftAt INT(10) NOT NULL DEFAULT 0,
lastMessageID INT(10) NULL,
+ leftByOwnChoice TINYINT(1) NOT NULL DEFAULT 1,
UNIQUE KEY (participantID, conversationID),
KEY (participantID, hideConversation)
UNIQUE KEY (labelID, conversationID)
);
+ALTER TABLE wcf1_user_group ADD canBeAddedAsConversationParticipant TINYINT(1) NOT NULL DEFAULT 0;
+
ALTER TABLE wcf1_conversation ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE SET NULL;
ALTER TABLE wcf1_conversation ADD FOREIGN KEY (lastPosterID) REFERENCES wcf1_user (userID) ON DELETE SET NULL;
ALTER TABLE wcf1_conversation ADD FOREIGN KEY (firstMessageID) REFERENCES wcf1_conversation_message (messageID) ON DELETE SET NULL;
<?xml version="1.0" encoding="UTF-8"?>
-<language xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/vortex/language.xsd" languagecode="de">
+<language xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/2019/language.xsd" languagecode="de">
+ <category name="wcf.acp.content">
+ <item name="wcf.acp.content.provider.com.woltlab.wcf.conversation"><![CDATA[Konversationen]]></item>
+ <item name="wcf.acp.content.provider.com.woltlab.wcf.conversation.message"><![CDATA[Konversations-Nachrichten]]></item>
+ </category>
<category name="wcf.acp.group">
<item name="wcf.acp.group.option.category.user.conversation"><![CDATA[Konversationen]]></item>
<item name="wcf.acp.group.option.category.mod.conversation"><![CDATA[Konversationen]]></item>
<item name="wcf.acp.group.option.user.conversation.allowedAttachmentExtensions"><![CDATA[Erlaubte Dateiendungen]]></item>
<item name="wcf.acp.group.option.user.conversation.allowedAttachmentExtensions.description"><![CDATA[Eine Dateiendung pro Zeile]]></item>
<item name="wcf.acp.group.option.user.conversation.maxAttachmentCount"><![CDATA[Maximale Dateianhänge pro Nachricht]]></item>
- <item name="wcf.acp.group.option.user.conversation.maxAttachmentCount.description"><![CDATA[]]></item>
+ <item name="wcf.acp.group.option.user.conversation.maxAttachmentCount.description"/>
<item name="wcf.acp.group.option.user.conversation.canEditMessage"><![CDATA[Kann eigene Nachrichten bearbeiten]]></item>
<item name="wcf.acp.group.option.user.conversation.canEditMessage.description"><![CDATA[Mitglieder dieser Benutzergruppe können eigene Nachrichten in Konversationen nachträglich verändern, auch wenn diese bereits vom Empfänger gelesen wurden.]]></item>
+ <item name="wcf.acp.group.option.user.conversation.canAddGroupParticipants"><![CDATA[Kann Benutzergruppen als Teilnehmer hinzufügen]]></item>
+ <item name="wcf.acp.group.canBeAddedAsConversationParticipant"><![CDATA[Benutzergruppe kann als Teilnehmer in Konversationen hinzufügt werden]]></item>
<item name="wcf.acp.group.option.user.conversation.maxStartedConversationsPer24Hours"><![CDATA[Maximale Anzahl gestarteter Konversation innerhalb von 24 Stunden]]></item>
<item name="wcf.acp.group.option.user.conversation.maxStartedConversationsPer24Hours.description"><![CDATA[Beschränkt die Anzahl der Konversationen die ein Benutzer innerhalb von 24 Stunden starten darf. [-1 für unbegrenzt]]]></item>
</category>
-
<category name="wcf.acp.option">
<item name="wcf.acp.option.category.message.conversation"><![CDATA[Konversationen]]></item>
<item name="wcf.acp.option.conversation_list_default_sort_field"><![CDATA[Sortierung]]></item>
- <item name="wcf.acp.option.conversation_list_default_sort_field.description"><![CDATA[]]></item>
+ <item name="wcf.acp.option.conversation_list_default_sort_field.description"/>
<item name="wcf.acp.option.conversation_list_default_sort_order"><![CDATA[Reihenfolge]]></item>
- <item name="wcf.acp.option.conversation_list_default_sort_order.description"><![CDATA[]]></item>
+ <item name="wcf.acp.option.conversation_list_default_sort_order.description"/>
<item name="wcf.acp.option.conversation_messages_per_page"><![CDATA[Nachrichten pro Seite]]></item>
<item name="wcf.acp.option.conversations_per_page"><![CDATA[Konversationen pro Seite]]></item>
<item name="wcf.acp.option.module_conversation"><![CDATA[Konversationen]]></item>
</category>
-
<category name="wcf.acp.rebuildData">
<item name="wcf.acp.rebuildData.com.woltlab.wcf.conversation"><![CDATA[Konversationen aktualisieren]]></item>
<item name="wcf.acp.rebuildData.com.woltlab.wcf.conversation.description"><![CDATA[Aktualisiert Zähler der Konversationen]]></item>
<item name="wcf.acp.rebuildData.com.woltlab.wcf.conversation.message"><![CDATA[Konversationsnachrichten aktualisieren]]></item>
<item name="wcf.acp.rebuildData.com.woltlab.wcf.conversation.message.description"><![CDATA[Aktualisiert den Suchindex für Konversationsnachrichten]]></item>
</category>
-
<category name="wcf.acp.stat">
<item name="wcf.acp.stat.com.woltlab.wcf.conversation"><![CDATA[Konversationen]]></item>
<item name="wcf.acp.stat.com.woltlab.wcf.conversation.message"><![CDATA[Konversations-Nachrichten]]></item>
<item name="wcf.acp.stat.category.com.woltlab.wcf.conversation"><![CDATA[Konversationen]]></item>
</category>
-
<category name="wcf.clipboard">
<item name="wcf.clipboard.item.com.woltlab.wcf.conversation.conversation.assignLabel"><![CDATA[Label zuweisen ({#$count})]]></item>
<item name="wcf.clipboard.item.com.woltlab.wcf.conversation.conversation.close"><![CDATA[Schließen ({#$count})]]></item>
<item name="wcf.clipboard.item.com.woltlab.wcf.conversation.conversation.restore"><![CDATA[Als aktiv markieren ({#$count})]]></item>
<item name="wcf.clipboard.label.com.woltlab.wcf.conversation.conversation.marked"><![CDATA[{if $count == 1}Eine Konversation{else}{#$count} Konversationen{/if}]]></item>
</category>
-
<category name="wcf.conversation">
<item name="wcf.conversation.add"><![CDATA[Neue Konversation starten]]></item>
<item name="wcf.conversation.button.add"><![CDATA[Konversation starten]]></item>
<item name="wcf.conversation.lastPostTime"><![CDATA[Letzte Antwort]]></item>
<item name="wcf.conversation.lastVisitTime"><![CDATA[Konversation gelesen]]></item>
<item name="wcf.conversation.leave.title"><![CDATA[Teilnahme verwalten]]></item>
- <item name="wcf.conversation.markAllAsRead.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} wirklich alle Konversationen als gelesen markieren?]]></item>
<item name="wcf.conversation.message"><![CDATA[Nachricht]]></item>
<item name="wcf.conversation.message.button.add"><![CDATA[Antworten]]></item>
<item name="wcf.conversation.message.edit"><![CDATA[Nachricht bearbeiten]]></item>
<item name="wcf.conversation.visibility.new"><![CDATA[Nur neue Nachrichten]]></item>
<item name="wcf.conversation.visibility.new.description"><![CDATA[Die neuen Teilnehmer sehen nur neue Nachrichten, alle vorherigen Nachrichten werden nicht angezeigt.]]></item>
<item name="wcf.conversation.visibility.previousMessages"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Du wurdest{else}Sie wurden{/if} einer laufenden Konversation hinzugefügt, vorherige Nachrichten werden {if LANGUAGE_USE_INFORMAL_VARIANT}dir{else}Ihnen{/if} nicht angezeigt.]]></item>
+ <item name="wcf.conversation.time"><![CDATA[Erstellung]]></item>
+ <item name="wcf.conversation.username"><![CDATA[Autor]]></item>
<item name="wcf.conversation.error.floodControl"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Du hast{else}Sie haben{/if} innerhalb der letzten 24 Stunden bereits {if $limit == 1}eine Konversation{else}{#$limit} Konversationen{/if} gestartet. Bitte {if LANGUAGE_USE_INFORMAL_VARIANT}warte{else}warten Sie{/if} bis zum <strong>{@$notBefore|time}</strong>, bevor {if LANGUAGE_USE_INFORMAL_VARIANT}du{else}Sie{/if} eine neue Konversation {if LANGUAGE_USE_INFORMAL_VARIANT}startest{else}starten{/if}.]]></item>
</category>
-
<category name="wcf.conversation.edit">
<item name="wcf.conversation.edit.addParticipants"><![CDATA[Teilnehmer hinzufügen]]></item>
<item name="wcf.conversation.edit.addParticipants.success"><![CDATA[{if $count == 1}Ein{else}{#$count}{/if} Teilnehmer erfolgreich hinzugefügt]]></item>
<item name="wcf.conversation.edit.open"><![CDATA[Öffnen]]></item>
<item name="wcf.conversation.edit.subject"><![CDATA[Betreff bearbeiten]]></item>
</category>
-
<category name="wcf.conversation.label">
<item name="wcf.conversation.label"><![CDATA[Filter nach Label]]></item>
<item name="wcf.conversation.label.cssClassName"><![CDATA[Aussehen]]></item>
<item name="wcf.conversation.label.placeholder"><![CDATA[Label]]></item>
<item name="wcf.conversation.label.assignLabels"><![CDATA[Label zuweisen]]></item>
</category>
-
<category name="wcf.conversation.log">
<item name="wcf.conversation.log.conversation.open"><![CDATA[Hat die Konversation wieder geöffnet.]]></item>
<item name="wcf.conversation.log.conversation.close"><![CDATA[Hat die Konversation für neue Nachrichten geschlossen.]]></item>
<item name="wcf.conversation.log.conversation.addParticipants"><![CDATA[Hat folgende Teilnehmer hinzugefügt: {implode from=$additionalData[participants] item=participant}<a href="{link controller='User' id=$participant[userID] title=$participant[username]}{/link}" class="userLink" data-user-id="{@$participant[userID]}">{$participant[username]}</a>{/implode}]]></item>
<item name="wcf.conversation.log.conversation.removeParticipant"><![CDATA[Hat folgenden Teilnehmer entfernt: <a href="{link controller='User' id=$additionalData[userID] title=$additionalData[username]}{/link}" class="userLink" data-user-id="{@$additionalData[userID]}">{$additionalData[username]}</a>]]></item>
</category>
-
<category name="wcf.acp.dataImport">
<item name="wcf.acp.dataImport.data.com.woltlab.wcf.conversation"><![CDATA[Konversationen]]></item>
<item name="wcf.acp.dataImport.data.com.woltlab.wcf.conversation.label"><![CDATA[Labels]]></item>
<item name="wcf.acp.dataImport.data.com.woltlab.wcf.conversation.user"><![CDATA[Teilnehmer]]></item>
<item name="wcf.acp.dataImport.data.com.woltlab.wcf.conversation.attachment"><![CDATA[Dateianhänge]]></item>
</category>
-
<category name="wcf.moderation">
<item name="wcf.moderation.type.com.woltlab.wcf.conversation.message"><![CDATA[Konversation]]></item>
</category>
-
<category name="wcf.page">
<item name="wcf.page.onlineLocation.com.woltlab.wcf.conversation.Conversation"><![CDATA[Konversation <a href="{link controller='Conversation' object=$conversation}{/link}" class="conversationLink" data-conversation-id="{@$conversation->conversationID}">{$conversation->subject}</a>]]></item>
</category>
-
<category name="wcf.search">
<item name="wcf.search.type.com.woltlab.wcf.conversation"><![CDATA[Diese Konversation]]></item>
<item name="wcf.search.type.com.woltlab.wcf.conversation.message"><![CDATA[Konversationen]]></item>
<item name="wcf.search.object.com.woltlab.wcf.conversation.message"><![CDATA[Konversation]]></item>
</category>
-
<category name="wcf.user.notification">
<item name="wcf.user.notification.conversation.message.message"><![CDATA[{@$author->getAnchorTag()} hat auf die Konversation <a href="{link controller='Conversation' object=$message->getConversation()}{/link}">{$message->getConversation()->getTitle()}</a> geantwortet.]]></item>
<item name="wcf.user.notification.conversation.message.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count == 2} und {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3} und {@$authors[2]->getAnchorTag()}{/if}{else}{@$authors[0]->getAnchorTag()} und {#$count} weitere Benutzer{/if} haben auf die Konversation <a href="{link controller='Conversation' object=$message->getConversation()}{/link}">{$message->getConversation()->getTitle()}</a> geantwortet.]]></item>
<item name="wcf.user.notification.conversation.message.title.stacked"><![CDATA[{#$count} Teilnehmer haben auf eine Konversation geantwortet]]></item>
<item name="wcf.user.notification.conversation.message.mail.plaintext"><![CDATA[{@$authorList} {if $count == 1 && !$guestTimesTriggered}hat{else}haben{/if} auf die Konversation „{@$event->getUserNotificationObject()->getConversation()->subject}“ [URL:{link controller='Conversation' object=$event->getUserNotificationObject()->getConversation() isEmail=true}{/link}] geantwortet{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
<item name="wcf.user.notification.conversation.message.mail.html"><![CDATA[<p>{@$authorList} {if $count == 1 && !$guestTimesTriggered}hat{else}haben{/if} auf die Konversation <a href="{link controller='Conversation' object=$event->getUserNotificationObject()->getConversation() isHtmlEmail=true}{/link}">{$event->getUserNotificationObject()->getConversation()->subject}</a> geantwortet:</p>]]></item>
+ <item name="wcf.user.notification.conversation.message.mail.title"><![CDATA["{@$author->username}" hat auf die Konversation "{@$message->getConversation()->subject}" geantwortet]]></item>
<item name="wcf.user.notification.conversation.message"><![CDATA[{@$author->getAnchorTag()} hat die Konversation <a href="{link controller='Conversation' object=$conversation}{/link}">{$conversation->subject}</a> gestartet.]]></item>
<item name="wcf.user.notification.conversation.title"><![CDATA[Neue Konversation]]></item>
<item name="wcf.user.notification.conversation.mail.plaintext"><![CDATA[{@$event->getAuthor()->username} [URL:{link controller='User' object=$event->getAuthor() isEmail=true}{/link}] hat die Konversation „{@$event->getUserNotificationObject()->subject}“ [URL:{link controller='Conversation' object=$event->getUserNotificationObject() isEmail=true}{/link}] gestartet:]]></item>
<item name="wcf.user.notification.conversation.mail.html"><![CDATA[<p><a href="{link controller='User' object=$event->getAuthor() isHtmlEmail=true}{/link}">{$event->getAuthor()->username}</a> hat die Konversation <a href="{link controller='Conversation' object=$event->getUserNotificationObject() isHtmlEmail=true}{/link}">{$event->getUserNotificationObject()->subject}</a> gestartet:</p>]]></item>
+ <item name="wcf.user.notification.conversation.mail.title"><![CDATA[Neue Konversation von "{@$author->username}"]]></item>
<item name="wcf.user.notification.com.woltlab.wcf.conversation"><![CDATA[Konversationen]]></item>
<item name="wcf.user.notification.com.woltlab.wcf.conversation.notification.conversation"><![CDATA[Neue Konversation]]></item>
<item name="wcf.user.notification.com.woltlab.wcf.conversation.message.notification.conversationMessage"><![CDATA[Antwort auf bestehende Konversation]]></item>
</category>
-
<category name="wcf.user.option">
<item name="wcf.user.option.conversationMessagesPerPage"><![CDATA[Nachrichten pro Seite]]></item>
<item name="wcf.user.option.conversationsPerPage"><![CDATA[Konversationen pro Seite]]></item>
<?xml version="1.0" encoding="UTF-8"?>
-<language xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/vortex/language.xsd" languagecode="en">
+<language xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/2019/language.xsd" languagecode="en">
+ <category name="wcf.acp.content">
+ <item name="wcf.acp.content.provider.com.woltlab.wcf.conversation"><![CDATA[Conversations]]></item>
+ <item name="wcf.acp.content.provider.com.woltlab.wcf.conversation.message"><![CDATA[Conversation Messages]]></item>
+ </category>
<category name="wcf.acp.group">
<item name="wcf.acp.group.option.category.user.conversation"><![CDATA[Conversations]]></item>
<item name="wcf.acp.group.option.category.mod.conversation"><![CDATA[Conversations]]></item>
<item name="wcf.acp.group.option.user.conversation.maxAttachmentCount.description"><![CDATA[The maximum number of attachments allowed per message.]]></item>
<item name="wcf.acp.group.option.user.conversation.canEditMessage"><![CDATA[Can edit their messages]]></item>
<item name="wcf.acp.group.option.user.conversation.canEditMessage.description"><![CDATA[Users can edit their messages, regardless if they have been read by one or more recipients.]]></item>
+ <item name="wcf.acp.group.option.user.conversation.canAddGroupParticipants"><![CDATA[Can add user groups as participants]]></item>
+ <item name="wcf.acp.group.canBeAddedAsConversationParticipant"><![CDATA[User group can be added as participant in conversations]]></item>
<item name="wcf.acp.group.option.user.conversation.maxStartedConversationsPer24Hours"><![CDATA[Maximum Number of Started Conversations per 24 Hours]]></item>
<item name="wcf.acp.group.option.user.conversation.maxStartedConversationsPer24Hours.description"><![CDATA[Limits the number of conversations that a user can start within 24 hours. Use -1 for infinite.]]></item>
</category>
-
<category name="wcf.acp.option">
<item name="wcf.acp.option.category.message.conversation"><![CDATA[Conversations]]></item>
<item name="wcf.acp.option.conversation_list_default_sort_field"><![CDATA[Sort by]]></item>
<item name="wcf.acp.option.conversations_per_page"><![CDATA[Conversations per Page]]></item>
<item name="wcf.acp.option.module_conversation"><![CDATA[Conversations]]></item>
</category>
-
<category name="wcf.acp.rebuildData">
<item name="wcf.acp.rebuildData.com.woltlab.wcf.conversation"><![CDATA[Rebuild Conversations]]></item>
<item name="wcf.acp.rebuildData.com.woltlab.wcf.conversation.description"><![CDATA[Rebuilds the conversation counters.]]></item>
<item name="wcf.acp.rebuildData.com.woltlab.wcf.conversation.message"><![CDATA[Rebuild Conversation Messages]]></item>
<item name="wcf.acp.rebuildData.com.woltlab.wcf.conversation.message.description"><![CDATA[Rebuilds the search index for the conversation messages.]]></item>
</category>
-
<category name="wcf.acp.stat">
<item name="wcf.acp.stat.com.woltlab.wcf.conversation"><![CDATA[Conversations]]></item>
<item name="wcf.acp.stat.com.woltlab.wcf.conversation.message"><![CDATA[Conversation Messages]]></item>
<item name="wcf.acp.stat.category.com.woltlab.wcf.conversation"><![CDATA[Conversations]]></item>
</category>
-
<category name="wcf.clipboard">
<item name="wcf.clipboard.item.com.woltlab.wcf.conversation.conversation.assignLabel"><![CDATA[Assign Label ({#$count})]]></item>
<item name="wcf.clipboard.item.com.woltlab.wcf.conversation.conversation.close"><![CDATA[Close ({#$count})]]></item>
<item name="wcf.clipboard.item.com.woltlab.wcf.conversation.conversation.restore"><![CDATA[Mark as Active ({#$count})]]></item>
<item name="wcf.clipboard.label.com.woltlab.wcf.conversation.conversation.marked"><![CDATA[{if $count == 1}One Conversation{else}{#$count} Conversations{/if}]]></item>
</category>
-
<category name="wcf.conversation">
<item name="wcf.conversation.add"><![CDATA[Create Conversation]]></item>
<item name="wcf.conversation.button.add"><![CDATA[Create Conversation]]></item>
<item name="wcf.conversation.lastPostTime"><![CDATA[Last Reply]]></item>
<item name="wcf.conversation.lastVisitTime"><![CDATA[Conversation Read]]></item>
<item name="wcf.conversation.leave.title"><![CDATA[Manage Participation]]></item>
- <item name="wcf.conversation.markAllAsRead.confirmMessage"><![CDATA[Do you really want to mark all conversations as read?]]></item>
<item name="wcf.conversation.message"><![CDATA[Message]]></item>
<item name="wcf.conversation.message.button.add"><![CDATA[Reply]]></item>
<item name="wcf.conversation.message.edit"><![CDATA[Edit Message]]></item>
- <item name="wcf.conversation.noConversations"><![CDATA[There are not any conversations at the moment.]]></item>
+ <item name="wcf.conversation.noConversations"><![CDATA[There are no conversations at the moment.]]></item>
<item name="wcf.conversation.noMoreItems"><![CDATA[You have no recent conversations.]]></item>
<item name="wcf.conversation.participantCanInvite"><![CDATA[Participants can add new participants]]></item>
<item name="wcf.conversation.participants"><![CDATA[Participants]]></item>
<item name="wcf.conversation.visibility.new"><![CDATA[New messages only]]></item>
<item name="wcf.conversation.visibility.new.description"><![CDATA[The new participants will see new messages only, older messages will be hidden from them.]]></item>
<item name="wcf.conversation.visibility.previousMessages"><![CDATA[You have been added to an existing conversation, previously written messages are hidden.]]></item>
+ <item name="wcf.conversation.time"><![CDATA[Creation]]></item>
+ <item name="wcf.conversation.username"><![CDATA[Author]]></item>
<item name="wcf.conversation.error.floodControl"><![CDATA[You have already started {if $limit == 1}one conversation{else}{#$limit} conversations{/if} in the past 24 hours. Please wait until <strong>{@$notBefore|time}</strong> before you start a new conversation.]]></item>
</category>
-
<category name="wcf.conversation.edit">
<item name="wcf.conversation.edit.addParticipants"><![CDATA[Add Participants]]></item>
<item name="wcf.conversation.edit.addParticipants.success"><![CDATA[Added {#$count} participant{if $count != 1}s{/if}]]></item>
<item name="wcf.conversation.edit.open"><![CDATA[Open]]></item>
<item name="wcf.conversation.edit.subject"><![CDATA[Edit Subject]]></item>
</category>
-
<category name="wcf.conversation.label">
<item name="wcf.conversation.label"><![CDATA[Filter by Label]]></item>
<item name="wcf.conversation.label.cssClassName"><![CDATA[Appearance]]></item>
<item name="wcf.conversation.label.placeholder"><![CDATA[Label]]></item>
<item name="wcf.conversation.label.assignLabels"><![CDATA[Assign Label]]></item>
</category>
-
<category name="wcf.conversation.log">
<item name="wcf.conversation.log.conversation.open"><![CDATA[Opened the conversation again.]]></item>
<item name="wcf.conversation.log.conversation.close"><![CDATA[Closed the conversation.]]></item>
<item name="wcf.conversation.log.conversation.addParticipants"><![CDATA[Added the following participants: {implode from=$additionalData[participants] item=participant}<a href="{link controller='User' id=$participant[userID] title=$participant[username]}{/link}" class="userLink" data-user-id="{@$participant[userID]}">{$participant[username]}</a>{/implode}.]]></item>
<item name="wcf.conversation.log.conversation.removeParticipant"><![CDATA[Removed a participant: <a href="{link controller='User' id=$additionalData[userID] title=$additionalData[username]}{/link}" class="userLink" data-user-id="{@$additionalData[userID]}">{$additionalData[username]}</a>.]]></item>
</category>
-
<category name="wcf.acp.dataImport">
<item name="wcf.acp.dataImport.data.com.woltlab.wcf.conversation"><![CDATA[Conversations]]></item>
<item name="wcf.acp.dataImport.data.com.woltlab.wcf.conversation.label"><![CDATA[Labels]]></item>
<item name="wcf.acp.dataImport.data.com.woltlab.wcf.conversation.user"><![CDATA[Participants]]></item>
<item name="wcf.acp.dataImport.data.com.woltlab.wcf.conversation.attachment"><![CDATA[Attachments]]></item>
</category>
-
<category name="wcf.moderation">
<item name="wcf.moderation.type.com.woltlab.wcf.conversation.message"><![CDATA[Conversation]]></item>
</category>
-
<category name="wcf.page">
<item name="wcf.page.onlineLocation.com.woltlab.wcf.conversation.Conversation"><![CDATA[Conversation <a href="{link controller='Conversation' object=$conversation}{/link}" class="conversationLink" data-conversation-id="{@$conversation->conversationID}">{$conversation->subject}</a>]]></item>
</category>
-
<category name="wcf.search">
<item name="wcf.search.type.com.woltlab.wcf.conversation"><![CDATA[This Conversation]]></item>
<item name="wcf.search.type.com.woltlab.wcf.conversation.message"><![CDATA[Conversations]]></item>
<item name="wcf.search.object.com.woltlab.wcf.conversation.message"><![CDATA[Conversation]]></item>
</category>
-
<category name="wcf.user.notification">
<item name="wcf.user.notification.conversation.message.message"><![CDATA[{@$author->getAnchorTag()} replied to the conversation <a href="{link controller='Conversation' object=$message->getConversation()}{/link}">{$message->getConversation()->getTitle()}</a>.]]></item>
<item name="wcf.user.notification.conversation.message.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count == 2} and {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3} and {@$authors[2]->getAnchorTag()}{/if}{else}{@$authors[0]->getAnchorTag()} and {#$count} other users{/if} replied to the conversation <a href="{link controller='Conversation' object=$message->getConversation()}{/link}">{$message->getConversation()->getTitle()}</a>.]]></item>
<item name="wcf.user.notification.conversation.message.title.stacked"><![CDATA[{#$count} participants replied to a conversation]]></item>
<item name="wcf.user.notification.conversation.message.mail.plaintext"><![CDATA[{@$authorList} replied to the conversation “{@$event->getUserNotificationObject()->getConversation()->subject}” [URL:{link controller='Conversation' object=$event->getUserNotificationObject()->getConversation() isEmail=true}{/link}]{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
<item name="wcf.user.notification.conversation.message.mail.html"><![CDATA[<p>{@$authorList} replied to the conversation <a href="{link controller='Conversation' object=$event->getUserNotificationObject()->getConversation() isHtmlEmail=true}{/link}">{$event->getUserNotificationObject()->getConversation()->subject}</a>:</p>]]></item>
+ <item name="wcf.user.notification.conversation.message.mail.title"><![CDATA["{@$author->username}" replied to the conversation "{@$message->getConversation()->subject}"]]></item>
<item name="wcf.user.notification.conversation.message"><![CDATA[{@$author->getAnchorTag()} started the conversation <a href="{link controller='Conversation' object=$conversation}{/link}">{$conversation->subject}</a>.]]></item>
<item name="wcf.user.notification.conversation.title"><![CDATA[New Conversation]]></item>
<item name="wcf.user.notification.conversation.mail.plaintext"><![CDATA[{@$event->getAuthor()->username} [URL:{link controller='User' object=$event->getAuthor() isEmail=true}{/link}] started the conversation “{@$event->getUserNotificationObject()->subject}” [URL:{link controller='Conversation' object=$event->getUserNotificationObject() isEmail=true}{/link}]:]]></item>
<item name="wcf.user.notification.conversation.mail.html"><![CDATA[<p><a href="{link controller='User' object=$event->getAuthor() isHtmlEmail=true}{/link}">{$event->getAuthor()->username}</a> started the conversation <a href="{link controller='Conversation' object=$event->getUserNotificationObject() isHtmlEmail=true}{/link}">{$event->getUserNotificationObject()->subject}</a>:</p>]]></item>
+ <item name="wcf.user.notification.conversation.mail.title"><![CDATA[New Conversation from "{@$author->username}"]]></item>
<item name="wcf.user.notification.com.woltlab.wcf.conversation"><![CDATA[Conversations]]></item>
<item name="wcf.user.notification.com.woltlab.wcf.conversation.notification.conversation"><![CDATA[Notify me of new conversations]]></item>
<item name="wcf.user.notification.com.woltlab.wcf.conversation.message.notification.conversationMessage"><![CDATA[Notify me of new replies in conversations]]></item>
</category>
-
<category name="wcf.user.option">
<item name="wcf.user.option.conversationMessagesPerPage"><![CDATA[Messages per Page]]></item>
<item name="wcf.user.option.conversationsPerPage"><![CDATA[Conversations per Page]]></item>
<?xml version="1.0" encoding="UTF-8"?>
-<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/vortex/objectType.xsd">
+<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/2019/objectType.xsd">
<import>
<type>
<name>com.woltlab.wcf.conversation.message</name>
<classname>wcf\system\search\ConversationMessageSearch</classname>
<searchindex>wcf1_conversation_message_search_index</searchindex>
</type>
-
<type>
<name>com.woltlab.wcf.conversation.message</name>
<definitionname>com.woltlab.wcf.attachment.objectType</definitionname>
<classname>wcf\system\attachment\ConversationMessageAttachmentObjectType</classname>
<private>1</private>
</type>
-
<type>
<name>com.woltlab.wcf.conversation.message</name>
<definitionname>com.woltlab.wcf.message</definitionname>
</type>
-
<type>
<name>com.woltlab.wcf.conversation.conversation</name>
<definitionname>com.woltlab.wcf.clipboardItem</definitionname>
<listclassname>wcf\data\conversation\ConversationList</listclassname>
</type>
-
<type>
<name>com.woltlab.wcf.conversation.notification</name>
<definitionname>com.woltlab.wcf.notification.objectType</definitionname>
<classname>wcf\system\user\notification\object\type\ConversationMessageNotificationObjectType</classname>
<category>com.woltlab.wcf.conversation</category>
</type>
-
<type>
<name>com.woltlab.wcf.conversation.message</name>
<definitionname>com.woltlab.wcf.message.quote</definitionname>
<classname>wcf\system\message\quote\ConversationMessageQuoteHandler</classname>
</type>
-
<type>
<name>com.woltlab.wcf.conversation.message</name>
<definitionname>com.woltlab.wcf.moderation.report</definitionname>
<classname>wcf\system\moderation\queue\report\ConversationMessageModerationQueueReportHandler</classname>
</type>
-
<!-- Modification Log -->
<type>
<name>com.woltlab.wcf.conversation.conversation</name>
<definitionname>com.woltlab.wcf.modifiableContent</definitionname>
+ <classname>wcf\system\log\modification\ConversationModificationLogHandler</classname>
</type>
<!-- /Modification Log -->
-
<!-- importers -->
<type>
<name>com.woltlab.wcf.conversation</name>
<classname>wcf\system\importer\ConversationAttachmentImporter</classname>
</type>
<!-- /importers -->
-
<!-- rebuild data workers -->
<type>
<name>com.woltlab.wcf.conversation</name>
<nicevalue>-5</nicevalue>
</type>
<!-- /rebuild data workers -->
-
<!-- stat handlers -->
<type>
<name>com.woltlab.wcf.conversation</name>
<categoryname>com.woltlab.wcf.conversation</categoryname>
</type>
<!-- /stat handlers -->
+ <!-- user content provider -->
+ <type>
+ <name>com.woltlab.wcf.conversation</name>
+ <definitionname>com.woltlab.wcf.content.userContentProvider</definitionname>
+ <classname>wcf\system\user\content\provider\ConversationUserContentProvider</classname>
+ </type>
+ <type>
+ <name>com.woltlab.wcf.conversation.message</name>
+ <definitionname>com.woltlab.wcf.content.userContentProvider</definitionname>
+ <classname>wcf\system\user\content\provider\ConversationMessageUserContentProvider</classname>
+ </type>
+ <!-- /user content provider -->
</import>
</data>
-<?xml version="1.0" encoding="UTF-8"?>
-<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/vortex/option.xsd">
+<?xml version="1.0" encoding="UTF-8"?>
+<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/2019/option.xsd">
<import>
<categories>
<category name="message.conversation">
<options>module_conversation</options>
</category>
</categories>
-
<options>
<option name="module_conversation">
<categoryname>module.community</categoryname>
<optiontype>boolean</optiontype>
<defaultvalue>1</defaultvalue>
</option>
-
<option name="conversations_per_page">
<categoryname>message.conversation</categoryname>
<optiontype>integer</optiontype>
<minvalue>5</minvalue>
<maxvalue>40</maxvalue>
</option>
-
<option name="conversation_list_default_sort_field">
<categoryname>message.conversation</categoryname>
<optiontype>select</optiontype>
</option>
</options>
</import>
-
- <delete>
- <option name="conversation_reply_show_messages_max" />
- </delete>
-</data>
\ No newline at end of file
+</data>
<?xml version="1.0" encoding="UTF-8"?>
-<package name="com.woltlab.wcf.conversation" xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/tornado/package.xsd">
+<package name="com.woltlab.wcf.conversation" xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/2019/package.xsd">
<packageinformation>
<packagename>WoltLab Suite Core: Conversations</packagename>
+ <packagename language="de">WoltLab Suite Core: Konversationen</packagename>
<packagedescription>Private conversations between multiple users</packagedescription>
- <version>3.1.17</version>
- <date>2020-08-28</date>
+ <packagedescription language="de">Private Konversationen zwischen mehreren Benutzern</packagedescription>
+ <version>5.2.13</version>
+ <date>2021-03-03</date>
</packageinformation>
<authorinformation>
<author>WoltLab GmbH</author>
- <authorurl>http://www.woltlab.com</authorurl>
+ <authorurl>https://www.woltlab.com</authorurl>
</authorinformation>
<requiredpackages>
- <requiredpackage minversion="3.1.17">com.woltlab.wcf</requiredpackage>
+ <requiredpackage minversion="5.2.13">com.woltlab.wcf</requiredpackage>
</requiredpackages>
<excludedpackages>
- <excludedpackage version="5.2.0 Alpha 1">com.woltlab.wcf</excludedpackage>
+ <excludedpackage version="5.3.0 Alpha 1">com.woltlab.wcf</excludedpackage>
</excludedpackages>
- <compatibility>
- <api version="2018" />
- </compatibility>
-
<instructions type="install">
<instruction type="file" />
<instruction type="userGroupOption" />
<instruction type="sql" run="standalone" />
<instruction type="template" />
+ <instruction type="acpTemplate" />
<instruction type="option" />
<instruction type="templateListener" />
<instruction type="language" />
<instruction type="page" />
</instructions>
- <instructions type="update" fromversion="3.0.*">
- <instruction type="file" />
- <instruction type="template" />
+ <instructions type="update" fromversion="3.1.*">
+ <instruction type="acpTemplate" run="standalone" />
+ <instruction type="file" run="standalone" />
+ <instruction type="template" run="standalone" />
- <instruction type="script" run="standalone">acp/update_com.woltlab.wcf.conversation_3.1_addColumn.php</instruction>
-
- <instruction type="sql">update_3.1.sql</instruction>
+ <instruction type="script">acp/update_com.woltlab.wcf.conversation_5.2.php</instruction>
<instruction type="language" />
+ <instruction type="clipboardAction" />
+ <instruction type="coreObject" />
+ <instruction type="eventListener" />
+ <instruction type="objectType" />
<instruction type="option" />
<instruction type="page" />
+ <instruction type="templateListener" />
<instruction type="userGroupOption" />
+ <instruction type="userNotificationEvent" />
+ <instruction type="userOption" />
</instructions>
- <instructions type="update" fromversion="3.1.16">
- <instruction type="file">files_update.tar</instruction>
+ <instructions type="update" fromversion="5.2.10">
+ <instruction type="language" />
</instructions>
</package>
<?xml version="1.0" encoding="UTF-8"?>
-<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/tornado/page.xsd">
+<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/2019/page.xsd">
<import>
<page identifier="com.woltlab.wcf.conversation.ConversationList">
+ <pageType>system</pageType>
<controller>wcf\page\ConversationListPage</controller>
- <handler>wcf\system\page\handler\ConversationListPageHandler</handler>
<name language="de">Konversationenliste</name>
<name language="en">Conversation List</name>
- <pageType>system</pageType>
+ <handler>wcf\system\page\handler\ConversationListPageHandler</handler>
<excludeFromLandingPage>1</excludeFromLandingPage>
-
<content language="en">
<title>Conversations</title>
</content>
</content>
</page>
<page identifier="com.woltlab.wcf.conversation.Conversation">
+ <pageType>system</pageType>
<controller>wcf\page\ConversationPage</controller>
- <handler>wcf\system\page\handler\DefaultConversationRelatedPageHandler</handler>
<name language="de">Konversation</name>
<name language="en">Conversation</name>
- <pageType>system</pageType>
- <requireObjectID>1</requireObjectID>
+ <handler>wcf\system\page\handler\DefaultConversationRelatedPageHandler</handler>
<hasFixedParent>1</hasFixedParent>
<parent>com.woltlab.wcf.conversation.ConversationList</parent>
+ <requireObjectID>1</requireObjectID>
</page>
<page identifier="com.woltlab.wcf.conversation.ConversationAdd">
+ <pageType>system</pageType>
<controller>wcf\form\ConversationAddForm</controller>
<name language="de">Konversation starten</name>
<name language="en">New Conversation</name>
- <pageType>system</pageType>
<hasFixedParent>1</hasFixedParent>
<parent>com.woltlab.wcf.conversation.ConversationList</parent>
<excludeFromLandingPage>1</excludeFromLandingPage>
-
<content language="en">
<title>New Conversation</title>
</content>
</content>
</page>
<page identifier="com.woltlab.wcf.conversation.ConversationDraftEdit">
+ <pageType>system</pageType>
<controller>wcf\form\ConversationDraftEditForm</controller>
<name language="de">Konversations-Entwurf bearbeiten</name>
<name language="en">Edit Conversation Draft</name>
- <pageType>system</pageType>
- <requireObjectID>1</requireObjectID>
<hasFixedParent>1</hasFixedParent>
<parent>com.woltlab.wcf.conversation.ConversationList</parent>
-
+ <requireObjectID>1</requireObjectID>
<content language="en">
<title>Edit Conversation Draft</title>
</content>
<?xml version="1.0" encoding="UTF-8"?>
-<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/vortex/templateListener.xsd">
+<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/2019/templateListener.xsd">
<import>
<templatelistener name="userPanel">
<environment>user</environment>
<eventname>menuItems</eventname>
<templatecode><![CDATA[{include file='__userPanelConversationDropdown'}]]></templatecode>
</templatelistener>
-
<templatelistener name="userInformationButtons">
<environment>user</environment>
<templatename>userInformationButtons</templatename>
<templatecode><![CDATA[{include file='__userInformationStartConversation'}]]></templatecode>
</templatelistener>
- <templatelistener name="userButtons">
- <environment>user</environment>
- <templatename>user</templatename>
- <eventname>buttons</eventname>
- <templatecode><![CDATA[{include file='__userStartConversation'}]]></templatecode>
- </templatelistener>
-
<templatelistener name="searchAreaConversationSettings">
<environment>user</environment>
<templatename>pageHeaderSearch</templatename>
<eventname>settings</eventname>
<templatecode><![CDATA[{include file='__searchAreaConversationSettings'}]]></templatecode>
</templatelistener>
-
<templatelistener name="userProfileStartConversation">
<environment>user</environment>
<templatename>user</templatename>
<eventname>menuInteraction</eventname>
<templatecode><![CDATA[{include file='__userProfileStartConversation'}]]></templatecode>
</templatelistener>
-
<templatelistener name="mobileMenuConversation">
<environment>user</environment>
<templatename>pageMenuMobile</templatename>
<eventname>userMenuItems</eventname>
<templatecode><![CDATA[{include file='__mobileMenuConversation'}]]></templatecode>
</templatelistener>
+ <templatelistener name="userGroupAddCanBeAddedAsConversationParticipant">
+ <environment>admin</environment>
+ <templatename>userGroupAdd</templatename>
+ <eventname>dataFields</eventname>
+ <templatecode><![CDATA[{include file='__userGroupAddCanBeAddedAsConversationParticipant'}]]></templatecode>
+ </templatelistener>
</import>
-
<delete>
- <templatelistener name="userPanel">
- <environment>user</environment>
- <templatename>userPanel</templatename>
- <eventname>menuItems</eventname>
- </templatelistener>
-
- <templatelistener name="searchAreaConversationSettings">
+ <templatelistener name="userButtons">
<environment>user</environment>
- <templatename>searchArea</templatename>
- <eventname>settings</eventname>
+ <templatename>user</templatename>
+ <eventname>buttons</eventname>
</templatelistener>
</delete>
</data>
<script data-relocate="true">
$(function() {
new WCF.User.Panel.Conversation({
- markAllAsReadConfirmMessage: '{lang}wcf.conversation.markAllAsRead.confirmMessage{/lang}',
newConversation: '{lang}wcf.conversation.add{/lang}',
newConversationLink: '{link controller='ConversationAdd' encode=false}{/link}',
noItems: '{lang}wcf.conversation.noMoreItems{/lang}',
</script>
{/if}
</li>
-{/if}
\ No newline at end of file
+{/if}
+++ /dev/null
-{if MODULE_CONVERSATION && $__wcf->user->userID && $__wcf->session->getPermission('user.conversation.canUseConversation') && $__wcf->session->getPermission('user.conversation.canStartConversation') && $user->userID != $__wcf->user->userID}<li><a class="button jsTooltip" href="{link controller='ConversationAdd'}userID={@$user->userID}{/link}" title="{lang}wcf.conversation.button.add{/lang}"><span class="icon icon16 fa-comments"></span> <span class="invisible">{lang}wcf.conversation.button.add{/lang}</span></a></li>{/if}
\ No newline at end of file
<nav class="contentHeaderNavigation">
<ul class="conversation jsConversationInlineEditorContainer" data-conversation-id="{@$conversation->conversationID}" data-label-ids="[ {implode from=$conversation->getAssignedLabels() item=label}{@$label->labelID}{/implode} ]" data-is-closed="{@$conversation->isClosed}" data-can-close-conversation="{if $conversation->userID == $__wcf->getUser()->userID}1{else}0{/if}" data-can-add-participants="{if $conversation->canAddParticipants()}1{else}0{/if}" data-is-draft="{if $conversation->isDraft}1{else}0{/if}">
<li class="jsOnly"><a href="{if $conversation->isDraft}{link controller='ConversationDraftEdit' id=$conversation->conversationID}{/link}{else}#{/if}" class="button jsConversationInlineEditor"><span class="icon icon16 fa-pencil"></span> <span>{lang}wcf.global.button.edit{/lang}</span></a></li>
- {if $conversation->canReply()}<li class="jsOnly"><a href="#" class="button buttonPrimary jsQuickReply"><span class="icon icon16 fa-plus"></span> <span>{lang}wcf.conversation.message.button.add{/lang}</span></a></li>{/if}
+ {if $conversation->canReply()}<li class="jsOnly"><a href="#" class="button buttonPrimary jsQuickReply"><span class="icon icon16 fa-reply"></span> <span>{lang}wcf.conversation.message.button.add{/lang}</span></a></li>{/if}
{event name='contentHeaderNavigation'}
</ul>
</nav>
<div class="section">
<ul class="messageList">
- {if $pageNo == 1 && !$conversation->joinedAt|empty}<li><p class="info">{lang}wcf.conversation.visibility.previousMessages{/lang}</p></li>{/if}
+ {if $pageNo == 1 && !$conversation->joinedAt|empty}<li><p class="info" role="status">{lang}wcf.conversation.visibility.previousMessages{/lang}</p></li>{/if}
{include file='conversationMessageList'}
{hascontent}
<li class="messageListPagination">
</li>
{/hascontent}
{if $conversation->canReply()}{include file='conversationQuickReply'}{/if}
- {if $pageNo == $pages && !$conversation->leftAt|empty}<li><p class="info">{lang}wcf.conversation.visibility.nextMessages{/lang}</p></li>{/if}
+ {if $pageNo == $pages && !$conversation->leftAt|empty}<li><p class="info" role="status">{lang}wcf.conversation.visibility.nextMessages{/lang}</p></li>{/if}
</ul>
</div>
new WCF.Moderation.Report.Content('com.woltlab.wcf.conversation.message', '.jsReportConversationMessage');
{/if}
new WCF.Conversation.RemoveParticipant({@$conversation->conversationID});
- new WCF.Message.BBCode.CodeViewer();
});
</script>
<dl{if $errorField == 'participants'} class="formError"{/if}>
<dt><label for="participants">{lang}wcf.conversation.participants{/lang}</label></dt>
<dd>
- <input type="text" id="participants" name="participants" class="long" value="{$participants}">
+ <input type="text" id="participants" name="participants" class="long" value="">
{if $errorField == 'participants'}
<small class="innerError">
{if $errorType == 'empty'}
<dl{if $errorField == 'invisibleParticipants'} class="formError"{/if}>
<dt><label for="invisibleParticipants">{lang}wcf.conversation.invisibleParticipants{/lang}</label></dt>
<dd>
- <input type="text" id="invisibleParticipants" name="invisibleParticipants" class="long" value="{$invisibleParticipants}">
+ <input type="text" id="invisibleParticipants" name="invisibleParticipants" class="long" value="">
{if $errorField == 'invisibleParticipants'}
<small class="innerError">
{if $errorType == 'empty'}
<script data-relocate="true">
require(['WoltLabSuite/Core/Ui/ItemList/User'], function(UiItemListUser) {
UiItemListUser.init('participants', {
- maxItems: {@$__wcf->getSession()->getPermission('user.conversation.maxParticipants')}
+ maxItems: {@$__wcf->getSession()->getPermission('user.conversation.maxParticipants')},
+ includeUserGroups: {if $__wcf->getSession()->getPermission('user.conversation.canAddGroupParticipants')}true{else}false{/if},
+ restrictUserGroupIDs: [-1, {implode from=$allowedUserGroupIDs item=allowedUserGroupID}{@$allowedUserGroupID}{/implode}],
+ csvPerType: true,
+ callbackSetupValues: function() {
+ return [
+ {implode from=$participantsData item=participant}
+ { objectId: {@$participant['objectId']}, value: '{@$participant['value']|encodeJS}', type: '{@$participant['type']}' }
+ {/implode}
+ ];
+ }
});
UiItemListUser.init('invisibleParticipants', {
- maxItems: {@$__wcf->getSession()->getPermission('user.conversation.maxParticipants')}
+ maxItems: {@$__wcf->getSession()->getPermission('user.conversation.maxParticipants')},
+ includeUserGroups: {if $__wcf->getSession()->getPermission('user.conversation.canAddGroupParticipants')}true{else}false{/if},
+ restrictUserGroupIDs: [-1, {implode from=$allowedUserGroupIDs item=allowedUserGroupID}{@$allowedUserGroupID}{/implode}],
+ csvPerType: true,
+ callbackSetupValues: function() {
+ return [
+ {implode from=$invisibleParticipantsData item=participant}
+ { objectId: {@$participant['objectId']}, value: '{@$participant['value']|encodeJS}', type: '{@$participant['type']}' }
+ {/implode}
+ ];
+ }
});
});
</dl>
{if !$conversation->isDraft}
{if $conversation->canAddParticipantsUnrestricted()}
- <dl class="jsRestrictVisibility">
- <dt>{lang}wcf.conversation.visibility{/lang}</dt>
+ <dl role="group" aria-labelledby="messageVisibilityLabel" class="jsRestrictVisibility">
+ <dt><label id="messageVisibilityLabel">{lang}wcf.conversation.visibility{/lang}</label></dt>
<dd>
<label><input type="radio" name="messageVisibility" value="all" checked> {lang}wcf.conversation.visibility.all{/lang}</label>
<small>{lang}wcf.conversation.visibility.all.description{/lang}</small>
<dl>
<dt>{lang}wcf.conversation.label.cssClassName{/lang}</dt>
<dd>
- <ul id="labelManagementList">
+ <ul role="group" aria-label="{lang}wcf.conversation.label.cssClassName{/lang}" id="labelManagementList">
{foreach from=$cssClassNames item=cssClassName}
<li><label>
<input type="radio" name="cssClassName" value="{@$cssClassName}"{if $cssClassName == 'none'} checked{/if}>
-<dl class="wide">
+<dl role="group" aria-label="{lang}wcf.conversation.hideConversation{/lang}" class="wide">
{if $hideConversation == 1}
<dd>
<label><input type="radio" name="hideConversation" value="0"> {lang}wcf.conversation.hideConversation.restore{/lang}</label>
<div class="formSubmit">
<button id="hideConversation" class="buttonPrimary">{lang}wcf.global.button.submit{/lang}</button>
-</div>
\ No newline at end of file
+</div>
{/hascontent}
{if !$items}
- <p class="info">{lang}wcf.conversation.noConversations{/lang}</p>
+ <p class="info" role="status">{lang}wcf.conversation.noConversations{/lang}</p>
{else}
<div class="section tabularBox messageGroupList conversationList jsClipboardContainer" data-type="com.woltlab.wcf.conversation.conversation">
<ol class="tabularList">
<li class="tabularListRow tabularListRowHead">
<ol class="tabularListColumns">
<li class="columnMark jsOnly"><label><input type="checkbox" class="jsClipboardMarkAll"></label></li>
- <li class="columnSubject{if $sortField === 'subject'} active {@$sortOrder}{/if}"><a href="{link controller='ConversationList'}{if $filter}filter={@$filter}&{/if}{if !$participants|empty}participants={implode from=$participants item=participant}{$participant|rawurlencode}{/implode}&{/if}pageNo={@$pageNo}&sortField=subject&sortOrder={if $sortField == 'subject' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{if $labelID}&labelID={@$labelID}{/if}{/link}">{lang}wcf.global.subject{/lang}</a></li>
- <li class="columnStats{if $sortField == 'replies'} active {@$sortOrder}{/if}"><a href="{link controller='ConversationList'}{if $filter}filter={@$filter}&{/if}{if !$participants|empty}participants={implode from=$participants item=participant}{$participant|rawurlencode}{/implode}&{/if}pageNo={@$pageNo}&sortField=replies&sortOrder={if $sortField == 'replies' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{if $labelID}&labelID={@$labelID}{/if}{/link}">{lang}wcf.conversation.replies{/lang}</a></li>
- <li class="columnLastPost{if $sortField === 'lastPostTime'} active {@$sortOrder}{/if}"><a href="{link controller='ConversationList'}{if $filter}filter={@$filter}&{/if}{if !$participants|empty}participants={implode from=$participants item=participant}{$participant|rawurlencode}{/implode}&{/if}pageNo={@$pageNo}&sortField=lastPostTime&sortOrder={if $sortField == 'lastPostTime' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{if $labelID}&labelID={@$labelID}{/if}{/link}">{lang}wcf.conversation.lastPostTime{/lang}</a></li>
- {event name='columnHeads'}
+ <li class="columnSort">
+ <ul class="inlineList">
+ <li>
+ <a rel="nofollow" href="{link controller='ConversationList'}{if $filter}filter={@$filter}&{/if}{if !$participants|empty}participants={implode from=$participants item=participant}{$participant|rawurlencode}{/implode}&{/if}pageNo={@$pageNo}&sortField={$sortField}&sortOrder={if $sortOrder == 'ASC'}DESC{else}ASC{/if}{if $labelID}&labelID={@$labelID}{/if}{/link}">
+ <span class="icon icon16 fa-sort-amount-{$sortOrder|strtolower} jsTooltip" title="{lang}wcf.global.sorting{/lang} ({lang}wcf.global.sortOrder.{if $sortOrder === 'ASC'}ascending{else}descending{/if}{/lang})"></span>
+ </a>
+ </li>
+ <li>
+ <div class="dropdown">
+ <span class="dropdownToggle">{if $sortField == 'subject'}{lang}wcf.global.subject{/lang}{else}{lang}wcf.conversation.{$sortField}{/lang}{/if}</span>
+
+ <ul class="dropdownMenu">
+ {foreach from=$validSortFields item=_sortField}
+ <li{if $_sortField === $sortField} class="active"{/if}><a rel="nofollow" href="{link controller='ConversationList'}{if $filter}filter={@$filter}&{/if}{if !$participants|empty}participants={implode from=$participants item=participant}{$participant|rawurlencode}{/implode}&{/if}pageNo={@$pageNo}&sortField={$_sortField}&sortOrder={if $sortField === $_sortField}{if $sortOrder === 'DESC'}ASC{else}DESC{/if}{else}{$sortOrder}{/if}{if $labelID}&labelID={@$labelID}{/if}{/link}">{if $_sortField == 'subject'}{lang}wcf.global.subject{/lang}{else}{lang}wcf.conversation.{$_sortField}{/lang}{/if}</a></li>
+ {/foreach}
+ </ul>
+ </div>
+ </li>
+ </ul>
+ </li>
</ol>
</li>
{/if}
</h3>
- <aside class="statusDisplay">
+ <aside class="statusDisplay" role="presentation">
<ul class="statusIcons">
{if $conversation->isClosed}<li><span class="icon icon16 fa-lock jsIconLock jsTooltip" title="{lang}wcf.global.state.closed{/lang}"></span></li>{/if}
{if $conversation->attachments}<li><span class="icon icon16 fa-paperclip jsIconAttachment jsTooltip" title="{lang}wcf.conversation.attachments{/lang}"></span></li>{/if}
<ul class="inlineList dotSeparated small messageGroupInfo">
<li class="messageGroupAuthor">{if $conversation->userID}<a href="{link controller='User' object=$conversation->getUserProfile()->getDecoratedObject()}{/link}" class="userLink" data-user-id="{@$conversation->userID}">{$conversation->username}</a>{else}{$conversation->username}{/if}</li>
<li class="messageGroupTime">{@$conversation->time|time}</li>
- <li class="messageGroupEditLink jsOnly"><a class="jsConversationInlineEditor">{lang}wcf.global.button.edit{/lang}</a></li>
+ <li class="messageGroupEditLink jsOnly"><a href="#" class="jsConversationInlineEditor">{lang}wcf.global.button.edit{/lang}</a></li>
{event name='messageGroupInfo'}
</ul>
<dd>{@$conversation->participants|shortUnit}</dd>
</dl>
- <div class="messageGroupListStatsSimple">{@$conversation->replies|shortUnit}</div>
+ <div class="messageGroupListStatsSimple" aria-label="{lang}wcf.conversation.replies{/lang}">{@$conversation->replies|shortUnit}</div>
</li>
<li class="columnLastPost">
{if $conversation->replies != 0 && $conversation->lastPostTime}
<div class="messageContent">
<div class="messageHeader">
<div class="box32 messageHeaderWrapper">
- <a href="{link controller='User' object=$modificationLogEntry->getUserProfile()}{/link}">{@$modificationLogEntry->getUserProfile()->getAvatar()->getImageTag(32)}</a>
+ <a href="{link controller='User' object=$modificationLogEntry->getUserProfile()}{/link}" aria-hidden="true">{@$modificationLogEntry->getUserProfile()->getAvatar()->getImageTag(32)}</a>
<div class="messageHeaderBox">
<h2 class="messageTitle">
<div class="box48">
{if $message->getUserProfile()->getAvatar()}
- <a href="{link controller='User' object=$message->getUserProfile()->getDecoratedObject()}{/link}">{@$message->getUserProfile()->getAvatar()->getImageTag(48)}</a>
+ <a href="{link controller='User' object=$message->getUserProfile()->getDecoratedObject()}{/link}" aria-hidden="true">{@$message->getUserProfile()->getAvatar()->getImageTag(48)}</a>
{/if}
<div>
<div class="messageContent messageQuickReplyContent"{if $pageNo < $pages} data-placeholder="{lang}wcf.conversation.reply{/lang}"{/if}>
<div class="messageBody">
{if !$conversation->isDraft && !$conversation->hasOtherParticipants()}
- <p class="warning" style="margin-bottom: 14px">{lang}wcf.conversation.noParticipantsWarning{/lang}</p>
+ <p class="warning" role="status" style="margin-bottom: 14px">{lang}wcf.conversation.noParticipantsWarning{/lang}</p>
{/if}
<textarea id="text" name="text" class="wysiwygTextarea"
<header class="messageHeader">
<div class="box32 messageHeaderWrapper">
{if $message->userID}
- <a href="{link controller='User' object=$message->getUserProfile()->getDecoratedObject()}{/link}">{@$message->getUserProfile()->getAvatar()->getImageTag(32)}</a>
+ <a href="{link controller='User' object=$message->getUserProfile()->getDecoratedObject()}{/link}" aria-hidden="true">{@$message->getUserProfile()->getAvatar()->getImageTag(32)}</a>
{else}
<span>{@$message->getUserProfile()->getAvatar()->getImageTag(32)}</span>
{/if}
+++ /dev/null
-ALTER TABLE wcf1_conversation_to_user ADD FOREIGN KEY (lastMessageID) REFERENCES wcf1_conversation_message (messageID) ON DELETE SET NULL;
<?xml version="1.0" encoding="UTF-8"?>
-<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/vortex/userGroupOption.xsd">
+<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/2019/userGroupOption.xsd">
<import>
<categories>
<category name="user.conversation">
<options>module_conversation</options>
</category>
</categories>
-
<options>
<!-- mod.conversation -->
<option name="mod.conversation.canModerateConversation">
<categoryname>mod.conversation</categoryname>
<optiontype>boolean</optiontype>
<defaultvalue>0</defaultvalue>
- <admindefaultvalue>1</admindefaultvalue>
<options>module_conversation</options>
+ <admindefaultvalue>1</admindefaultvalue>
<usersonly>1</usersonly>
</option>
<option name="mod.conversation.canAlwaysInviteUsers">
<categoryname>mod.conversation</categoryname>
<optiontype>boolean</optiontype>
<defaultvalue>0</defaultvalue>
- <admindefaultvalue>1</admindefaultvalue>
<options>module_conversation</options>
+ <admindefaultvalue>1</admindefaultvalue>
<usersonly>1</usersonly>
</option>
<!-- /mod.conversation -->
-
<!-- user.conversation -->
<option name="user.conversation.canUseConversation">
<categoryname>user.conversation</categoryname>
<defaultvalue>1</defaultvalue>
<usersonly>1</usersonly>
</option>
+ <option name="user.conversation.canAddGroupParticipants">
+ <categoryname>user.conversation</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>0</defaultvalue>
+ <usersonly>1</usersonly>
+ <admindefaultvalue>1</admindefaultvalue>
+ </option>
+
<option name="user.conversation.maxParticipants">
<categoryname>user.conversation</categoryname>
<optiontype>integer</optiontype>
<categoryname>user.conversation</categoryname>
<optiontype>integer</optiontype>
<defaultvalue>10000</defaultvalue>
- <admindefaultvalue>100000</admindefaultvalue>
<minvalue>1000</minvalue>
+ <admindefaultvalue>100000</admindefaultvalue>
<usersonly>1</usersonly>
</option>
-
<option name="user.conversation.canUploadAttachment">
<categoryname>user.conversation</categoryname>
<optiontype>boolean</optiontype>
txt
pdf</defaultvalue>
<options>module_attachment</options>
- <wildcard>*</wildcard>
<usersonly>1</usersonly>
+ <wildcard>*</wildcard>
</option>
<option name="user.conversation.maxAttachmentCount">
<categoryname>user.conversation</categoryname>
<maxvalue>100</maxvalue>
<usersonly>1</usersonly>
</option>
- <!-- /user.conversation -->
</options>
</import>
</data>
-
<?xml version="1.0" encoding="UTF-8"?>
-<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/vortex/userNotificationEvent.xsd">
+<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/2019/userNotificationEvent.xsd">
<import>
<event>
<name>conversation</name>
<classname>wcf\system\user\notification\event\ConversationUserNotificationEvent</classname>
<options>module_conversation</options>
</event>
-
<event>
<name>conversationMessage</name>
<objecttype>com.woltlab.wcf.conversation.message.notification</objecttype>
-<?xml version="1.0" encoding="utf-8"?>
-<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/vortex/userOption.xsd">
+<?xml version="1.0" encoding="UTF-8"?>
+<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/2019/userOption.xsd">
<import>
<options>
<option name="canSendConversation">
<categoryname>settings.privacy.messaging</categoryname>
<optiontype>select</optiontype>
- <editable>3</editable>
+ <defaultvalue>0</defaultvalue>
+ <options>module_conversation</options>
<selectoptions>0:wcf.user.access.registered
1:wcf.user.access.following
2:wcf.user.access.nobody</selectoptions>
- <defaultvalue>0</defaultvalue>
- <options>module_conversation</options>
+ <editable>3</editable>
</option>
-
<option name="conversationsPerPage">
<categoryname>settings.general.appearance</categoryname>
<optiontype>select</optiontype>
- <editable>3</editable>
<defaultvalue>0</defaultvalue>
+ <options>module_conversation</options>
<selectoptions>0:wcf.global.defaultValue
5
10
20
30
40</selectoptions>
- <options>module_conversation</options>
+ <editable>3</editable>
</option>
<option name="conversationMessagesPerPage">
<categoryname>settings.general.appearance</categoryname>
<optiontype>select</optiontype>
- <editable>3</editable>
<defaultvalue>0</defaultvalue>
+ <options>module_conversation</options>
<selectoptions>0:wcf.global.defaultValue
5
10
20
30
40</selectoptions>
- <options>module_conversation</options>
+ <editable>3</editable>
</option>
</options>
</import>