Add possibility to sort labels in ACP
authorMatthias Schmidt <gravatronics@live.com>
Sun, 27 Mar 2016 14:20:24 +0000 (16:20 +0200)
committerMatthias Schmidt <gravatronics@live.com>
Sun, 27 Mar 2016 14:20:24 +0000 (16:20 +0200)
17 files changed:
CHANGELOG.md
wcfsetup/install/files/acp/templates/labelAdd.tpl
wcfsetup/install/files/acp/templates/labelList.tpl
wcfsetup/install/files/js/WCF.js
wcfsetup/install/files/lib/acp/form/LabelAddForm.class.php
wcfsetup/install/files/lib/acp/form/LabelEditForm.class.php
wcfsetup/install/files/lib/acp/page/LabelListPage.class.php
wcfsetup/install/files/lib/data/label/Label.class.php
wcfsetup/install/files/lib/data/label/LabelAction.class.php
wcfsetup/install/files/lib/data/label/LabelEditor.class.php
wcfsetup/install/files/lib/data/label/LabelList.class.php
wcfsetup/install/files/lib/system/cache/builder/LabelCacheBuilder.class.php
wcfsetup/install/files/style/ui/listSortable.scss
wcfsetup/install/files/style/ui/tableSortable.scss [new file with mode: 0644]
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml
wcfsetup/setup/db/install.sql

index 6df2c54ead778bc560f7c3df302c9f6f3b5c9c09..45c829267a0fa606b7db534ad629303f5a8f39e6 100644 (file)
@@ -30,6 +30,7 @@
 * Notifications for comments in moderation.
 * Continuous numeration of edit history version in template.
 * `\wcf\data\user\UserProfile::getGuestUserProfile()` added.
+* Make labels sortable in ACP.
 
 #### CMS
 
index d017feed9e85683e848b8b47ea96b2aaf9646cef..5349f556c42381be702a79fb136953c942c38964 100644 (file)
                        </dl>
                        {include file='multipleLanguageInputJavascript' elementIdentifier='label' forceSelection=false}
                        
+                       <dl>
+                               <dt><label for="showOrder">{lang}wcf.acp.label.showOrder{/lang}</label></dt>
+                               <dd>
+                                       <input type="number" min="0" id="showOrder" name="showOrder" class="tiny" value="{if $showOrder}{@$showOrder}{/if}" />
+                                       <small>{lang}wcf.acp.label.showOrder.description{/lang}</small>
+                               </dd>
+                       </dl>
+                       
                        <dl{if $errorField == 'cssClassName'} class="formError"{/if}>
                                <dt><label for="cssClassName">{lang}wcf.acp.label.cssClassName{/lang}</label></dt>
                                <dd>
index 06177ef9a0631785d38d2e2f9e34bd34595a4107..b50422257705d93b098c2aed98ff34c2e6103463 100644 (file)
                {/if}
                
                new WCF.Table.EmptyTableHandler($('#labelTableContainer'), 'jsLabelRow', options);
+               
+               {if $labelGroup && !$labelSearch && !$cssClassName && $items > 1}
+                       new WCF.Sortable.List('labelTableContainer', 'wcf\\data\\label\\LabelAction', {@$startIndex}, {
+                               items: 'tr',
+                               toleranceElement: null
+                       }, true);
+               {/if}
        });
        //]]>
 </script>
        <h1 class="contentTitle">{lang}wcf.acp.label.list{/lang}</h1>
 </header>
 
+{if $items || $labelSearch || $labelGroup || $cssClassName}
+       <p class="info">{lang}wcf.acp.label.sortAfterGroupFiltering{/lang}</p>
+       
+       <form action="{link controller='LabelList'}{/link}" method="post">
+               <section class="section">
+                       <h2 class="sectionTitle">{lang}wcf.acp.label.filter{/lang}</h2>
+                       
+                       <div class="row rowColGap">
+                               <dl class="col-xs-12 col-md-4">
+                                       <dt><label for="label">{lang}wcf.acp.label.label{/lang}</label></dt>
+                                       <dd>
+                                               <input type="text" id="label" name="label" value="{$labelSearch}" class="long" />
+                                       </dd>
+                               </dl>
+                               
+                               <dl class="col-xs-12 col-md-4">
+                                       <dt><label for="groupID">{lang}wcf.acp.label.group{/lang}</label></dt>
+                                       <dd>
+                                               <select id="groupID" name="groupID">
+                                                       <option value="0">{lang}wcf.global.noSelection{/lang}</option>
+                                                       {foreach from=$labelGroupList item=group}
+                                                               <option value="{@$group->groupID}"{if $group->groupID == $groupID} selected="selected"{/if}>{$group}{if $group->groupDescription} / {$group->groupDescription}{/if}</option>
+                                                       {/foreach}
+                                               </select>
+                                       </dd>
+                               </dl>
+                               
+                               <dl class="col-xs-12 col-md-4">
+                                       <dt><label for="cssClassName">{lang}wcf.acp.label.cssClassName{/lang}</label></dt>
+                                       <dd>
+                                               <input type="text" id="cssClassName" name="cssClassName" value="{$cssClassName}" class="long" />
+                                       </dd>
+                               </dl>
+                       </div>
+               </section>
+               
+               <div class="formSubmit">
+                       <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" />
+                       {@SID_INPUT_TAG}
+                       {@SECURITY_TOKEN_INPUT_TAG}
+               </div>
+       </form>
+{/if}
+
 <div class="contentNavigation">
