Merged com.woltlab.wcf.label into WCF
authorMarcel Werk <burntime@woltlab.com>
Mon, 20 May 2013 22:44:50 +0000 (00:44 +0200)
committerMarcel Werk <burntime@woltlab.com>
Mon, 20 May 2013 22:44:50 +0000 (00:44 +0200)
39 files changed:
com.woltlab.wcf/aclOption.xml [new file with mode: 0644]
com.woltlab.wcf/acpMenu.xml
com.woltlab.wcf/objectType.xml
com.woltlab.wcf/objectTypeDefinition.xml
com.woltlab.wcf/package.xml
com.woltlab.wcf/userGroupOption.xml
wcfsetup/install/files/acp/templates/labelAdd.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/labelGroupAdd.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/labelGroupList.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/labelList.tpl [new file with mode: 0644]
wcfsetup/install/files/js/WCF.Label.js [new file with mode: 0644]
wcfsetup/install/files/js/WCF.Label.min.js [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/LabelAddForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/LabelEditForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/LabelGroupAddForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/LabelGroupEditForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/page/LabelGroupListPage.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/page/LabelListPage.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/label/Label.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/label/LabelAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/label/LabelEditor.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/label/LabelList.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/label/group/LabelGroup.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/label/group/LabelGroupAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/label/group/LabelGroupEditor.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/label/group/LabelGroupList.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/label/group/ViewableLabelGroup.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/cache/builder/LabelCacheBuilder.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/label/LabelHandler.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/label/object/AbstractLabelObjectHandler.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/label/object/ILabelObjectHandler.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/label/object/type/AbstractLabelObjectTypeHandler.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/label/object/type/ILabelObjectTypeHandler.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/label/object/type/LabelObjectType.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/label/object/type/LabelObjectTypeContainer.class.php [new file with mode: 0644]
wcfsetup/install/files/style/label.less [new file with mode: 0644]
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml
wcfsetup/setup/db/install.sql

diff --git a/com.woltlab.wcf/aclOption.xml b/com.woltlab.wcf/aclOption.xml
new file mode 100644 (file)
index 0000000..a608f24
--- /dev/null
@@ -0,0 +1,13 @@
+<?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
index e20f71dfd4fc89e0a7d31a7c94e8406ac6c55001..2f042a83f96487781fcd87fa39b349788a44a8be 100644 (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>
index 785418ca827a94c0677ba99095565a4b3366fc3a..7d9b6e13304872ec93f25ce4ec6c8675042afda6 100644 (file)
                        <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
index f836db66731d4ef9b6bdda81d0b7ac26a3f5aa62..5498a02015769386769b2e7458fb991efa9611d9 100644 (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>
index e8f0bca26d92c68164d8de9c6d66d6372382a96e..90a5376580bf8d25eddf1182c9b9508a3dcad115 100644 (file)
@@ -38,6 +38,7 @@
                <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">
index dc89504aa2b1ce49a7a56b477ba89a044ce7363b..9fb1123cfbfc868639c1cd5d8a58889978b1ef18 100644 (file)
@@ -92,6 +92,9 @@
                        <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>
@@ -535,6 +538,13 @@ png]]></defaultvalue>
                                <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>
diff --git a/wcfsetup/install/files/acp/templates/labelAdd.tpl b/wcfsetup/install/files/acp/templates/labelAdd.tpl
new file mode 100644 (file)
index 0000000..5929108
--- /dev/null
@@ -0,0 +1,122 @@
+{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'}
diff --git a/wcfsetup/install/files/acp/templates/labelGroupAdd.tpl b/wcfsetup/install/files/acp/templates/labelGroupAdd.tpl
new file mode 100644 (file)
index 0000000..2e86afa
--- /dev/null
@@ -0,0 +1,117 @@
+{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'}
diff --git a/wcfsetup/install/files/acp/templates/labelGroupList.tpl b/wcfsetup/install/files/acp/templates/labelGroupList.tpl
new file mode 100644 (file)
index 0000000..132def0
--- /dev/null
@@ -0,0 +1,93 @@
+{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'}
diff --git a/wcfsetup/install/files/acp/templates/labelList.tpl b/wcfsetup/install/files/acp/templates/labelList.tpl
new file mode 100644 (file)
index 0000000..3663f89
--- /dev/null
@@ -0,0 +1,95 @@
+{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'}
diff --git a/wcfsetup/install/files/js/WCF.Label.js b/wcfsetup/install/files/js/WCF.Label.js
new file mode 100644 (file)
index 0000000..23d3eb3
--- /dev/null
@@ -0,0 +1,259 @@
+/**
+ * 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
diff --git a/wcfsetup/install/files/js/WCF.Label.min.js b/wcfsetup/install/files/js/WCF.Label.min.js
new file mode 100644 (file)
index 0000000..c9bab18
--- /dev/null
@@ -0,0 +1 @@
+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
diff --git a/wcfsetup/install/files/lib/acp/form/LabelAddForm.class.php b/wcfsetup/install/files/lib/acp/form/LabelAddForm.class.php
new file mode 100644 (file)
index 0000000..feee063
--- /dev/null
@@ -0,0 +1,213 @@
+<?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
+               ));
+       }
+}
diff --git a/wcfsetup/install/files/lib/acp/form/LabelEditForm.class.php b/wcfsetup/install/files/lib/acp/form/LabelEditForm.class.php
new file mode 100644 (file)
index 0000000..9509def
--- /dev/null
@@ -0,0 +1,124 @@
+<?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'
+               ));
+       }
+}
diff --git a/wcfsetup/install/files/lib/acp/form/LabelGroupAddForm.class.php b/wcfsetup/install/files/lib/acp/form/LabelGroupAddForm.class.php
new file mode 100644 (file)
index 0000000..3f615f7
--- /dev/null
@@ -0,0 +1,250 @@
+<?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);
+                                       }
+                               }
+                       }
+               }
+       }
+}
diff --git a/wcfsetup/install/files/lib/acp/form/LabelGroupEditForm.class.php b/wcfsetup/install/files/lib/acp/form/LabelGroupEditForm.class.php
new file mode 100644 (file)
index 0000000..96f95a1
--- /dev/null
@@ -0,0 +1,133 @@
+<?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);
+       }
+}
diff --git a/wcfsetup/install/files/lib/acp/page/LabelGroupListPage.class.php b/wcfsetup/install/files/lib/acp/page/LabelGroupListPage.class.php
new file mode 100644 (file)
index 0000000..ae7e2c0
--- /dev/null
@@ -0,0 +1,40 @@
+<?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';
+}
diff --git a/wcfsetup/install/files/lib/acp/page/LabelListPage.class.php b/wcfsetup/install/files/lib/acp/page/LabelListPage.class.php
new file mode 100644 (file)
index 0000000..c77ebea
--- /dev/null
@@ -0,0 +1,50 @@
+<?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)";
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/label/Label.class.php b/wcfsetup/install/files/lib/data/label/Label.class.php
new file mode 100644 (file)
index 0000000..c723ec3
--- /dev/null
@@ -0,0 +1,70 @@
+<?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);
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/label/LabelAction.class.php b/wcfsetup/install/files/lib/data/label/LabelAction.class.php
new file mode 100644 (file)
index 0000000..97e3bf0
--- /dev/null
@@ -0,0 +1,74 @@
+<?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();
+                       }
+               }
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/label/LabelEditor.class.php b/wcfsetup/install/files/lib/data/label/LabelEditor.class.php
new file mode 100644 (file)
index 0000000..d2c4262
--- /dev/null
@@ -0,0 +1,29 @@
+<?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();
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/label/LabelList.class.php b/wcfsetup/install/files/lib/data/label/LabelList.class.php
new file mode 100644 (file)
index 0000000..de35b94
--- /dev/null
@@ -0,0 +1,20 @@
+<?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';
+}
diff --git a/wcfsetup/install/files/lib/data/label/group/LabelGroup.class.php b/wcfsetup/install/files/lib/data/label/group/LabelGroup.class.php
new file mode 100644 (file)
index 0000000..2c57c9c
--- /dev/null
@@ -0,0 +1,60 @@
+<?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;
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/label/group/LabelGroupAction.class.php b/wcfsetup/install/files/lib/data/label/group/LabelGroupAction.class.php
new file mode 100644 (file)
index 0000000..f14f1db
--- /dev/null
@@ -0,0 +1,35 @@
+<?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');
+}
diff --git a/wcfsetup/install/files/lib/data/label/group/LabelGroupEditor.class.php b/wcfsetup/install/files/lib/data/label/group/LabelGroupEditor.class.php
new file mode 100644 (file)
index 0000000..478cf60
--- /dev/null
@@ -0,0 +1,43 @@
+<?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();
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/label/group/LabelGroupList.class.php b/wcfsetup/install/files/lib/data/label/group/LabelGroupList.class.php
new file mode 100644 (file)
index 0000000..a2ecb1d
--- /dev/null
@@ -0,0 +1,20 @@
+<?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';
+}
diff --git a/wcfsetup/install/files/lib/data/label/group/ViewableLabelGroup.class.php b/wcfsetup/install/files/lib/data/label/group/ViewableLabelGroup.class.php
new file mode 100644 (file)
index 0000000..9df525f
--- /dev/null
@@ -0,0 +1,232 @@
+<?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;
+               }
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/cache/builder/LabelCacheBuilder.class.php b/wcfsetup/install/files/lib/system/cache/builder/LabelCacheBuilder.class.php
new file mode 100644 (file)
index 0000000..dd758cd
--- /dev/null
@@ -0,0 +1,70 @@
+<?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;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/label/LabelHandler.class.php b/wcfsetup/install/files/lib/system/label/LabelHandler.class.php
new file mode 100644 (file)
index 0000000..f014d1f
--- /dev/null
@@ -0,0 +1,333 @@
+<?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;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/label/object/AbstractLabelObjectHandler.class.php b/wcfsetup/install/files/lib/system/label/object/AbstractLabelObjectHandler.class.php
new file mode 100644 (file)
index 0000000..1c7e5dc
--- /dev/null
@@ -0,0 +1,137 @@
+<?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);
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/label/object/ILabelObjectHandler.class.php b/wcfsetup/install/files/lib/system/label/object/ILabelObjectHandler.class.php
new file mode 100644 (file)
index 0000000..a9dd9b2
--- /dev/null
@@ -0,0 +1,67 @@
+<?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);
+}
diff --git a/wcfsetup/install/files/lib/system/label/object/type/AbstractLabelObjectTypeHandler.class.php b/wcfsetup/install/files/lib/system/label/object/type/AbstractLabelObjectTypeHandler.class.php
new file mode 100644 (file)
index 0000000..7fae261
--- /dev/null
@@ -0,0 +1,48 @@
+<?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;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/label/object/type/ILabelObjectTypeHandler.class.php b/wcfsetup/install/files/lib/system/label/object/type/ILabelObjectTypeHandler.class.php
new file mode 100644 (file)
index 0000000..3557428
--- /dev/null
@@ -0,0 +1,40 @@
+<?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();
+}
diff --git a/wcfsetup/install/files/lib/system/label/object/type/LabelObjectType.class.php b/wcfsetup/install/files/lib/system/label/object/type/LabelObjectType.class.php
new file mode 100644 (file)
index 0000000..43d60c2
--- /dev/null
@@ -0,0 +1,112 @@
+<?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;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/label/object/type/LabelObjectTypeContainer.class.php b/wcfsetup/install/files/lib/system/label/object/type/LabelObjectTypeContainer.class.php
new file mode 100644 (file)
index 0000000..32061d4
--- /dev/null
@@ -0,0 +1,135 @@
+<?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);
+       }
+}
diff --git a/wcfsetup/install/files/style/label.less b/wcfsetup/install/files/style/label.less
new file mode 100644 (file)
index 0000000..576a4b1
--- /dev/null
@@ -0,0 +1,37 @@
+/* #### 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
index 22faf708bde5dd5562f84c53d67311901922ab42..5826fad39403a14a880b06c976ea115b23314e0d 100644 (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">
@@ -1519,6 +1552,14 @@ Erlaubte Dateiendungen: {', '|implode:$attachmentHandler->getAllowedExtensions()
                <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>
index 25b234c65b51f50cc84d77d24c75eb4ede3eee9b..193ba8c37ce03e52a95607db5112db11eccfae35 100644 (file)
                <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>
@@ -303,6 +308,8 @@ Examples for medium ID detection:
                <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">
@@ -325,6 +332,27 @@ Examples for medium ID detection:
                <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>
@@ -451,6 +479,11 @@ Examples for medium ID detection:
                <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">
@@ -1517,6 +1550,14 @@ Allowed extensions: {', '|implode:$attachmentHandler->getAllowedExtensions()}]]>
                <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>
index 257c15ab9e4083f4b8c40b339f0196575de9a025..038e6d2e95649e473f56b1e5dbd2105791fbf551 100644 (file)
@@ -326,6 +326,38 @@ CREATE TABLE wcf1_event_listener (
        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,
@@ -1384,6 +1416,14 @@ ALTER TABLE wcf1_comment ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID)
 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