### 2.2.0 Alpha 1 (XXXX-YY-ZZ)
+* Clipboard support for tags in ACP ("delete" and "set as synonyms").
* `wcf\data\user\UserProfileCache` for caching user profiles during runtime.
* instruction file name for most PIPs has default value provided by `wcf\system\package\plugin\IPackageInstallationPlugin::getDefaultFilename()`.
* `options` support for cronjobs.
<?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/maelstrom/clipboardAction.xsd">
<import>
+ <!-- com.woltlab.wcf.user -->
<action name="assignToGroup">
<actionclassname><![CDATA[wcf\system\clipboard\action\UserClipboardAction]]></actionclassname>
<showorder>1</showorder>
<page><![CDATA[wcf\acp\page\UserListPage]]></page>
</pages>
</action>
-
<action name="delete">
<actionclassname><![CDATA[wcf\system\clipboard\action\UserClipboardAction]]></actionclassname>
<showorder>2</showorder>
<page><![CDATA[wcf\acp\page\UserListPage]]></page>
</pages>
</action>
-
<action name="ban">
<actionclassname><![CDATA[wcf\system\clipboard\action\UserClipboardAction]]></actionclassname>
<showorder>3</showorder>
<page><![CDATA[wcf\acp\page\UserListPage]]></page>
</pages>
</action>
-
<action name="merge">
<actionclassname><![CDATA[wcf\system\clipboard\action\UserClipboardAction]]></actionclassname>
<showorder>4</showorder>
<page><![CDATA[wcf\acp\page\UserListPage]]></page>
</pages>
</action>
-
<action name="enable">
<actionclassname><![CDATA[wcf\system\clipboard\action\UserClipboardAction]]></actionclassname>
<showorder>5</showorder>
<page><![CDATA[wcf\acp\page\UserListPage]]></page>
</pages>
</action>
-
<action name="sendMail">
<actionclassname><![CDATA[wcf\system\clipboard\action\UserClipboardAction]]></actionclassname>
<showorder>6</showorder>
<page><![CDATA[wcf\acp\page\UserListPage]]></page>
</pages>
</action>
-
<action name="exportMailAddress">
<actionclassname><![CDATA[wcf\system\clipboard\action\UserClipboardAction]]></actionclassname>
<showorder>7</showorder>
<page><![CDATA[wcf\acp\page\UserListPage]]></page>
</pages>
</action>
-
<action name="sendNewPassword">
<actionclassname><![CDATA[wcf\system\clipboard\action\UserClipboardAction]]></actionclassname>
<showorder>8</showorder>
<page><![CDATA[wcf\acp\page\UserListPage]]></page>
</pages>
</action>
-
<action name="revertContentChanges">
<actionclassname><![CDATA[wcf\system\clipboard\action\UserContentClipboardAction]]></actionclassname>
<showorder>9</showorder>
<page><![CDATA[wcf\acp\page\UserListPage]]></page>
</pages>
</action>
+ <!-- /com.woltlab.wcf.user -->
+
+ <!-- com.woltlab.wcf.tag -->
+ <action name="delete">
+ <actionclassname><![CDATA[wcf\system\clipboard\action\TagClipboardAction]]></actionclassname>
+ <showorder>1</showorder>
+ <pages>
+ <page><![CDATA[wcf\acp\page\TagListPage]]></page>
+ </pages>
+ </action>
+ <action name="setAsSynonyms">
+ <actionclassname><![CDATA[wcf\system\clipboard\action\TagClipboardAction]]></actionclassname>
+ <showorder>2</showorder>
+ <pages>
+ <page><![CDATA[wcf\acp\page\TagListPage]]></page>
+ </pages>
+ </action>
+ <!-- com.woltlab.wcf.tag -->
</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/maelstrom/objectType.xsd">
<import>
+ <!-- clipboard items -->
<type>
<name>com.woltlab.wcf.user</name>
<definitionname>com.woltlab.wcf.clipboardItem</definitionname>
<listclassname><![CDATA[wcf\data\user\UserList]]></listclassname>
</type>
+ <type>
+ <name>com.woltlab.wcf.tag</name>
+ <definitionname>com.woltlab.wcf.clipboardItem</definitionname>
+ <listclassname><![CDATA[wcf\data\tag\TagList]]></listclassname>
+ </type>
+ <!-- /clipboard items -->
<type>
<name>com.woltlab.wcf.collapsibleSidebar</name>
}
}
});
+
+/**
+ * Initialize WCF.ACP.Tag namespace.
+ */
+WCF.ACP.Tag = { };
+
+/**
+ * Handles setting tags as synonyms of another tag by clipboard.
+ */
+WCF.ACP.Tag.SetAsSynonymsHandler = Class.extend({
+ /**
+ * dialog to select the "main" tag
+ * @var jQuery
+ */
+ _dialog: null,
+
+ /**
+ * ids of the selected tags
+ * @var array<integer>
+ */
+ _objectIDs: [ ],
+
+ /**
+ * Initializes the SetAsSynonymsHandler object.
+ *
+ * @param array<integer> objectIDs
+ */
+ init: function() {
+ // bind listener
+ $('.jsClipboardEditor').each($.proxy(function(index, container) {
+ var $container = $(container);
+ var $types = eval($container.data('types'));
+ if (WCF.inArray('com.woltlab.wcf.tag', $types)) {
+ $container.on('clipboardAction', $.proxy(this._execute, this));
+ return false;
+ }
+ }, this));
+ },
+
+ /**
+ * Handles clipboard actions.
+ *
+ * @param object event
+ * @param string type
+ * @param string actionName
+ * @param object parameters
+ */
+ _execute: function(event, type, actionName, parameters) {
+ if (type !== 'com.woltlab.wcf.tag' || actionName !== 'com.woltlab.wcf.tag.setAsSynonyms') {
+ return;
+ }
+
+ this._objectIDs = parameters.objectIDs;
+ if (this._dialog === null) {
+ this._dialog = $('<div id="setAsSynonymsDialog" />').hide().appendTo(document.body);
+ this._dialog.wcfDialog({
+ closable: false,
+ title: WCF.Language.get('wcf.acp.tag.setAsSynonyms')
+ });
+ }
+
+ this._dialog.html(parameters.template);
+ $button = this._dialog.find('button[data-type="submit"]').disable().click($.proxy(this._submit, this));
+ this._dialog.find('input[type=radio]').change(function() { $button.enable(); });
+ },
+
+ /**
+ * Saves the tags as synonyms.
+ */
+ _submit: function() {
+ new WCF.Action.Proxy({
+ autoSend: true,
+ data: {
+ actionName: 'setAsSynonyms',
+ className: 'wcf\\data\\tag\\TagAction',
+ objectIDs: this._objectIDs,
+ parameters: {
+ tagID: this._dialog.find('input[name="tagID"]:checked').val()
+ }
+ },
+ success: $.proxy(function() {
+ this._dialog.wcfDialog('close');
+
+ new WCF.System.Notification().show(function() {
+ window.location.reload();
+ });
+ }, this)
+ });
+ }
+});
<script data-relocate="true">
//<![CDATA[
$(function() {
- new WCF.Action.Delete('wcf\\data\\tag\\TagAction', '.jsTagRow');
+ WCF.Language.add('wcf.acp.tag.setAsSynonyms', '{lang}wcf.acp.tag.setAsSynonyms{/lang}');
+
+ WCF.Clipboard.init('wcf\\acp\\page\\TagListPage', {@$hasMarkedItems}, {
+ 'com.woltlab.wcf.tag': {
+ 'delete': new WCF.Action.Delete('wcf\\data\\tag\\TagAction', '.jsTagRow')
+ }
+ });
+
+ new WCF.ACP.Tag.SetAsSynonymsHandler();
});
//]]>
</script>
<nav>
<ul>
<li><a href="{link controller='TagAdd'}{/link}" class="button"><span class="icon icon16 icon-plus"></span> <span>{lang}wcf.acp.tag.add{/lang}</span></a></li>
-
+
{event name='contentNavigationButtonsTop'}
</ul>
</nav>
<h2>{lang}wcf.acp.tag.list{/lang} <span class="badge badgeInverse">{#$items}</span></h2>
</header>
- <table class="table">
+ <table data-type="com.woltlab.wcf.tag" class="table jsClipboardContainer">
<thead>
<tr>
+ <th class="columnMark"><label><input type="checkbox" class="jsClipboardMarkAll" /></label></th>
<th class="columnID columnTagID{if $sortField == 'tagID'} active {@$sortOrder}{/if}" colspan="2"><a href="{link controller='TagList'}pageNo={@$pageNo}&sortField=tagID&sortOrder={if $sortField == 'tagID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}&search={@$search|rawurlencode}{/link}">{lang}wcf.global.objectID{/lang}</a></th>
<th class="columnTitle columnName{if $sortField == 'name'} active {@$sortOrder}{/if}"><a href="{link controller='TagList'}pageNo={@$pageNo}&sortField=name&sortOrder={if $sortField == 'name' && $sortOrder == 'ASC'}DESC{else}ASC{/if}&search={@$search|rawurlencode}{/link}">{lang}wcf.acp.tag.name{/lang}</a></th>
<th class="columnDigits columnUsageCount{if $sortField == 'usageCount'} active {@$sortOrder}{/if}"><a href="{link controller='TagList'}pageNo={@$pageNo}&sortField=usageCount&sortOrder={if $sortField == 'usageCount' && $sortOrder == 'ASC'}DESC{else}ASC{/if}&search={@$search|rawurlencode}{/link}">{lang}wcf.acp.tag.usageCount{/lang}</a></th>
<tbody>
{foreach from=$objects item=tag}
- <tr class="jsTagRow">
+ <tr class="jsTagRow jsClipboardObject">
+ <td class="columnMark"><input type="checkbox" class="jsClipboardItem" data-object-id="{@$tag->tagID}" /></td>
<td class="columnIcon">
<a href="{link controller='TagEdit' object=$tag}{/link}" title="{lang}wcf.global.button.edit{/lang}" class="jsTooltip"><span class="icon icon16 icon-pencil"></span></a>
<span class="icon icon16 icon-remove jsDeleteButton jsTooltip pointer" title="{lang}wcf.global.button.delete{/lang}" data-object-id="{@$tag->tagID}" data-confirm-message="{lang}wcf.acp.tag.delete.sure{/lang}"></span>
<nav>
<ul>
<li><a href="{link controller='TagAdd'}{/link}" class="button"><span class="icon icon16 icon-plus"></span> <span>{lang}wcf.acp.tag.add{/lang}</span></a></li>
-
+
{event name='contentNavigationButtonsBottom'}
</ul>
</nav>
+
+ <nav class="jsClipboardEditor" data-types="[ 'com.woltlab.wcf.tag' ]"></nav>
</div>
{else}
<p class="info">{lang}wcf.global.noItems{/lang}</p>
</tbody>
</table>
</div>
-
+
<div class="contentNavigation">
{@$pagesLinks}
<?php
namespace wcf\acp\page;
use wcf\page\SortablePage;
+use wcf\system\clipboard\ClipboardHandler;
use wcf\system\WCF;
use wcf\util\StringUtil;
parent::assignVariables();
WCF::getTPL()->assign(array(
+ 'hasMarkedItems' => ClipboardHandler::getInstance()->hasMarkedItems(ClipboardHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.tag')),
'search' => $this->search
));
}
namespace wcf\data\tag;
use wcf\data\AbstractDatabaseObjectAction;
use wcf\data\ISearchAction;
+use wcf\system\clipboard\ClipboardHandler;
use wcf\system\database\util\PreparedStatementConditionBuilder;
use wcf\system\exception\UserInputException;
use wcf\system\WCF;
*/
protected $requireACP = array('delete', 'update');
+ /**
+ * tag for which other tags will be used as synonyms
+ * @var \wcf\data\tag\TagEditor
+ */
+ public $tagEditor = null;
+
/**
* @see \wcf\data\ISearchAction::validateGetSearchResultList()
*/
return $list;
}
+
+ /**
+ * @see \wcf\data\IDeleteAction::delete()
+ */
+ public function delete() {
+ $returnValue = parent::delete();
+
+ $this->unmarkItems();
+
+ return $returnValue;
+ }
+
+ /**
+ * Validates the 'setAsSynonyms' action.
+ */
+ public function validateSetAsSynonyms() {
+ WCF::getSession()->checkPermissions([ 'admin.content.tag.canManageTag' ]);
+ if (empty($this->objects)) {
+ $this->readObjects();
+
+ if (count($this->objects) < 2) {
+ throw new UserInputException('objectIDs');
+ }
+ }
+
+ $this->readInteger('tagID');
+ $this->tagEditor = new TagEditor(new Tag($this->parameters['tagID']));
+ if (!$this->tagEditor->tagID) {
+ throw new UserInputException('tagID');
+ }
+ }
+
+ /**
+ * Sets a number of tags as a synonyms of another tag.
+ */
+ public function setAsSynonyms() {
+ // the "main" tag may not be a synonym itself
+ if ($this->tagEditor->synonymFor) {
+ $this->tagEditor->update([
+ 'synonymFor' => null
+ ]);
+ }
+
+ foreach ($this->objects as $tagEditor) {
+ $this->tagEditor->addSynonym($tagEditor->getDecoratedObject());
+ }
+
+ $this->unmarkItems();
+ }
+
+ /**
+ * Unmarks tags.
+ *
+ * @param array<integer> $tagIDs
+ */
+ protected function unmarkItems(array $tagIDs = array()) {
+ if (empty($tagIDs)) {
+ $tagIDs = $this->objectIDs;
+ }
+
+ if (!empty($tagIDs)) {
+ ClipboardHandler::getInstance()->unmark($tagIDs, ClipboardHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.tag'));
+ }
+ }
}
*/
public function addSynonym(Tag $synonym) {
if ($synonym->tagID == $this->tagID) return;
-
+
// assign all associations for the synonym with this tag
$sql = "UPDATE IGNORE wcf".WCF_N."_tag_to_object
SET tagID = ?
<item name="wcf.acp.tag.synonymFor"><![CDATA[Synonym für]]></item>
<item name="wcf.acp.tag.usageCount"><![CDATA[Verwendungen]]></item>
<item name="wcf.acp.tag.error.name.duplicate"><![CDATA[Ein Tag mit diesem Namen existiert bereits.]]></item>
+ <item name="wcf.acp.tag.setAsSynonyms"><![CDATA[Als Synonyme verwenden]]></item>
+ <item name="wcf.acp.tag.setAsSynonyms.description"><![CDATA[Wählen Sie einen Tag aus. Die übrigen Tags werden als Synonyme des ausgewählten Tags verwendet.]]></item>
</category>
<category name="wcf.acp.template">
<category name="wcf.clipboard">
<item name="wcf.clipboard.item.unmarkAll"><![CDATA[Demarkieren]]></item>
+
+ <item name="wcf.clipboard.item.com.woltlab.wcf.tag.delete"><![CDATA[Löschen ({#$count})]]></item>
+ <item name="wcf.clipboard.item.com.woltlab.wcf.tag.delete.confirmMessage"><![CDATA[{if $count == 1}Einen Tag{else}{#$count} Tag{/if} löschen?]]></item>
+ <item name="wcf.clipboard.item.com.woltlab.wcf.tag.setAsSynonyms"><![CDATA[Als Synonyme verwenden ({#$count})]]></item>
+
<item name="wcf.clipboard.item.com.woltlab.wcf.user.assignToGroup"><![CDATA[Benutzergruppe zuweisen]]></item>
<item name="wcf.clipboard.item.com.woltlab.wcf.user.ban"><![CDATA[Sperren ({#$count})]]></item>
<item name="wcf.clipboard.item.com.woltlab.wcf.user.delete"><![CDATA[Löschen ({#$count})]]></item>
<item name="wcf.clipboard.item.com.woltlab.wcf.user.delete.confirmMessage"><![CDATA[{if $count == 1}Einen{else}{#$count}{/if} Benutzer löschen?]]></item>
+ <item name="wcf.clipboard.item.com.woltlab.wcf.user.enable"><![CDATA[Aktivieren]]></item>
<item name="wcf.clipboard.item.com.woltlab.wcf.user.exportMailAddress"><![CDATA[E-Mail-Adressen exportieren]]></item>
+ <item name="wcf.clipboard.item.com.woltlab.wcf.user.merge"><![CDATA[Zusammenfügen]]></item>
+ <item name="wcf.clipboard.item.com.woltlab.wcf.user.revertContentChanges"><![CDATA[Änderungen an Inhalten zurücksetzen]]></item>
<item name="wcf.clipboard.item.com.woltlab.wcf.user.sendMail"><![CDATA[E-Mail senden]]></item>
<item name="wcf.clipboard.item.com.woltlab.wcf.user.sendNewPassword"><![CDATA[Neues Kennwort senden ({#$count})]]></item>
<item name="wcf.clipboard.item.com.woltlab.wcf.user.sendNewPassword.confirmMessage"><![CDATA[Wollen Sie wirklich {if $count == 1}einem{else}{#$count}{/if} Benutzer ein neues Kennwort zusenden?]]></item>
+
+ <item name="wcf.clipboard.label.com.woltlab.wcf.tag.marked"><![CDATA[{if $count == 1}Ein Tag{else}{#$count} Tags{/if} markiert]]></item>
<item name="wcf.clipboard.label.com.woltlab.wcf.user.marked"><![CDATA[{if $count == 1}Ein{else}{#$count}{/if} Benutzer markiert]]></item>
- <item name="wcf.clipboard.item.com.woltlab.wcf.user.merge"><![CDATA[Zusammenfügen]]></item>
- <item name="wcf.clipboard.item.com.woltlab.wcf.user.revertContentChanges"><![CDATA[Änderungen an Inhalten zurücksetzen]]></item>
- <item name="wcf.clipboard.item.com.woltlab.wcf.user.enable"><![CDATA[Aktivieren]]></item>
</category>
<category name="wcf.comment">
<item name="wcf.acp.tag.synonymFor"><![CDATA[Synonym for]]></item>
<item name="wcf.acp.tag.usageCount"><![CDATA[Usages]]></item>
<item name="wcf.acp.tag.error.name.duplicate"><![CDATA[This tag name is already in use.]]></item>
+ <item name="wcf.acp.tag.setAsSynonyms"><![CDATA[Set as Synonyms]]></item>
+ <item name="wcf.acp.tag.setAsSynonyms.description"><![CDATA[Select one of the tags. The other tags will be set as synonyms of the selected tag.]]></item>
</category>
<category name="wcf.acp.template">
<category name="wcf.clipboard">
<item name="wcf.clipboard.item.unmarkAll"><![CDATA[Unmark All]]></item>
+
+ <item name="wcf.clipboard.item.com.woltlab.wcf.tag.delete"><![CDATA[Delete ({#$count})]]></item>
+ <item name="wcf.clipboard.item.com.woltlab.wcf.tag.delete.confirmMessage"><![CDATA[Do you really want to delete {#$count} tag{if $count != 1}s{/if}?]]></item>
+ <item name="wcf.clipboard.item.com.woltlab.wcf.tag.setAsSynonyms"><![CDATA[Set as Synonyms ({#$count})]]></item>
+
<item name="wcf.clipboard.item.com.woltlab.wcf.user.assignToGroup"><![CDATA[Assign to User Group]]></item>
<item name="wcf.clipboard.item.com.woltlab.wcf.user.ban"><![CDATA[Ban ({#$count})]]></item>
<item name="wcf.clipboard.item.com.woltlab.wcf.user.delete"><![CDATA[Delete ({#$count})]]></item>
<item name="wcf.clipboard.item.com.woltlab.wcf.user.delete.confirmMessage"><![CDATA[Do you really want to delete {#$count} user{if $count != 1}s{/if}?]]></item>
+ <item name="wcf.clipboard.item.com.woltlab.wcf.user.enable"><![CDATA[Approve]]></item>
<item name="wcf.clipboard.item.com.woltlab.wcf.user.exportMailAddress"><![CDATA[Export Email addresses]]></item>
+ <item name="wcf.clipboard.item.com.woltlab.wcf.user.merge"><![CDATA[Merge]]></item>
+ <item name="wcf.clipboard.item.com.woltlab.wcf.user.revertContentChanges"><![CDATA[Revert Changes To Contents]]></item>
<item name="wcf.clipboard.item.com.woltlab.wcf.user.sendMail"><![CDATA[Send Email]]></item>
<item name="wcf.clipboard.item.com.woltlab.wcf.user.sendNewPassword"><![CDATA[Send New Password ({#$count})]]></item>
<item name="wcf.clipboard.item.com.woltlab.wcf.user.sendNewPassword.confirmMessage"><![CDATA[Do you really want to send a new password to {#$count} user{if $count != 1}s{/if}?]]></item>
+
+ <item name="wcf.clipboard.label.com.woltlab.wcf.tag.marked"><![CDATA[{#$count} Tag{if $count != 1}s{/if} marked]]></item>
<item name="wcf.clipboard.label.com.woltlab.wcf.user.marked"><![CDATA[{#$count} User{if $count != 1}s{/if} marked]]></item>
- <item name="wcf.clipboard.item.com.woltlab.wcf.user.merge"><![CDATA[Merge]]></item>
- <item name="wcf.clipboard.item.com.woltlab.wcf.user.revertContentChanges"><![CDATA[Revert Changes To Contents]]></item>
- <item name="wcf.clipboard.item.com.woltlab.wcf.user.enable"><![CDATA[Approve]]></item>
</category>
<category name="wcf.comment">