<?xml version="1.0" encoding="UTF-8"?>
<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/maelstrom/objectTypeDefinition.xsd">
<import>
+ <definition>
+ <name>com.woltlab.wcf.acl</name>
+ </definition>
+
<definition>
<name>com.woltlab.wcf.collapsibleContent</name>
</definition>
<?xml version="1.0" encoding="UTF-8"?>
<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/maelstrom/packageInstallationPlugin.xsd">
<import>
+ <pip name="aclOption">wcf\system\package\plugin\ACLOptionPackageInstallationPlugin</pip>
<pip name="acpMenu">wcf\system\package\plugin\ACPMenuPackageInstallationPlugin</pip>
<pip name="acpTemplate">wcf\system\package\plugin\ACPTemplatePackageInstallationPlugin</pip>
<pip name="clipboardAction">wcf\system\package\plugin\ClipboardActionPackageInstallationPlugin</pip>
--- /dev/null
+<script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ {if $aclValues[$objectTypeID]|isset}
+ var initialPermissions = {
+ returnValues: {
+ options: {
+ {implode from=$aclValues[$objectTypeID][options] key='__optionID' item='__optionData'}
+ {@$__optionID}: {
+ categoryName: '{@$__optionData[categoryName]|encodeJS}',
+ label: '{@$__optionData[label]|encodeJS}',
+ optionName: '{@$__optionData[optionName]|encodeJS}'
+ }
+ {/implode}
+ },
+ categories: {
+ {implode from=$aclValues[$objectTypeID][categories] key='__category' item='__categoryName'}
+ '{@$__category|encodeJS}': '{@$__categoryName|encodeJS}'
+ {/implode}
+ },
+ user: {
+ {if $aclValues[$objectTypeID][user]|isset}
+ option: {
+ {implode from=$aclValues[$objectTypeID][user][option] key='__userID' item='__optionData'}
+ {@$__userID}: {
+ {implode from=$__optionData key='__optionID' item='__optionValue'}
+ {@$__optionID}: {@$__optionValue}
+ {/implode}
+ }
+ {/implode}
+ },
+ label: {
+ {implode from=$aclValues[$objectTypeID][user][label] key='__userID' item='__label'}
+ {@$__userID}: '{@$__label|encodeJS}'
+ {/implode}
+ }
+ {/if}
+ },
+ group: {
+ {if $aclValues[$objectTypeID][group]|isset}
+ option: {
+ {implode from=$aclValues[$objectTypeID][group][option] key='__groupID' item='__optionData'}
+ {@$__groupID}: {
+ {implode from=$__optionData key='__optionID' item='__optionValue'}
+ {@$__optionID}: {@$__optionValue}
+ {/implode}
+ }
+ {/implode}
+ },
+ label: {
+ {implode from=$aclValues[$objectTypeID][group][label] key='__groupID' item='__label'}
+ {@$__groupID}: '{@$__label|encodeJS}'
+ {/implode}
+ }
+ {/if}
+ }
+ }
+ };
+ {/if}
+ new {if $aclListClassName|isset}{@$aclListClassName}{else}WCF.ACL.List{/if}($('#{@$containerID}'), {@$objectTypeID}, {if $categoryName|isset}'{@$categoryName}'{else}null{/if}, {if $objectID|isset}{@$objectID}{else}0{/if}, {if !$includeUserGroups|isset || $includeUserGroups}true{else}false{/if}{if $aclValues[$objectTypeID]|isset}, initialPermissions{/if});
+ });
+ //]]>
+</script>
\ No newline at end of file
--- /dev/null
+<script type="text/javascript" src="{@$__wcf->getPath()}js/WCF.ACL.js"></script>
+<script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ WCF.Icon.addObject({
+ 'wcf.icon.delete': '{@$__wcf->getPath()}icon/delete1.svg',
+ 'wcf.icon.user': '{@$__wcf->getPath()}icon/user1.svg',
+ 'wcf.icon.users': '{@$__wcf->getPath()}icon/users1.svg'
+ });
+
+ WCF.Language.addObject({
+ 'wcf.acl.option.deny': '{lang}wcf.acl.option.deny{/lang}',
+ 'wcf.acl.option.fullAccess': '{lang}wcf.acl.option.fullAccess{/lang}',
+ 'wcf.acl.option.grant': '{lang}wcf.acl.option.grant{/lang}',
+ 'wcf.global.button.delete': '{lang}wcf.global.button.delete{/lang}'
+ });
+ });
+ //]]>
+</script>
\ No newline at end of file
--- /dev/null
+<script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ {if $aclValues[$objectTypeID]|isset}
+ var initialPermissions = {
+ returnValues: {
+ options: {
+ {implode from=$aclValues[$objectTypeID][options] key='__optionID' item='__optionData'}
+ {@$__optionID}: {
+ categoryName: '{@$__optionData[categoryName]|encodeJS}',
+ label: '{@$__optionData[label]|encodeJS}',
+ optionName: '{@$__optionData[optionName]|encodeJS}'
+ }
+ {/implode}
+ },
+ categories: {
+ {implode from=$aclValues[$objectTypeID][categories] key='__category' item='__categoryName'}
+ '{@$__category|encodeJS}': '{@$__categoryName|encodeJS}'
+ {/implode}
+ },
+ user: {
+ {if $aclValues[$objectTypeID][user]|isset}
+ option: {
+ {implode from=$aclValues[$objectTypeID][user][option] key='__userID' item='__optionData'}
+ {@$__userID}: {
+ {implode from=$__optionData key='__optionID' item='__optionValue'}
+ {@$__optionID}: {@$__optionValue}
+ {/implode}
+ }
+ {/implode}
+ },
+ label: {
+ {implode from=$aclValues[$objectTypeID][user][label] key='__userID' item='__label'}
+ {@$__userID}: '{@$__label|encodeJS}'
+ {/implode}
+ }
+ {/if}
+ },
+ group: {
+ {if $aclValues[$objectTypeID][group]|isset}
+ option: {
+ {implode from=$aclValues[$objectTypeID][group][option] key='__groupID' item='__optionData'}
+ {@$__groupID}: {
+ {implode from=$__optionData key='__optionID' item='__optionValue'}
+ {@$__optionID}: {@$__optionValue}
+ {/implode}
+ }
+ {/implode}
+ },
+ label: {
+ {implode from=$aclValues[$objectTypeID][group][label] key='__groupID' item='__label'}
+ {@$__groupID}: '{@$__label|encodeJS}'
+ {/implode}
+ }
+ {/if}
+ }
+ }
+ };
+ {/if}
+ new {if $aclListClassName|isset}{@$aclListClassName}{else}WCF.ACL.List{/if}($('#{@$containerID}'), {@$objectTypeID}, {if $categoryName|isset}'{@$categoryName}'{else}null{/if}, {if $objectID|isset}{@$objectID}{else}0{/if}, {if !$includeUserGroups|isset || $includeUserGroups}true{else}false{/if}{if $aclValues[$objectTypeID]|isset}, initialPermissions{/if});
+ });
+ //]]>
+</script>
\ No newline at end of file
--- /dev/null
+<script type="text/javascript" src="{@$__wcf->getPath()}js/WCF.ACL.js"></script>
+<script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ WCF.Icon.addObject({
+ 'wcf.icon.delete': '{@$__wcf->getPath()}icon/delete.svg',
+ 'wcf.icon.user': '{@$__wcf->getPath()}icon/user.svg',
+ 'wcf.icon.users': '{@$__wcf->getPath()}icon/users.svg'
+ });
+
+ WCF.Language.addObject({
+ 'wcf.acl.option.deny': '{lang}wcf.acl.option.deny{/lang}',
+ 'wcf.acl.option.fullAccess': '{lang}wcf.acl.option.fullAccess{/lang}',
+ 'wcf.acl.option.grant': '{lang}wcf.acl.option.grant{/lang}',
+ 'wcf.global.button.delete': '{lang}wcf.global.button.delete{/lang}'
+ });
+ });
+ //]]>
+</script>
\ No newline at end of file
--- /dev/null
+{include file='header'}
+
+{if $aclObjectTypeID}
+ {include file='aclPermissions'}
+
+ {if !$category|isset}
+ {include file='aclPermissionJavaScript' containerID='groupPermissions' objectTypeID=$aclObjectTypeID}
+ {else}
+ {include file='aclPermissionJavaScript' containerID='groupPermissions' objectTypeID=$aclObjectTypeID objectID=$category->categoryID}
+ {/if}
+{/if}
+
+{include file='multipleLanguageInputJavascript' elementIdentifier='title' forceSelection=false}
+{if $objectType->getProcessor()->hasDescription()}
+ {include file='multipleLanguageInputJavascript' elementIdentifier='description' forceSelection=false}
+{/if}
+
+<header class="boxHeadline">
+ <hgroup>
+ <h1>{@$objectType->getProcessor()->getLanguageVariable($action)}</h1>
+ </hgroup>
+</header>
+
+{if $errorField}
+ <p class="error">{lang}wcf.global.form.error{/lang}</p>
+{/if}
+
+{if $success|isset}
+ <p class="success">{lang}wcf.global.form.{@$action}.success{/lang}</p>
+{/if}
+
+{hascontent}
+ <div class="contentNavigation">
+ <nav>
+ <ul>
+ {content}
+ {if $objectType->getProcessor()->canDeleteCategory() || $objectType->getProcessor()->canEditCategory()}
+ <li><a href="{link controller=$listController}{/link}" title="{$objectType->getProcessor()->getLanguageVariable('button.list')}" class="button"><img src="{@$__wcf->getPath()}icon/list.svg" alt="" class="icon24" /> <span>{@$objectType->getProcessor()->getLanguageVariable('button.list')}</span></a></li>
+ {/if}
+
+ {event name='contentNavigationButtons'}
+ {/content}
+ </ul>
+ </nav>
+ </div>
+{/hascontent}
+
+<form method="post" action="{if $action == 'add'}{link controller=$addController}{/link}{else}{link controller=$editController object=$category}{/link}{/if}">
+ <div class="container containerPadding marginTop">
+ <fieldset>
+ <legend>{lang}wcf.global.form.data{/lang}</legend>
+
+ {if $objectType->getProcessor()->getMaximumNestingLevel() && $categoryNodeList|count}
+ <dl{if $errorField == 'parentCategoryID'} class="formError"{/if}>
+ <dt><label for="parentCategoryID">{@$objectType->getProcessor()->getLanguageVariable('parentCategoryID')}</label></dt>
+ <dd>
+ <select id="parentCategoryID" name="parentCategoryID">
+ <option value="0"></option>
+ {include file='categoryOptionList' categoryID=$parentCategoryID maximumNestingLevel=$objectType->getProcessor()->getMaximumNestingLevel() - 1}
+ </select>
+ {if $errorField == 'parentCategoryID'}
+ <small class="innerError">
+ {assign var=__languageVariable value='parentCategoryID.error.'|concat:$errorType}
+ {@$objectType->getProcessor()->getLanguageVariable($__languageVariable)}
+ </small>
+ {/if}
+ {hascontent}<small>{content}{@$objectType->getProcessor()->getLanguageVariable('parentCategoryID.description', true)}{/content}</small>{/hascontent}
+ </dd>
+ </dl>
+ {/if}
+
+ <dl{if $errorField == 'title'} class="formError"{/if}>
+ <dt><label for="title">{@$objectType->getProcessor()->getLanguageVariable('title')}</label></dt>
+ <dd>
+ <input type="text" id="title" name="title" value="{$i18nPlainValues['title']}" class="long" />
+ {if $errorField == 'title'}
+ <small class="innerError">
+ {if $errorType == 'empty'}
+ {lang}wcf.global.form.error.empty{/lang}
+ {else}
+ {assign var=__languageVariable value='title.error.'|concat:$errorType}
+ {@$objectType->getProcessor()->getLanguageVariable($__languageVariable)}
+ {/if}
+ </small>
+ {/if}
+ {hascontent}<small>{content}{@$objectType->getProcessor()->getLanguageVariable('title.description', true)}{/content}</small>{/hascontent}
+ </dd>
+ </dl>
+
+ {if $objectType->getProcessor()->hasDescription()}
+ <dl{if $errorField == 'description'} class="formError"{/if}>
+ <dt><label for="description">{@$objectType->getProcessor()->getLanguageVariable('description')}</label></dt>
+ <dd>
+ <textarea cols="40" rows="10" id="description" name="description">{$i18nPlainValues['description']}</textarea>
+ {if $errorField == 'description'}
+ <small class="innerError">
+ {if $errorType == 'empty'}
+ {lang}wcf.global.form.error.empty{/lang}
+ {else}
+ {assign var=__languageVariable value='description.error.'|concat:$errorType}
+ {@$objectType->getProcessor()->getLanguageVariable($__languageVariable)}
+ {/if}
+ </small>
+ {/if}
+ {hascontent}<small>{content}{@$objectType->getProcessor()->getLanguageVariable('description.description', true)}{/content}</small>{/hascontent}
+ </dd>
+ </dl>
+ {/if}
+
+ <dl{if $errorField == 'isDisabled'} class="formError"{/if}>
+ <dt class="reversed"><label for="isDisabled">{@$objectType->getProcessor()->getLanguageVariable('isDisabled')}</label></dt>
+ <dd>
+ <input type="checkbox" id="isDisabled" name="isDisabled"{if $isDisabled} checked="checked"{/if} />
+ {hascontent}<small>{content}{@$objectType->getProcessor()->getLanguageVariable('isDisabled.description', true)}{/content}</small>{/hascontent}
+ </dd>
+ </dl>
+
+ <dl{if $errorField == 'showOrder'} class="formError"{/if}>
+ <dt><label for="showOrder">{@$objectType->getProcessor()->getLanguageVariable('showOrder')}</label></dt>
+ <dd>
+ <input type="text" id="showOrder" name="showOrder" value="{$showOrder}" class="short" />
+ {if $errorField == 'showOrder'}
+ <small class="innerError">
+ {assign var=__languageVariable value='showOrder.error.'|concat:$errorType}
+ {@$objectType->getProcessor()->getLanguageVariable($__languageVariable)}
+ </small>
+ {/if}
+ {hascontent}<small>{content}{@$objectType->getProcessor()->getLanguageVariable('showOrder.description', true)}{/content}</small>{/hascontent}
+ </dd>
+ </dl>
+
+ {event name='dataFields'}
+ </fieldset>
+
+ {if $aclObjectTypeID}
+ <fieldset>
+ <legend>{lang}wcf.acp.acl.permissions{/lang}</legend>
+
+ <dl id="groupPermissions" class="wide">
+ <dt>{lang}wcf.acp.acl.permissions{/lang}</dt>
+ <dd></dd>
+ </dl>
+
+ {event name='permissionFields'}
+ </fieldset>
+ {/if}
+
+ {event name='fieldsets'}
+ </div>
+
+ <div class="formSubmit">
+ <input type="reset" value="{lang}wcf.global.button.reset{/lang}" accesskey="r" />
+ <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" />
+ </div>
+</form>
+
+{include file='footer'}
\ No newline at end of file
--- /dev/null
+{include file='header'}
+
+{if $categoryNodeList|count}
+ <script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ {if $collapsibleObjectTypeID && $categoryNodeList|count > 1}
+ new WCF.ACP.Category.Collapsible('wcf\\data\\category\\CategoryAction', {@$collapsibleObjectTypeID});
+ {/if}
+
+ {if $objectType->getProcessor()->canDeleteCategory()}
+ new WCF.ACP.Category.Delete('wcf\\data\\category\\CategoryAction', $('.jsCategory'));
+ {/if}
+ {if $objectType->getProcessor()->canEditCategory()}
+ new WCF.Action.Toggle('wcf\\data\\category\\CategoryAction', $('.jsCategory'), '> .buttons > .jsToggleButton');
+
+ {if $categoryNodeList|count > 1}
+ var sortableNodes = $('.sortableNode');
+ sortableNodes.each(function(index, node) {
+ $(node).wcfIdentify();
+ });
+
+ new WCF.Sortable.List('categoryList', 'wcf\\data\\category\\CategoryAction', 0{if $objectType->getProcessor()->getMaximumNestingLevel() != -1}, {
+ /**
+ * Updates the sortable nodes after a sorting is started with
+ * regard to their possibility to have child the currently sorted
+ * category as a child category.
+ */
+ start: function(event, ui) {
+ var sortedListItem = $(ui.item);
+ var itemNestingLevel = sortedListItem.find('.sortableList:has(.sortableNode)').length;
+
+ sortableNodes.each(function(index, node) {
+ node = $(node);
+
+ if (node.attr('id') != sortedListItem.attr('id')) {
+ if (node.parents('.sortableList').length + itemNestingLevel >= {@$objectType->getProcessor()->getMaximumNestingLevel() + 1}) {
+ node.addClass('sortableNoNesting');
+ }
+ else if (node.hasClass('sortableNoNesting')) {
+ node.removeClass('sortableNoNesting');
+ }
+ }
+ });
+ },
+ /**
+ * Updates the sortable nodes after a sorting is completed with
+ * regard to their possibility to have child categories.
+ */
+ stop: function(event, ui) {
+ sortableNodes.each(function(index, node) {
+ node = $(node);
+
+ if (node.parents('.sortableList').length == {@$objectType->getProcessor()->getMaximumNestingLevel() + 1}) {
+ node.addClass('sortableNoNesting');
+ }
+ else if (node.hasClass('sortableNoNesting')) {
+ node.removeClass('sortableNoNesting');
+ }
+ });
+ }
+ }{/if});
+ {/if}
+ {/if}
+ });
+ //]]>
+ </script>
+{/if}
+
+<header class="box48 boxHeadline">
+ <hgroup>
+ <h1>{@$objectType->getProcessor()->getLanguageVariable('list')}</h1>
+ </hgroup>
+</header>
+
+{hascontent}
+ <div class="contentNavigation">
+ <nav>
+ <ul>
+ {content}
+ {if $objectType->getProcessor()->canAddCategory()}
+ <li><a href="{link controller=$addController}{/link}" title="{$objectType->getProcessor()->getLanguageVariable('add')}" class="button"><img src="{@$__wcf->getPath()}icon/add.svg" alt="" class="icon24" /> <span>{@$objectType->getProcessor()->getLanguageVariable('add')}</span></a></li>
+ {/if}
+
+ {event name='contentNavigationButtons'}
+ {/content}
+ </ul>
+ </nav>
+ </div>
+{/hascontent}
+
+{if $categoryNodeList|count}
+ <section id="categoryList" class="container containerPadding marginTop{if $objectType->getProcessor()->canEditCategory() && $categoryNodeList|count > 1} sortableListContainer{/if}">
+ <ol class="categoryList sortableList" data-object-id="0">
+ {assign var=oldDepth value=0}
+ {foreach from=$categoryNodeList item=category}
+ {section name=i loop=$oldDepth-$categoryNodeList->getDepth()}</ol></li>{/section}
+
+ <li class="{if $objectType->getProcessor()->canEditCategory() && $categoryNodeList|count > 1}sortableNode {if $categoryNodeList->getDepth() == $objectType->getProcessor()->getMaximumNestingLevel()}sortableNoNesting {/if}{/if}jsCategory" data-object-id="{@$category->categoryID}"{if $collapsedCategoryIDs|is_array} data-is-open="{if $collapsedCategoryIDs[$category->categoryID]|isset}0{else}1{/if}"{/if}>
+ <span class="sortableNodeLabel">
+ <span class="buttons">
+ {if $objectType->getProcessor()->canEditCategory()}
+ <a href="{link controller=$editController id=$category->categoryID title=$category->getTitle()}{/link}"><img src="{@$__wcf->getPath()}icon/edit.svg" alt="" title="{lang}wcf.global.button.edit{/lang}" class="icon16 jsTooltip" /></a>
+ {else}
+ <img src="{@$__wcf->getPath()}icon/edit.svg" alt="" title="{lang}wcf.global.button.edit{/lang}" class="icon16 disabled" />
+ {/if}
+
+ {if $objectType->getProcessor()->canDeleteCategory()}
+ <img src="{@$__wcf->getPath()}icon/delete.svg" alt="" title="{lang}wcf.global.button.delete{/lang}" class="icon16 jsDeleteButton jsTooltip" data-object-id="{@$category->categoryID}" data-confirm-message="{@$objectType->getProcessor()->getLanguageVariable('delete.sure')}" />
+ {else}
+ <img src="{@$__wcf->getPath()}icon/delete.svg" alt="" title="{lang}wcf.global.button.delete{/lang}" class="icon16 disabled" />
+ {/if}
+
+ {if $objectType->getProcessor()->canEditCategory()}
+ {* todo: toggle icons aren't clickable *}
+ <img src="{@$__wcf->getPath()}icon/{if !$category->isDisabled}enabled{else}disabled{/if}.svg" alt="" title="{lang}wcf.global.button.{if !$category->isDisabled}disable{else}enable{/if}{/lang}" class="icon16 jsToggleButton jsTooltip" data-object-id="{@$category->categoryID}" />
+ {else}
+ <img src="{@$__wcf->getPath()}icon/{if !$category->isDisabled}enabled{else}disabled{/if}.svg" alt="" title="{lang}wcf.global.button.{if !$category->isDisabled}enable{else}disable{/if}{/lang}" class="icon16 disabled" />
+ {/if}
+
+ {event name='buttons'}
+ </span>
+
+ <span class="title">
+ {$category->getTitle()}
+ </span>
+ </span>
+
+ <ol class="categoryList sortableList" data-object-id="{@$category->categoryID}">
+ {if !$categoryNodeList->current()->hasChildren()}
+ </ol></li>
+ {/if}
+ {assign var=oldDepth value=$categoryNodeList->getDepth()}
+ {/foreach}
+ {section name=i loop=$oldDepth}</ol></li>{/section}
+ </ol>
+
+ {if $objectType->getProcessor()->canEditCategory() && $categoryNodeList|count > 1}
+ <div class="formSubmit">
+ <button class="button default" data-type="submit">{lang}wcf.global.button.save{/lang}</button>
+ </div>
+ {/if}
+ </section>
+
+ {hascontent}
+ <div class="contentNavigation">
+ <nav>
+ <ul>
+ {content}
+ {if $objectType->getProcessor()->canAddCategory()}
+ <li><a href="{link controller=$addController}{/link}" title="{$objectType->getProcessor()->getLanguageVariable('add')}" class="button"><img src="{@$__wcf->getPath()}icon/add.svg" alt="" class="icon24" /> <span>{@$objectType->getProcessor()->getLanguageVariable('add')}</span></a></li>
+ {/if}
+
+ {event name='contentNavigationButtons'}
+ {/content}
+ </ul>
+ </nav>
+ </div>
+ {/hascontent}
+{else}
+ <p class="info">{@$objectType->getProcessor()->getLanguageVariable('noneAvailable')}</p>
+{/if}
+
+{include file='footer'}
\ No newline at end of file
--- /dev/null
+/**
+ * Namespace for ACL
+ */
+WCF.ACL = {};
+
+/**
+ * ACL support for WCF
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2011 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ */
+WCF.ACL.List = Class.extend({
+ /**
+ * name of the category the acl options belong to
+ * @var string
+ */
+ _categoryName: '',
+
+ /**
+ * ACL container
+ * @var jQuery
+ */
+ _container: null,
+
+ /**
+ * list of ACL container elements
+ * @var object
+ */
+ _containerElements: { },
+
+ /**
+ * object id
+ * @var integer
+ */
+ _objectID: 0,
+
+ /**
+ * object type id
+ * @var integer
+ */
+ _objectTypeID: null,
+
+ /**
+ * list of available ACL options
+ * @var object
+ */
+ _options: { },
+
+ /**
+ * action proxy
+ * @var WCF.Action.Proxy
+ */
+ _proxy: null,
+
+ /**
+ * user search handler
+ * @var WCF.Search.User
+ */
+ _search: null,
+
+ /**
+ * list of ACL settings
+ * @var object
+ */
+ _values: {
+ group: { },
+ user: { }
+ },
+
+ /**
+ * Initializes the ACL configuration.
+ *
+ * @param string containerSelector
+ * @param integer objectTypeID
+ * @param string categoryName
+ * @param integer objectID
+ * @param boolean includeUserGroups
+ */
+ init: function(containerSelector, objectTypeID, categoryName, objectID, includeUserGroups, initialPermissions) {
+ this._objectID = objectID;
+ this._objectTypeID = objectTypeID;
+ this._categoryName = categoryName;
+ if (includeUserGroups === undefined) {
+ includeUserGroups = true;
+ }
+
+ this._proxy = new WCF.Action.Proxy({
+ showLoadingOverlay: false,
+ success: $.proxy(this._success, this)
+ });
+
+ // bind hidden container
+ this._container = $(containerSelector).hide().addClass('aclContainer');
+
+ // insert container elements
+ var $elementContainer = this._container.children('dd');
+ var $aclList = $('<ul class="aclList container" />').appendTo($elementContainer);
+ var $searchInput = $('<input type="text" class="long" />').appendTo($elementContainer);
+ var $permissionList = $('<ul class="aclPermissionList container" />').hide().appendTo($elementContainer);
+
+ // set elements
+ this._containerElements = {
+ aclList: $aclList,
+ denyAll: null,
+ grantAll: null,
+ permissionList: $permissionList,
+ searchInput: $searchInput
+ };
+
+ // prepare search input
+ this._search = new WCF.Search.User($searchInput, $.proxy(this.addObject, this), includeUserGroups);
+
+ // bind event listener for submit
+ var $form = this._container.parents('form:eq(0)');
+ $form.submit($.proxy(this.submit, this));
+
+ // reset ACL on click
+ var $resetButton = $form.find('input[type=reset]:eq(0)');
+ if ($resetButton.length) {
+ $resetButton.click($.proxy(this._reset, this));
+ }
+
+ if (initialPermissions) {
+ this._success(initialPermissions);
+ }
+ else {
+ this._loadACL();
+ }
+ },
+
+ /**
+ * Restores the original ACL state.
+ */
+ _reset: function() {
+ // reset stored values
+ this._values = {
+ group: { },
+ user: { }
+ };
+
+ // remove entries
+ this._containerElements.aclList.empty();
+ this._containerElements.searchInput.val('');
+
+ // deselect all input elements
+ this._containerElements.permissionList.hide().find('input[type=checkbox]').removeAttr('checked');
+ },
+
+ /**
+ * Loads current ACL configuration.
+ */
+ _loadACL: function() {
+ this._proxy.setOption('data', {
+ actionName: 'loadAll',
+ className: 'wcf\\data\\acl\\option\\ACLOptionAction',
+ parameters: {
+ data: {
+ categoryName: this._categoryName,
+ objectID: this._objectID,
+ objectTypeID: this._objectTypeID
+ }
+ }
+ });
+ this._proxy.sendRequest();
+ },
+
+ /**
+ * Adds a new object to acl list.
+ *
+ * @param object data
+ */
+ addObject: function(data) {
+ var $listItem = $('<li><img src="' + WCF.Icon.get('wcf.icon.user' + ((data.type == 'group') ? 's' : '')) + '" alt="" class="icon16" /> <span>' + data.label + '</span></li>').appendTo(this._containerElements.aclList);
+ $listItem.data('objectID', data.objectID).data('type', data.type).click($.proxy(this._click, this));
+ $('<img src="' + WCF.Icon.get('wcf.icon.delete') + '" alt="" title="' + WCF.Language.get('wcf.global.button.delete') + '" class="icon16 jsTooltip" />').click($.proxy(this._removeItem, this)).appendTo($listItem);
+
+ // toggle element
+ this._savePermissions();
+ this._containerElements.aclList.children('li').removeClass('active');
+ $listItem.addClass('active');
+
+ this._search.addExcludedSearchValue(data.label);
+
+ // uncheck all option values
+ this._containerElements.permissionList.find('input[type=checkbox]').removeAttr('checked');
+
+ // clear search input
+ this._containerElements.searchInput.val('');
+
+ // show permissions
+ this._containerElements.permissionList.show();
+ },
+
+ /**
+ * Removes an item from list.
+ *
+ * @param object event
+ */
+ _removeItem: function(event) {
+ var $listItem = $(event.currentTarget).parent();
+ var $type = $listItem.data('type');
+ var $objectID = $listItem.data('objectID');
+
+ this._search.removeExcludedSearchValue($listItem.children('span:eq(0)').text());
+ $listItem.remove();
+
+ // remove stored data
+ if (this._values[$type][$objectID]) {
+ delete this._values[$type][$objectID];
+ }
+
+ // try to select something else
+ this._selectFirstEntry();
+ },
+
+ /**
+ * Selects the first available entry.
+ */
+ _selectFirstEntry: function() {
+ var $listItem = this._containerElements.aclList.children('li:eq(0)');
+ if ($listItem.length) {
+ this._select($listItem, false);
+ }
+ else {
+ this._reset();
+ }
+ },
+
+ /**
+ * Parses current ACL configuration.
+ *
+ * @param object data
+ * @param string textStatus
+ * @param jQuery jqXHR
+ */
+ _success: function(data, textStatus, jqXHR) {
+ if (!$.getLength(data.returnValues.options)) {
+ return;
+ }
+
+ // prepare options
+ var $count = 0;
+ var $structure = { };
+ for (var $optionID in data.returnValues.options) {
+ var $option = data.returnValues.options[$optionID];
+
+ var $listItem = $('<li><span>' + $option.label + '</span></li>').data('optionID', $optionID).data('optionName', $option.optionName);
+ var $grantPermission = $('<input type="checkbox" id="grant' + $optionID + '" />').appendTo($listItem).wrap('<label for="grant' + $optionID + '" />');
+ var $denyPermission = $('<input type="checkbox" id="deny' + $optionID + '" />').appendTo($listItem).wrap('<label for="deny' + $optionID + '" />');
+
+ $grantPermission.data('type', 'grant').data('optionID', $optionID).change($.proxy(this._change, this));
+ $denyPermission.data('type', 'deny').data('optionID', $optionID).change($.proxy(this._change, this));
+
+ if (!$structure[$option.categoryName]) {
+ $structure[$option.categoryName] = [ ];
+ }
+
+ if ($option.categoryName === '') {
+ $listItem.appendTo(this._containerElements.permissionList);
+ }
+ else {
+ $structure[$option.categoryName].push($listItem);
+ }
+
+ $count++;
+ }
+
+ // add a "full access" permission if there are more than one option
+ if ($count > 1) {
+ var $listItem = $('<li class="aclFullAccess"><span>' + WCF.Language.get('wcf.acl.option.fullAccess') + '</span></li>').prependTo(this._containerElements.permissionList);
+ this._containerElements.grantAll = $('<input type="checkbox" id="grantAll" />').appendTo($listItem).wrap('<label for="grantAll" />');
+ this._containerElements.denyAll = $('<input type="checkbox" id="denyAll" />').appendTo($listItem).wrap('<label for="denyAll" />');
+
+ // bind events
+ this._containerElements.grantAll.data('type', 'grant').change($.proxy(this._changeAll, this));
+ this._containerElements.denyAll.data('type', 'deny').change($.proxy(this._changeAll, this));
+ }
+
+ if ($.getLength($structure)) {
+ for (var $categoryName in $structure) {
+ var $listItems = $structure[$categoryName];
+
+ if (data.returnValues.categories[$categoryName]) {
+ $('<li class="aclCategory">' + data.returnValues.categories[$categoryName] + '</li>').appendTo(this._containerElements.permissionList);
+ }
+
+ for (var $i = 0, $length = $listItems.length; $i < $length; $i++) {
+ $listItems[$i].appendTo(this._containerElements.permissionList);
+ }
+ }
+ }
+
+ // set data
+ this._parseData(data, 'group');
+ this._parseData(data, 'user');
+
+ // show container
+ this._container.show();
+
+ // pre-select an entry
+ this._selectFirstEntry();
+ },
+
+ /**
+ * Parses user and group data.
+ *
+ * @param object data
+ * @param string type
+ */
+ _parseData: function(data, type) {
+ if (!$.getLength(data.returnValues[type].option)) {
+ return;
+ }
+
+ // add list items
+ for (var $typeID in data.returnValues[type].label) {
+ this.addObject({
+ label: data.returnValues[type].label[$typeID],
+ objectID: $typeID,
+ type: type
+ });
+
+ this._search.addExcludedSearchValue(data.returnValues[type].label[$typeID]);
+ }
+
+ // add options
+ this._values[type] = data.returnValues[type].option;
+ },
+
+ /**
+ * Prepares permission list for a specific object.
+ *
+ * @param object event
+ */
+ _click: function(event) {
+ var $listItem = $(event.currentTarget);
+ if ($listItem.hasClass('active')) {
+ return;
+ }
+
+ this._select($listItem, true);
+ },
+
+ /**
+ * Selects the given item and marks it as active.
+ *
+ * @param jQuery listItem
+ * @param boolean savePermissions
+ */
+ _select: function(listItem, savePermissions) {
+ // save previous permissions
+ if (savePermissions) {
+ this._savePermissions();
+ }
+
+ // switch active item
+ this._containerElements.aclList.children('li').removeClass('active');
+ listItem.addClass('active');
+
+ // apply permissions for current item
+ this._setupPermissions(listItem.data('type'), listItem.data('objectID'));
+ },
+
+ /**
+ * Toggles between deny and grant.
+ *
+ * @param object event
+ */
+ _change: function(event) {
+ var $checkbox = $(event.currentTarget);
+ var $optionID = $checkbox.data('optionID');
+ var $type = $checkbox.data('type');
+
+ if ($checkbox.is(':checked')) {
+ if ($type === 'deny') {
+ $('#grant' + $optionID).removeAttr('checked');
+
+ if (this._containerElements.grantAll !== null) {
+ this._containerElements.grantAll.removeAttr('checked');
+ }
+ }
+ else {
+ $('#deny' + $optionID).removeAttr('checked');
+
+ if (this._containerElements.denyAll !== null) {
+ this._containerElements.denyAll.removeAttr('checked');
+ }
+ }
+ }
+ else {
+ if ($type === 'deny' && this._containerElements.denyAll !== null) {
+ this._containerElements.denyAll.removeAttr('checked');
+ }
+ else if ($type === 'grant' && this._containerElements.grantAll !== null) {
+ this._containerElements.grantAll.removeAttr('checked');
+ }
+ }
+
+ var $allChecked = true;
+ this._containerElements.permissionList.find('input[type=checkbox]').each(function(index, item) {
+ var $item = $(item);
+
+ if ($item.data('type') === $type && $item.attr('id') !== $type + 'All') {
+ if (!$item.is(':checked')) {
+ $allChecked = false;
+ return false;
+ }
+ }
+ });
+ if ($type == 'deny') {
+ if (this._containerElements.denyAll !== null) {
+ if ($allChecked) this._containerElements.denyAll.prop('checked', 'checked')
+ else this._containerElements.denyAll.removeAttr('checked');
+ }
+ }
+ else {
+ if (this._containerElements.grantAll !== null) {
+ if ($allChecked) this._containerElements.grantAll.prop('checked', 'checked')
+ else this._containerElements.grantAll.removeAttr('checked');
+ }
+ }
+ },
+
+ /**
+ * Toggles all options between deny and grant.
+ *
+ * @param object event
+ */
+ _changeAll: function(event) {
+ var $checkbox = $(event.currentTarget);
+ var $type = $checkbox.data('type');
+
+ if ($checkbox.is(':checked')) {
+ if ($type === 'deny') {
+ this._containerElements.grantAll.removeAttr('checked');
+
+ this._containerElements.permissionList.find('input[type=checkbox]').each(function(index, item) {
+ var $item = $(item);
+
+ if ($item.data('type') === 'deny' && $item.attr('id') !== 'denyAll') {
+ $item.prop('checked', 'checked').trigger('change');
+ }
+ });
+ }
+ else {
+ this._containerElements.denyAll.removeAttr('checked');
+
+ this._containerElements.permissionList.find('input[type=checkbox]').each(function(index, item) {
+ var $item = $(item);
+
+ if ($item.data('type') === 'grant' && $item.attr('id') !== 'grantAll') {
+ $item.prop('checked', 'checked').trigger('change');
+ }
+ });
+ }
+ }
+ else {
+ if ($type === 'deny') {
+ this._containerElements.grantAll.removeAttr('checked');
+
+ this._containerElements.permissionList.find('input[type=checkbox]').each(function(index, item) {
+ var $item = $(item);
+
+ if ($item.data('type') === 'deny' && $item.attr('id') !== 'denyAll') {
+ $item.removeAttr('checked').trigger('change');
+ }
+ });
+ }
+ else {
+ this._containerElements.denyAll.removeAttr('checked');
+
+ this._containerElements.permissionList.find('input[type=checkbox]').each(function(index, item) {
+ var $item = $(item);
+
+ if ($item.data('type') === 'grant' && $item.attr('id') !== 'grantAll') {
+ $item.removeAttr('checked').trigger('change');
+ }
+ });
+ }
+ }
+ },
+
+ /**
+ * Setups permission input for given object.
+ *
+ * @param string type
+ * @param integer objectID
+ */
+ _setupPermissions: function(type, objectID) {
+ // reset all checkboxes to unchecked
+ this._containerElements.permissionList.find("input[type='checkbox']").removeAttr('checked');
+
+ // use stored permissions if applicable
+ if (this._values[type] && this._values[type][objectID]) {
+ for (var $optionID in this._values[type][objectID]) {
+ if (this._values[type][objectID][$optionID] == 1) {
+ $('#grant' + $optionID).attr('checked', 'checked').trigger('change');
+ }
+ else {
+ $('#deny' + $optionID).attr('checked', 'checked').trigger('change');
+ }
+ }
+ }
+
+ // show permissions
+ this._containerElements.permissionList.show();
+ },
+
+ /**
+ * Saves currently set permissions.
+ */
+ _savePermissions: function() {
+ // get active object
+ var $activeObject = this._containerElements.aclList.find('li.active');
+ var $objectID = $activeObject.data('objectID');
+ var $type = $activeObject.data('type');
+
+ var self = this;
+ this._containerElements.permissionList.find("input[type='checkbox']").each(function(index, checkbox) {
+ var $checkbox = $(checkbox);
+ if ($checkbox.attr('id') != 'grantAll' && $checkbox.attr('id') != 'denyAll') {
+ var $optionValue = ($checkbox.data('type') === 'deny') ? 0 : 1;
+ var $optionID = $checkbox.data('optionID');
+
+ if ($checkbox.is(':checked')) {
+ if (!self._values[$type][$objectID]) {
+ self._values[$type][$objectID] = { };
+ }
+
+ // store value
+ self._values[$type][$objectID][$optionID] = $optionValue;
+
+ // reset value afterwards
+ $checkbox.removeAttr('checked');
+ }
+ else if (self._values[$type] && self._values[$type][$objectID] && self._values[$type][$objectID][$optionID] && self._values[$type][$objectID][$optionID] == $optionValue) {
+ delete self._values[$type][$objectID][$optionID];
+ }
+ }
+ });
+ },
+
+ /**
+ * Prepares ACL values on submit.
+ *
+ * @param object event
+ */
+ submit: function(event) {
+ this._savePermissions();
+
+ this._save('group');
+ this._save('user');
+ },
+
+ /**
+ * Inserts hidden form elements for each value.
+ *
+ * @param string $type
+ */
+ _save: function($type) {
+ if ($.getLength(this._values[$type])) {
+ var $form = this._container.parents('form:eq(0)');
+
+ for (var $objectID in this._values[$type]) {
+ var $object = this._values[$type][$objectID];
+
+ for (var $optionID in $object) {
+ $('<input type="hidden" name="aclValues[' + $type + '][' + $objectID + '][' + $optionID + ']" value="' + $object[$optionID] + '" />').appendTo($form);
+ }
+ }
+ }
+ }
+});
--- /dev/null
+<?php
+namespace wcf\acp\form;
+use wcf\data\category\CategoryAction;
+use wcf\data\category\CategoryEditor;
+use wcf\data\category\CategoryNodeList;
+use wcf\form\AbstractForm;
+use wcf\system\acl\ACLHandler;
+use wcf\system\category\CategoryHandler;
+use wcf\system\category\CategoryPermissionHandler;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\exception\SystemException;
+use wcf\system\exception\UserInputException;
+use wcf\system\language\I18nHandler;
+use wcf\system\WCF;
+use wcf\util\ArrayUtil;
+use wcf\util\StringUtil;
+
+/**
+ * Abstract implementation of a form to create categories.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage acp.form
+ * @category Community Framework
+ */
+abstract class AbstractCategoryAddForm extends AbstractForm {
+ /**
+ * id of the category acl object type
+ * @var integer
+ */
+ public $aclObjectTypeID = 0;
+
+ /**
+ * name of the controller used to add new categories
+ * @var string
+ */
+ public $addController = '';
+
+ /**
+ * additional category data
+ * @var array
+ */
+ public $additionalData = array();
+
+ /**
+ * list with the category nodes
+ * @var wcf\data\category\CategoryNodeList
+ */
+ public $categoryNodeList = null;
+
+ /**
+ * description of the category
+ * @var string
+ */
+ public $description = '';
+
+ /**
+ * indicates if the category is disabled
+ * @var integer
+ */
+ public $isDisabled = 0;
+
+ /**
+ * name of the controller used to edit categories
+ * @var string
+ */
+ public $editController = '';
+
+ /**
+ * name of the controller used to list the categories
+ * @var string
+ */
+ public $listController = '';
+
+ /**
+ * category object type object
+ * @var wcf\data\object\type\ObjectType
+ */
+ public $objectType = null;
+
+ /**
+ * name of the category object type
+ * @var string
+ */
+ public $objectTypeName = '';
+
+ /**
+ * id of the package the created package belongs to
+ * @var integer
+ */
+ public $packageID = 0;
+
+ /**
+ * id of the parent category id
+ * @var integer
+ */
+ public $parentCategoryID = 0;
+
+ /**
+ * category show order
+ * @var integer
+ */
+ public $showOrder = 0;
+
+ /**
+ * @see wcf\page\AbstractPage::$templateName
+ */
+ public $templateName = 'categoryAdd';
+
+ /**
+ * title of the category
+ * @var string
+ */
+ public $title = '';
+
+ /**
+ * @see wcf\page\AbstractPage::__run()
+ */
+ public function __run() {
+ $classNameParts = explode('\\', get_called_class());
+ $className = array_pop($classNameParts);
+
+ // autoset controllers
+ if (empty($this->addController)) {
+ $this->addController = StringUtil::replace(array('AddForm', 'EditForm'), 'Add', $className);
+ }
+ if (empty($this->editController)) {
+ $this->editController = StringUtil::replace(array('AddForm', 'EditForm'), 'Edit', $className);
+ }
+ if (empty($this->listController)) {
+ $this->listController = StringUtil::replace(array('AddForm', 'EditForm'), 'List', $className);
+ }
+
+ parent::__run();
+ }
+
+ /**
+ * @see wcf\page\IPage::assignVariables()
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ I18nHandler::getInstance()->assignVariables();
+ if ($this->aclObjectTypeID) {
+ ACLHandler::getInstance()->assignVariables($this->aclObjectTypeID);
+ }
+
+ WCF::getTPL()->assign(array(
+ 'aclObjectTypeID' => $this->aclObjectTypeID,
+ 'action' => 'add',
+ 'addController' => $this->addController,
+ 'categoryNodeList' => $this->categoryNodeList,
+ 'description' => $this->description,
+ 'editController' => $this->editController,
+ 'isDisabled' => $this->isDisabled,
+ 'listController' => $this->listController,
+ 'objectType' => $this->objectType,
+ 'parentCategoryID' => $this->parentCategoryID,
+ 'showOrder' => $this->showOrder,
+ 'title' => $this->title
+ ));
+ }
+
+ /**
+ * Checks if the active user has the needed permissions to add a new category.
+ */
+ protected function checkCategoryPermissions() {
+ if (!$this->objectType->getProcessor()->canAddCategory()) {
+ throw new PermissionDeniedException();
+ }
+ }
+
+ /**
+ * Reads the categories.
+ */
+ protected function readCategories() {
+ $this->categoryNodeList = new CategoryNodeList($this->objectType->objectType, 0, true);
+ }
+
+ /**
+ * @see wcf\page\IPage::readData()
+ */
+ public function readData() {
+ $this->objectType = CategoryHandler::getInstance()->getObjectTypeByName($this->objectTypeName);
+ if ($this->objectType === null) {
+ throw new SystemException("Unknown category object type with name '".$this->objectTypeName."'");
+ }
+
+ // check permissions
+ $this->checkCategoryPermissions();
+
+ // get acl object type id
+ $aclObjectTypeName = $this->objectType->getProcessor()->getObjectTypeName('com.woltlab.wcf.acl');
+ if ($aclObjectTypeName) {
+ $this->aclObjectTypeID = ACLHandler::getInstance()->getObjectTypeID($aclObjectTypeName);
+ }
+
+ // autoset package id
+ if (!$this->packageID) {
+ $this->packageID = $this->objectType->packageID;
+ }
+
+ if ($this->objectType->getProcessor()->hasDescription()) {
+ I18nHandler::getInstance()->register('description');
+ }
+ I18nHandler::getInstance()->register('title');
+
+ $this->readCategories();
+
+ parent::readData();
+ }
+
+ /**
+ * @see wcf\page\IForm::readFormParameters()
+ */
+ public function readFormParameters() {
+ parent::readFormParameters();
+
+ I18nHandler::getInstance()->readValues();
+
+ if (isset($_POST['additionalData'])) {
+ $this->additionalData = ArrayUtil::trim($_POST['additionalData']);
+ }
+ if ($this->objectType->getProcessor()->hasDescription() && isset($_POST['description'])) {
+ $this->description = StringUtil::trim($_POST['description']);
+ }
+ if (isset($_POST['isDisabled'])) {
+ $this->isDisabled = 1;
+ }
+ if (isset($_POST['parentCategoryID'])) {
+ $this->parentCategoryID = intval($_POST['parentCategoryID']);
+ }
+ if (isset($_POST['showOrder'])) {
+ $this->showOrder = intval($_POST['showOrder']);
+ }
+ if (isset($_POST['title'])) {
+ $this->title = StringUtil::trim($_POST['title']);
+ }
+ }
+
+ /**
+ * @see wcf\page\IForm::save()
+ */
+ public function save() {
+ parent::save();
+
+ $this->objectAction = new CategoryAction(array(), 'create', array(
+ 'data' => array(
+ 'additionalData' => serialize($this->additionalData),
+ 'description' => $this->description,
+ 'isDisabled' => $this->isDisabled,
+ 'objectTypeID' => $this->objectType->objectTypeID,
+ 'parentCategoryID' => $this->parentCategoryID,
+ 'showOrder' => $this->showOrder > 0 ? $this->showOrder : null,
+ 'title' => $this->title
+ )
+ ));
+ $this->objectAction->executeAction();
+ $returnValues = $this->objectAction->getReturnValues();
+
+ if (($this->objectType->getProcessor()->hasDescription() && !I18nHandler::getInstance()->isPlainValue('description')) || !I18nHandler::getInstance()->isPlainValue('title')) {
+ $categoryID = $returnValues['returnValues']->categoryID;
+
+ $updateData = array();
+ if ($this->objectType->getProcessor()->hasDescription() && !I18nHandler::getInstance()->isPlainValue('description')) {
+ $updateData['description'] = $this->objectType->getProcessor()->getI18nLangVarPrefix().'.description.category'.$categoryID;
+ I18nHandler::getInstance()->save('description', $updateData['description'], $this->objectType->getProcessor()->getDescriptionLangVarCategory(), $this->packageID);
+ }
+ if (!I18nHandler::getInstance()->isPlainValue('title')) {
+ $updateData['title'] = $this->objectType->getProcessor()->getI18nLangVarPrefix().'.title.category'.$categoryID;
+ I18nHandler::getInstance()->save('title', $updateData['title'], $this->objectType->getProcessor()->getTitleLangVarCategory(), $this->packageID);
+ }
+
+ // update description/title
+ $editor = new CategoryEditor($returnValues['returnValues']);
+ $editor->update($updateData);
+ }
+
+ // save acl
+ if ($this->aclObjectTypeID) {
+ ACLHandler::getInstance()->save($returnValues['returnValues']->categoryID, $this->aclObjectTypeID);
+ ACLHandler::getInstance()->disableAssignVariables();
+ CategoryPermissionHandler::getInstance()->resetCache();
+ }
+
+ // reload cache
+ CategoryHandler::getInstance()->reloadCache();
+
+ // reset values
+ $this->parentCategoryID = 0;
+ $this->showOrder = 0;
+
+ $this->saved();
+
+ // disable assignment of i18n values
+ I18nHandler::getInstance()->disableAssignValueVariables();
+
+ // show success message
+ WCF::getTPL()->assign('success', true);
+ }
+
+ /**
+ * @see wcf\page\IForm::validate()
+ */
+ public function validate() {
+ parent::validate();
+
+ $this->validateParentCategory();
+
+ if (!I18nHandler::getInstance()->validateValue('title')) {
+ throw new UserInputException('title');
+ }
+
+ if ($this->objectType->getProcessor()->hasDescription() && !I18nHandler::getInstance()->validateValue('description', false, !$this->objectType->getProcessor()->forceDescription())) {
+ throw new UserInputException('description');
+ }
+ }
+
+ /**
+ * Validates the parent category.
+ */
+ protected function validateParentCategory() {
+ if ($this->parentCategoryID) {
+ if (!$this->objectType->getProcessor()->getMaximumNestingLevel()) {
+ $this->parentCategoryID = 0;
+ return;
+ }
+
+ if (CategoryHandler::getInstance()->getCategory($this->parentCategoryID) === null) {
+ throw new UserInputException('parentCategoryID', 'invalid');
+ }
+
+ if ($this->objectType->getProcessor()->getMaximumNestingLevel() != -1) {
+ foreach ($this->categoryNodeList as $category) {
+ if ($category->categoryID == $this->parentCategoryID) {
+ if ($this->categoryNodeList->getDepth() > $this->objectType->getProcessor()->getMaximumNestingLevel() - 1) {
+ throw new UserInputException('parentCategoryID', 'invalid');
+ }
+
+ break;
+ }
+ }
+ }
+ }
+ }
+}
--- /dev/null
+<?php
+namespace wcf\acp\form;
+use wcf\data\category\Category;
+use wcf\data\category\CategoryAction;
+use wcf\data\category\CategoryNodeList;
+use wcf\form\AbstractForm;
+use wcf\system\acl\ACLHandler;
+use wcf\system\category\CategoryHandler;
+use wcf\system\category\CategoryPermissionHandler;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\exception\UserInputException;
+use wcf\system\language\I18nHandler;
+use wcf\system\WCF;
+
+/**
+ * Abstract implementation of a form to edit a category.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage acp.form
+ * @category Community Framework
+ */
+class AbstractCategoryEditForm extends AbstractCategoryAddForm {
+ /**
+ * edited category
+ * @var wcf\data\category\Category
+ */
+ public $category = null;
+
+ /**
+ * id of the edited category
+ * @var integer
+ */
+ public $categoryID = 0;
+
+ /**
+ * @see wcf\page\IPage::assignVariables()
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ I18nHandler::getInstance()->assignVariables(!empty($_POST));
+
+ WCF::getTPL()->assign(array(
+ 'action' => 'edit',
+ 'category' => $this->category
+ ));
+ }
+
+ /**
+ * @see wcf\acp\form\AbstractCategoryAddForm::checkCategoryPermissions()
+ */
+ protected function checkCategoryPermissions() {
+ if (!$this->objectType->getProcessor()->canEditCategory()) {
+ throw new PermissionDeniedException();
+ }
+ }
+
+ /**
+ * @see wcf\acp\form\AbstractCategoryAddForm::readCategories()
+ */
+ protected function readCategories() {
+ $this->categoryNodeList = new CategoryNodeList($this->objectType->objectType, 0, true, array($this->category->categoryID));
+ }
+
+ /**
+ * @see wcf\page\IPage::readParameters()
+ */
+ public function readParameters() {
+ parent::readParameters();
+
+ if (isset($_REQUEST['id'])) {
+ $this->categoryID = intval($_REQUEST['id']);
+ }
+ $this->category = new Category($this->categoryID);
+ if (!$this->category->categoryID) {
+ throw new IllegalLinkException();
+ }
+ }
+
+ /**
+ * @see wcf\page\IPage::readData()
+ */
+ public function readData() {
+ parent::readData();
+
+ if (empty($_POST)) {
+ if ($this->objectType->getProcessor()->hasDescription()) {
+ I18nHandler::getInstance()->setOptions('description', $this->packageID, $this->category->description, $this->objectType->getProcessor()->getI18nLangVarPrefix().'.description.category\d+');
+ }
+ I18nHandler::getInstance()->setOptions('title', $this->packageID, $this->category->title, $this->objectType->getProcessor()->getI18nLangVarPrefix().'.title.category\d+');
+
+ $this->additionalData = $this->category->additionalData;
+ $this->isDisabled = $this->category->isDisabled;
+ $this->parentCategoryID = $this->category->parentCategoryID;
+ $this->showOrder = $this->category->showOrder;
+ }
+ }
+
+ /**
+ * @see wcf\form\IForm::save()
+ */
+ public function save() {
+ AbstractForm::save();
+
+ // handle description
+ if ($this->objectType->getProcessor()->hasDescription()) {
+ $this->description = $this->objectType->getProcessor()->getI18nLangVarPrefix().'.description.category'.$this->category->categoryID;
+ if (I18nHandler::getInstance()->isPlainValue('description')) {
+ I18nHandler::getInstance()->remove($this->description, $this->packageID);
+ $this->description = I18nHandler::getInstance()->getValue('description');
+ }
+ else {
+ I18nHandler::getInstance()->save('description', $this->description, $this->objectType->getProcessor()->getDescriptionLangVarCategory(), $this->packageID);
+ }
+ }
+
+ // handle title
+ $this->title = $this->objectType->getProcessor()->getI18nLangVarPrefix().'.title.category'.$this->category->categoryID;
+ if (I18nHandler::getInstance()->isPlainValue('title')) {
+ I18nHandler::getInstance()->remove($this->title, $this->packageID);
+ $this->title = I18nHandler::getInstance()->getValue('title');
+ }
+ else {
+ I18nHandler::getInstance()->save('title', $this->title, $this->objectType->getProcessor()->getTitleLangVarCategory(), $this->packageID);
+ }
+
+ // update category
+ $this->objectAction = new CategoryAction(array($this->category), 'update', array(
+ 'data' => array(
+ 'additionalData' => serialize($this->additionalData),
+ 'description' => $this->description,
+ 'isDisabled' => $this->isDisabled,
+ 'parentCategoryID' => $this->parentCategoryID,
+ 'showOrder' => $this->showOrder,
+ 'title' => $this->title
+ )
+ ));
+ $this->objectAction->executeAction();
+
+ // update acl
+ if ($this->aclObjectTypeID) {
+ ACLHandler::getInstance()->save($this->category->categoryID, $this->aclObjectTypeID);
+ CategoryPermissionHandler::getInstance()->resetCache();
+ }
+
+ // reload cache
+ CategoryHandler::getInstance()->reloadCache();
+
+ $this->saved();
+
+ // show success message
+ WCF::getTPL()->assign('success', true);
+ }
+
+ /**
+ * @see wcf\acp\form\AbstractCategoryAddForm::validateParentCategory()
+ */
+ protected function validateParentCategory() {
+ parent::validateParentCategory();
+
+ // check if new parent category is no child category of the category
+ $childCategories = CategoryHandler::getInstance()->getChildCategories($this->category);
+ if (isset($childCategories[$this->parentCategoryID])) {
+ throw new UserInputException('parentCategoryID', 'invalid');
+ }
+ }
+}
--- /dev/null
+<?php
+namespace wcf\acp\page;
+use wcf\data\category\CategoryNodeList;
+use wcf\page\AbstractPage;
+use wcf\system\category\CategoryHandler;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\exception\SystemException;
+use wcf\system\menu\acp\ACPMenu;
+use wcf\system\user\collapsible\content\UserCollapsibleContentHandler;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+
+/**
+ * Abstract implementation of a page with lists all categories of a certain object
+ * type.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage acp.page
+ * @category Community Framework
+ */
+abstract class AbstractCategoryListPage extends AbstractPage {
+ /**
+ * name of the controller used to add new categories
+ * @var string
+ */
+ public $addController = '';
+
+ /**
+ * category node list
+ * @var wcf\data\category\CategoryNodeList
+ */
+ public $categoryNodeList = null;
+
+ /**
+ * ids of collapsed categories
+ * @var array<integer>
+ */
+ public $collapsedCategoryIDs = null;
+
+ /**
+ * id of the collapsible category object type
+ * @var integer
+ */
+ public $collapsibleObjectTypeID = 0;
+
+ /**
+ * name of the controller used to edit categories
+ * @var string
+ */
+ public $editController = '';
+
+ /**
+ * category object type object
+ * @var wcf\data\object\type\ObjectType
+ */
+ public $objectType = null;
+
+ /**
+ * name of the category object type
+ * @var string
+ */
+ public $objectTypeName = '';
+
+ /**
+ * @see wcf\page\AbstractPage::$templateName
+ */
+ public $templateName = 'categoryList';
+
+ /**
+ * @see wcf\page\AbstractPage::__run()
+ */
+ public function __run() {
+ $classNameParts = explode('\\', get_called_class());
+ $className = array_pop($classNameParts);
+
+ // autoset controllers
+ if (empty($this->addController)) {
+ $this->addController = StringUtil::replace('ListPage', 'Add', $className);
+ }
+ if (empty($this->editController)) {
+ $this->editController = StringUtil::replace('ListPage', 'Edit', $className);
+ }
+
+ parent::__run();
+ }
+
+ /**
+ * @see wcf\page\IPage::assignVariables()
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ WCF::getTPL()->assign(array(
+ 'addController' => $this->addController,
+ 'categoryNodeList' => $this->categoryNodeList,
+ 'collapsedCategoryIDs' => $this->collapsedCategoryIDs,
+ 'collapsibleObjectTypeID' => $this->collapsibleObjectTypeID,
+ 'editController' => $this->editController,
+ 'objectType' => $this->objectType
+ ));
+ }
+
+ /**
+ * Checks if the active user has the needed permissions to view this list.
+ */
+ protected function checkCategoryPermissions() {
+ if (!$this->objectType->getProcessor()->canDeleteCategory() && !$this->objectType->getProcessor()->canEditCategory()) {
+ throw new PermissionDeniedException();
+ }
+ }
+
+ /**
+ * Reads the categories.
+ */
+ protected function readCategories() {
+ $this->categoryNodeList = new CategoryNodeList($this->objectType->objectType, 0, true);
+ }
+
+ /**
+ * @see wcf\page\IPage::readData()
+ */
+ public function readData() {
+ $this->objectType = CategoryHandler::getInstance()->getObjectTypeByName($this->objectTypeName);
+ if ($this->objectType === null) {
+ throw new SystemException("Unknown category object type with name '".$this->objectTypeName."'");
+ }
+
+ // check permissions
+ $this->checkCategoryPermissions();
+
+ $this->readCategories();
+
+ // note that the implementation of wcf\system\category\ICategoryType
+ // needs to support a object type of the pseudo definition
+ // 'com.woltlab.wcf.collapsibleContent.acp' which has to be registered
+ // during package installation as a 'com.woltlab.wcf.collapsibleContent'
+ // object type if you want to support collapsible categories in the
+ // acp; the pseudo object type is used to distinguish between
+ // collapsible categories in the frontend and the acp
+ $collapsibleObjectTypeName = $this->objectType->getProcessor()->getObjectTypeName('com.woltlab.wcf.collapsibleContent.acp');
+ if ($collapsibleObjectTypeName) {
+ $this->collapsibleObjectTypeID = UserCollapsibleContentHandler::getInstance()->getObjectTypeID($collapsibleObjectTypeName);
+ // get ids of collapsed category
+ if ($this->collapsibleObjectTypeID !== null) {
+ $this->collapsedCategoryIDs = UserCollapsibleContentHandler::getInstance()->getCollapsedContent($this->collapsibleObjectTypeID);
+ $this->collapsedCategoryIDs = array_flip($this->collapsedCategoryIDs);
+ }
+ }
+
+ parent::readData();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\acl\option;
+use wcf\data\DatabaseObject;
+
+/**
+ * Represents an acl option.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2011 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage data.acl.option
+ * @category Community Framework
+ */
+class ACLOption extends DatabaseObject {
+ /**
+ * @see wcf\data\DatabaseObject::$databaseTableName
+ */
+ protected static $databaseTableName = 'acl_option';
+
+ /**
+ * @see wcf\data\DatabaseObject::$databaseTableIndexName
+ */
+ protected static $databaseTableIndexName = 'optionID';
+
+ /**
+ * Returns a list of options by object type id.
+ *
+ * @param integer $objectTypeID
+ * @return wcf\data\acl\option\ACLOptionList
+ */
+ public static function getOptions($objectTypeID) {
+ $optionList = new ACLOptionList();
+ $optionList->getConditionBuilder()->add("acl_option.objectTypeID = ?", array($objectTypeID));
+ $optionList->sqlLimit = 0;
+ $optionList->readObjects();
+
+ return $optionList;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\acl\option;
+use wcf\data\AbstractDatabaseObjectAction;
+use wcf\system\exception\UserInputException;
+use wcf\system\acl\ACLHandler;
+
+/**
+ * Executes acl option-related actions.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage data.acl.option
+ * @category Community Framework
+ */
+class ACLOptionAction extends AbstractDatabaseObjectAction {
+ /**
+ * @see wcf\data\AbstractDatabaseObjectAction::$className
+ */
+ protected $className = 'wcf\data\acl\option\ACLOptionEditor';
+
+ /**
+ * Validates parameters for ACL options.
+ */
+ public function validateLoadAll() {
+ if (!isset($this->parameters['data']['objectTypeID'])) {
+ throw new UserInputException('objectTypeID');
+ }
+ }
+
+ /**
+ * Returns a set of permissions and their values if applicable.
+ *
+ * @return array
+ */
+ public function loadAll() {
+ $objectIDs = (isset($this->parameters['data']['objectID'])) ? array($this->parameters['data']['objectID']) : array();
+ $permissions = ACLHandler::getInstance()->getPermissions($this->parameters['data']['objectTypeID'], $objectIDs, null, true, true);
+
+ return $permissions;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\acl\option;
+use wcf\data\DatabaseObjectEditor;
+
+/**
+ * Provides functions to edit acl options.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2011 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage data.acl.option
+ * @category Community Framework
+ */
+class ACLOptionEditor extends DatabaseObjectEditor {
+ /**
+ * @see wcf\data\DatabaseObjectDecorator::$baseClass
+ */
+ public static $baseClass = 'wcf\data\acl\option\ACLOption';
+}
--- /dev/null
+<?php
+namespace wcf\data\acl\option;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list of acl options.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2011 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage data.acl.option
+ * @category Community Framework
+ */
+class ACLOptionList extends DatabaseObjectList {
+ /**
+ * @see wcf\data\DatabaseObjectList::$className
+ */
+ public $className = 'wcf\data\acl\option\ACLOption';
+}
--- /dev/null
+<?php
+namespace wcf\data\acl\option\category;
+use wcf\data\DatabaseObject;
+
+/**
+ * Represents an acl option category.
+ *
+ * @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
+ * @subpackage data.acl.option.category
+ * @category Community Framework
+ */
+class ACLOptionCategory extends DatabaseObject {
+ /**
+ * @see wcf\data\DatabaseObject::$databaseTableName
+ */
+ protected static $databaseTableName = 'acl_option_category';
+
+ /**
+ * @see wcf\data\DatabaseObject::$databaseTableIndexName
+ */
+ protected static $databaseTableIndexName = 'categoryID';
+}
--- /dev/null
+<?php
+namespace wcf\data\acl\option\category;
+use wcf\data\AbstractDatabaseObjectAction;
+
+/**
+ * Executes acl option category-related actions.
+ *
+ * @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
+ * @subpackage data.acl.option.category
+ * @category Community Framework
+ */
+class ACLOptionCategoryAction extends AbstractDatabaseObjectAction {
+ /**
+ * @see wcf\data\AbstractDatabaseObjectAction::$className
+ */
+ protected $className = 'wcf\data\acl\option\category\ACLOptionCategoryEditor';
+}
--- /dev/null
+<?php
+namespace wcf\data\acl\option\category;
+use wcf\data\DatabaseObjectEditor;
+
+/**
+ * Provides functions to edit acl option categories.
+ *
+ * @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
+ * @subpackage data.acl.option.category
+ * @category Community Framework
+ */
+class ACLOptionCategoryEditor extends DatabaseObjectEditor {
+ /**
+ * @see wcf\data\DatabaseObjectDecorator::$baseClass
+ */
+ public static $baseClass = 'wcf\data\acl\option\category\ACLOptionCategory';
+}
--- /dev/null
+<?php
+namespace wcf\data\acl\option\category;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list of acl option categories.
+ *
+ * @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
+ * @subpackage data.acl.option.category
+ * @category Community Framework
+ */
+class ACLOptionCategoryList extends DatabaseObjectList {
+ /**
+ * @see wcf\data\DatabaseObjectList::$className
+ */
+ public $className = 'wcf\data\acl\option\category\ACLOptionCategory';
+}
--- /dev/null
+<?php
+namespace wcf\data\category;
+use wcf\data\DatabaseObjectDecorator;
+use wcf\system\category\CategoryPermissionHandler;
+use wcf\system\exception\PermissionDeniedException;
+
+/**
+ * Represents a viewable category.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage data.category
+ * @category Community Framework
+ */
+class ViewableCategory extends DatabaseObjectDecorator {
+ /**
+ * @see wcf\data\DatabaseObjectDecorator::$baseClass
+ */
+ protected static $baseClass = 'wcf\data\category\Category';
+
+ /**
+ * acl permissions for the active user of this category
+ * @var array<boolean>
+ */
+ protected $permissions = null;
+
+ /**
+ * Checks if the active user has all given permissions and throws a
+ * PermissionDeniedException if that isn't the case.
+ *
+ * @param array<string> $permissions
+ */
+ public function checkPermissions(array $permissions) {
+ foreach ($permissions as $permission) {
+ if (!$this->getPermission($permission)) {
+ throw new PermissionDeniedException();
+ }
+ }
+ }
+
+ /**
+ * Returns the acl permission value of the given permission for the active
+ * user and of this category.
+ *
+ * @param string $permission
+ * @return boolean
+ */
+ public function getPermission($permission) {
+ if ($this->permissions === null) {
+ $this->permissions = CategoryPermissionHandler::getInstance()->getPermissions($this->getDecoratedObject());
+ }
+
+ if (isset($this->permissions[$permission])) {
+ return $this->permissions[$permission];
+ }
+
+ return false;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\category;
+
+/**
+ * Represents a viewable category node.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage data.category
+ * @category Community Framework
+ */
+class ViewableCategoryNode extends CategoryNode {
+ /**
+ * @see wcf\data\DatabaseObjectDecorator::__construct()
+ */
+ public function __construct(DatabaseObject $object, $includeDisabledCategories = false, array $excludedCategoryIDs = array()) {
+ parent::__construct(new ViewableCategory($object), $includeDisabledCategories, $excludedCategoryIDs);
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\category;
+
+/**
+ * Represents a list of viewable category nodes.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage data.category
+ * @category Community Framework
+ */
+class ViewableCategoryNodeList extends CategoryNodeList {
+ /**
+ * @see wcf\data\category\CategoryNodeList::$nodeClassName
+ */
+ protected $nodeClassName = 'wcf\data\category\ViewableCategoryNode';
+}
--- /dev/null
+<?php
+namespace wcf\system\acl;
+use wcf\data\acl\option\category\ACLOptionCategory;
+use wcf\data\acl\option\category\ACLOptionCategoryList;
+use wcf\data\acl\option\ACLOption;
+use wcf\data\acl\option\ACLOptionList;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\data\user\User;
+use wcf\system\cache\CacheHandler;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\exception\SystemException;
+use wcf\system\SingletonFactory;
+use wcf\system\WCF;
+
+/**
+ * Handles ACL permissions.
+ *
+ * @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
+ * @subpackage system.acl
+ * @category Community Framework
+ */
+class ACLHandler extends SingletonFactory {
+ /**
+ * indicates if assignment of variables is disabled
+ * @var integer
+ */
+ protected $assignVariablesDisabled = false;
+
+ /**
+ * list of available object types
+ * @var array
+ */
+ protected $availableObjectTypes = array();
+
+ /**
+ * list of acl option categories sorted by their object type id and name
+ * @var array<array>
+ */
+ protected $categories = array();
+
+ /**
+ * Assignes the acl values to the template.
+ *
+ * @param integer $objectTypeID
+ */
+ public function assignVariables($objectTypeID) {
+ if (WCF::getTPL()->get('aclValues') === null) {
+ WCF::getTPL()->assign('aclValues', array());
+ }
+
+ if (!$this->assignVariablesDisabled && isset($_POST['aclValues'])) {
+ $values = $_POST['aclValues'];
+
+ $data = $this->getPermissions($objectTypeID, array(), null, true, true);
+
+ foreach ($values as $type => $optionData) {
+ if ($type === 'user') {
+ $users = User::getUsers(array_keys($optionData));
+ }
+
+ $values[$type] = array(
+ 'label' => array(),
+ 'option' => array()
+ );
+
+ foreach ($optionData as $typeID => $optionValues) {
+ foreach ($optionValues as $optionID => $optionValue) {
+ if (!isset($data['options'][$optionID])) {
+ unset($optionValues[$optionID]);
+ }
+ }
+
+ if (empty($optionValues)) {
+ continue;
+ }
+
+ $values[$type]['option'][$typeID] = $optionValues;
+
+ if ($type === 'group') {
+ $values[$type]['label'][$typeID] = WCF::getLanguage()->get('wcf.acp.group.group'.$typeID);
+ }
+ else {
+ $values[$type]['label'][$typeID] = $users[$typeID]->username;
+ }
+ }
+ }
+
+ $values['options'] = $data['options'];
+ $values['categories'] = $data['categories'];
+
+ WCF::getTPL()->append('aclValues', array(
+ $objectTypeID => $values
+ ));
+ }
+ }
+
+ /**
+ * Disables assignment of variables to template.
+ */
+ public function disableAssignVariables() {
+ $this->assignVariablesDisabled = true;
+ }
+
+ /**
+ * Enables assignment of variables to template.
+ */
+ public function enableAssignVariables() {
+ $this->assignVariablesDisabled = false;
+ }
+
+ /**
+ * @see wcf\system\SingletonFactory::init()
+ */
+ protected function init() {
+ $this->availableObjectTypes = ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.acl');
+
+ CacheHandler::getInstance()->addResource(
+ 'aclOptionCategory',
+ WCF_DIR.'cache/cache.aclOptionCategory.php',
+ 'wcf\system\cache\builder\ACLOptionCategoryCacheBuilder'
+ );
+ $this->categories = CacheHandler::getInstance()->get('aclOptionCategory');
+ }
+
+ /**
+ * Gets the object type id.
+ *
+ * @param string $objectType
+ * @return integer
+ */
+ public function getObjectTypeID($objectType) {
+ if (!isset($this->availableObjectTypes[$objectType])) {
+ throw new SystemException("unknown object type '".$objectType."'");
+ }
+
+ return $this->availableObjectTypes[$objectType]->objectTypeID;
+ }
+
+ /**
+ * Returns the acl option category with the given object type id and name.
+ *
+ * @param integer $objectTypeID
+ * @param string $categoryName
+ * @return wcf\data\acl\option\category\ACLOptionCategory
+ */
+ public function getCategory($objectTypeID, $categoryName) {
+ if (isset($this->categories[$objectTypeID][$categoryName])) {
+ return $this->categories[$objectTypeID][$categoryName];
+ }
+
+ return null;
+ }
+
+ /**
+ * Saves acl for a given object.
+ *
+ * @param integer $objectID
+ * @param integer $objectTypeID
+ */
+ public function save($objectID, $objectTypeID) {
+ // get options
+ $optionList = ACLOption::getOptions($objectTypeID);
+
+ $this->replaceValues($optionList, 'group', $objectID);
+ $this->replaceValues($optionList, 'user', $objectID);
+ }
+
+ /**
+ * Replaces values for given type and object.
+ *
+ * @param wcf\data\acl\option\ACLOptionList $optionList
+ * @param string $type
+ * @param integer $objectID
+ */
+ protected function replaceValues(ACLOptionList $optionList, $type, $objectID) {
+ $options = $optionList->getObjects();
+
+ // remove previous values
+ $conditions = new PreparedStatementConditionBuilder();
+ $conditions->add("optionID IN (?)", array(array_keys($options)));
+ $conditions->add("objectID = ?", array($objectID));
+
+ $sql = "DELETE FROM wcf".WCF_N."_acl_option_to_".$type."
+ ".$conditions;
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute($conditions->getParameters());
+
+ // add new values if given
+ if (!isset($_POST['aclValues']) || !isset($_POST['aclValues'][$type])) {
+ return;
+ }
+
+ $sql = "INSERT INTO wcf".WCF_N."_acl_option_to_".$type."
+ (optionID, objectID, ".$type."ID, optionValue)
+ VALUES (?, ?, ?, ?)";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $values =& $_POST['aclValues'][$type];
+
+ WCF::getDB()->beginTransaction();
+ foreach ($values as $typeID => $optionData) {
+ foreach ($optionData as $optionID => $optionValue) {
+ // ignore invalid option ids
+ if (!isset($options[$optionID])) {
+ continue;
+ }
+
+ $statement->execute(array(
+ $optionID,
+ $objectID,
+ $typeID,
+ $optionValue
+ ));
+ }
+ }
+ WCF::getDB()->commitTransaction();
+ }
+
+ /**
+ * Returns a list of permissions by object type id.
+ *
+ * @param integer $objectTypeID
+ * @param array $objectIDs
+ * @param wcf\data\acl\option\category\ACLOptionCategory $category
+ * @param boolean $usePackageDependencies
+ * @param boolean $settingsView
+ * @return array
+ */
+ public function getPermissions($objectTypeID, array $objectIDs, ACLOptionCategory $category = null, $usePackageDependencies = true, $settingsView = false) {
+ $optionList = $this->getOptions($objectTypeID, $category, $usePackageDependencies, $settingsView);
+
+ $data = array(
+ 'options' => $optionList,
+ 'group' => array(),
+ 'user' => array()
+ );
+
+ if (!empty($objectIDs)) {
+ $this->getValues($optionList, 'group', $objectIDs, $data, $settingsView);
+ $this->getValues($optionList, 'user', $objectIDs, $data, $settingsView);
+ }
+
+ // use alternative data structure for settings
+ if ($settingsView) {
+ $data['options'] = array();
+ $data['categories'] = array();
+
+ $categoryNames = array();
+ foreach ($optionList as $option) {
+ $data['options'][$option->optionID] = array(
+ 'categoryName' => $option->categoryName,
+ 'label' => WCF::getLanguage()->get('wcf.acl.option.' . $option->package . '.' . $option->optionName),
+ 'optionName' => $option->optionName
+ );
+
+ if (!in_array($option->categoryName, $categoryNames)) {
+ $categoryNames[] = $option->categoryName;
+ }
+ }
+
+ // load categories
+ $categoryList = new ACLOptionCategoryList();
+ $categoryList->sqlLimit = 0;
+ $categoryList->sqlSelects = "package.package";
+ $categoryList->sqlJoins = "LEFT JOIN wcf".WCF_N."_package package ON (package.packageID = acl_option_category.packageID)";
+ $categoryList->getConditionBuilder()->add("acl_option_category.categoryName IN (?)", array($categoryNames));
+ $categoryList->getConditionBuilder()->add("acl_option_category.objectTypeID = ?", array($objectTypeID));
+ $categoryList->readObjects();
+
+ foreach ($categoryList as $category) {
+ $data['categories'][$category->categoryName] = WCF::getLanguage()->get('wcf.acl.option.category.'.$category->package.'.'.$category->categoryName);
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * Fetches ACL option values by type.
+ *
+ * @param wcf\data\acl\option\ACLOptionList $optionList
+ * @param string $type
+ * @param array $objectIDs
+ * @param array $data
+ * @param boolean $settingsView
+ */
+ protected function getValues(ACLOptionList $optionList, $type, array $objectIDs, array &$data, $settingsView) {
+ $data[$type] = array();
+ $optionsIDs = array();
+ foreach ($optionList as $option) {
+ $optionsIDs[] = $option->optionID;
+ }
+
+ $columnID = $type.'ID';
+ $conditions = new PreparedStatementConditionBuilder();
+ $conditions->add("optionID IN (?)", array($optionsIDs));
+ $conditions->add("objectID IN (?)", array($objectIDs));
+ $sql = "SELECT *
+ FROM wcf".WCF_N."_acl_option_to_".$type."
+ ".$conditions;
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute($conditions->getParameters());
+ while ($row = $statement->fetchArray()) {
+ if (!isset($data[$type][$row['objectID']])) {
+ $data[$type][$row['objectID']] = array();
+ }
+
+ if (!isset($data[$type][$row['objectID']][$row[$columnID]])) {
+ $data[$type][$row['objectID']][$row[$columnID]] = array();
+ }
+
+ $data[$type][$row['objectID']][$row[$columnID]][$row['optionID']] = $row['optionValue'];
+ }
+
+ // use alternative data structure for settings
+ if ($settingsView) {
+ $objectID = current($objectIDs);
+ if (!isset($data[$type][$objectID])) {
+ $data[$type][$objectID] = array();
+ }
+
+ // build JS-compilant structure
+ $data[$type] = array(
+ 'label' => array(),
+ 'option' => $data[$type][$objectID]
+ );
+
+ // load labels
+ if (!empty($data[$type]['option'])) {
+ $conditions = new PreparedStatementConditionBuilder();
+
+ if ($type == 'group') {
+ $conditions->add("groupID IN (?)", array(array_keys($data[$type]['option'])));
+ $sql = "SELECT groupID, groupName
+ FROM wcf".WCF_N."_user_group
+ ".$conditions;
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute($conditions->getParameters());
+
+ while ($row = $statement->fetchArray()) {
+ $data['group']['label'][$row['groupID']] = WCF::getLanguage()->get($row['groupName']);
+ }
+ }
+ else {
+ $conditions->add("userID IN (?)", array(array_keys($data[$type]['option'])));
+ $sql = "SELECT userID, username
+ FROM wcf".WCF_N."_user
+ ".$conditions;
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute($conditions->getParameters());
+
+ while ($row = $statement->fetchArray()) {
+ $data['user']['label'][$row['userID']] = $row['username'];
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns a list of options by object type id.
+ *
+ * @param integer $objectTypeID
+ * @param wcf\data\acl\option\category\ACLOptionCategory $category
+ * @param boolean $usePackageDependencies
+ * @param boolean $settingsView
+ * @param boolean $settingsView
+ * @return wcf\data\acl\option\ACLOptionList
+ */
+ public function getOptions($objectTypeID, ACLOptionCategory $category = null, $usePackageDependencies = true, $settingsView = false) {
+ $optionList = new ACLOptionList();
+ $optionList->sqlLimit = 0;
+ if ($category !== null) {
+ $optionList->getConditionBuilder()->add("acl_option.categoryName = ?", array($category->categoryName));
+ }
+ if ($settingsView) {
+ $optionList->sqlSelects = "package.package";
+ $optionList->sqlJoins = "LEFT JOIN wcf".WCF_N."_package package ON (package.packageID = acl_option.packageID)";
+ }
+ $optionList->getConditionBuilder()->add("acl_option.objectTypeID = ?", array($objectTypeID));
+ $optionList->readObjects();
+
+ return $optionList;
+ }
+
+ /**
+ * Removes ACL values from database.
+ *
+ * @param integer $objectTypeID
+ * @param array<integer> $objectIDs
+ * @param wcf\data\acl\option\category\ACLOptionCategory $category
+ * @param boolean $usePackageDependencies
+ */
+ public function removeValues($objectTypeID, array $objectIDs, ACLOptionCategory $category = null, $usePackageDependencies = true) {
+ $optionList = $this->getOptions($objectTypeID, $category, $usePackageDependencies);
+ $options = $optionList->getObjects();
+
+ $conditions = new PreparedStatementConditionBuilder();
+ $conditions->add("optionID IN (?)", array(array_keys($options)));
+ $conditions->add("objectID IN (?)", array($objectIDs));
+
+ WCF::getDB()->beginTransaction();
+ foreach (array('group', 'user') as $type) {
+ $sql = "DELETE FROM wcf".WCF_N."_acl_option_to_".$type."
+ ".$conditions;
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute($conditions->getParameters());
+ }
+ WCF::getDB()->commitTransaction();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\cache\builder;
+use wcf\data\acl\option\category\ACLOptionCategoryList;
+
+/**
+ * Caches the acl categories for a certain package.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage system.cache.builder
+ * @category Community Framework
+ */
+class ACLOptionCategoryCacheBuilder implements ICacheBuilder {
+ /**
+ * @see wcf\system\cache\ICacheBuilder::getData()
+ */
+ public function getData(array $cacheResource) {
+ $list = new ACLOptionCategoryList();
+ $list->sqlLimit = 0;
+ $list->readObjects();
+
+ $data = array();
+ foreach ($list as $aclOptionCategory) {
+ if (!isset($data[$aclOptionCategory->objectTypeID])) {
+ $data[$aclOptionCategory->objectTypeID] = array();
+ }
+
+ $data[$aclOptionCategory->objectTypeID][$aclOptionCategory->categoryName] = $aclOptionCategory;
+ }
+
+ return $data;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\cache\builder;
+use wcf\system\category\CategoryHandler;
+use wcf\system\acl\ACLHandler;
+
+/**
+ * Caches the acl options of categories.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage system.cache.builder
+ * @category Community Framework
+ */
+class CategoryACLOptionCacheBuilder implements ICacheBuilder {
+ /**
+ * @see wcf\system\cache\ICacheBuilder::getData()
+ */
+ public function getData(array $cacheResource) {
+ $data = array();
+ foreach (CategoryHandler::getInstance()->getCategories() as $objectTypeName => $categories) {
+ $objectType = CategoryHandler::getInstance()->getObjectTypeByName($objectTypeName);
+ $aclObjectType = $objectType->getProcessor()->getObjectTypeName('com.woltlab.wcf.acl');
+ if (!$aclObjectType) {
+ continue;
+ }
+
+ $aclOptions = ACLHandler::getInstance()->getPermissions(ACLHandler::getInstance()->getObjectTypeID($aclObjectType), array_keys($categories));
+ $options = $aclOptions['options']->getObjects();
+
+ $data = array();
+ foreach (array('group', 'user') as $type) {
+ foreach ($aclOptions[$type] as $categoryID => $optionData) {
+ if (!isset($aclValues[$categoryID])) {
+ $data[$categoryID] = array(
+ 'group' => array(),
+ 'user' => array()
+ );
+ }
+
+ foreach ($optionData as $typeID => $optionValues) {
+ $data[$categoryID][$type][$typeID] = array();
+
+ foreach ($optionValues as $optionID => $optionValue) {
+ $data[$categoryID][$type][$typeID][$options[$optionID]->optionName] = $optionValue;
+ }
+ }
+ }
+ }
+ }
+
+ return $data;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\category;
+use wcf\data\category\Category;
+use wcf\data\user\User;
+use wcf\system\cache\CacheHandler;
+use wcf\system\SingletonFactory;
+use wcf\system\WCF;
+
+/**
+ * Handles the category permissions.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage system.category
+ * @category Community Framework
+ */
+class CategoryPermissionHandler extends SingletonFactory {
+ /**
+ * cached category acl options
+ * @var array
+ */
+ protected $categoryPermissions = array();
+
+ /**
+ * Returns the acl options for the given category and for the given user.
+ * If no user is given, the active user is used.
+ *
+ * @param wcf\data\category\Category $category
+ * @param wcf\data\user\User $user
+ */
+ public function getPermissions(Category $category, User $user = null) {
+ if ($user === null) {
+ $user = WCF::getUser();
+ }
+
+ $permissions = array();
+ if (isset($this->categoryPermissions[$category->categoryID])) {
+ if (isset($this->categoryPermissions[$category->categoryID]['group'])) {
+ foreach ($user->getGroupIDs() as $groupID) {
+ if (isset($this->categoryPermissions[$category->categoryID]['group'][$groupID])) {
+ foreach ($this->categoryPermissions[$category->categoryID]['group'][$groupID] as $optionName => $optionValue) {
+ if (isset($permissions[$optionName])) {
+ $permissions[$optionName] = $permissions[$optionName] || $optionValue;
+ }
+ else {
+ $permissions[$optionName] = $optionValue;
+ }
+ }
+ }
+ }
+ }
+
+ if (isset($this->categoryPermissions[$category->categoryID]['user']) && isset($this->categoryPermissions[$category->categoryID]['user'][$user->userID])) {
+ foreach ($this->categoryPermissions[$category->categoryID]['user'][$user->userID] as $optionName => $optionValue) {
+ $permissions[$optionName] = $optionValue;
+ }
+ }
+ }
+
+ return $permissions;
+ }
+
+ /**
+ * @see wcf\system\SingletonFactory::init()
+ */
+ protected function init() {
+ CacheHandler::getInstance()->addResource(
+ 'categoryACLOption',
+ WCF_DIR.'cache/cache.categoryACLOption.php',
+ 'wcf\system\cache\builder\CategoryACLOptionCacheBuilder'
+ );
+ $this->categoryPermissions = CacheHandler::getInstance()->get('categoryACLOption');
+ }
+
+ /**
+ * Resets the category permission cache.
+ */
+ public function resetCache() {
+ CacheHandler::getInstance()->clearResource('categoryACLOption');
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\package\plugin;
+use wcf\system\exception\SystemException;
+use wcf\system\WCF;
+
+/**
+ * This PIP installs, updates or deletes acl options.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2011 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage system.package.plugin
+ * @category Community Framework
+ */
+class ACLOptionPackageInstallationPlugin extends AbstractOptionPackageInstallationPlugin {
+ /**
+ * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::$className
+ */
+ public $className = 'wcf\data\acl\option\ACLOptionEditor';
+
+ /**
+ * list of loaded acl object type ids sorted by their option type name
+ * @var array<integer>
+ */
+ protected $optionTypeIDs = array();
+
+ /**
+ * @see wcf\system\package\plugin\AbstractPackageInstallationPlugin::$tableName
+ */
+ public $tableName = 'acl_option';
+
+ /**
+ * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::$tagName
+ */
+ public $tagName = 'option';
+
+ /**
+ * @see wcf\system\package\plugin\AbstractOptionPackageInstallationPlugin::importCategories()
+ */
+ protected function importCategories(\DOMXPath $xpath) {
+ $elements = $xpath->query('/ns:data/ns:import/ns:categories/ns:category');
+ foreach ($elements as $element) {
+ $data = array('categoryName' => $element->getAttribute('name'));
+
+ // get child elements
+ $children = $xpath->query('child::*', $element);
+ foreach ($children as $child) {
+ $data[$child->tagName] = $child->nodeValue;
+ }
+
+ $this->saveCategory($data);
+ }
+ }
+
+ /**
+ * @see wcf\system\package\plugin\AbstractOptionPackageInstallationPlugin::saveCategory()
+ */
+ protected function saveCategory($category) {
+ $objectTypeID = $this->getObjectTypeID($category['objecttype']);
+
+ // search existing category
+ $sql = "SELECT categoryID
+ FROM wcf".WCF_N."_".$this->tableName."_category
+ WHERE categoryName = ?
+ AND objectTypeID = ?
+ AND packageID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array(
+ $category['categoryName'],
+ $objectTypeID,
+ $this->installation->getPackageID()
+ ));
+ $row = $statement->fetchArray();
+ if (!$row) {
+ // insert new category
+ $sql = "INSERT INTO wcf".WCF_N."_".$this->tableName."_category
+ (packageID, objectTypeID, categoryName)
+ VALUES (?, ?, ?)";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array(
+ $this->installation->getPackageID(),
+ $objectTypeID,
+ $category['categoryName']
+ ));
+ }
+ }
+
+ /**
+ * Imports options.
+ *
+ * @param \DOMXPath $xpath
+ */
+ protected function importOptions(\DOMXPath $xpath) {
+ $elements = $xpath->query('/ns:data/ns:import/ns:options/ns:option');
+ foreach ($elements as $element) {
+ $data = array();
+ $children = $xpath->query('child::*', $element);
+ foreach ($children as $child) {
+ $data[$child->tagName] = $child->nodeValue;
+ }
+
+ $objectTypeID = $this->getObjectTypeID($data['objecttype']);
+
+ // validate category name
+ if (isset($data['categoryname'])) {
+ $sql = "SELECT COUNT(categoryID) AS count
+ FROM wcf".WCF_N."_".$this->tableName."_category
+ WHERE categoryName = ?
+ AND objectTypeID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array(
+ $data['categoryname'],
+ $objectTypeID
+ ));
+ $row = $statement->fetchArray();
+ if (!$row) {
+ throw new SystemException("unknown category '".$data['categoryname']."' for acl object type '".$data['objecttype']."' given");
+ }
+ }
+
+ $data = array(
+ 'categoryName' => (isset($data['categoryname'])) ? $data['categoryname'] : '',
+ 'optionName' => $element->getAttribute('name'),
+ 'objectTypeID' => $objectTypeID
+ );
+
+ // check for option existence
+ $sql = "SELECT optionID
+ FROM wcf".WCF_N."_".$this->tableName."
+ WHERE optionName = ?
+ AND objectTypeID = ?
+ AND packageID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array(
+ $data['optionName'],
+ $data['objectTypeID'],
+ $this->installation->getPackageID()
+ ));
+ $row = $statement->fetchArray();
+ if (!$row) {
+ $sql = "INSERT INTO wcf".WCF_N."_".$this->tableName."
+ (packageID, objectTypeID, optionName, categoryName)
+ VALUES (?, ?, ?, ?)";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array(
+ $this->installation->getPackageID(),
+ $data['objectTypeID'],
+ $data['optionName'],
+ $data['categoryName']
+ ));
+ }
+ else {
+ $sql = "UPDATE wcf".WCF_N."_".$this->tableName."
+ SET categoryName = ?
+ WHERE optionID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array(
+ $data['categoryName'],
+ $row['optionID']
+ ));
+ }
+ }
+ }
+
+ /**
+ * @see wcf\system\package\plugin\AbstractOptionPackageInstallationPlugin::saveOption()
+ */
+ protected function saveOption($option, $categoryName, $existingOptionID = 0) {
+ /* Does nothing */
+ }
+
+ /**
+ * Returns the object type id of the acl option type with the given name
+ * or throws a SystemException if no such option type exists.
+ *
+ * @param string $optionType
+ * @return integer
+ */
+ protected function getObjectTypeID($optionType) {
+ if (!isset($this->optionTypeIDs[$optionType])) {
+ $sql = "SELECT objectTypeID
+ FROM wcf".WCF_N."_object_type
+ WHERE objectType = ?
+ AND definitionID IN (
+ SELECT definitionID
+ FROM wcf".WCF_N."_object_type_definition
+ WHERE definitionName = 'com.woltlab.wcf.acl'
+ )";
+ $statement = WCF::getDB()->prepareStatement($sql, 1);
+ $statement->execute(array($optionType));
+ $row = $statement->fetchArray();
+ if (!$row) {
+ throw new SystemException("unknown object type '".$optionType."' given");
+ }
+
+ $this->optionTypeIDs[$optionType] = $row['objectTypeID'];
+ }
+
+ return $this->optionTypeIDs[$optionType];
+ }
+}
--- /dev/null
+/* todo */
+.aclContainer > dd > span {
+ position: relative;
+}
+
+.aclList,
+.aclPermissionList {
+ margin-top: 0;
+ min-height: 100px;
+
+ > li {
+ display: block;
+ padding: @wcfGapTiny;
+
+ &:first-child {
+ border-top-left-radius: @wcfContainerBorderRadius;
+ border-top-right-radius: @wcfContainerBorderRadius;
+ }
+
+ &:last-child:not(:first-child) {
+ border-bottom-left-radius: @wcfContainerBorderRadius;
+ border-bottom-right-radius: @wcfContainerBorderRadius;
+ }
+ }
+}
+
+.aclList {
+ margin-bottom: @wcfGapSmall;
+
+ > li {
+ cursor: pointer;
+
+ &:hover,
+ &.active:hover {
+ background-color: @wcfButtonHoverBackgroundColor;
+ }
+
+ &.active {
+ background-color: @wcfContainerAccentBackgroundColor;
+ }
+
+ > img:last-child {
+ float: right;
+ margin-right: @wcfGapSmall;
+ }
+ }
+}
+
+.aclPermissionList {
+ margin-top: @wcfGapSmall;
+ min-height: 200px;
+ text-align: right;
+
+ > li {
+ &:hover {
+ background-color: @wcfButtonHoverBackgroundColor;
+ }
+
+ &.aclCategory {
+ background-color: @wcfContainerHoverBackgroundColor;
+ padding: @wcfGapSmall;
+ text-align: left;
+ }
+
+ &.aclFullAccess {
+ background-color: @wcfContainerAccentBackgroundColor;
+ }
+
+ > span {
+ float: left;
+ padding-left: @wcfGapSmall;
+ }
+
+ > label {
+ cursor: pointer;
+ margin: 0 @wcfGapSmall;
+ padding: 0 @wcfGapLarge;
+ }
+ }
+}
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<language xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/maelstrom/language.xsd" languagecode="de" languagename="Deutsch" countrycode="de">
+ <category name="wcf.acl">
+ <item name="wcf.acl.option.deny"><![CDATA[Verweigern]]></item>
+ <item name="wcf.acl.option.fullAccess"><![CDATA[Vollzugriff]]></item>
+ <item name="wcf.acl.option.grant"><![CDATA[Erlauben]]></item>
+ <item name="wcf.acl.permissions"><![CDATA[Benutzer-/Benutzergruppenrechte]]></item>
+ </category>
+
<category name="wcf.acp">
<item name="wcf.acp"><![CDATA[Administrationsoberfläche]]></item>
</category>
<?xml version="1.0" encoding="UTF-8"?>
<language xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/maelstrom/language.xsd" languagecode="en" languagename="English" countrycode="gb">
+ <category name="wcf.acl">
+ <item name="wcf.acl.option.deny"><![CDATA[Deny]]></item>
+ <item name="wcf.acl.option.fullAccess"><![CDATA[Full access]]></item>
+ <item name="wcf.acl.option.grant"><![CDATA[Grant]]></item>
+ <item name="wcf.acl.permissions"><![CDATA[User/User group permissions]]></item>
+ </category>
+
<category name="wcf.acp">
<item name="wcf.acp"><![CDATA[Administration]]></item>
</category>
/* tables */
+DROP TABLE IF EXISTS wcf1_acl_option;
+CREATE TABLE wcf1_acl_option (
+ optionID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ packageID INT(10) NOT NULL,
+ objectTypeID INT(10) NOT NULL,
+ optionName VARCHAR(255) NOT NULL,
+ categoryName VARCHAR(255) NOT NULL,
+ UNIQUE KEY (packageID, objectTypeID, optionName)
+);
+
+DROP TABLE IF EXISTS wcf1_acl_option_category;
+CREATE TABLE wcf1_acl_option_category (
+ categoryID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ packageID INT(10) NOT NULL,
+ objectTypeID INT(10) NOT NULL,
+ categoryName VARCHAR(255) NOT NULL,
+ UNIQUE KEY (packageID, objectTypeID, categoryName)
+);
+
+DROP TABLE IF EXISTS wcf1_acl_option_to_user;
+CREATE TABLE wcf1_acl_option_to_user (
+ optionID INT(10) NOT NULL,
+ objectID INT(10) NOT NULL,
+ userID INT(10) NOT NULL,
+ optionValue TINYINT(1) NOT NULL DEFAULT 0,
+ UNIQUE KEY userID (userID, objectID, optionID)
+);
+
+DROP TABLE IF EXISTS wcf1_acl_option_to_group;
+CREATE TABLE wcf1_acl_option_to_group (
+ optionID INT(10) NOT NULL,
+ objectID INT(10) NOT NULL,
+ groupID INT(10) NOT NULL,
+ optionValue TINYINT(1) NOT NULL DEFAULT 0,
+ UNIQUE KEY groupID (groupID, objectID, optionID)
+);
+
DROP TABLE IF EXISTS wcf1_acp_menu_item;
CREATE TABLE wcf1_acp_menu_item (
menuItemID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
);
/* foreign keys */
+ALTER TABLE wcf1_acl_option ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
+ALTER TABLE wcf1_acl_option ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
+
+ALTER TABLE wcf1_acl_option_category ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
+ALTER TABLE wcf1_acl_option_category ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
+
+ALTER TABLE wcf1_acl_option_to_user ADD FOREIGN KEY (optionID) REFERENCES wcf1_acl_option (optionID) ON DELETE CASCADE;
+ALTER TABLE wcf1_acl_option_to_user ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;
+
+ALTER TABLE wcf1_acl_option_to_group ADD FOREIGN KEY (optionID) REFERENCES wcf1_acl_option (optionID) ON DELETE CASCADE;
+ALTER TABLE wcf1_acl_option_to_group ADD FOREIGN KEY (groupID) REFERENCES wcf1_user_group (groupID) ON DELETE CASCADE;
+
ALTER TABLE wcf1_acp_menu_item ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
ALTER TABLE wcf1_acp_search_provider ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;