--- /dev/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/XSD/maelstrom/aclOption.xsd">
+ <import>
+ <options>
+ <option name="canViewLabel">
+ <objecttype>com.woltlab.wcf.label</objecttype>
+ </option>
+ <option name="canSetLabel">
+ <objecttype>com.woltlab.wcf.label</objecttype>
+ </option>
+ </options>
+ </import>
+</data>
\ No newline at end of file
<permissions>admin.content.dashboard.canEditDashboard</permissions>
</acpmenuitem>
+ <acpmenuitem name="wcf.acp.menu.link.label">
+ <parent>wcf.acp.menu.link.content</parent>
+ </acpmenuitem>
+
+ <acpmenuitem name="wcf.acp.menu.link.label.list">
+ <controller><![CDATA[wcf\acp\page\LabelListPage]]></controller>
+ <parent>wcf.acp.menu.link.label</parent>
+ <permissions>admin.content.label.canManageLabel</permissions>
+ <showorder>1</showorder>
+ </acpmenuitem>
+
+ <acpmenuitem name="wcf.acp.menu.link.label.add">
+ <controller><![CDATA[wcf\acp\form\LabelAddForm]]></controller>
+ <parent>wcf.acp.menu.link.label</parent>
+ <permissions>admin.content.label.canManageLabel</permissions>
+ <showorder>2</showorder>
+ </acpmenuitem>
+
+ <acpmenuitem name="wcf.acp.menu.link.label.group.list">
+ <controller><![CDATA[wcf\acp\page\LabelGroupListPage]]></controller>
+ <parent>wcf.acp.menu.link.label</parent>
+ <permissions>admin.content.label.canManageLabel</permissions>
+ <showorder>3</showorder>
+ </acpmenuitem>
+
+ <acpmenuitem name="wcf.acp.menu.link.label.group.add">
+ <controller><![CDATA[wcf\acp\form\LabelGroupAddForm]]></controller>
+ <parent>wcf.acp.menu.link.label</parent>
+ <permissions>admin.content.label.canManageLabel</permissions>
+ <showorder>4</showorder>
+ </acpmenuitem>
+
<acpmenuitem name="wcf.acp.menu.link.community">
<showorder>5</showorder>
</acpmenuitem>
<classname>wcf\system\moderation\queue\report\CommentResponseModerationQueueReportHandler</classname>
</type>
<!-- /moderation -->
+
+ <type>
+ <name>com.woltlab.wcf.label</name>
+ <definitionname>com.woltlab.wcf.acl</definitionname>
+ </type>
</import>
</data>
\ No newline at end of file
<name>com.woltlab.wcf.comment.commentableContent</name>
<interfacename>wcf\system\comment\manager\ICommentManager</interfacename>
</definition>
+
+ <definition>
+ <name>com.woltlab.wcf.label.object</name>
+ <interfacename>wcf\system\label\object\ILabelObjectHandler</interfacename>
+ </definition>
+
+ <definition>
+ <name>com.woltlab.wcf.label.objectType</name>
+ <interfacename>wcf\system\label\object\type\ILabelObjectTypeHandler</interfacename>
+ </definition>
</import>
</data>
<instruction type="userProfileMenu">userProfileMenu.xml</instruction>
<instruction type="userMenu">userMenu.xml</instruction>
<instruction type="userNotificationEvent">userNotificationEvent.xml</instruction>
+ <instruction type="aclOption">aclOption.xml</instruction>
</instructions>
<instructions type="update" fromversion="2.0.0 Alpha 1">
<category name="admin.content.dashboard">
<parent>admin.content</parent>
</category>
+ <category name="admin.content.label">
+ <parent>admin.content</parent>
+ </category>
<category name="admin.community">
<parent>admin</parent>
<admindefaultvalue>1</admindefaultvalue>
</option>
<!-- /user.profileComment -->
+
+ <option name="admin.content.label.canManageLabel">
+ <categoryname>admin.content.label</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>0</defaultvalue>
+ <admindefaultvalue>1</admindefaultvalue>
+ </option>
</options>
</import>
</data>
--- /dev/null
+{include file='header'}
+<script type="text/javascript" src="{@$__wcf->getPath()}js/WCF.Label{if !ENABLE_DEBUG_MODE}.min{/if}.js"></script>
+<script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ WCF.Language.addObject({
+ 'wcf.acp.label.defaultValue': '{lang}wcf.acp.label.defaultValue{/lang}'
+ });
+
+ new WCF.Label.ACPList();
+ });
+ //]]>
+</script>
+
+<header class="boxHeadline">
+ <h1>{lang}wcf.acp.label.{$action}{/lang}</h1>
+</header>
+
+{if $errorField}
+ <p class="error">{lang}wcf.global.form.error{/lang}</p>
+{/if}
+
+{if $success|isset}
+ <p class="success">{lang}wcf.global.success.{$action}{/lang}</p>
+{/if}
+
+<div class="contentNavigation">
+ <nav>
+ <ul>
+ <li><a href="{link controller='LabelList'}{/link}" class="button"><span class="icon icon16 icon-list"></span> <span>{lang}wcf.acp.menu.link.label.list{/lang}</span></a></li>
+
+ {event name='contentNavigationButtons'}
+ </ul>
+ </nav>
+</div>
+
+{if $labelGroupList|count}
+ <form method="post" action="{if $action == 'add'}{link controller='LabelAdd'}{/link}{else}{link controller='LabelEdit' object=$label}{/link}{/if}">
+ <div class="container containerPadding marginTop">
+ <fieldset>
+ <legend>{lang}wcf.global.form.data{/lang}</legend>
+
+ <dl{if $errorField == 'groupID'} class="formError"{/if}>
+ <dt><label for="groupID">{lang}wcf.acp.label.group{/lang}</label></dt>
+ <dd>
+ <select id="groupID" name="groupID">
+ <option value="0"></option>
+ {foreach from=$labelGroupList item=group}
+ <option value="{@$group->groupID}"{if $group->groupID == $groupID} selected="selected"{/if}>{$group->groupName}</option>
+ {/foreach}
+ </select>
+ {if $errorField == 'groupID'}
+ <small class="innerError">
+ {if $errorType == 'empty'}
+ {lang}wcf.global.form.error.empty{/lang}
+ {else}
+ {lang}wcf.acp.label.group.error.{@$errorType}{/lang}
+ {/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+
+ <dl{if $errorField == 'label'} class="formError"{/if}>
+ <dt><label for="label">{lang}wcf.acp.label.label{/lang}</label></dt>
+ <dd>
+ <input type="text" id="label" name="label" value="{$label}" autofocus="autofocus" class="long" />
+ {if $errorField == 'label'}
+ <small class="innerError">
+ {if $errorType == 'empty'}
+ {lang}wcf.global.form.error.empty{/lang}
+ {elseif $errorType == 'multilingual'}
+ {lang}wcf.global.form.error.multilingual{/lang}
+ {else}
+ {lang}wcf.acp.label.label.error.{@$errorType}{/lang}
+ {/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+ {include file='multipleLanguageInputJavascript' elementIdentifier='label' forceSelection=false}
+
+ <dl{if $errorField == 'cssClassName'} class="formError"{/if}>
+ <dt><label for="cssClassName">{lang}wcf.acp.label.cssClassName{/lang}</label></dt>
+ <dd>
+ <ul id="labelList">
+ {foreach from=$availableCssClassNames item=className}
+ {if $className == 'custom'}
+ <li class="labelCustomClass"><label><input type="radio" name="cssClassName" value="custom"{if $cssClassName == 'custom'} checked="checked"{/if} /> <span><input type="text" id="customCssClassName" name="customCssClassName" value="{$customCssClassName}" class="long" /></span></label></li>
+ {else}
+ <li><label><input type="radio" name="cssClassName" value="{$className}"{if $cssClassName == $className} checked="checked"{/if} /> <span class="badge label{if $className != 'none'} {$className}{/if}">Label</span></label></li>
+ {/if}
+ {/foreach}
+ </ul>
+
+ {if $errorField == 'cssClassName'}
+ <small class="innerError">
+ {if $errorType == 'empty'}
+ {lang}wcf.global.form.error.empty{/lang}
+ {else}
+ {lang}wcf.acp.label.cssClassName.error.{@$errorType}{/lang}
+ {/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+
+ {event name='dataFields'}
+ </fieldset>
+
+ {event name='fieldsets'}
+ </div>
+
+ <div class="formSubmit">
+ <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" />
+ </div>
+ </form>
+{else}
+ <p class="error">{lang}wcf.acp.label.error.noGroups{/lang}</p>
+{/if}
+
+{include file='footer'}
--- /dev/null
+{include file='header'}
+
+{include file='aclPermissions'}
+<script type="text/javascript" src="{@$__wcf->getPath()}js/WCF.Label{if !ENABLE_DEBUG_MODE}.min{/if}.js"></script>
+<script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ new WCF.ACL.List($('#groupPermissions'), {@$objectTypeID}{if $groupID|isset}, '', {@$groupID}{/if});
+ new WCF.Label.ACPList.Connect();
+
+ WCF.TabMenu.init();
+ });
+ //]]>
+</script>
+
+<header class="boxHeadline">
+ <h1>{lang}wcf.acp.label.group.{$action}{/lang}</h1>
+</header>
+
+{if $errorField}
+ <p class="error">{lang}wcf.global.form.error{/lang}</p>
+{/if}
+
+{if $success|isset}
+ <p class="success">{lang}wcf.global.success.{$action}{/lang}</p>
+{/if}
+
+<div class="contentNavigation">
+ <nav>
+ <ul>
+ <li><a href="{link controller='LabelGroupList'}{/link}" class="button"><span class="icon icon16 icon-list"></span> <span>{lang}wcf.acp.menu.link.label.group.list{/lang}</span></a></li>
+
+ {event name='contentNavigationButtons'}
+ </ul>
+ </nav>
+</div>
+
+<form method="post" action="{if $action == 'add'}{link controller='LabelGroupAdd'}{/link}{else}{link controller='LabelGroupEdit' object=$labelGroup}{/link}{/if}">
+ <div class="tabMenuContainer">
+ <nav class="tabMenu">
+ <ul>
+ <li><a href="{@$__wcf->getAnchor('general')}">{lang}wcf.global.form.data{/lang}</a></li>
+ <li><a href="{@$__wcf->getAnchor('connect')}">{lang}wcf.acp.label.group.category.connect{/lang}</a></li>
+ </ul>
+ </nav>
+
+ <div id="general" class="container containerPadding tabMenuContainer tabMenuContent">
+ <fieldset>
+ <legend>{lang}wcf.global.form.data{/lang}</legend>
+
+ <dl{if $errorField == 'groupName'} class="formError"{/if}>
+ <dt><label for="groupName">{lang}wcf.acp.label.group.groupName{/lang}</label></dt>
+ <dd>
+ <input type="text" id="groupName" name="groupName" value="{$groupName}" autofocus="autofocus" class="long" />
+ {if $errorField == 'groupName'}
+ <small class="innerError">
+ {if $errorType == 'empty'}
+ {lang}wcf.global.form.error.empty{/lang}
+ {else}
+ {lang}wcf.acp.label.group.groupName.error.{@$errorType}{/lang}
+ {/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+
+ <dl>
+ <dt class="reversed"><label for="forceSelection">{lang}wcf.acp.label.group.forceSelection{/lang}</label></dt>
+ <dd><input type="checkbox" name="forceSelection" id="forceSelection" value="1"{if $forceSelection} checked="checked"{/if} /></dd>
+ </dl>
+
+ <dl id="groupPermissions">
+ <dt>{lang}wcf.acl.permissions{/lang}</dt>
+ <dd></dd>
+ </dl>
+
+ {event name='dataFields'}
+ </fieldset>
+
+ {event name='generalFieldsets'}
+ </div>
+
+ <div id="connect" class="container containerPadding tabMenuContainer tabMenuContent">
+ <fieldset>
+ <legend>{lang}wcf.acp.label.group.category.connect{/lang}</legend>
+
+ {foreach from=$labelObjectTypeContainers item=container}
+ {if $container->isBooleanOption()}
+ <!-- TODO: Implement boolean option mode -->
+ {else}
+ <dl>
+ <dt>objectTypeID = {@$container->getObjectTypeID()}</dt>
+ <dd>
+ <ul class="container structuredList">
+ {foreach from=$container item=objectType}
+ <li class="{if $objectType->isCategory()} category{/if}"{if $objectType->getDepth()} style="padding-left: {21 * $objectType->getDepth()}px"{/if} data-depth="{@$objectType->getDepth()}">
+ <span>{$objectType->getLabel()}</span>
+ <label><input id="checkbox_{@$container->getObjectTypeID()}_{@$objectType->getObjectID()}" type="checkbox" name="objectTypes[{@$container->getObjectTypeID()}][]" value="{@$objectType->getObjectID()}"{if $objectType->getOptionValue()} checked="checked"{/if} /></label>
+ </li>
+ {/foreach}
+ </ul>
+ </dd>
+ </dl>
+ {/if}
+ {/foreach}
+ </fieldset>
+
+ {event name='connectFieldsets'}
+ </div>
+ </div>
+
+ <div class="formSubmit">
+ <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" />
+ </div>
+</form>
+
+{include file='footer'}
--- /dev/null
+{include file='header'}
+
+<script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ new WCF.Action.Delete('wcf\\data\\label\\group\\LabelGroupAction', '.jsLabelGroupRow');
+
+ var options = { };
+ {if $pages > 1}
+ options.refreshPage = true;
+ {if $pages == $pageNo}
+ options.updatePageNumber = -1;
+ {/if}
+ {else}
+ options.emptyMessage = '{lang}wcf.acp.label.group.noneAvailable{/lang}';
+ {/if}
+
+ new WCF.Table.EmptyTableHandler($('#labelGroupTableContainer'), 'jsLabelGroupRow', options);
+ });
+ //]]>
+</script>
+
+<header class="boxHeadline">
+ <h1>{lang}wcf.acp.label.group.list{/lang}</h1>
+</header>
+
+<div class="contentNavigation">
+ {pages print=true assign=pagesLinks controller="LabelGroupList" link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder"}
+
+ <nav>
+ <ul>
+ <li><a href="{link controller='LabelGroupAdd'}{/link}" class="button"><span class="icon icon16 icon-plus"></span> <span>{lang}wcf.acp.label.group.add{/lang}</span></a></li>
+
+ {event name='contentNavigationButtonsTop'}
+ </ul>
+ </nav>
+</div>
+
+{if $objects|count}
+ <div id="labelGroupTableContainer" class="tabularBox tabularBoxTitle marginTop">
+ <header>
+ <h2>{lang}wcf.acp.label.group.list{/lang} <span class="badge badgeInverse">{#$items}</span></h2>
+ </header>
+
+ <table class="table">
+ <thead>
+ <tr>
+ <th class="columnID columnLabelGroupID{if $sortField == 'groupID'} active {@$sortOrder}{/if}" colspan="2"><a href="{link controller='LabelGroupList'}pageNo={@$pageNo}&sortField=groupID&sortOrder={if $sortField == 'groupID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.global.objectID{/lang}</a></th>
+ <th class="columnTitle columnGroupName{if $sortField == 'groupName'} active {@$sortOrder}{/if}"><a href="{link controller='LabelGroupList'}pageNo={@$pageNo}&sortField=groupName&sortOrder={if $sortField == 'groupName' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.acp.label.group.groupName{/lang}</a></th>
+
+ {event name='columnHeads'}
+ </tr>
+ </thead>
+
+ <tbody>
+ {foreach from=$objects item=group}
+ <tr class="jsLabelGroupRow">
+ <td class="columnIcon">
+ {if $group->isEditable()}
+ <a href="{link controller='LabelGroupEdit' object=$group}{/link}" title="{lang}wcf.global.button.edit{/lang}" class="jsTooltip"><span class="icon icon16 icon-pencil"></span></a>
+ {/if}
+ {if $group->isDeletable()}
+ <span class="icon icon16 icon-remove jsDeleteButton jsTooltip pointer" title="{lang}wcf.global.button.delete{/lang}" data-object-id="{@$group->groupID}" data-confirm-message="{lang}wcf.acp.label.group.delete.sure{/lang}"></span>
+ {/if}
+
+ {event name='rowButtons'}
+ </td>
+ <td class="columnID">{@$group->groupID}</td>
+ <td class="columnTitle columnGroupName">{if $group->isEditable()}<p class="labelGroup{if $group->cssClassName} {$group->cssClassName}{/if}"><a href="{link controller='LabelGroupEdit' object=$group}{/link}">{$group->groupName}</a>{else}{$group->groupName}</p>{/if}</td>
+
+ {event name='columns'}
+ </tr>
+ {/foreach}
+ </tbody>
+ </table>
+ </div>
+
+ <div class="contentNavigation">
+ {@$pagesLinks}
+
+ <nav>
+ <ul>
+ <li><a href="{link controller='LabelGroupAdd'}{/link}" class="button"><span class="icon icon16 icon-plus"></span> <span>{lang}wcf.acp.label.group.add{/lang}</span></a></li>
+
+ {event name='contentNavigationButtonsBottom'}
+ </ul>
+ </nav>
+ </div>
+{else}
+ <p class="info">{lang}wcf.acp.label.group.noneAvailable{/lang}</p>
+{/if}
+
+{include file='footer'}
--- /dev/null
+{include file='header'}
+
+<script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ new WCF.Action.Delete('wcf\\data\\label\\LabelAction', '.jsLabelRow');
+
+ var options = { };
+ {if $pages > 1}
+ options.refreshPage = true;
+ {if $pages == $pageNo}
+ options.updatePageNumber = -1;
+ {/if}
+ {else}
+ options.emptyMessage = '{lang}wcf.acp.label.noneAvailable{/lang}';
+ {/if}
+
+ new WCF.Table.EmptyTableHandler($('#labelTableContainer'), 'jsLabelRow', options);
+ });
+ //]]>
+</script>
+
+<header class="boxHeadline">
+ <h1>{lang}wcf.acp.label.list{/lang}</h1>
+</header>
+
+<div class="contentNavigation">
+ {pages print=true assign=pagesLinks controller="LabelList" link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder"}
+
+ <nav>
+ <ul>
+ <li><a href="{link controller='LabelAdd'}{/link}" class="button"><span class="icon icon16 icon-plus"></span> <span>{lang}wcf.acp.label.add{/lang}</span></a></li>
+
+ {event name='contentNavigationButtonsTop'}
+ </ul>
+ </nav>
+</div>
+
+{if $objects|count}
+ <div id="labelTableContainer" class="tabularBox tabularBoxTitle marginTop">
+ <header>
+ <h2>{lang}wcf.acp.label.list{/lang} <span class="badge badgeInverse">{#$items}</span></h2>
+ </header>
+
+ <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.groupName{/lang}</a></th>
+
+ {event name='columnHeads'}
+ </tr>
+ </thead>
+
+ <tbody>
+ {foreach from=$objects item=label}
+ <tr class="jsLabelRow">
+ <td class="columnIcon">
+ {if $label->isEditable()}
+ <a href="{link controller='LabelEdit' object=$label}{/link}" title="{lang}wcf.global.button.edit{/lang}" class="jsTooltip"><span class="icon icon16 icon-pencil"></span></a>
+ {/if}
+ {if $label->isDeletable()}
+ <span class="icon icon16 icon-remove 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>
+ {/if}
+
+ {event name='rowButtons'}
+ </td>
+ <td class="columnID">{@$label->labelID}</td>
+ <td class="columnTitle columnLabel">{if $label->isEditable()}<a href="{link controller='LabelEdit' object=$label}{/link}" title="{$label}" class="badge label{if $label->cssClassName} {$label->cssClassName}{/if}">{$label}</a></p>{else}<p class="badge label{if $label->cssClassName} {$label->cssClassName}{/if}">{$label}{/if}</td>
+ <td class="columnText columnGroup">{$label->groupName}</td>
+
+ {event name='columns'}
+ </tr>
+ {/foreach}
+ </tbody>
+ </table>
+ </div>
+
+ <div class="contentNavigation">
+ {@$pagesLinks}
+
+ <nav>
+ <ul>
+ <li><a href="{link controller='LabelAdd'}{/link}" class="button"><span class="icon icon16 icon-plus"></span> <span>{lang}wcf.acp.label.add{/lang}</span></a></li>
+
+ {event name='contentNavigationButtonsBottom'}
+ </ul>
+ </nav>
+ </div>
+{else}
+ <p class="info">{lang}wcf.acp.label.noneAvailable{/lang}</p>
+{/if}
+
+{include file='footer'}
--- /dev/null
+/**
+ * Namespace for labels.
+ */
+WCF.Label = {};
+
+/**
+ * Provides enhancements for ACP label management.
+ */
+WCF.Label.ACPList = Class.extend({
+ /**
+ * input element
+ * @var jQuery
+ */
+ _labelInput: null,
+
+ /**
+ * list of pre-defined label items
+ * @var array<jQuery>
+ */
+ _labelList: [ ],
+
+ /**
+ * Intitializes the ACP label list.
+ */
+ init: function() {
+ this._labelInput = $('#label').keydown($.proxy(this._keyPressed, this)).keyup($.proxy(this._keyPressed, this)).blur($.proxy(this._keyPressed, this));
+
+ $('#labelList').find('input[type="radio"]').each($.proxy(function(index, input) {
+ var $input = $(input);
+
+ // ignore custom values
+ if ($input.prop('value') !== 'custom') {
+ this._labelList.push($($input.next('span')));
+ }
+ }, this));
+ },
+
+ /**
+ * Renders label name as label or falls back to a default value if label is empty.
+ */
+ _keyPressed: function() {
+ var $text = this._labelInput.prop('value');
+ if ($text === '') $text = WCF.Language.get('wcf.acp.label.defaultValue');
+
+ for (var $i = 0, $length = this._labelList.length; $i < $length; $i++) {
+ this._labelList[$i].text($text);
+ }
+ }
+});
+
+/**
+ * Provides simple logic to inherit associations within structured lists.
+ */
+WCF.Label.ACPList.Connect = Class.extend({
+ /**
+ * Initializes inheritation for structured lists.
+ */
+ init: function() {
+ var $listItems = $('#connect .structuredList li');
+ if (!$listItems.length) return;
+
+ $listItems.each($.proxy(function(index, item) {
+ $(item).find('input[type="checkbox"]').click($.proxy(this._click, this));
+ }, this));
+ },
+
+ /**
+ * Marks items as checked if they're logically below current item.
+ *
+ * @param object event
+ */
+ _click: function(event) {
+ var $listItem = $(event.currentTarget);
+ if ($listItem.is(':checked')) {
+ $listItem = $listItem.parents('li');
+ var $depth = $listItem.data('depth');
+
+ while (true) {
+ $listItem = $listItem.next();
+ if (!$listItem.length) {
+ // no more siblings
+ return true;
+ }
+
+ // element is on the same or higher level (= lower depth)
+ if ($listItem.data('depth') <= $depth) {
+ return true;
+ }
+
+ $listItem.find('input[type="checkbox"]').prop('checked', 'checked');
+ }
+ }
+ }
+});
+
+/**
+ * Provides a flexible label chooser.
+ *
+ * @param object selectedLabelIDs
+ * @param string containerSelector
+ * @param string submitButtonSelector
+ * @param boolean showWithoutSelection
+ */
+WCF.Label.Chooser = Class.extend({
+ /**
+ * label container
+ * @var jQuery
+ */
+ _container: null,
+
+ /**
+ * list of label groups
+ * @var object
+ */
+ _groups: { },
+
+ /**
+ * show the 'without selection' option
+ * @var boolean
+ */
+ _showWithoutSelection: false,
+
+ /**
+ * Initializes a new label chooser.
+ *
+ * @param object selectedLabelIDs
+ * @param string containerSelector
+ * @param string submitButtonSelector
+ * @param boolean showWithoutSelection
+ */
+ init: function(selectedLabelIDs, containerSelector, submitButtonSelector, showWithoutSelection) {
+ this._container = null;
+ this._groups = { };
+ this._showWithoutSelection = (showWithoutSelection === true);
+
+ // init containers
+ this._initContainers(containerSelector);
+
+ // pre-select labels
+ if ($.getLength(selectedLabelIDs)) {
+ for (var $groupID in selectedLabelIDs) {
+ this._groups[$groupID].find('.dropdownMenu > li').each($.proxy(function(index, label) {
+ var $label = $(label);
+ var $labelID = $label.data('labelID') || 0;
+ if ($labelID && selectedLabelIDs[$groupID] == $labelID) {
+ this._selectLabel($label, true);
+ }
+ }, this));
+ }
+ }
+
+ // mark all containers as initialized
+ for (var $containerID in this._containers) {
+ var $dropdown = this._containers[$containerID];
+ if ($dropdown.data('labelID') === undefined) {
+ $dropdown.data('labelID', 0);
+ }
+ }
+
+ this._container = $(containerSelector);
+ if (submitButtonSelector) {
+ $(submitButtonSelector).click($.proxy(this._submit, this));
+ }
+ else if (this._container.is('form')) {
+ this._container.submit($.proxy(this._submit, this));
+ }
+ },
+
+ /**
+ * Initializes label groups.
+ *
+ * @param string containerSelector
+ */
+ _initContainers: function(containerSelector) {
+ $(containerSelector).find('.labelChooser').each($.proxy(function(index, group) {
+ var $group = $(group);
+ var $groupID = $group.data('groupID');
+
+ if (!this._groups[$groupID]) {
+ this._groups[$groupID] = $group;
+ var $dropdownMenu = $group.find('.dropdownMenu');
+ $dropdownMenu.find('li').click($.proxy(this._click, this));
+
+ if (!$group.data('forceSelection') || this._showWithoutSelection) {
+ $('<li class="dropdownDivider" />').appendTo($dropdownMenu);
+ }
+
+ if (this._showWithoutSelection) {
+ $('<li data-label-id="-1"><span><span class="badge label">' + WCF.Language.get('wcf.label.withoutSelection') + '</span></span></li>').appendTo($dropdownMenu).click($.proxy(this._click, this));
+ }
+
+ if (!$group.data('forceSelection')) {
+ var $buttonEmpty = $('<li data-label-id="0"><span><span class="badge label">' + WCF.Language.get('wcf.label.none') + '</span></span></li>').appendTo($dropdownMenu);
+ $buttonEmpty.click($.proxy(this._click, this));
+ }
+ }
+ }, this));
+ },
+
+ /**
+ * Handles label selections.
+ *
+ * @param object event
+ */
+ _click: function(event) {
+ this._selectLabel($(event.currentTarget), false);
+ },
+
+ /**
+ * Selects a label.
+ *
+ * @param jQuery label
+ * @param boolean onInit
+ */
+ _selectLabel: function(label, onInit) {
+ var $group = label.parents('.dropdown');
+
+ // already initialized, ignore
+ if (onInit && $group.data('labelID') !== undefined) {
+ return;
+ }
+
+ // save label id
+ if (label.data('labelID')) {
+ $group.data('labelID', label.data('labelID'));
+ }
+ else {
+ $group.data('labelID', 0);
+ }
+
+ // replace button
+ label = label.find('span > span');
+ $group.find('.dropdownToggle > span').removeClass().addClass(label.attr('class')).text(label.text());
+ },
+
+ /**
+ * Creates hidden input elements on submit.
+ */
+ _submit: function() {
+ // get form submit area
+ var $formSubmit = this._container.find('.formSubmit');
+
+ // remove old, hidden values
+ $formSubmit.find('input[type="hidden"]').each(function(index, input) {
+ var $input = $(input);
+ if ($input.attr('name').indexOf('labelIDs[') === 0) {
+ $input.remove();
+ }
+ });
+
+ // insert label ids
+ for (var $groupID in this._groups) {
+ var $group = this._groups[$groupID];
+ if ($group.data('labelID')) {
+ $('<input type="hidden" name="labelIDs[' + $groupID + ']" value="' + $group.data('labelID') + '" />').appendTo($formSubmit);
+ }
+ }
+ }
+});
\ No newline at end of file
--- /dev/null
+WCF.Label={};WCF.Label.ACPList=Class.extend({_labelInput:null,_labelList:[],init:function(){this._labelInput=$("#label").keydown($.proxy(this._keyPressed,this)).keyup($.proxy(this._keyPressed,this)).blur($.proxy(this._keyPressed,this));$("#labelList").find('input[type="radio"]').each($.proxy(function(b,a){var c=$(a);if(c.prop("value")!=="custom"){this._labelList.push($(c.next("span")))}},this))},_keyPressed:function(){var a=this._labelInput.prop("value");if(a===""){a=WCF.Language.get("wcf.acp.label.defaultValue")}for(var c=0,b=this._labelList.length;c<b;c++){this._labelList[c].text(a)}}});WCF.Label.ACPList.Connect=Class.extend({init:function(){var a=$("#connect .structuredList li");if(!a.length){return}a.each($.proxy(function(b,c){$(c).find('input[type="checkbox"]').click($.proxy(this._click,this))},this))},_click:function(c){var a=$(c.currentTarget);if(a.is(":checked")){a=a.parents("li");var b=a.data("depth");while(true){a=a.next();if(!a.length){return true}if(a.data("depth")<=b){return true}a.find('input[type="checkbox"]').prop("checked","checked")}}}});WCF.Label.Chooser=Class.extend({_container:null,_groups:{},_showWithoutSelection:false,init:function(e,b,d,g){this._container=null;this._groups={};this._showWithoutSelection=(g===true);this._initContainers(b);if($.getLength(e)){for(var a in e){this._groups[a].find(".dropdownMenu > li").each($.proxy(function(j,i){var h=$(i);var k=h.data("labelID")||0;if(k&&e[a]==k){this._selectLabel(h,true)}},this))}}for(var c in this._containers){var f=this._containers[c];if(f.data("labelID")===undefined){f.data("labelID",0)}}this._container=$(b);if(d){$(d).click($.proxy(this._submit,this))}else{if(this._container.is("form")){this._container.submit($.proxy(this._submit,this))}}},_initContainers:function(a){$(a).find(".labelChooser").each($.proxy(function(d,g){var e=$(g);var b=e.data("groupID");if(!this._groups[b]){this._groups[b]=e;var c=e.find(".dropdownMenu");c.find("li").click($.proxy(this._click,this));if(!e.data("forceSelection")||this._showWithoutSelection){$('<li class="dropdownDivider" />').appendTo(c)}if(this._showWithoutSelection){$('<li data-label-id="-1"><span><span class="badge label">'+WCF.Language.get("wcf.label.withoutSelection")+"</span></span></li>").appendTo(c).click($.proxy(this._click,this))}if(!e.data("forceSelection")){var f=$('<li data-label-id="0"><span><span class="badge label">'+WCF.Language.get("wcf.label.none")+"</span></span></li>").appendTo(c);f.click($.proxy(this._click,this))}}},this))},_click:function(a){this._selectLabel($(a.currentTarget),false)},_selectLabel:function(a,c){var b=a.parents(".dropdown");if(c&&b.data("labelID")!==undefined){return}if(a.data("labelID")){b.data("labelID",a.data("labelID"))}else{b.data("labelID",0)}a=a.find("span > span");b.find(".dropdownToggle > span").removeClass().addClass(a.attr("class")).text(a.text())},_submit:function(){var b=this._container.find(".formSubmit");b.find('input[type="hidden"]').each(function(e,d){var f=$(d);if(f.attr("name").indexOf("labelIDs[")===0){f.remove()}});for(var a in this._groups){var c=this._groups[a];if(c.data("labelID")){$('<input type="hidden" name="labelIDs['+a+']" value="'+c.data("labelID")+'" />').appendTo(b)}}}});
\ No newline at end of file
--- /dev/null
+<?php
+namespace wcf\acp\form;
+use wcf\data\label\group\LabelGroupList;
+use wcf\data\label\LabelAction;
+use wcf\data\label\LabelEditor;
+use wcf\data\package\PackageCache;
+use wcf\form\AbstractForm;
+use wcf\system\exception\UserInputException;
+use wcf\system\language\I18nHandler;
+use wcf\system\Regex;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+
+/**
+ * Shows the label add form.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.label
+ * @subpackage acp.form
+ * @category Community Framework
+ */
+class LabelAddForm extends AbstractForm {
+ /**
+ * @see wcf\page\AbstractPage::$activeMenuItem
+ */
+ public $activeMenuItem = 'wcf.acp.menu.link.label.add';
+
+ /**
+ * @see wcf\page\AbstractPage::$neededPermissions
+ */
+ public $neededPermissions = array('admin.content.label.canManageLabel');
+
+ /**
+ * label group id
+ * @var integer
+ */
+ public $groupID = 0;
+
+ /**
+ * label value
+ * @var string
+ */
+ public $label = '';
+
+ /**
+ * label group list object
+ * @var wcf\data\label\group\LabelGroupList
+ */
+ public $labelGroupList = null;
+
+ /**
+ * CSS class name
+ * @var string
+ */
+ public $cssClassName = '';
+
+ /**
+ * custom CSS class name
+ * @var string
+ */
+ public $customCssClassName = '';
+
+ /**
+ * list of pre-defined css class names
+ * @var array<string>
+ */
+ public $availableCssClassNames = array(
+ 'yellow',
+ 'orange',
+ 'brown',
+ 'red',
+ 'pink',
+ 'purple',
+ 'blue',
+ 'green',
+ 'black',
+
+ 'none', /* not a real value */
+ 'custom' /* not a real value */
+ );
+
+ /**
+ * @see wcf\page\IPage::readParameters()
+ */
+ public function readParameters() {
+ parent::readParameters();
+
+ I18nHandler::getInstance()->register('label');
+ }
+
+ /**
+ * @see wcf\form\IForm::readFormParameters()
+ */
+ public function readFormParameters() {
+ parent::readFormParameters();
+
+ I18nHandler::getInstance()->readValues();
+
+ if (I18nHandler::getInstance()->isPlainValue('label')) $this->label = I18nHandler::getInstance()->getValue('label');
+ 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']);
+ }
+
+ /**
+ * @see wcf\form\IForm::validate()
+ */
+ public function validate() {
+ parent::validate();
+
+ // validate label
+ if (!I18nHandler::getInstance()->validateValue('label')) {
+ if (I18nHandler::getInstance()->isPlainValue('label')) {
+ throw new UserInputException('label');
+ }
+ else {
+ throw new UserInputException('label', 'multilingual');
+ }
+ }
+
+ // validate group
+ if (!$this->groupID) {
+ throw new UserInputException('groupID');
+ }
+ $groups = $this->labelGroupList->getObjects();
+ if (!isset($groups[$this->groupID])) {
+ throw new UserInputException('groupID', 'invalid');
+ }
+
+ if (empty($this->cssClassName)) {
+ throw new UserInputException('cssClassName', 'empty');
+ }
+ else if (!in_array($this->cssClassName, $this->availableCssClassNames)) {
+ throw new UserInputException('cssClassName', 'notValid');
+ }
+ else if ($this->cssClassName == 'custom') {
+ if (!empty($this->customCssClassName) && !Regex::compile('^-?[_a-zA-Z]+[_a-zA-Z0-9-]+$')->match($this->customCssClassName)) {
+ throw new UserInputException('cssClassName', 'notValid');
+ }
+ }
+ }
+
+ /**
+ * @see wcf\form\IForm::save()
+ */
+ public function save() {
+ parent::save();
+
+ // save label
+ $this->objectAction = new LabelAction(array(), 'create', array('data' => array(
+ 'label' => $this->label,
+ 'cssClassName' => ($this->cssClassName == 'custom' ? $this->customCssClassName : $this->cssClassName),
+ 'groupID' => $this->groupID
+ )));
+ $this->objectAction->executeAction();
+
+ if (!I18nHandler::getInstance()->isPlainValue('label')) {
+ $returnValues = $this->objectAction->getReturnValues();
+ $labelID = $returnValues['returnValues']->labelID;
+ I18nHandler::getInstance()->save('label', 'wcf.acp.label.label'.$labelID, 'wcf.acp.label', PackageCache::getInstance()->getPackageID('com.woltlab.wcf.label'));
+
+ // update group name
+ $labelEditor = new LabelEditor($returnValues['returnValues']);
+ $labelEditor->update(array(
+ 'label' => 'wcf.acp.label.label'.$labelID
+ ));
+ }
+
+ $this->saved();
+
+ // reset values
+ $this->label = $this->cssClassName = $this->customCssClassName = '';
+ $this->groupID = 0;
+
+ I18nHandler::getInstance()->reset();
+
+ // show success
+ WCF::getTPL()->assign(array(
+ 'success' => true
+ ));
+ }
+
+ /**
+ * @see wcf\page\IPage::readData()
+ */
+ public function readData() {
+ $this->labelGroupList = new LabelGroupList();
+ $this->labelGroupList->readObjects();
+
+ parent::readData();
+ }
+
+ /**
+ * @see wcf\page\IPage::assignVariables()
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ I18nHandler::getInstance()->assignVariables();
+
+ WCF::getTPL()->assign(array(
+ 'action' => 'add',
+ 'availableCssClassNames' => $this->availableCssClassNames,
+ 'cssClassName' => $this->cssClassName,
+ 'customCssClassName' => $this->customCssClassName,
+ 'groupID' => $this->groupID,
+ 'label' => $this->label,
+ 'labelGroupList' => $this->labelGroupList
+ ));
+ }
+}
--- /dev/null
+<?php
+namespace wcf\acp\form;
+use wcf\data\label\Label;
+use wcf\data\label\LabelAction;
+use wcf\data\package\PackageCache;
+use wcf\form\AbstractForm;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\language\I18nHandler;
+use wcf\system\WCF;
+
+/**
+ * Shows the label edit form.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.label
+ * @subpackage acp.form
+ * @category Community Framework
+ */
+class LabelEditForm extends LabelAddForm {
+ /**
+ * @see wcf\page\AbstractPage::$activeMenuItem
+ */
+ public $activeMenuItem = 'wcf.acp.menu.link.label';
+
+ /**
+ * @see wcf\page\AbstractPage::$neededPermissions
+ */
+ public $neededPermissions = array('admin.content.label.canManageLabel');
+
+ /**
+ * label id
+ * @var integer
+ */
+ public $labelID = 0;
+
+ /**
+ * label object
+ * @var wcf\data\label\Label
+ */
+ public $labelObj = null;
+
+ /**
+ * @see wcf\page\IPage::readParameters()
+ */
+ public function readParameters() {
+ parent::readParameters();
+
+ if (isset($_REQUEST['id'])) $this->labelID = intval($_REQUEST['id']);
+ $this->labelObj = new Label($this->labelID);
+ if (!$this->labelObj->labelID) {
+ throw new IllegalLinkException();
+ }
+ }
+
+ /**
+ * @see wcf\form\IForm::save()
+ */
+ public function save() {
+ AbstractForm::save();
+
+ $this->label = 'wcf.acp.label.label'.$this->labelObj->labelID;
+ if (I18nHandler::getInstance()->isPlainValue('label')) {
+ I18nHandler::getInstance()->remove($this->label, PackageCache::getInstance()->getPackageID('com.woltlab.wcf.label'));
+ $this->label = I18nHandler::getInstance()->getValue('label');
+ }
+ else {
+ I18nHandler::getInstance()->save('label', $this->label, 'wcf.acp.label', PackageCache::getInstance()->getPackageID('com.woltlab.wcf.label'));
+ }
+
+ // update label
+ $this->objectAction = new LabelAction(array($this->labelID), 'update', array('data' => array(
+ 'label' => $this->label,
+ 'cssClassName' => ($this->cssClassName == 'custom' ? $this->customCssClassName : $this->cssClassName),
+ 'groupID' => $this->groupID
+ )));
+ $this->objectAction->executeAction();
+
+ $this->saved();
+
+ // reset values if non-custom value was choosen
+ if ($this->cssClassName != 'custom') $this->customCssClassName = '';
+
+ // show success
+ WCF::getTPL()->assign(array(
+ 'success' => true
+ ));
+ }
+
+ /**
+ * @see wcf\page\IPage::readData()
+ */
+ public function readData() {
+ parent::readData();
+
+ if (empty($_POST)) {
+ I18nHandler::getInstance()->setOptions('label', PackageCache::getInstance()->getPackageID('com.woltlab.wcf.label'), $this->labelObj->label, 'wcf.acp.label.label\d+');
+ $this->label = $this->labelObj->label;
+
+ $this->cssClassName = $this->labelObj->cssClassName;
+ if (!in_array($this->cssClassName, $this->availableCssClassNames)) {
+ $this->customCssClassName = $this->cssClassName;
+ $this->cssClassName = 'custom';
+ }
+
+ $this->groupID = $this->labelObj->groupID;
+ }
+ }
+
+ /**
+ * @see wcf\page\IPage::assignVariables()
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ I18nHandler::getInstance()->assignVariables(!empty($_POST));
+
+ WCF::getTPL()->assign(array(
+ 'label' => $this->labelObj,
+ 'action' => 'edit'
+ ));
+ }
+}
--- /dev/null
+<?php
+namespace wcf\acp\form;
+use wcf\data\label\group\LabelGroupAction;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\form\AbstractForm;
+use wcf\system\acl\ACLHandler;
+use wcf\system\exception\UserInputException;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+
+/**
+ * Shows the label group add form.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.label
+ * @subpackage acp.form
+ * @category Community Framework
+ */
+class LabelGroupAddForm extends AbstractForm {
+ /**
+ * @see wcf\page\AbstractPage::$activeMenuItem
+ */
+ public $activeMenuItem = 'wcf.acp.menu.link.label.group.add';
+
+ /**
+ * @see wcf\page\AbstractPage::$neededPermissions
+ */
+ public $neededPermissions = array('admin.content.label.canManageLabel');
+
+ /**
+ * force users to select a label
+ * @var boolean
+ */
+ public $forceSelection = false;
+
+ /**
+ * group name
+ * @var string
+ */
+ public $groupName = '';
+
+ /**
+ * list of label object type handlers
+ * @var array<wcf\system\label\object\type\ILabelObjectTypeHandler>
+ */
+ public $labelObjectTypes = array();
+
+ /**
+ * list of label object type containers
+ * @var array<wcf\system\label\object\type\LabelObjectTypeContainer>
+ */
+ public $labelObjectTypeContainers = array();
+
+ /**
+ * list of label group to object type relations
+ * @var array<array>
+ */
+ public $objectTypes = array();
+
+ /**
+ * object type id
+ * @var integer
+ */
+ public $objectTypeID = 0;
+
+ /**
+ * @see wcf\page\AbstractPage::readParameters()
+ */
+ public function readParameters() {
+ parent::readParameters();
+
+ $this->objectTypeID = ACLHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.label');
+ }
+
+ /**
+ * @see wcf\form\IForm::readFormParameters()
+ */
+ public function readFormParameters() {
+ parent::readFormParameters();
+
+ if (isset($_POST['forceSelection'])) $this->forceSelection = true;
+ if (isset($_POST['groupName'])) $this->groupName = StringUtil::trim($_POST['groupName']);
+ if (isset($_POST['objectTypes']) && is_array($_POST['objectTypes'])) $this->objectTypes = $_POST['objectTypes'];
+ }
+
+ /**
+ * @see wcf\page\IPage::readData()
+ */
+ public function readData() {
+ // get label object type handlers
+ $objectTypes = ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.label.objectType');
+ foreach ($objectTypes as $objectType) {
+ $this->labelObjectTypes[$objectType->objectTypeID] = $objectType->getProcessor();
+ $this->labelObjectTypes[$objectType->objectTypeID]->setObjectTypeID($objectType->objectTypeID);
+ }
+
+ foreach ($this->labelObjectTypes as $objectTypeID => $labelObjectType) {
+ $this->labelObjectTypeContainers[$objectTypeID] = $labelObjectType->getContainer();
+ }
+
+ parent::readData();
+
+ // assign new values for object relations
+ $this->setObjectTypeRelations();
+ }
+
+ /**
+ * @see wcf\form\IForm::validate()
+ */
+ public function validate() {
+ parent::validate();
+
+ // validate class name
+ if (empty($this->groupName)) {
+ throw new UserInputException('groupName');
+ }
+
+ // validate object type relations
+ foreach ($this->objectTypes as $objectTypeID => $data) {
+ if (!isset($this->labelObjectTypes[$objectTypeID])) {
+ unset($this->objectTypes[$objectTypeID]);
+ }
+ }
+ }
+
+ /**
+ * @see wcf\form\IForm::save()
+ */
+ public function save() {
+ parent::save();
+
+ // save label
+ $this->objectAction = new LabelGroupAction(array(), 'create', array('data' => array(
+ 'forceSelection' => ($this->forceSelection ? 1 : 0),
+ 'groupName' => $this->groupName
+ )));
+ $returnValues = $this->objectAction->executeAction();
+
+ // save acl
+ ACLHandler::getInstance()->save($returnValues['returnValues']->groupID, $this->objectTypeID);
+
+ // save object type relations
+ $this->saveObjectTypeRelations($returnValues['returnValues']->groupID);
+
+ $this->saved();
+
+ // reset values
+ $this->forceSelection = false;
+ $this->groupName = '';
+ $this->objectTypes = array();
+ $this->setObjectTypeRelations();
+
+ // show success
+ WCF::getTPL()->assign(array(
+ 'success' => true
+ ));
+ }
+
+ /**
+ * @see wcf\page\IPage::assignVariables()
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ WCF::getTPL()->assign(array(
+ 'action' => 'add',
+ 'forceSelection' => $this->forceSelection,
+ 'groupName' => $this->groupName,
+ 'labelObjectTypeContainers' => $this->labelObjectTypeContainers,
+ 'objectTypeID' => $this->objectTypeID
+ ));
+ }
+
+ /**
+ * Saves label group to object relations.
+ *
+ * @param integer $groupID
+ */
+ protected function saveObjectTypeRelations($groupID) {
+ WCF::getDB()->beginTransaction();
+
+ // remove old relations
+ if ($groupID !== null) {
+ $sql = "DELETE FROM wcf".WCF_N."_label_group_to_object
+ WHERE groupID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array($groupID));
+ }
+
+ // insert new relations
+ if (!empty($this->objectTypes)) {
+ $sql = "INSERT INTO wcf".WCF_N."_label_group_to_object
+ (groupID, objectTypeID, objectID)
+ VALUES (?, ?, ?)";
+ $statement = WCF::getDB()->prepareStatement($sql);
+
+ foreach ($this->objectTypes as $objectTypeID => $data) {
+ foreach ($data as $objectID) {
+ // use "0" (stored as NULL) for simple true/false states
+ if (!$objectID) $objectID = null;
+
+ $statement->execute(array(
+ $groupID,
+ $objectTypeID,
+ $objectID
+ ));
+ }
+ }
+ }
+
+ WCF::getDB()->commitTransaction();
+ }
+
+ /**
+ * Sets object type relations.
+ */
+ protected function setObjectTypeRelations($data = null) {
+ if (!empty($_POST)) {
+ // use POST data
+ $data = &$this->objectTypes;
+ }
+
+ // no data provided and no POST data exists
+ if ($data === null || !is_array($data)) {
+ // nothing to do here
+ return;
+ }
+
+ foreach ($this->labelObjectTypeContainers as $objectTypeID => $container) {
+ if ($container->isBooleanOption()) {
+ $optionValue = (isset($data[$objectTypeID])) ? 1 : 0;
+ $container->setOptionValue($optionValue);
+ }
+ else {
+ $hasData = (isset($data[$objectTypeID]));
+ foreach ($container as $object) {
+ if (!$hasData) {
+ $object->setOptionValue(0);
+ }
+ else {
+ $optionValue = (in_array($object->getObjectID(), $data[$objectTypeID])) ? 1 : 0;
+ $object->setOptionValue($optionValue);
+ }
+ }
+ }
+ }
+ }
+}
--- /dev/null
+<?php
+namespace wcf\acp\form;
+use wcf\data\label\group\LabelGroup;
+use wcf\data\label\group\LabelGroupAction;
+use wcf\form\AbstractForm;
+use wcf\system\acl\ACLHandler;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\WCF;
+
+/**
+ * Shows the label group edit form.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.label
+ * @subpackage acp.form
+ * @category Community Framework
+ */
+class LabelGroupEditForm extends LabelGroupAddForm {
+ /**
+ * @see wcf\page\AbstractPage::$activeMenuItem
+ */
+ public $activeMenuItem = 'wcf.acp.menu.link.label';
+
+ /**
+ * @see wcf\page\AbstractPage::$neededPermissions
+ */
+ public $neededPermissions = array('admin.content.label.canManageLabel');
+
+ /**
+ * group id
+ * @var integer
+ */
+ public $groupID = 0;
+
+ /**
+ * label group object
+ * @var wcf\data\label\group\LabelGroup
+ */
+ public $group = null;
+
+ /**
+ * @see wcf\page\IPage::readParameters()
+ */
+ public function readParameters() {
+ parent::readParameters();
+
+ if (isset($_REQUEST['id'])) $this->groupID = intval($_REQUEST['id']);
+ $this->group = new LabelGroup($this->groupID);
+ if (!$this->group->groupID) {
+ throw new IllegalLinkException();
+ }
+ }
+
+ /**
+ * @see wcf\form\IForm::save()
+ */
+ public function save() {
+ AbstractForm::save();
+
+ // update label
+ $this->objectAction = new LabelGroupAction(array($this->groupID), 'update', array('data' => array(
+ 'forceSelection' => ($this->forceSelection ? 1 : 0),
+ 'groupName' => $this->groupName
+ )));
+ $this->objectAction->executeAction();
+
+ // update acl
+ ACLHandler::getInstance()->save($this->groupID, $this->objectTypeID);
+
+ // update object type relations
+ $this->saveObjectTypeRelations($this->groupID);
+
+ $this->saved();
+
+ // show success
+ WCF::getTPL()->assign(array(
+ 'success' => true
+ ));
+ }
+
+ /**
+ * @see wcf\page\IPage::readData()
+ */
+ public function readData() {
+ parent::readData();
+
+ if (empty($_POST)) {
+ $this->forceSelection = ($this->group->forceSelection ? true : false);
+ $this->groupName = $this->group->groupName;
+ }
+ }
+
+ /**
+ * @see wcf\page\IPage::assignVariables()
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ WCF::getTPL()->assign(array(
+ 'action' => 'edit',
+ 'groupID' => $this->groupID,
+ 'labelGroup' => $this->group
+ ));
+ }
+
+ /**
+ * @see wcf\acp\form\LabelGroupAddForm::setObjectTypeRelations()
+ */
+ protected function setObjectTypeRelations($data = null) {
+ if (empty($_POST)) {
+ // read database values
+ $sql = "SELECT objectTypeID, objectID
+ FROM wcf".WCF_N."_label_group_to_object
+ WHERE groupID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array($this->groupID));
+
+ $data = array();
+ while ($row = $statement->fetchArray()) {
+ if (!isset($data[$row['objectTypeID']])) {
+ $data[$row['objectTypeID']] = array();
+ }
+
+ // prevent NULL values which confuse isset()
+ $data[$row['objectTypeID']][] = ($row['objectID']) ?: 0;
+ }
+ }
+
+ parent::setObjectTypeRelations($data);
+ }
+}
--- /dev/null
+<?php
+namespace wcf\acp\page;
+use wcf\page\SortablePage;
+
+/**
+ * Lists available label groups.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.label
+ * @subpackage acp.page
+ * @category Community Framework
+ */
+class LabelGroupListPage extends SortablePage {
+ /**
+ * @see wcf\page\AbstractPage::$activeMenuItem
+ */
+ public $activeMenuItem = 'wcf.acp.menu.link.label.group.list';
+
+ /**
+ * @see wcf\page\SortablePage::$defaultSortField
+ */
+ public $defaultSortField = 'groupName';
+
+ /**
+ * @see wcf\page\SortablePage::$validSortFields
+ */
+ public $validSortFields = array('groupID', 'groupName');
+
+ /**
+ * @see wcf\page\AbstractPage::$neededPermissions
+ */
+ public $neededPermissions = array('admin.content.label.canManageLabel');
+
+ /**
+ * @see wcf\page\MultipleLinkPage::$objectListClassName
+ */
+ public $objectListClassName = 'wcf\data\label\group\LabelGroupList';
+}
--- /dev/null
+<?php
+namespace wcf\acp\page;
+use wcf\page\SortablePage;
+
+/**
+ * Lists available labels
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.label
+ * @subpackage acp.page
+ * @category Community Framework
+ */
+class LabelListPage extends SortablePage {
+ /**
+ * @see wcf\page\AbstractPage::$activeMenuItem
+ */
+ public $activeMenuItem = 'wcf.acp.menu.link.label.list';
+
+ /**
+ * @see wcf\page\SortablePage::$defaultSortField
+ */
+ public $defaultSortField = 'label';
+
+ /**
+ * @see wcf\page\SortablePage::$validSortFields
+ */
+ public $validSortFields = array('labelID', 'label', 'groupName');
+
+ /**
+ * @see wcf\page\AbstractPage::$neededPermissions
+ */
+ public $neededPermissions = array('admin.content.label.canManageLabel');
+
+ /**
+ * @see wcf\page\MultipleLinkPage::$objectListClassName
+ */
+ public $objectListClassName = 'wcf\data\label\LabelList';
+
+ /**
+ * @see wcf\page\MultipleLinkPage::initObjectList()
+ */
+ protected function initObjectList() {
+ parent::initObjectList();
+
+ $this->objectList->sqlSelects = "label_group.groupName";
+ $this->objectList->sqlJoins = "LEFT JOIN wcf".WCF_N."_label_group label_group ON (label_group.groupID = label.groupID)";
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\label;
+use wcf\data\DatabaseObject;
+use wcf\system\request\IRouteController;
+use wcf\system\WCF;
+
+/**
+ * Represents a label.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.label
+ * @subpackage data.label
+ * @category Community Framework
+ */
+class Label extends DatabaseObject implements IRouteController {
+ /**
+ * @see wcf\data\DatabaseObject::$databaseTableName
+ */
+ protected static $databaseTableName = 'label';
+
+ /**
+ * @see wcf\data\DatabaseObject::$databaseIndexName
+ */
+ protected static $databaseTableIndexName = 'labelID';
+
+ /**
+ * Returns the label's textual representation if a label is treated as a
+ * string.
+ *
+ * @return string
+ */
+ public function __toString() {
+ return $this->getTitle();
+ }
+
+ /**
+ * Returns true, if label is editable by current user.
+ *
+ * @return boolean
+ */
+ public function isEditable() {
+ if (WCF::getSession()->getPermission('admin.content.label.canManageLabel')) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns true, if label is deletable by current user.
+ *
+ * @return boolean
+ */
+ public function isDeletable() {
+ if (WCF::getSession()->getPermission('admin.content.label.canManageLabel')) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * @see wcf\data\ITitledObject::getTitle()
+ */
+ public function getTitle() {
+ return WCF::getLanguage()->get($this->label);
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\label;
+use wcf\data\language\item\LanguageItemAction;
+use wcf\data\AbstractDatabaseObjectAction;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\WCF;
+
+/**
+ * Executes label-related actions.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.label
+ * @subpackage data.label
+ * @category Community Framework
+ */
+class LabelAction extends AbstractDatabaseObjectAction {
+ /**
+ * @see wcf\data\AbstractDatabaseObjectAction::$className
+ */
+ protected $className = 'wcf\data\label\LabelEditor';
+
+ /**
+ * @see wcf\data\AbstractDatabaseObjectAction::$permissionsCreate
+ */
+ protected $permissionsCreate = array('admin.content.label.canManageLabel');
+
+ /**
+ * @see wcf\data\AbstractDatabaseObjectAction::$permissionsDelete
+ */
+ protected $permissionsDelete = array('admin.content.label.canManageLabel');
+
+ /**
+ * @see wcf\data\AbstractDatabaseObjectAction::$permissionsUpdate
+ */
+ protected $permissionsUpdate = array('admin.content.label.canManageLabel');
+
+ /**
+ * @see wcf\data\AbstractDatabaseObjectAction::delete()
+ */
+ public function delete() {
+ parent::delete();
+
+ if (!empty($this->objects)) {
+ // identify i18n labels
+ $languageVariables = array();
+ foreach ($this->objects as $object) {
+ if (preg_match('~wcf.acp.label.label\d+~', $object->label)) {
+ $languageVariables[] = $object->label;
+ }
+ }
+
+ // remove language variables
+ if (!empty($languageVariables)) {
+ $conditions = new PreparedStatementConditionBuilder();
+ $conditions->add("languageItem IN (?)", array($languageVariables));
+
+ $sql = "SELECT languageItemID
+ FROM wcf".WCF_N."_language_item
+ ".$conditions;
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute($conditions->getParameters());
+ $languageItemIDs = array();
+ while ($row = $statement->fetchArray()) {
+ $languageItemIDs[] = $row['languageItemID'];
+ }
+
+ $objectAction = new LanguageItemAction($languageItemIDs, 'delete');
+ $objectAction->executeAction();
+ }
+ }
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\label;
+use wcf\data\DatabaseObjectEditor;
+use wcf\data\IEditableCachedObject;
+use wcf\system\cache\builder\LabelCacheBuilder;
+
+/**
+ * Provides functions to edit labels.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.label
+ * @subpackage data.label
+ * @category Community Framework
+ */
+class LabelEditor extends DatabaseObjectEditor implements IEditableCachedObject {
+ /**
+ * @see wcf\data\DatabaseObjectEditor::$baseClass
+ */
+ protected static $baseClass = 'wcf\data\label\Label';
+
+ /**
+ * @see wcf\data\IEditableCachedObject::resetCache()
+ */
+ public static function resetCache() {
+ LabelCacheBuilder::getInstance()->reset();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\label;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list of labels.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2011 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.label
+ * @subpackage data.label
+ * @category Community Framework
+ */
+class LabelList extends DatabaseObjectList {
+ /**
+ * @see wcf\data\DatabaseObjectList::$className
+ */
+ public $className = 'wcf\data\label\Label';
+}
--- /dev/null
+<?php
+namespace wcf\data\label\group;
+use wcf\data\DatabaseObject;
+use wcf\system\request\IRouteController;
+use wcf\system\WCF;
+
+/**
+ * Represents a label group.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.label
+ * @subpackage data.label.group
+ * @category Community Framework
+ */
+class LabelGroup extends DatabaseObject implements IRouteController {
+ /**
+ * @see wcf\data\DatabaseObject::$databaseTableName
+ */
+ protected static $databaseTableName = 'label_group';
+
+ /**
+ * @see wcf\data\DatabaseObject::$databaseIndexName
+ */
+ protected static $databaseTableIndexName = 'groupID';
+
+ /**
+ * Returns true, if label is editable by current user.
+ *
+ * @return boolean
+ */
+ public function isEditable() {
+ if (WCF::getSession()->getPermission('admin.content.label.canManageLabel')) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns true, if label is deletable by current user.
+ *
+ * @return boolean
+ */
+ public function isDeletable() {
+ if (WCF::getSession()->getPermission('admin.content.label.canManageLabel')) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * @see wcf\data\ITitledObject::getTitle()
+ */
+ public function getTitle() {
+ return $this->groupName;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\label\group;
+use wcf\data\AbstractDatabaseObjectAction;
+
+/**
+ * Executes label group-related actions.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2011 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.label
+ * @subpackage data.label.group
+ * @category Community Framework
+ */
+class LabelGroupAction extends AbstractDatabaseObjectAction {
+ /**
+ * @see wcf\data\AbstractDatabaseObjectAction::$className
+ */
+ protected $className = 'wcf\data\label\group\LabelGroupEditor';
+
+ /**
+ * @see wcf\data\AbstractDatabaseObjectAction::$permissionsCreate
+ */
+ protected $permissionsCreate = array('admin.content.label.canManageLabel');
+
+ /**
+ * @see wcf\data\AbstractDatabaseObjectAction::$permissionsDelete
+ */
+ protected $permissionsDelete = array('admin.content.label.canManageLabel');
+
+ /**
+ * @see wcf\data\AbstractDatabaseObjectAction::$permissionsUpdate
+ */
+ protected $permissionsUpdate = array('admin.content.label.canManageLabel');
+}
--- /dev/null
+<?php
+namespace wcf\data\label\group;
+use wcf\data\DatabaseObjectEditor;
+use wcf\data\IEditableCachedObject;
+use wcf\system\acl\ACLHandler;
+use wcf\system\cache\builder\LabelCacheBuilder;
+
+/**
+ * Provides functions to edit label groups.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.label
+ * @subpackage data.label.group
+ * @category Community Framework
+ */
+class LabelGroupEditor extends DatabaseObjectEditor implements IEditableCachedObject {
+ /**
+ * @see wcf\data\DatabaseObjectEditor::$baseClass
+ */
+ protected static $baseClass = 'wcf\data\label\group\LabelGroup';
+
+ /**
+ * @see wcf\data\IEditableObject::deleteAll()
+ */
+ public static function deleteAll(array $objectIDs = array()) {
+ $count = parent::deleteAll($objectIDs);
+
+ // remove ACL values
+ $objectTypeID = ACLHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.label');
+ ACLHandler::getInstance()->removeValues($objectTypeID, $objectIDs);
+
+ return $count;
+ }
+
+ /**
+ * @see wcf\data\IEditableCachedObject::resetCache()
+ */
+ public static function resetCache() {
+ LabelCacheBuilder::getInstance()->reset();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\label\group;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list of label groups.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2011 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.label
+ * @subpackage data.label.group
+ * @category Community Framework
+ */
+class LabelGroupList extends DatabaseObjectList {
+ /**
+ * @see wcf\data\DatabaseObjectList::$className
+ */
+ public $className = 'wcf\data\label\group\LabelGroup';
+}
--- /dev/null
+<?php
+namespace wcf\data\label\group;
+use wcf\data\label\Label;
+use wcf\data\DatabaseObjectDecorator;
+use wcf\data\ITraversableObject;
+use wcf\system\exception\SystemException;
+use wcf\system\WCF;
+
+/**
+ * Represents a viewable label group.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.label
+ * @subpackage data.label.group
+ * @category Community Framework
+ */
+class ViewableLabelGroup extends DatabaseObjectDecorator implements \Countable, ITraversableObject {
+ /**
+ * @see wcf\data\DatabaseObjectDecorator::$baseClass
+ */
+ protected static $baseClass = 'wcf\data\label\group\LabelGroup';
+
+ /**
+ * list of labels
+ * @var array<wcf\data\label\Label>
+ */
+ protected $labels = array();
+
+ /**
+ * list of permissions by type
+ * @var array<array>
+ */
+ protected $permissions = array(
+ 'group' => array(),
+ 'user' => array()
+ );
+
+ /**
+ * current iterator index
+ * @var integer
+ */
+ protected $index = 0;
+
+ /**
+ * list of index to object relation
+ * @var array<integer>
+ */
+ protected $indexToObject = null;
+
+ /**
+ * Adds a label.
+ *
+ * @param wcf\data\label\Label $label
+ */
+ public function addLabel(Label $label) {
+ $this->labels[$label->labelID] = $label;
+ $this->indexToObject[] = $label->labelID;
+ }
+
+ /**
+ * Sets group permissions.
+ *
+ * @param array $permissions
+ */
+ public function setGroupPermissions(array $permissions) {
+ $this->permissions['group'] = $permissions;
+ }
+
+ /**
+ * Sets user permissions.
+ *
+ * @param array $permissions
+ */
+ public function setUserPermissions(array $permissions) {
+ $this->permissions['user'] = $permissions;
+ }
+
+ /**
+ * Returns true, if label is known.
+ *
+ * @param integer $labelID
+ * @return boolean
+ */
+ public function isValid($labelID) {
+ return isset($this->labels[$labelID]);
+ }
+
+ /**
+ * Returns true, if current user fulfils option id permissions.
+ *
+ * @param integer $optionID
+ * @return boolean
+ */
+ public function getPermission($optionID) {
+ // validate by user id
+ $userID = WCF::getUser()->userID;
+ if ($userID) {
+ if (isset($this->permissions['user'][$userID]) && isset($this->permissions['user'][$userID][$optionID])) {
+ if ($this->permissions['user'][$userID][$optionID] == 1) {
+ return true;
+ }
+ }
+ }
+
+ // validate by group id
+ $groupIDs = WCF::getUser()->getGroupIDs();
+ foreach ($groupIDs as $groupID) {
+ if (isset($this->permissions['group'][$groupID]) && isset($this->permissions['group'][$groupID][$optionID])) {
+ if ($this->permissions['group'][$groupID][$optionID] == 1) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns a list of label ids.
+ *
+ * @return array<integer>
+ */
+ public function getLabelIDs() {
+ return array_keys($this->labels);
+ }
+
+ /**
+ * Returns a list of labels.
+ *
+ * @return array<wcf\data\label\Label>
+ */
+ public function getLabels() {
+ return $this->labels;
+ }
+
+ /**
+ * Returns a label by id.
+ *
+ * @param integer $labelID
+ * @return wcf\data\label\Label
+ */
+ public function getLabel($labelID) {
+ if (isset($this->labels[$labelID])) {
+ return $this->labels[$labelID];
+ }
+
+ return null;
+ }
+
+ /**
+ * @see \Countable::count()
+ */
+ public function count() {
+ return count($this->labels);
+ }
+
+ /**
+ * @see \Iterator::current()
+ */
+ public function current() {
+ $objectID = $this->indexToObject[$this->index];
+ return $this->labels[$objectID];
+ }
+
+ /**
+ * CAUTION: This methods does not return the current iterator index,
+ * rather than the object key which maps to that index.
+ *
+ * @see \Iterator::key()
+ */
+ public function key() {
+ return $this->indexToObject[$this->index];
+ }
+
+ /**
+ * @see \Iterator::next()
+ */
+ public function next() {
+ ++$this->index;
+ }
+
+ /**
+ * @see \Iterator::rewind()
+ */
+ public function rewind() {
+ $this->index = 0;
+ }
+
+ /**
+ * @see \Iterator::valid()
+ */
+ public function valid() {
+ return isset($this->indexToObject[$this->index]);
+ }
+
+ /**
+ * @see \SeekableIterator::seek()
+ */
+ public function seek($index) {
+ $this->index = $index;
+
+ if (!$this->valid()) {
+ throw new \OutOfBoundsException();
+ }
+ }
+
+ /**
+ * @see wcf\data\ITraversableObject::seekTo()
+ */
+ public function seekTo($objectID) {
+ $this->index = array_search($objectID, $this->indexToObject);
+
+ if ($this->index === false) {
+ throw new SystemException("object id '".$objectID."' is invalid");
+ }
+ }
+
+ /**
+ * @see wcf\data\ITraversableObject::search()
+ */
+ public function search($objectID) {
+ try {
+ $this->seekTo($objectID);
+ return $this->current();
+ }
+ catch (SystemException $e) {
+ return null;
+ }
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\cache\builder;
+use wcf\data\label\group\LabelGroupList;
+use wcf\data\label\group\ViewableLabelGroup;
+use wcf\data\label\LabelList;
+use wcf\system\acl\ACLHandler;
+
+/**
+ * Caches labels and groups.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.label
+ * @subpackage system.cache.builder
+ * @category Community Framework
+ */
+class LabelCacheBuilder extends AbstractCacheBuilder {
+ /**
+ * @see wcf\system\cache\builder\AbstractCacheBuilder::rebuild()
+ */
+ protected function rebuild(array $parameters) {
+ $data = array(
+ 'options' => array(),
+ 'groups' => array()
+ );
+
+ // get label groups
+ $groupList = new LabelGroupList();
+ $groupList->readObjects();
+ $groups = $groupList->getObjects();
+ foreach ($groups as &$group) {
+ $data['groups'][$group->groupID] = new ViewableLabelGroup($group);
+ }
+ unset($group);
+
+ // get permissions for groups
+ $permissions = ACLHandler::getInstance()->getPermissions(
+ ACLHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.label'),
+ array_keys($data['groups'])
+ );
+
+ // store options
+ $data['options'] = $permissions['options']->getObjects();
+
+ // assign permissions for each label group
+ foreach ($data['groups'] as $groupID => $group) {
+ // group permissions
+ if (isset($permissions['group'][$groupID])) {
+ $group->setGroupPermissions($permissions['group'][$groupID]);
+ }
+
+ // user permissions
+ if (isset($permissions['user'][$groupID])) {
+ $group->setUserPermissions($permissions['user'][$groupID]);
+ }
+ }
+
+ if (count($groupList)) {
+ // get labels
+ $labelList = new LabelList();
+ $labelList->readObjects();
+ foreach ($labelList as $label) {
+ $data['groups'][$label->groupID]->addLabel($label);
+ }
+ }
+
+ return $data;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\label;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\system\cache\builder\LabelCacheBuilder;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\exception\SystemException;
+use wcf\system\SingletonFactory;
+use wcf\system\WCF;
+
+/**
+ * Manages labels and label-to-object associations.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.label
+ * @subpackage system.label
+ * @category Community Framework
+ */
+class LabelHandler extends SingletonFactory {
+ /**
+ * cached list of object types
+ * @var array<array>
+ */
+ protected $cache = null;
+
+ /**
+ * list of label groups
+ * @var array<wcf\data\label\group\ViewableLabelGroup>
+ */
+ protected $labelGroups = null;
+
+ /**
+ * @see wcf\system\SingletonFactory::init()
+ */
+ protected function init() {
+ $this->cache = array(
+ 'objectTypes' => array(),
+ 'objectTypeNames' => array()
+ );
+
+ $cache = ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.label.object');
+ foreach ($cache as $objectType) {
+ $this->cache['objectTypes'][$objectType->objectTypeID] = $objectType;
+ $this->cache['objectTypeNames'][$objectType->objectType] = $objectType->objectTypeID;
+ }
+
+ $this->labelGroups = LabelCacheBuilder::getInstance()->getData();
+ }
+
+ /**
+ * Returns the id of the label ACL option with the given name or null if
+ * no such option exists.
+ *
+ * @param string $optionName
+ * @return integer
+ */
+ public function getOptionID($optionName) {
+ foreach ($this->labelGroups['options'] as $option) {
+ if ($option->optionName == $optionName) {
+ return $option->optionID;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the label object type with the given name or null of no such
+ * object.
+ *
+ * @param string $objectType
+ * @return wcf\data\object\type\ObjectType
+ */
+ public function getObjectType($objectType) {
+ if (isset($this->cache['objectTypeNames'][$objectType])) {
+ $objectTypeID = $this->cache['objectTypeNames'][$objectType];
+ return $this->cache['objectTypes'][$objectTypeID];
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns an array with view permissions for the labels with the given id.
+ *
+ * @param array<integer> $labelIDs
+ * @return array
+ * @see wcf\system\label\LabelHandler::getPermissions()
+ */
+ public function validateCanView(array $labelIDs) {
+ return $this->getPermissions('canViewLabel', $labelIDs);
+ }
+
+ /**
+ * Returns an array with use permissions for the labels with the given id.
+ *
+ * @param array<integer> $labelIDs
+ * @return array
+ * @see wcf\system\label\LabelHandler::getPermissions()
+ */
+ public function validateCanUse(array $labelIDs) {
+ return $this->getPermissions('canUseLabel', $labelIDs);
+ }
+
+ /**
+ * Returns an array with boolean values for each given label id.
+ *
+ * @param string $optionName
+ * @param array<integer> $labelIDs
+ * @return array
+ */
+ public function getPermissions($optionName, array $labelIDs) {
+ if (empty($labelIDs)) {
+ // nothing to validate anyway
+ return array();
+ }
+
+ if (empty($this->labelGroups['groups'])) {
+ // pretend given label ids aren't valid
+ $data = array();
+ foreach ($labelIDs as $labelID) $data[$labelID] = false;
+
+ return $data;
+ }
+
+ $optionID = $this->getOptionID($optionName);
+ if ($optionID === null) {
+ throw new SystemException("cannot validate label ids, ACL options missing");
+ }
+
+ // validate each label
+ $data = array();
+ foreach ($labelIDs as $labelID) {
+ $isValid = false;
+
+ foreach ($this->labelGroups['groups'] as $group) {
+ if (!$group->isValid($labelID)) {
+ continue;
+ }
+
+ if ($group->getPermission($optionID)) {
+ $isValid = true;
+ }
+ }
+
+ $data[$labelID] = $isValid;
+ }
+
+ return $data;
+ }
+
+ /**
+ * Sets labels for given object id, pass an empty array to remove all previously
+ * assigned labels.
+ *
+ * @param array<integer> $labelIDs
+ * @param integer $objectTypeID
+ * @param integer $objectID
+ * @param boolean $validatePermissions
+ */
+ public function setLabels(array $labelIDs, $objectTypeID, $objectID, $validatePermissions = true) {
+ // get accessible label ids to prevent unaccessible ones to be removed
+ $accessibleLabelIDs = $this->getAccessibleLabelIDs();
+
+ // delete previous labels
+ $conditions = new PreparedStatementConditionBuilder();
+ if ($validatePermissions) $conditions->add("labelID IN (?)", array($accessibleLabelIDs));
+ $conditions->add("objectTypeID = ?", array($objectTypeID));
+ $conditions->add("objectID = ?", array($objectID));
+
+ if (!$validatePermissions || ($validatePermissions && !empty($accessibleLabelIDs))) {
+ $sql = "DELETE FROM wcf".WCF_N."_label_object
+ ".$conditions;
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute($conditions->getParameters());
+ }
+
+ // insert new labels
+ if (!empty($labelIDs)) {
+ $sql = "INSERT INTO wcf".WCF_N."_label_object
+ (labelID, objectTypeID, objectID)
+ VALUES (?, ?, ?)";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ foreach ($labelIDs as $labelID) {
+ $statement->execute(array(
+ $labelID,
+ $objectTypeID,
+ $objectID
+ ));
+ }
+ }
+ }
+
+ /**
+ * Returns all assigned labels, optionally filtered to validate permissions.
+ *
+ * @param integer $objectTypeID
+ * @param array<integer> $objectIds
+ * @param boolean $validatePermissions
+ * @return array
+ */
+ public function getAssignedLabels($objectTypeID, array $objectIDs, $validatePermissions = true) {
+ $conditions = new PreparedStatementConditionBuilder();
+ $conditions->add("objectTypeID = ?", array($objectTypeID));
+ $conditions->add("objectID IN (?)", array($objectIDs));
+ $sql = "SELECT objectID, labelID
+ FROM wcf".WCF_N."_label_object
+ ".$conditions;
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute($conditions->getParameters());
+
+ $labels = array();
+ while ($row = $statement->fetchArray()) {
+ if (!isset($labels[$row['labelID']])) {
+ $labels[$row['labelID']] = array();
+ }
+
+ $labels[$row['labelID']][] = $row['objectID'];
+ }
+
+ // optionally filter out labels without permissions
+ if ($validatePermissions) {
+ $labelIDs = array_keys($labels);
+ $result = $this->validateCanView($labelIDs);
+
+ foreach ($labelIDs as $labelID) {
+ if (!$result[$labelID]) {
+ unset($labels[$labelID]);
+ }
+ }
+ }
+
+ // reorder the array by object id
+ $data = array();
+ foreach ($labels as $labelID => $objectIDs) {
+ foreach ($objectIDs as $objectID) {
+ if (!isset($data[$objectID])) {
+ $data[$objectID] = array();
+ }
+
+ foreach ($this->labelGroups['groups'] as $group) {
+ $label = $group->getLabel($labelID);
+ if ($label !== null) {
+ $data[$objectID][$labelID] = $label;
+ }
+ }
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * Removes all previously assigned labels.
+ *
+ * @param integer $objectTypeID
+ * @param integer $objectID
+ * @param boolean $validatePermissions
+ * @see wcf\system\label\LabelHandler::setLabel()
+ */
+ public function removeLabels($objectTypeID, $objectID, $validatePermissions = true) {
+ $this->setLabel(array(), $objectTypeID, $objectID, $validatePermissions);
+ }
+
+ /**
+ * Returns given label groups by id.
+ *
+ * @param array<integer> $groupID
+ * @param boolean $validatePermissions
+ * @param string $permission
+ * @return array<wcf\data\label\group\ViewableLabelGroup>
+ */
+ public function getLabelGroups(array $groupIDs = array(), $validatePermissions = true, $permission = 'canSetLabel') {
+ $data = array();
+
+ if ($validatePermissions) {
+ $optionID = $this->getOptionID($permission);
+ if ($optionID === null) {
+ throw new SystemException("cannot validate group ids, ACL options missing");
+ }
+ }
+
+ if (empty($groupIDs)) $groupIDs = array_keys($this->labelGroups['groups']);
+ foreach ($groupIDs as $groupID) {
+ // validate given group ids
+ if (!isset($this->labelGroups['groups'][$groupID])) {
+ throw new SystemException("unknown label group identified by group id '".$groupID."'");
+ }
+
+ // validate permissions
+ if ($validatePermissions) {
+ if (!$this->labelGroups['groups'][$groupID]->getPermission($optionID)) {
+ continue;
+ }
+ }
+
+ $data[$groupID] = $this->labelGroups['groups'][$groupID];
+ }
+
+ return $data;
+ }
+
+ /**
+ * Returns a list of accessible label ids.
+ *
+ * @return array<integer>
+ */
+ public function getAccessibleLabelIDs() {
+ $labelIDs = array();
+ $groups = $this->getLabelGroups();
+
+ foreach ($groups as $group) {
+ $labelIDs = array_merge($labelIDs, $group->getLabelIDs());
+ }
+
+ return $labelIDs;
+ }
+
+ /**
+ * Returns label group by id.
+ *
+ * @param integer $groupID
+ * @return wcf\data\label\group\ViewableLabelGroup
+ */
+ public function getLabelGroup($groupID) {
+ if (isset($this->labelGroups['groups'][$groupID])) {
+ return $this->labelGroups['groups'][$groupID];
+ }
+
+ return null;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\label\object;
+use wcf\system\exception\SystemException;
+use wcf\system\label\LabelHandler;
+use wcf\system\SingletonFactory;
+
+/**
+ * Abstract implementation of a label object handler.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.label
+ * @subpackage system.label.object
+ * @category Community Framework
+ */
+abstract class AbstractLabelObjectHandler extends SingletonFactory implements ILabelObjectHandler {
+ /**
+ * list of available label groups
+ * @var array<wcf\data\label\group\ViewableLabelGroup>
+ */
+ protected $labelGroups = array();
+
+ /**
+ * object type name
+ * @var string
+ */
+ protected $objectType = '';
+
+ /**
+ * object type id
+ * @var integer
+ */
+ protected $objectTypeID = 0;
+
+ /**
+ * @see wcf\system\SingletonFactory::init()
+ */
+ protected function init() {
+ $this->labelGroups = LabelHandler::getInstance()->getLabelGroups();
+
+ $objectType = LabelHandler::getInstance()->getObjectType($this->objectType);
+ if ($objectType === null) {
+ throw new SystemException("object type '".$this->objectType."' is invalid");
+ }
+ $this->objectTypeID = $objectType->objectTypeID;
+ }
+
+ /**
+ * @see wcf\system\label\manager\ILabelObjectHandler::getLabelGroupIDs()
+ */
+ public function getLabelGroupIDs(array $parameters = array()) {
+ return array_keys($this->labelGroups);
+ }
+
+ /**
+ * @see wcf\system\label\manager\ILabelObjectHandler::getLabelGroups()
+ */
+ public function getLabelGroups(array $parameters = array()) {
+ $groupIDs = $this->getLabelGroupIDs($parameters);
+
+ $data = array();
+ foreach ($groupIDs as $groupID) {
+ $data[$groupID] = $this->labelGroups[$groupID];
+ }
+
+ return $data;
+ }
+
+ /**
+ * @see wcf\system\label\manager\ILabelObjectHandler::validateLabelIDs()
+ */
+ public function validateLabelIDs(array $labelIDs, $optionName = '') {
+ $optionID = 0;
+ if (!empty($optionName)) {
+ $optionID = LabelHandler::getInstance()->getOptionID($optionName);
+ if ($optionID === null) {
+ throw new SystemException("Cannot validate label permissions, option '".$optionName."' is unknown");
+ }
+ }
+
+ $satisfiedGroups = array();
+ foreach ($labelIDs as $groupID => $labelID) {
+ // only one label per group is allowed
+ if (is_array($labelID)) {
+ return false;
+ }
+
+ // label group id is unknown or label id is invalid for this group
+ if (!isset($this->labelGroups[$groupID]) || !$this->labelGroups[$groupID]->isValid($labelID)) {
+ return false;
+ }
+
+ // check permission
+ if ($optionID && !$this->labelGroups[$groupID]->getPermission($optionID)) {
+ return false;
+ }
+
+ $satisfiedGroups[] = $groupID;
+ }
+
+ // check if required label groups were set
+ foreach ($this->labelGroups as $labelGroup) {
+ if ($labelGroup->forceSelection && !in_array($labelGroup->groupID, $satisfiedGroups)) {
+ // check if group wasn't set, but is not accessible for this user anyway
+ if (!$labelGroup->getPermission($optionID)) {
+ continue;
+ }
+
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * @see wcf\system\label\manager\ILabelObjectHandler::setLabels()
+ */
+ public function setLabels(array $labelIDs, $objectID, $validatePermissions = true) {
+ LabelHandler::getInstance()->setLabels($labelIDs, $this->objectTypeID, $objectID, $validatePermissions);
+ }
+
+ /**
+ * @see wcf\system\label\manager\ILabelObjectHandler::removeLabels()
+ */
+ public function removeLabels($objectID, $validatePermissions = true) {
+ LabelHandler::getInstance()->removeLabels($this->objectTypeID, $objectID, $validatePermissions);
+ }
+
+ /**
+ * @see wcf\system\label\manager\ILabelObjectHandler::getAssignedLabels()
+ */
+ public function getAssignedLabels(array $objectIDs, $validatePermissions = true) {
+ return LabelHandler::getInstance()->getAssignedLabels($this->objectTypeID, $objectIDs, $validatePermissions);
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\label\object;
+
+/**
+ * Every label object handler has to implement this interface.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.label
+ * @subpackage system.label.object
+ * @category Community Framework
+ */
+interface ILabelObjectHandler {
+ /**
+ * Returns a list of label group ids.
+ *
+ * @param array $parameters
+ * @return array<integer>
+ */
+ public function getLabelGroupIDs(array $parameters = array());
+
+ /**
+ * Returns a list of label groups.
+ *
+ * @param array $parameters
+ * @return array<wcf\data\label\group\ViewableLabelGroup>
+ */
+ public function getLabelGroups(array $parameters = array());
+
+ /**
+ * Returns true, if all given label ids are valid and accessible.
+ *
+ * @param array<integer> $labelIDs
+ * @param array $optionName
+ * @return boolean
+ */
+ public function validateLabelIDs(array $labelIDs, $optionName = '');
+
+ /**
+ * Assigns labels to an object.
+ *
+ * @param array<integer> $labelIDs
+ * @param integer $objectID
+ * @param boolean $validatePermissions
+ * @see wcf\system\label\LabelHandler::setLabels()
+ */
+ public function setLabels(array $labelIDs, $objectID, $validatePermissions = true);
+
+ /**
+ * Removes all assigned labels.
+ *
+ * @param integer $objectID
+ * @param boolean $validatePermissions
+ * @see wcf\system\label\LabelHandler::removeLabels()
+ */
+ public function removeLabels($objectID, $validatePermissions = true);
+
+ /**
+ * Returns a list of assigned labels.
+ *
+ * @param array<integer> $objectIDs
+ * @param boolean $validatePermissions
+ * @return array<array>
+ */
+ public function getAssignedLabels(array $objectIDs, $validatePermissions = true);
+}
--- /dev/null
+<?php
+namespace wcf\system\label\object\type;
+use wcf\system\SingletonFactory;
+
+/**
+ * Abstract implementation of a label object type handler.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.label
+ * @subpackage system.label.object.type
+ * @category Community Framework
+ */
+abstract class AbstractLabelObjectTypeHandler extends SingletonFactory implements ILabelObjectTypeHandler {
+ /**
+ * label object type container
+ * @var wcf\system\label\object\type\LabelObjectTypeContainer
+ */
+ public $container = null;
+
+ /**
+ * object type id
+ * @var integer
+ */
+ public $objectTypeID = 0;
+
+ /**
+ * @see wcf\system\label\object\type\ILabelObjectTypeHandler::setObjectTypeID()
+ */
+ public function setObjectTypeID($objectTypeID) {
+ $this->objectTypeID = $objectTypeID;
+ }
+
+ /**
+ * @see wcf\system\label\object\type\ILabelObjectTypeHandler::getObjectTypeID()
+ */
+ public function getObjectTypeID() {
+ return $this->objectTypeID;
+ }
+
+ /**
+ * @see wcf\system\label\object\type\ILabelObjectTypeHandler::getContainer()
+ */
+ public function getContainer() {
+ return $this->container;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\label\object\type;
+
+/**
+ * Every label object type handler has to implement this interface.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.label
+ * @subpackage system.label.object.type
+ * @category Community Framework
+ */
+interface ILabelObjectTypeHandler {
+ /**
+ * Sets object type id.
+ *
+ * @param integer $objectTypeID
+ */
+ public function setObjectTypeID($objectTypeID);
+
+ /**
+ * Returns object type id.
+ *
+ * @return integer
+ */
+ public function getObjectTypeID();
+
+ /**
+ * Returns a label object type container.
+ *
+ * @return wcf\system\label\object\type\LabelObjectTypeContainer
+ */
+ public function getContainer();
+
+ /**
+ * Performs save actions.
+ */
+ public function save();
+}
--- /dev/null
+<?php
+namespace wcf\system\label\object\type;
+
+/**
+ * Label object type.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.label
+ * @subpackage system.label.object.type
+ * @category Community Framework
+ */
+class LabelObjectType {
+ /**
+ * indentation level
+ * @var integer
+ */
+ public $depth = 0;
+
+ /**
+ * object type is a category
+ * @var boolean
+ */
+ public $isCategory = false;
+
+ /**
+ * object type label
+ * @var string
+ */
+ public $label = '';
+
+ /**
+ * object id
+ * @var integer
+ */
+ public $objectID = 0;
+
+ /**
+ * option value
+ * @var integer
+ */
+ public $optionValue = 0;
+
+ /**
+ * Creates a new LabelObjectType object.
+ *
+ * @param string $label
+ * @param integer $objectID
+ * @param integer $depth
+ * @param boolean $isCategory
+ */
+ public function __construct($label, $objectID = 0, $depth = 0, $isCategory = false) {
+ $this->depth = $depth;
+ $this->isCategory = $isCategory;
+ $this->label = $label;
+ $this->objectID = $objectID;
+ }
+
+ /**
+ * Returns the label.
+ *
+ * @return string
+ */
+ public function getLabel() {
+ return $this->label;
+ }
+
+ /**
+ * Returns the object id.
+ * @return integer
+ */
+ public function getObjectID() {
+ return $this->objectID;
+ }
+
+ /**
+ * Returns true, if object type is a category.
+ *
+ * @return boolean
+ */
+ public function isCategory() {
+ return $this->isCategory;
+ }
+
+ /**
+ * Returns indentation level.
+ *
+ * @return integer
+ */
+ public function getDepth() {
+ return $this->depth;
+ }
+
+ /**
+ * Sets option value.
+ *
+ * @param integer $optionValue
+ */
+ public function setOptionValue($optionValue) {
+ $this->optionValue = $optionValue;
+ }
+
+ /**
+ * Returns option value.
+ *
+ * @return integer
+ */
+ public function getOptionValue() {
+ return $this->optionValue;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\label\object\type;
+
+/**
+ * Label object type container.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.label
+ * @subpackage system.label.object.type
+ * @category Community Framework
+ */
+class LabelObjectTypeContainer implements \Countable, \Iterator {
+ /**
+ * true if container represents only a boolean option
+ * @var boolean
+ */
+ public $isBooleanOption = false;
+
+ /**
+ * list of object types
+ * @var array<wcf\system\label\object\type>
+ */
+ public $objectTypes = array();
+
+ /**
+ * object type id
+ * @var integer
+ */
+ public $objectTypeID = 0;
+
+ /**
+ * option value if container is a boolean option itself
+ * @var boolean
+ */
+ public $optionValue = false;
+
+ /**
+ * iterator position
+ * @var integer
+ */
+ private $position = 0;
+
+ /**
+ * Creates a new LabelObjectTypeContainer object.
+ *
+ * @param integer $objectTypeID
+ * @param boolean $isBooleanOption
+ * @param boolean $optionValue
+ */
+ public function __construct($objectTypeID, $isBooleanOption = false, $optionValue = false) {
+ $this->objectTypeID = $objectTypeID;
+ $this->isBooleanOption = $isBooleanOption;
+ $this->optionValue = $optionValue;
+ }
+
+ /**
+ * Adds a label object type.
+ *
+ * @param wcf\system\label\object\type\LabelObjectType $objectType
+ */
+ public function add(LabelObjectType $objectType) {
+ $this->objectTypes[] = $objectType;
+ }
+
+ /**
+ * Returns the object type id.
+ *
+ * @return integer
+ */
+ public function getObjectTypeID() {
+ return $this->objectTypeID;
+ }
+
+ /**
+ * Returns true, if container represents only a boolean option.
+ *
+ * @return boolean
+ */
+ public function isBooleanOption() {
+ return $this->isBooleanOption;
+ }
+
+ /**
+ * Returns option value.
+ *
+ * @return boolean
+ */
+ public function getOptionValue() {
+ return $this->optionValue;
+ }
+
+ /**
+ * @see \Iterator::current()
+ */
+ public function current() {
+ return $this->objectTypes[$this->position];
+ }
+
+ /**
+ * @see \Iterator::key()
+ */
+ public function key() {
+ return $this->position;
+ }
+
+ /**
+ * @see \Iterator::next()
+ */
+ public function next() {
+ $this->position++;
+ }
+
+ /**
+ * @see \Iterator::rewind()
+ */
+ public function rewind() {
+ $this->position = 0;
+ }
+
+ /**
+ * @see \Iterator::valid()
+ */
+ public function valid() {
+ return isset($this->objectTypes[$this->position]);
+ }
+
+ /**
+ * @see \Countable::count()
+ */
+ public function count() {
+ return count($this->objectTypes);
+ }
+}
--- /dev/null
+/* #### Labels #### */
+/* todo: move into wcf */
+
+/* label list */
+.labelList, .labelList > li {
+ display: inline-block;
+}
+
+/* ACP label list */
+#labelList {
+ .clearfix();
+
+ li {
+ float: left;
+ margin-right: 1%;
+ width: 30%;
+
+ &.labelCustomClass {
+ position: relative;
+
+ input[type='radio'] {
+ left: 0;
+ position: absolute;
+ top: 0;
+ }
+
+ span {
+ display: block;
+ margin-left: 24px;
+ }
+ }
+ }
+}
+
+.labelChooser > .dropdownToggle > span {
+ cursor: pointer;
+}
\ No newline at end of file
<item name="wcf.acl.search.user.description"><![CDATA[Benutzername eingeben …]]></item>
</category>
+ <category name="wcf.acl.option">
+ <item name="wcf.acl.option.com.woltlab.wcf.label.canViewLabel"><![CDATA[Kann Labels sehen]]></item>
+ <item name="wcf.acl.option.com.woltlab.wcf.label.canSetLabel"><![CDATA[Kann Labels setzen]]></item>
+ </category>
+
<category name="wcf.acp.application">
<item name="wcf.acp.application.cookie"><![CDATA[Cookie-Einstellungen]]></item>
<item name="wcf.acp.application.cookie.warning"><![CDATA[Die folgenden Einstellungen stellen sicher, dass der Login gespeichert wird und ein automatischer Login möglich ist. Sie müssen diese Einstellungen anpassen, sollten sich Domain und/oder Pfad geändert haben. Sollten Sie sich bei den korrekten Werten nicht sicher sein, übernehmen Sie bitte die exakten Angaben der beiden obigen Eingabefelder.]]></item>
<item name="wcf.acp.group.option.mod.profileComment.canDeleteComment"><![CDATA[Kann Pinnwand-Kommentare löschen]]></item>
<item name="wcf.acp.group.option.mod.profileComment.canModerateComment"><![CDATA[Kann Pinnwand-Kommentare moderieren]]></item>
<item name="wcf.acp.group.option.category.mod.profileComment"><![CDATA[Benutzerprofil-Pinnwand]]></item>
+ <item name="wcf.acp.group.option.admin.content.label.canManageLabel"><![CDATA[Kann Labels verwalten]]></item>
+ <item name="wcf.acp.group.option.category.admin.content.label"><![CDATA[Labels]]></item>
</category>
<category name="wcf.acp.index">
<item name="wcf.acp.index.system.mySQLVersion"><![CDATA[MySQL-Version]]></item>
</category>
+ <category name="wcf.acp.label">
+ <item name="wcf.acp.label.add"><![CDATA[Label hinzufügen]]></item>
+ <item name="wcf.acp.label.cssClassName"><![CDATA[CSS-Klassen]]></item>
+ <item name="wcf.acp.label.defaultValue"><![CDATA[Label]]></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.group.edit"><![CDATA[Labelgruppe bearbeiten]]></item>
+ <item name="wcf.acp.label.group.groupName"><![CDATA[Gruppenname]]></item>
+ <item name="wcf.acp.label.group.list"><![CDATA[Labelgruppen]]></item>
+ <item name="wcf.acp.label.group.noneAvailable"><![CDATA[Es wurde noch keine Labelgruppe hinzugefügt.]]></item>
+ <item name="wcf.acp.label.label"><![CDATA[Label]]></item>
+ <item name="wcf.acp.label.list"><![CDATA[Labels]]></item>
+ <item name="wcf.acp.label.noneAvailable"><![CDATA[Es wurde noch kein Label hinzugefügt.]]></item>
+ <item name="wcf.acp.label.group.forceSelection"><![CDATA[Label aus dieser Gruppe muss zwingend ausgewählt werden]]></item>
+ <item name="wcf.acp.label.group.delete.sure"><![CDATA[Wollen Sie die Labelgruppe „{$group->groupName}“ wirklich löschen?]]></item>
+ <item name="wcf.acp.label.group.category.connect"><![CDATA[Verfügbarkeit]]></item>
+ <item name="wcf.acp.label.delete.sure"><![CDATA[Wollen Sie das Label „{$label}“ wirklich löschen?]]></item>
+ </category>
+
<category name="wcf.acp.language">
<item name="wcf.acp.language.add"><![CDATA[Sprache hinzufügen]]></item>
<item name="wcf.acp.language.add.languageCode.error.notUnique"><![CDATA[Dieser Sprachcode wird bereits von einer anderen im System installierten Sprache verwendet.]]></item>
<item name="wcf.acp.menu.link.user.rank"><![CDATA[Benutzerränge]]></item>
<item name="wcf.acp.menu.link.user.rank.list"><![CDATA[Benutzerränge auflisten]]></item>
<item name="wcf.acp.menu.link.user.rank.add"><![CDATA[Benutzerrang hinzufügen]]></item>
+ <item name="wcf.acp.menu.link.label"><![CDATA[Labels]]></item>
+ <item name="wcf.acp.menu.link.label.add"><![CDATA[Label hinzufügen]]></item>
+ <item name="wcf.acp.menu.link.label.list"><![CDATA[Labels auflisten]]></item>
+ <item name="wcf.acp.menu.link.label.group.add"><![CDATA[Labelgruppe hinzufügen]]></item>
+ <item name="wcf.acp.menu.link.label.group.list"><![CDATA[Labelgruppen auflisten]]></item>
</category>
<category name="wcf.acp.option">
<item name="wcf.imageViewer.previous"><![CDATA[Vorheriges Bild]]></item>
</category>
+ <category name="wcf.label">
+ <item name="wcf.label.all"><![CDATA[Alle]]></item>
+ <item name="wcf.label.label"><![CDATA[Label]]></item>
+ <item name="wcf.label.labels"><![CDATA[Labels]]></item>
+ <item name="wcf.label.none"><![CDATA[Keine Auswahl]]></item>
+ <item name="wcf.label.withoutSelection"><![CDATA[Ohne Label]]></item>
+ </category>
+
<category name="wcf.like">
<item name="wcf.like.cumulativeLikes"><![CDATA[Likes]]></item>
<item name="wcf.like.tooltip"><![CDATA[{if $likes}{#$likes} Like{if $likes != 1}s{/if}{if $dislikes}, {/if}{/if}{if $dislikes}{#$dislikes} Dislike{if $dislikes != 1}s{/if}{/if}]]></item>
<item name="wcf.acl.search.user.description"><![CDATA[Enter username …]]></item>
</category>
+ <category name="wcf.acl.option">
+ <item name="wcf.acl.option.com.woltlab.wcf.label.canViewLabel"><![CDATA[Can view labels]]></item>
+ <item name="wcf.acl.option.com.woltlab.wcf.label.canSetLabel"><![CDATA[Can set labels]]></item>
+ </category>
+
<category name="wcf.acp.application">
<item name="wcf.acp.application.cookie"><![CDATA[Cookie Settings]]></item>
<item name="wcf.acp.application.cookie.warning"><![CDATA[The settings below are used to establish a persistent login for your website; Keep in mind to update these values whenever the domain name or path changes. If you are unsure which values you need to provide, you can safely copy both values from above.]]></item>
<item name="wcf.acp.group.option.mod.profileComment.canDeleteComment"><![CDATA[Can delete comments]]></item>
<item name="wcf.acp.group.option.mod.profileComment.canModerateComment"><![CDATA[Can moderate comments]]></item>
<item name="wcf.acp.group.option.category.mod.profileComment"><![CDATA[User Profile Wall]]></item>
+ <item name="wcf.acp.group.option.admin.content.label.canManageLabel"><![CDATA[Can manage labels]]></item>
+ <item name="wcf.acp.group.option.category.admin.content.label"><![CDATA[Labels]]></item>
</category>
<category name="wcf.acp.index">
<item name="wcf.acp.index.system.mySQLVersion"><![CDATA[MySQL Version]]></item>
</category>
+ <category name="wcf.acp.label">
+ <item name="wcf.acp.label.add"><![CDATA[Add Label]]></item>
+ <item name="wcf.acp.label.cssClassName"><![CDATA[CSS Class Name]]></item>
+ <item name="wcf.acp.label.defaultValue"><![CDATA[Label]]></item>
+ <item name="wcf.acp.label.edit"><![CDATA[Edit Label]]></item>
+ <item name="wcf.acp.label.error.noGroups"><![CDATA[Please <a href="{link controller='LabelGroupAdd'}{/link}">add a label group</a> prior to creating labels.]]></item>
+ <item name="wcf.acp.label.group"><![CDATA[Label Group]]></item>
+ <item name="wcf.acp.label.group.add"><![CDATA[Add Label Group]]></item>
+ <item name="wcf.acp.label.group.edit"><![CDATA[Edit Label Group]]></item>
+ <item name="wcf.acp.label.group.groupName"><![CDATA[Title]]></item>
+ <item name="wcf.acp.label.group.list"><![CDATA[Label Groups]]></item>
+ <item name="wcf.acp.label.group.noneAvailable"><![CDATA[There are no label groups yet.]]></item>
+ <item name="wcf.acp.label.label"><![CDATA[Label]]></item>
+ <item name="wcf.acp.label.list"><![CDATA[Labels]]></item>
+ <item name="wcf.acp.label.noneAvailable"><![CDATA[There are no labels yet.]]></item>
+ <item name="wcf.acp.label.group.forceSelection"><![CDATA[Force selection of a label]]></item>
+ <item name="wcf.acp.label.group.delete.sure"><![CDATA[Do you really want to delete the label group “{$group->groupName}”?]]></item>
+ <item name="wcf.acp.label.group.category.connect"><![CDATA[Availability]]></item>
+ <item name="wcf.acp.label.delete.sure"><![CDATA[Do you really want to delete the group “{$label}”?]]></item>
+ </category>
+
<category name="wcf.acp.language">
<item name="wcf.acp.language.add"><![CDATA[Add Language]]></item>
<item name="wcf.acp.language.add.languageCode.error.notUnique"><![CDATA[Language code is already in use by an installed language.]]></item>
<item name="wcf.acp.menu.link.user.rank"><![CDATA[User Ranks]]></item>
<item name="wcf.acp.menu.link.user.rank.list"><![CDATA[List User Ranks]]></item>
<item name="wcf.acp.menu.link.user.rank.add"><![CDATA[Add User Rank]]></item>
+ <item name="wcf.acp.menu.link.label"><![CDATA[Labels]]></item>
+ <item name="wcf.acp.menu.link.label.add"><![CDATA[Add Label]]></item>
+ <item name="wcf.acp.menu.link.label.list"><![CDATA[List Labels]]></item>
+ <item name="wcf.acp.menu.link.label.group.add"><![CDATA[Add Label Group]]></item>
+ <item name="wcf.acp.menu.link.label.group.list"><![CDATA[List Label Groups]]></item>
</category>
<category name="wcf.acp.option">
<item name="wcf.imageViewer.previous"><![CDATA[Previous Image]]></item>
</category>
+ <category name="wcf.label">
+ <item name="wcf.label.all"><![CDATA[All]]></item>
+ <item name="wcf.label.label"><![CDATA[Label]]></item>
+ <item name="wcf.label.labels"><![CDATA[Labels]]></item>
+ <item name="wcf.label.none"><![CDATA[No Selection]]></item>
+ <item name="wcf.label.withoutSelection"><![CDATA[Without Label]]></item>
+ </category>
+
<category name="wcf.like">
<item name="wcf.like.cumulativeLikes"><![CDATA[Likes]]></item>
<item name="wcf.like.tooltip"><![CDATA[{if $likes}{#$likes} Like{if $likes != 1}s{/if}{if $dislikes}, {/if}{/if}{if $dislikes}{#$dislikes} Dislike{if $dislikes != 1}s{/if}{/if}]]></item>
UNIQUE KEY packageID (packageID, environment, eventClassName, eventName, listenerClassName)
);
+DROP TABLE IF EXISTS wcf1_label;
+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
+);
+
+DROP TABLE IF EXISTS wcf1_label_group;
+CREATE TABLE wcf1_label_group (
+ groupID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ groupName VARCHAR(80) NOT NULL,
+ forceSelection TINYINT(1) NOT NULL DEFAULT 0,
+);
+
+DROP TABLE IF EXISTS wcf1_label_group_to_object;
+CREATE TABLE wcf1_label_group_to_object (
+ groupID INT(10) NOT NULL,
+ objectTypeID INT(10) NOT NULL,
+ objectID INT(10) NULL
+);
+
+DROP TABLE IF EXISTS wcf1_label_object;
+CREATE TABLE wcf1_label_object (
+ labelID INT(10) NOT NULL,
+ objectTypeID INT(10) NOT NULL,
+ objectID INT(10) NOT NULL,
+
+ KEY (objectTypeID, labelID),
+ KEY (objectTypeID, objectID)
+);
+
DROP TABLE IF EXISTS wcf1_language;
CREATE TABLE wcf1_language (
languageID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
ALTER TABLE wcf1_comment_response ADD FOREIGN KEY (commentID) REFERENCES wcf1_comment (commentID) ON DELETE CASCADE;
ALTER TABLE wcf1_comment_response ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE SET NULL;
+ALTER TABLE wcf1_label ADD FOREIGN KEY (groupID) REFERENCES wcf1_label_group (groupID) ON DELETE CASCADE;
+
+ALTER TABLE wcf1_label_group_to_object ADD FOREIGN KEY (groupID) REFERENCES wcf1_label_group (groupID) ON DELETE CASCADE;
+ALTER TABLE wcf1_label_group_to_object ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
+
+ALTER TABLE wcf1_label_object ADD FOREIGN KEY (labelID) REFERENCES wcf1_label (labelID) ON DELETE CASCADE;
+ALTER TABLE wcf1_label_object ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
+
/* default inserts */
-- default user groups