-       {pages print=true assign=pagesLinks controller="LabelList" link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder"}
+       {assign var='linkParameters' value=''}
+       {if $labelSearch}
+               {append var='linkParameters' value='&label='}
+               {append var='linkParameters' value=$labelSearch|rawurlencode}
+       {/if}
+       {if $cssClassName}
+               {append var='linkParameters' value='&cssClassName='}
+               {append var='linkParameters' value=$cssClassName|rawurlencode}
+       {/if}
+       {pages print=true assign=pagesLinks controller="LabelList" object=$labelGroup link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder$linkParameters"}
        
        <nav>
                <ul>
 </div>
 
 {if $objects|count}
-       <div id="labelTableContainer" class="section tabularBox">
+       <div id="labelTableContainer" class="section tabularBox{if $labelGroup && !$labelSearch && !$cssClassName && $items > 1} sortableListContainer{/if}">
                <table class="table">
                        <thead>
                                <tr>
-                                       <th class="columnID columnLabelID{if $sortField == 'labelID'} active {@$sortOrder}{/if}" colspan="2"><a href="{link controller='LabelList'}pageNo={@$pageNo}&sortField=labelID&sortOrder={if $sortField == 'labelID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.global.objectID{/lang}</a></th>
-                                       <th class="columnTitle columnLabel{if $sortField == 'label'} active {@$sortOrder}{/if}"><a href="{link controller='LabelList'}pageNo={@$pageNo}&sortField=label&sortOrder={if $sortField == 'label' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.acp.label.label{/lang}</a></th>
-                                       <th class="columnText columnGroup{if $sortField == 'groupName'} active {@$sortOrder}{/if}"><a href="{link controller='LabelList'}pageNo={@$pageNo}&sortField=groupName&sortOrder={if $sortField == 'groupName' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.acp.label.group{/lang}</a></th>
+                                       <th class="columnID columnLabelID{if $sortField == 'labelID'} active {@$sortOrder}{/if}" colspan="2"><a href="{link controller='LabelList' object=$labelGroup}pageNo={@$pageNo}&sortField=labelID&sortOrder={if $sortField == 'labelID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.global.objectID{/lang}</a></th>
+                                       <th class="columnTitle columnLabel{if $sortField == 'label'} active {@$sortOrder}{/if}"><a href="{link controller='LabelList' object=$labelGroup}pageNo={@$pageNo}&sortField=label&sortOrder={if $sortField == 'label' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.acp.label.label{/lang}</a></th>
+                                       <th class="columnText columnGroup{if $sortField == 'groupName'} active {@$sortOrder}{/if}"><a href="{link controller='LabelList' object=$labelGroup}pageNo={@$pageNo}&sortField=groupName&sortOrder={if $sortField == 'groupName' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.acp.label.group{/lang}</a></th>
+                                       {if $labelGroup && !$labelSearch && !$cssClassName && $items > 1}
+                                               <th class="columnDigits columnShowOrder{if $sortField == 'showOrder'} active {@$sortOrder}{/if}"><a href="{link controller='LabelList' object=$labelGroup}pageNo={@$pageNo}&sortField=showOrder&sortOrder={if $sortField == 'groupName' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.acp.label.showOrder{/lang}</a></th>
+                                       {/if}
                                        
                                        {event name='columnHeads'}
                                </tr>
                        </thead>
                        
-                       <tbody>
+                       <tbody{if $labelGroup && !$labelSearch && !$cssClassName && $items > 1} class="sortableList" data-object-id="{@$labelGroup->groupID}"{/if}>
                                {foreach from=$objects item=label}
-                                       <tr class="jsLabelRow">
+                                       <tr class="jsLabelRow{if $labelGroup && !$labelSearch && !$cssClassName && $items > 1} sortableNode" data-object-id="{@$label->labelID}{/if}">
                                                <td class="columnIcon">
                                                        <a href="{link controller='LabelEdit' object=$label}{/link}" title="{lang}wcf.global.button.edit{/lang}" class="jsTooltip"><span class="icon icon16 fa-pencil"></span></a>
                                                        <span class="icon icon16 fa-times jsDeleteButton jsTooltip pointer" title="{lang}wcf.global.button.delete{/lang}" data-object-id="{@$label->labelID}" data-confirm-message="{lang}wcf.acp.label.delete.sure{/lang}"></span>
                                                <td class="columnID">{@$label->labelID}</td>
                                                <td class="columnTitle columnLabel"><a href="{link controller='LabelEdit' object=$label}{/link}" title="{$label}" class="badge label{if $label->getClassNames()} {$label->getClassNames()}{/if}">{$label}</a></td>
                                                <td class="columnText columnGroup">{lang}{$label->groupName}{/lang}{if $label->groupDescription} / {$label->groupDescription}{/if}</td>
+                                               {if $labelGroup && !$labelSearch && !$cssClassName && $items > 1}
+                                                       <td class="columnDigits columnShowOrder">{#$label->showOrder}</td>
+                                               {/if}
                                                
                                                {event name='columns'}
                                        </tr>
                </table>
        </div>
        
+       {if $labelGroup && !$labelSearch && !$cssClassName && $items > 1}
+               <div class="formSubmit">
+                       <button data-type="submit">{lang}wcf.global.button.saveSorting{/lang}</button>
+               </div>
+       {/if}
+       
        <div class="contentNavigation">
                {@$pagesLinks}
                
index 3d75ce09b1987ce9d5e64bff47b0873fffba8029..fca070a86da87f6764849bb734d00f454564aac8 100755 (executable)
@@ -6777,11 +6777,16 @@ WCF.Sortable.List = Class.extend({
                        toleranceElement: '> span'
                }, options || { });
                
+               var sortableList = $('#' + this._containerID + ' .sortableList');
+               if (sortableList.is('tbody') && this._options.helper === 'clone') {
+                       this._options.helper = this._tableRowHelper.bind(this);
+               }
+               
                if (isSimpleSorting) {
-                       $('#' + this._containerID + ' .sortableList').sortable(this._options);
+                       sortableList.sortable(this._options);
                }
                else {
-                       $('#' + this._containerID + ' > .sortableList').nestedSortable(this._options);
+                       sortableList.nestedSortable(this._options);
                }
                
                if (this._className) {
@@ -6798,6 +6803,23 @@ WCF.Sortable.List = Class.extend({
                }
        },
        
+       /**
+        * Fixes the width of the cells of the dragged table row.
+        * 
+        * @param       {Event}         event
+        * @param       {jQuery}        ui
+        * @return      {jQuery}
+        */
+       _tableRowHelper: function(event, ui) {
+               ui.children('td').each(function(index, element) {
+                       element = $(element);
+                       
+                       element.width(element.width());
+               });
+               
+               return ui;
+       },
+       
        /**
         * Saves object structure.
         */
index 9cf311b4f8fa3462002c8d2af84d96392faabeac..a992954cd9f2ba2625e0a78e531f5cc7698eb534 100644 (file)
@@ -15,7 +15,7 @@ use wcf\util\StringUtil;
  * Shows the label add form.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2015 WoltLab GmbH
+ * @copyright  2001-2016 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    com.woltlab.wcf
  * @subpackage acp.form
@@ -23,14 +23,14 @@ use wcf\util\StringUtil;
  */
 class LabelAddForm extends AbstractForm {
        /**
-        * @see \wcf\page\AbstractPage::$activeMenuItem
+        * @inheritDoc
         */
        public $activeMenuItem = 'wcf.acp.menu.link.label.add';
        
        /**
-        * @see \wcf\page\AbstractPage::$neededPermissions
+        * @inheritDoc
         */
-       public $neededPermissions = array('admin.content.label.canManageLabel');
+       public $neededPermissions = ['admin.content.label.canManageLabel'];
        
        /**
         * label group id
@@ -64,9 +64,9 @@ class LabelAddForm extends AbstractForm {
        
        /**
         * list of pre-defined css class names
-        * @var array<string>
+        * @var string[]
         */
-       public $availableCssClassNames = array(
+       public $availableCssClassNames = [
                'yellow',
                'orange',
                'brown',
@@ -79,10 +79,16 @@ class LabelAddForm extends AbstractForm {
                
                'none', /* not a real value */
                'custom' /* not a real value */
-       );
+       ];
        
        /**
-        * @see \wcf\page\IPage::readParameters()
+        * show order
+        * @var integer
+        */
+       public $showOrder = 0;
+       
+       /**
+        * @inheritDoc
         */
        public function readParameters() {
                parent::readParameters();
@@ -91,7 +97,7 @@ class LabelAddForm extends AbstractForm {
        }
        
        /**
-        * @see \wcf\form\IForm::readFormParameters()
+        * @inheritDoc
         */
        public function readFormParameters() {
                parent::readFormParameters();
@@ -102,10 +108,11 @@ class LabelAddForm extends AbstractForm {
                if (isset($_POST['cssClassName'])) $this->cssClassName = StringUtil::trim($_POST['cssClassName']);
                if (isset($_POST['customCssClassName'])) $this->customCssClassName = StringUtil::trim($_POST['customCssClassName']);
                if (isset($_POST['groupID'])) $this->groupID = intval($_POST['groupID']);
+               if (isset($_POST['showOrder'])) $this->showOrder = intval($_POST['showOrder']);
        }
        
        /**
-        * @see \wcf\form\IForm::validate()
+        * @inheritDoc
         */
        public function validate() {
                parent::validate();
@@ -141,20 +148,23 @@ class LabelAddForm extends AbstractForm {
                                throw new UserInputException('cssClassName', 'notValid');
                        }
                }
+               
+               if ($this->showOrder < 0) $this->showOrder = 0;
        }
        
        /**
-        * @see \wcf\form\IForm::save()
+        * @inheritDoc
         */
        public function save() {
                parent::save();
                
                // save label
-               $this->objectAction = new LabelAction(array(), 'create', array('data' => array_merge($this->additionalFields, array(
+               $this->objectAction = new LabelAction([], 'create', ['data' => array_merge($this->additionalFields, [
                        'label' => $this->label,
                        'cssClassName' => ($this->cssClassName == 'custom' ? $this->customCssClassName : $this->cssClassName),
-                       'groupID' => $this->groupID
-               ))));
+                       'groupID' => $this->groupID,
+                       'showOrder' => $this->showOrder
+               ])]);
                $this->objectAction->executeAction();
                
                if (!I18nHandler::getInstance()->isPlainValue('label')) {
@@ -164,9 +174,9 @@ class LabelAddForm extends AbstractForm {
                        
                        // update group name
                        $labelEditor = new LabelEditor($returnValues['returnValues']);
-                       $labelEditor->update(array(
+                       $labelEditor->update([
                                'label' => 'wcf.acp.label.label'.$labelID
-                       ));
+                       ]);
                }
                
                $objectTypes = ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.label.objectType');
@@ -178,18 +188,16 @@ class LabelAddForm extends AbstractForm {
                
                // reset values
                $this->label = $this->cssClassName = $this->customCssClassName = '';
-               $this->groupID = 0;
+               $this->groupID = $this->showOrder = 0;
                
                I18nHandler::getInstance()->reset();
                
                // show success
-               WCF::getTPL()->assign(array(
-                       'success' => true
-               ));
+               WCF::getTPL()->assign('success', true);
        }
        
        /**
-        * @see \wcf\page\IPage::readData()
+        * @inheritDoc
         */
        public function readData() {
                $this->labelGroupList = new LabelGroupList();
@@ -199,21 +207,22 @@ class LabelAddForm extends AbstractForm {
        }
        
        /**
-        * @see \wcf\page\IPage::assignVariables()
+        * @inheritDoc
         */
        public function assignVariables() {
                parent::assignVariables();
                
                I18nHandler::getInstance()->assignVariables();
                
-               WCF::getTPL()->assign(array(
+               WCF::getTPL()->assign([
                        'action' => 'add',
                        'availableCssClassNames' => $this->availableCssClassNames,
                        'cssClassName' => $this->cssClassName,
                        'customCssClassName' => $this->customCssClassName,
                        'groupID' => $this->groupID,
                        'label' => $this->label,
-                       'labelGroupList' => $this->labelGroupList
-               ));
+                       'labelGroupList' => $this->labelGroupList,
+                       'showOrder' => $this->showOrder
+               ]);
        }
 }
index de3610345a231598d914a8335bd547f8bb3db2ff..5e0234cc3a5b2cc05bab5d6ba8debabb65885639 100644 (file)
@@ -12,7 +12,7 @@ use wcf\system\WCF;
  * Shows the label edit form.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2015 WoltLab GmbH
+ * @copyright  2001-2016 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    com.woltlab.wcf
  * @subpackage acp.form
@@ -73,7 +73,8 @@ class LabelEditForm extends LabelAddForm {
                $this->objectAction = new LabelAction(array($this->labelID), 'update', array('data' => array_merge($this->additionalFields, array(
                        'label' => $this->label,
                        'cssClassName' => ($this->cssClassName == 'custom' ? $this->customCssClassName : $this->cssClassName),
-                       'groupID' => $this->groupID
+                       'groupID' => $this->groupID,
+                       'showOrder' => $this->showOrder
                ))));
                $this->objectAction->executeAction();
                
@@ -110,6 +111,7 @@ class LabelEditForm extends LabelAddForm {
                        }
                        
                        $this->groupID = $this->labelObj->groupID;
+                       $this->showOrder = $this->labelObj->showOrder;
                }
        }
        
index 0c15e73a5c51e6f30584d53905a535636867351d..706baa692bd1ee276d59d34e821c6a1f0ab8d212 100644 (file)
@@ -1,12 +1,22 @@
 <?php
 namespace wcf\acp\page;
+use wcf\data\label\group\LabelGroup;
+use wcf\data\label\group\LabelGroupList;
+use wcf\data\label\LabelList;
+use wcf\data\language\item\LanguageItemList;
 use wcf\page\SortablePage;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\language\LanguageFactory;
+use wcf\system\request\LinkHandler;
+use wcf\system\WCF;
+use wcf\util\HeaderUtil;
+use wcf\util\StringUtil;
 
 /**
  * Lists available labels
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2015 WoltLab GmbH
+ * @copyright  2001-2016 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    com.woltlab.wcf
  * @subpackage acp.page
@@ -14,37 +24,151 @@ use wcf\page\SortablePage;
  */
 class LabelListPage extends SortablePage {
        /**
-        * @see \wcf\page\AbstractPage::$activeMenuItem
+        * @inheritDoc
         */
        public $activeMenuItem = 'wcf.acp.menu.link.label.list';
        
        /**
-        * @see \wcf\page\SortablePage::$defaultSortField
+        * @inheritDoc
         */
        public $defaultSortField = 'label';
        
        /**
-        * @see \wcf\page\SortablePage::$validSortFields
+        * @inheritDoc
         */
-       public $validSortFields = array('labelID', 'label', 'groupName');
+       public $validSortFields = ['labelID', 'label', 'groupName', 'showOrder'];
        
        /**
-        * @see \wcf\page\AbstractPage::$neededPermissions
+        * @inheritDoc
         */
-       public $neededPermissions = array('admin.content.label.canManageLabel');
+       public $neededPermissions = ['admin.content.label.canManageLabel'];
        
        /**
-        * @see \wcf\page\MultipleLinkPage::$objectListClassName
+        * @inheritDoc
         */
-       public $objectListClassName = 'wcf\data\label\LabelList';
+       public $objectListClassName = LabelList::class;
        
        /**
-        * @see \wcf\page\MultipleLinkPage::initObjectList()
+        * filter for css class name
+        * @var string
+        */
+       public $cssClassName = '';
+       
+       /**
+        * if of the label group to which the displayed labels belong
+        * @var integer
+        */
+       public $groupID = 0;
+       
+       /**
+        * filter for label name
+        * @var string
+        */
+       public $label = '';
+       
+       /**
+        * label group to which the displayed labels belong
+        * @var LabelGroup
+        */
+       public $labelGroup = null;
+       
+       /**
+        * list with available label groups
+        * @var LabelGroupList
+        */
+       public $labelGroupList = null;
+       
+       /**
+        * @inheritDoc
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               WCF::getTPL()->assign([
+                       'cssClassName' => $this->cssClassName,
+                       'groupID' => $this->groupID,
+                       'labelSearch' => $this->label,
+                       'labelGroup' => $this->labelGroup,
+                       'labelGroupList' => $this->labelGroupList
+               ]);
+       }
+       
+       /**
+        * @inheritDoc
         */
        protected function initObjectList() {
                parent::initObjectList();
                
                $this->objectList->sqlSelects = "label_group.groupName, label_group.groupDescription";
                $this->objectList->sqlJoins = "LEFT JOIN wcf".WCF_N."_label_group label_group ON (label_group.groupID = label.groupID)";
+               if ($this->labelGroup) {
+                       $this->objectList->getConditionBuilder()->add('label.groupID = ?', [$this->labelGroup->groupID]);
+               }
+               if ($this->cssClassName) {
+                       $this->objectList->getConditionBuilder()->add('label.cssClassName LIKE ?', ['%'.addcslashes($this->cssClassName, '_%').'%']);
+               }
+               
+               if ($this->label) {
+                       $languageItemList = new LanguageItemList();
+                       $languageItemList->getConditionBuilder()->add('languageCategoryID = ?', [LanguageFactory::getInstance()->getCategory('wcf.acp.label')->languageCategoryID]);
+                       $languageItemList->getConditionBuilder()->add('languageID = ?', [WCF::getLanguage()->languageID]);
+                       $languageItemList->getConditionBuilder()->add('languageItem LIKE ?', ['wcf.acp.label.label%']);
+                       $languageItemList->getConditionBuilder()->add('languageItemValue LIKE ?', ['%'.addcslashes($this->label, '_%').'%']);
+                       $languageItemList->readObjects();
+                       
+                       $labelIDs = [];
+                       foreach ($languageItemList as $languageItem) {
+                               $labelIDs[] = str_replace('wcf.acp.label.label', '', $languageItem->languageItem);
+                       }
+                       
+                       if (!empty($labelIDs)) {
+                               $this->objectList->getConditionBuilder()->add('(label LIKE ? OR labelID IN (?))', ['%'.addcslashes($this->label, '_%').'%', $labelIDs]);
+                       }
+                       else {
+                               $this->objectList->getConditionBuilder()->add('label LIKE ?', ['%'.addcslashes($this->label, '_%').'%',]);
+                       }
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function readData() {
+               parent::readData();
+               
+               $this->labelGroupList = new LabelGroupList();
+               $this->labelGroupList->readObjects();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function readParameters() {
+               parent::readParameters();
+               
+               if (!empty($_POST)) {
+                       $parameters = [];
+                       if (!empty($_POST['groupID'])) $parameters['id'] = intval($_POST['groupID']);
+                       if (!empty($_POST['label'])) $parameters['label'] = StringUtil::trim($_POST['label']);
+                       if (!empty($_POST['cssClassName'])) $parameters['cssClassName'] = StringUtil::trim($_POST['cssClassName']);
+                       
+                       if (!empty($parameters)) {
+                               HeaderUtil::redirect(LinkHandler::getInstance()->getLink('LabelList', $parameters));
+                               exit;
+                       }
+               }
+               
+               if (isset($_REQUEST['id'])) $this->groupID = intval($_REQUEST['id']);
+               if (isset($_REQUEST['label'])) $this->label = StringUtil::trim($_REQUEST['label']);
+               if (isset($_REQUEST['cssClassName'])) $this->cssClassName = StringUtil::trim($_REQUEST['cssClassName']);
+               
+               if ($this->groupID) {
+                       $this->labelGroup = new LabelGroup($this->groupID);
+                       if (!$this->labelGroup->groupID) {
+                               throw new IllegalLinkException();
+                       }
+                       
+                       $this->defaultSortField = 'showOrder';
+               }
        }
 }
index 1478258f8f306cca4a549a7fddcb7e33778b99a8..a701ca41824554b8982bdf4379a84343aa0c0584 100644 (file)
@@ -18,6 +18,7 @@ use wcf\system\WCF;
  * @property-read      integer         $groupID
  * @property-read      string          $label
  * @property-read      string          $cssClassName
+ * @property-read      integer         $showOrder
  */
 class Label extends DatabaseObject implements IRouteController {
        /**
index f2bcca982e5baeed4399f98432dca5b92123083d..206878eeff22dd44929eae669eac88d215e2fecb 100644 (file)
@@ -1,55 +1,89 @@
 <?php
 namespace wcf\data\label;
+use wcf\data\ISortableAction;
 use wcf\data\language\item\LanguageItemAction;
 use wcf\data\AbstractDatabaseObjectAction;
 use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\exception\UserInputException;
+use wcf\system\label\LabelHandler;
 use wcf\system\WCF;
 
 /**
  * Executes label-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2015 WoltLab GmbH
+ * @copyright  2001-2016 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    com.woltlab.wcf
  * @subpackage data.label
  * @category   Community Framework
  */
-class LabelAction extends AbstractDatabaseObjectAction {
+class LabelAction extends AbstractDatabaseObjectAction implements ISortableAction {
        /**
-        * @see \wcf\data\AbstractDatabaseObjectAction::$className
+        * @inheritDoc
         */
-       protected $className = 'wcf\data\label\LabelEditor';
+       protected $className = LabelEditor::class;
        
        /**
-        * @see \wcf\data\AbstractDatabaseObjectAction::$permissionsCreate
+        * @inheritDoc
         */
-       protected $permissionsCreate = array('admin.content.label.canManageLabel');
+       protected $permissionsCreate = ['admin.content.label.canManageLabel'];
        
        /**
-        * @see \wcf\data\AbstractDatabaseObjectAction::$permissionsDelete
+        * @inheritDoc
         */
-       protected $permissionsDelete = array('admin.content.label.canManageLabel');
+       protected $permissionsDelete = ['admin.content.label.canManageLabel'];
        
        /**
-        * @see \wcf\data\AbstractDatabaseObjectAction::$permissionsUpdate
+        * @inheritDoc
         */
-       protected $permissionsUpdate = array('admin.content.label.canManageLabel');
+       protected $permissionsUpdate = ['admin.content.label.canManageLabel'];
        
        /**
-        * @see \wcf\data\AbstractDatabaseObjectAction::$requireACP
+        * @inheritDoc
         */
-       protected $requireACP = array('create', 'delete', 'update');
+       protected $requireACP = ['create', 'delete', 'update', 'updatePosition'];
        
        /**
-        * @see \wcf\data\AbstractDatabaseObjectAction::delete()
+        * @inheritDoc
+        */
+       public function create() {
+               $showOrder = 0;
+               if (isset($this->parameters['data']['showOrder'])) {
+                       $showOrder = $this->parameters['data']['showOrder'];
+                       unset($this->parameters['data']['showOrder']);
+               }
+               
+               $label = parent::create();
+               
+               (new LabelEditor($label))->setShowOrder($label->groupID, $showOrder);
+               
+               return $label;
+       }
+       
+       /**
+        * @see \wcf\data\AbstractDatabaseObjectAction::update()
+        */
+       public function update() {
+               parent::update();
+               
+               // update showOrder if required
+               if (count($this->objects) === 1 && isset($this->parameters['data']['groupID']) && isset($this->parameters['data']['showOrder'])) {
+                       if ($this->objects[0]->groupID != $this->parameters['data']['groupID'] || $this->objects[0]->showOrder != $this->parameters['data']['showOrder']) {
+                               $this->objects[0]->setShowOrder($this->parameters['data']['groupID'], $this->parameters['data']['showOrder']);
+                       }
+               }
+       }
+       
+       /**
+        * @inheritDoc
         */
        public function delete() {
                parent::delete();
                
                if (!empty($this->objects)) {
                        // identify i18n labels
-                       $languageVariables = array();
+                       $languageVariables = [];
                        foreach ($this->objects as $object) {
                                if (preg_match('~wcf.acp.label.label\d+~', $object->label)) {
                                        $languageVariables[] = $object->label;
@@ -59,14 +93,14 @@ class LabelAction extends AbstractDatabaseObjectAction {
                        // remove language variables
                        if (!empty($languageVariables)) {
                                $conditions = new PreparedStatementConditionBuilder();
-                               $conditions->add("languageItem IN (?)", array($languageVariables));
+                               $conditions->add("languageItem IN (?)", [$languageVariables]);
                                
                                $sql = "SELECT  languageItemID
                                        FROM    wcf".WCF_N."_language_item
                                        ".$conditions;
                                $statement = WCF::getDB()->prepareStatement($sql);
                                $statement->execute($conditions->getParameters());
-                               $languageItemIDs = array();
+                               $languageItemIDs = [];
                                while ($row = $statement->fetchArray()) {
                                        $languageItemIDs[] = $row['languageItemID'];
                                }
@@ -76,4 +110,56 @@ class LabelAction extends AbstractDatabaseObjectAction {
                        }
                }
        }
+       
+       /**
+        * @inheritDoc
+        */
+       public function validateUpdatePosition() {
+               WCF::getSession()->checkPermissions(['admin.content.label.canManageLabel']);
+               
+               if (!isset($this->parameters['data']) || !isset($this->parameters['data']['structure']) || !is_array($this->parameters['data']['structure'])) {
+                       throw new UserInputException('structure');
+               }
+               
+               if (count($this->parameters['data']['structure']) !== 1) {
+                       throw new UserInputException('structure');
+               }
+               
+               $labelGroupID = key($this->parameters['data']['structure']);
+               $labelGroup = LabelHandler::getInstance()->getLabelGroup($labelGroupID);
+               if ($labelGroup === null) {
+                       throw new UserInputException('structure');
+               }
+               
+               $labelIDs = $this->parameters['data']['structure'][$labelGroupID];
+               
+               if (!empty(array_diff($labelIDs, $labelGroup->getLabelIDs()))) {
+                       throw new UserInputException('structure');
+               }
+               
+               $this->readInteger('offset', true, 'data');
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function updatePosition() {
+               $sql = "UPDATE  wcf".WCF_N."_label
+                       SET     showOrder = ?
+                       WHERE   labelID = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               
+               $showOrder = $this->parameters['data']['offset'];
+               
+               WCF::getDB()->beginTransaction();
+               foreach ($this->parameters['data']['structure'] as $labelIDs) {
+                       foreach ($labelIDs as $labelID) {
+                               $statement->execute(array(
+                                       $showOrder++,
+                                       $labelID
+                               ));
+                       }
+               }
+               WCF::getDB()->commitTransaction();
+       }
 }
index cb302f5a4f14a2c0f4f8acefef642bb75ede7d65..2ce817f6997c90bfdee5d1b2b8c58a925bf18041 100644 (file)
@@ -3,12 +3,13 @@ namespace wcf\data\label;
 use wcf\data\DatabaseObjectEditor;
 use wcf\data\IEditableCachedObject;
 use wcf\system\cache\builder\LabelCacheBuilder;
+use wcf\system\WCF;
 
 /**
  * Provides functions to edit labels.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2015 WoltLab GmbH
+ * @copyright  2001-2016 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    com.woltlab.wcf
  * @subpackage data.label
@@ -16,14 +17,56 @@ use wcf\system\cache\builder\LabelCacheBuilder;
  */
 class LabelEditor extends DatabaseObjectEditor implements IEditableCachedObject {
        /**
-        * @see \wcf\data\DatabaseObjectEditor::$baseClass
+        * @inheritDoc
         */
-       protected static $baseClass = 'wcf\data\label\Label';
+       protected static $baseClass = Label::class;
        
        /**
-        * @see \wcf\data\IEditableCachedObject::resetCache()
+        * @inheritDoc
         */
        public static function resetCache() {
                LabelCacheBuilder::getInstance()->reset();
        }
+       
+       /**
+        * Adds the label to a specific position in the label group.
+        *
+        * @param       integer         $groupID
+        * @param       integer         $showOrder
+        */
+       public function setShowOrder($groupID, $showOrder = 0) {
+               // shift back labels in old label group with higher showOrder
+               if ($this->showOrder) {
+                       $sql = "UPDATE  wcf".WCF_N."_label
+                                       SET     showOrder = showOrder - 1
+                                       WHERE   groupID = ?
+                                               AND showOrder >= ?";
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       $statement->execute([$this->groupID, $this->showOrder]);
+               }
+               
+               // shift labels in new label group with higher showOrder
+               if ($showOrder) {
+                       $sql = "UPDATE  wcf".WCF_N."_label
+                               SET     showOrder = showOrder + 1
+                               WHERE   groupID = ?
+                                       AND showOrder >= ?";
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       $statement->execute([$groupID, $showOrder]);
+               }
+               
+               // get maximum existing show order
+               $sql = "SELECT  MAX(showOrder)
+                       FROM    wcf".WCF_N."_label
+                       WHERE   groupID = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute([$groupID]);
+               $maxShowOrder = $statement->fetchColumn() ?: 0;
+               
+               if (!$showOrder || $showOrder > $maxShowOrder) {
+                       $showOrder = $maxShowOrder + 1;
+               }
+               
+               $this->update(['showOrder' => $showOrder]);
+       }
 }
index d0571a773ed21d290ecc1749198206f91257bbdf..99cf7e104200d614aa856fb48ddf6bc862b0850d 100644 (file)
@@ -6,7 +6,7 @@ use wcf\data\DatabaseObjectList;
  * Represents a list of labels.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2015 WoltLab GmbH
+ * @copyright  2001-2016 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    com.woltlab.wcf
  * @subpackage data.label
@@ -14,7 +14,12 @@ use wcf\data\DatabaseObjectList;
  */
 class LabelList extends DatabaseObjectList {
        /**
-        * @see \wcf\data\DatabaseObjectList::$className
+        * @inheritDoc
         */
-       public $className = 'wcf\data\label\Label';
+       public $className = Label::class;
+       
+       /**
+        * @inheritDoc
+        */
+       public $sqlOrderBy = 'label.showOrder ASC, label.labelID ASC';
 }
index 2f0c9b39f261fae15c448e3f0ad2ca9a1af51ae8..8306e9fe181eb06cbed01dc66476db1d66682687 100644 (file)
@@ -9,7 +9,7 @@ use wcf\system\acl\ACLHandler;
  * Caches labels and groups.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2015 WoltLab GmbH
+ * @copyright  2001-2016 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    com.woltlab.wcf
  * @subpackage system.cache.builder
@@ -17,13 +17,13 @@ use wcf\system\acl\ACLHandler;
  */
 class LabelCacheBuilder extends AbstractCacheBuilder {
        /**
-        * @see \wcf\system\cache\builder\AbstractCacheBuilder::rebuild()
+        * @inheritDoc
         */
        protected function rebuild(array $parameters) {
-               $data = array(
-                       'options' => array(),
-                       'groups' => array()
-               );
+               $data = [
+                       'options' => [],
+                       'groups' => []
+               ];
                
                // get label groups
                $groupList = new LabelGroupList();
@@ -56,7 +56,6 @@ class LabelCacheBuilder extends AbstractCacheBuilder {
                if (count($groupList)) {
                        // get labels
                        $labelList = new LabelList();
-                       $labelList->sqlOrderBy = 'label';
                        $labelList->readObjects();
                        foreach ($labelList as $label) {
                                $data['groups'][$label->groupID]->addLabel($label);
index eae8105389bdaded4aca3960bb0930556dd789a0..2e362e6b4d6591492dccc1ff63a6c0ef85f701d7 100644 (file)
@@ -1,4 +1,4 @@
-.sortableList {
+.sortableList:not(.tabularList) {
        list-style: decimal outside;
        margin-left: 20px;
 }
diff --git a/wcfsetup/install/files/style/ui/tableSortable.scss b/wcfsetup/install/files/style/ui/tableSortable.scss
new file mode 100644 (file)
index 0000000..1b0b7cf
--- /dev/null
@@ -0,0 +1,3 @@
+tr.sortableNode {
+       cursor: move;
+}
index 50882b7c3e298847154d68e7a6943c63245e65dd..211e552fc1d7f5db18d50d631376f67dadcde43d 100644 (file)
                <item name="wcf.acp.label.defaultValue"><![CDATA[Label]]></item>
                <item name="wcf.acp.label.delete.sure"><![CDATA[Wollen Sie das Label „{$label}“ wirklich löschen?]]></item>
                <item name="wcf.acp.label.edit"><![CDATA[Label bearbeiten]]></item>
+               <item name="wcf.acp.label.edit"><![CDATA[Label bearbeiten]]></item>
                <item name="wcf.acp.label.error.noGroups"><![CDATA[Bevor Sie ein Label hinzufügen können, müssen Sie eine <a href="{link controller='LabelGroupAdd'}{/link}">Labelgruppe hinzufügen</a>.]]></item>
                <item name="wcf.acp.label.group"><![CDATA[Labelgruppe]]></item>
                <item name="wcf.acp.label.group.add"><![CDATA[Labelgruppe hinzufügen]]></item>
                <item name="wcf.acp.label.list"><![CDATA[Labels]]></item>
                <item name="wcf.acp.label.group.groupName.description"><![CDATA[Der Titel ist für alle Benutzer sichtbar, die Zugriff auf diese Labelgruppe haben.]]></item>
                <item name="wcf.acp.label.group.groupDescription.description"><![CDATA[Die optionale Beschreibung wird nur in der Administrationsoberfläche angezeigt.]]></item>
+               <item name="wcf.acp.label.showOrder"><![CDATA[Reihenfolge]]></item>
+               <item name="wcf.acp.label.showOrder.description"><![CDATA[Reihenfolge des Labels innerhalb seiner Labelgruppe. Wenn Sie das Feld leer lassen, wird das Label an letzter Position einsortiert.]]></item>
+               <item name="wcf.acp.label.sortAfterGroupFiltering"><![CDATA[Wenn Sie die Label-Liste nur nach einer bestimmten Labelgruppe filtern, können Sie die Labels innerhalb dieser Gruppe durch Ziehen und Loslassen sortieren.]]></item>
+               <item name="wcf.acp.label.filter"><![CDATA[Filter]]></item>
        </category>
        
        <category name="wcf.acp.language">
index de8233ebb9c575952bf86fd1ad0d900f925edf57..0445b3f35fe86e0787e30550c52652d189b4afbe 100644 (file)
@@ -491,6 +491,10 @@ Examples for medium ID detection:
                <item name="wcf.acp.label.list"><![CDATA[Labels]]></item>
                <item name="wcf.acp.label.group.groupName.description"><![CDATA[The title is visible for every user who can interact with the label group.]]></item>
                <item name="wcf.acp.label.group.groupDescription.description"><![CDATA[This optional description is visible in the Administration Control Panel only and is intended to help telling groups with the same title apart.]]></item>
+               <item name="wcf.acp.label.showOrder"><![CDATA[Display Order]]></item>
+               <item name="wcf.acp.label.showOrder.description"><![CDATA[Display order of the label in its label group. If you leave this field empty, the label will be placed at the last position.]]></item>
+               <item name="wcf.acp.label.sortAfterGroupFiltering"><![CDATA[If you only filter the label list by a certain label group, you can sort the labels in this group using drag and drop.]]></item>
+               <item name="wcf.acp.label.filter"><![CDATA[Filter]]></item>
        </category>
        
        <category name="wcf.acp.language">
index b414024492329afdbe4f5b2ea4407ad185993ddc..89bb71a8fd3ef1e07331852f52a25a062abbf500 100644 (file)
@@ -441,7 +441,8 @@ CREATE TABLE wcf1_label (
        labelID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
        groupID INT(10) NOT NULL,
        label VARCHAR(80) NOT NULL,
-       cssClassName VARCHAR(255) NOT NULL DEFAULT ''
+       cssClassName VARCHAR(255) NOT NULL DEFAULT '',
+       showOrder INT(10) NOT NULL DEFAULT 0
 );
 
 DROP TABLE IF EXISTS wcf1_label_group;