<definition>
<name>com.woltlab.wcf.clipboardItem</name>
</definition>
+
+ <definition>
+ <interfacename>wcf\system\category\ICategoryType</interfacename>
+ <name>com.woltlab.wcf.category</name>
+ </definition>
</import>
</data>
\ No newline at end of file
--- /dev/null
+{foreach from=$categoryNodeList item=category}
+ <option value="{$category->objectTypeCategoryID}"{if $categoryID|isset && $categoryID == $category->objectTypeCategoryID} selected="selected"{/if}>{section name=i loop=$categoryNodeList->getDepth()} {/section}{$category->getTitle()}</option>
+{/foreach}
\ No newline at end of file
/**
* Class and function collection for WCF ACP
*
- * @author Alexander Ebert
- * @copyright 2001-2011 WoltLab GmbH
+ * @author Alexander Ebert, Matthias Schmidt
+ * @copyright 2001-2012 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
*/
}
}
};
+
+/**
+ * Namespace for category-related functions.
+ */
+WCF.ACP.Category = {};
+
+/**
+ * Handles collapsing categories.
+ *
+ * @param string className
+ * @param integer objectTypeID
+ */
+WCF.ACP.Category.Collapsible = WCF.Collapsible.SimpleRemote.extend({
+ /**
+ * @see WCF.Collapsible.Remote.init()
+ */
+ init: function(className, objectTypeID) {
+ this._objectTypeID = objectTypeID;
+
+ var sortButton = $('.formSubmit > button[data-type="submit"]');
+ if (sortButton) {
+ sortButton.click($.proxy(this._sort, this));
+ }
+
+ this._super(className);
+ },
+
+ /**
+ * @see WCF.Collapsible.Remote._getAdditionalParameters()
+ */
+ _getAdditionalParameters: function(containerID) {
+ return {objectTypeID : this._objectTypeID};
+ },
+
+ /**
+ * @see WCF.Collapsible.Remote._getButtonContainer()
+ */
+ _getButtonContainer: function(containerID) {
+ return $('#' + containerID + ' > .buttons');
+ },
+
+ /**
+ * @see WCF.Collapsible.Remote._getContainers()
+ */
+ _getContainers: function() {
+ return $('.jsCategory').has('ol').has('li');
+ },
+
+ /**
+ * @see WCF.Collapsible.Remote._getTarget()
+ */
+ _getTarget: function(containerID) {
+ return $('#' + containerID + ' > ol');
+ },
+
+ /**
+ * Handles a click on the sort button.
+ */
+ _sort: function() {
+ // remove existing collapsible buttons
+ $('.collapsibleButton').remove();
+
+ // reinit containers
+ this._containers = {};
+ this._containerData = {};
+
+ var $containers = this._getContainers();
+ if ($containers.length == 0) {
+ console.debug('[WCF.Category.Collapsible] Empty container set given, aborting.');
+ }
+ $containers.each($.proxy(function(index, container) {
+ var $container = $(container);
+ var $containerID = $container.wcfIdentify();
+ this._containers[$containerID] = $container;
+
+ this._initContainer($containerID);
+ }, this));
+ }
+});
+
+/**
+ * @see WCF.Action.Delete
+ */
+WCF.ACP.Category.Delete = WCF.Action.Delete.extend({
+ /**
+ * @see WCF.Action.Delete.triggerEffect()
+ */
+ triggerEffect: function(objectIDs) {
+ this.containerList.each($.proxy(function(index, container) {
+ container = $(container);
+ var $objectID = container.find('.jsDeleteButton').data('objectID');
+ if (WCF.inArray($objectID, objectIDs)) {
+ // move child categories up
+ if (container.has('ol').has('li')) {
+ if (container.is(':only-child')) {
+ container.parent().replaceWith(container.find('> ol'));
+ }
+ else {
+ container.replaceWith(container.find('> ol > li'));
+ }
+ }
+ else {
+ container.wcfBlindOut('up', function() {
+ container.empty().remove();
+ }, container);
+ }
+
+ // update badges
+ if (this.badgeList) {
+ this.badgeList.each(function(innerIndex, badge) {
+ $(badge).html($(badge).html() - 1);
+ });
+ }
+ }
+ }, this));
+ }
+});
--- /dev/null
+{include file='header'}
+
+{if $aclObjectTypeID}
+ {include file='aclPermissions'}
+{/if}
+<script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ {if $aclObjectTypeID}
+ new WCF.ACL.List($('#groupPermissions'), {@$aclObjectTypeID}{if $category|isset}, '', {@$category->categoryID}{/if});
+ {/if}
+
+ var $availableLanguages = { {implode from=$availableLanguages key=languageID item=languageName}{@$languageID}: '{$languageName}'{/implode} };
+
+ var $titleValues = { {implode from=$i18nValues['title'] key=languageID item=value}'{@$languageID}': '{$value}'{/implode} };
+ new WCF.MultipleLanguageInput('title', false, $titleValues, $availableLanguages);
+
+ var $descriptionValues = { {implode from=$i18nValues['description'] key=languageID item=value}'{@$languageID}': '{$value}'{/implode} };
+ new WCF.MultipleLanguageInput('description', false, $descriptionValues, $availableLanguages);
+ });
+ //]]>
+</script>
+
+<header class="boxHeadline">
+ <hgroup>
+ <h1>{hascontent}{content}{lang __optional=true}{@$objectType->getProcessor()->getLangVarPrefix()}.{@$action}{/lang}{/content}{hascontentelse}{lang}wcf.category.{@$action}{/lang}{/hascontent}</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}
+
+{capture assign='listLangVar'}{lang __optional=true}{@$objectType->getProcessor()->getLangVarPrefix()}.button.list{/lang}{/capture}
+{if !$listLangVar}
+ {capture assign='listLangVar'}{lang}wcf.category.button.list{/lang}{/capture}
+{/if}
+
+{hascontent}
+ <div class="contentNavigation">
+ <nav>
+ <ul>
+ {content}
+ {if $objectType->getProcessor()->canDeleteCategory() || $objectType->getProcessor()->canEditCategory()}
+ <li><a href="{link controller=$listController}{/link}" title="{$listLangVar}" class="button"><img src="{@$__wcf->getPath()}icon/list.svg" alt="" class="icon24" /> <span>{@$listLangVar}</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 id=$category->categoryID title=$category->getTitle()}{/link}{/if}">
+ <div class="container containerPadding marginTop shadow">
+ <fieldset>
+ <legend>{hascontent}{content}{lang __optional=true}{@$objectType->getProcessor()->getLangVarPrefix()}.data{/lang}{/content}{hascontentelse}{lang}wcf.category.data{/lang}{/hascontent}</legend>
+
+ {if $categoryNodeList|count}
+ <dl{if $errorField == 'parentCategoryID'} class="formError"{/if}>
+ <dt><label for="parentCategoryID">{hascontent}{content}{lang __optional=true}{@$objectType->getProcessor()->getLangVarPrefix()}.parentCategoryID{/lang}{/content}{hascontentelse}{lang}wcf.category.parentCategoryID{/lang}{/hascontent}</label></dt>
+ <dd>
+ <select id="parentCategoryID" name="parentCategoryID">
+ <option value="0"></option>
+ {include file='categorySelectList' categoryID=$parentCategoryID}
+ </select>
+ {if $errorField == 'parentCategoryID'}
+ <small class="innerError">
+ {hascontent}{content}{lang __optional=true}{@$objectType->getProcessor()->getLangVarPrefix()}.parentCategoryID.error.{@$errorType}{/lang}{/content}{hascontentelse}{lang}wcf.category.parentCategoryID.error.{@$errorType}{/lang}{/hascontent}
+ </small>
+ {/if}
+ {hascontent}<small>{content}{lang __optional=true}{@$objectType->getProcessor()->getLangVarPrefix()}.parentCategoryID.description{/lang}{/content}</small>{/hascontent}
+ </dd>
+ </dl>
+ {/if}
+
+ <dl{if $errorField == 'title'} class="formError"{/if}>
+ <dt><label for="title">{hascontent}{content}{lang __optional=true}{@$objectType->getProcessor()->getLangVarPrefix()}.title{/lang}{/content}{hascontentelse}{lang}wcf.category.title{/lang}{/hascontent}</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}
+ {lang}{@$objectType->getProcessor()->getLangVarPrefix()}.title.error.{@$errorType}{/lang}
+ {/if}
+ </small>
+ {/if}
+ {hascontent}<small>{content}{lang __optional=true}{@$objectType->getProcessor()->getLangVarPrefix()}.title.description{/lang}{/content}</small>{/hascontent}
+ </dd>
+ </dl>
+
+ <dl{if $errorField == 'description'} class="formError"{/if}>
+ <dt><label for="description">{hascontent}{content}{lang __optional=true}{@$objectType->getProcessor()->getLangVarPrefix()}.description{/lang}{/content}{hascontentelse}{lang}wcf.category.description{/lang}{/hascontent}</label></dt>
+ <dd>
+ <textarea cols="40" rows="10" id="description" name="description">{$i18nPlainValues['description']}</textarea>
+ {if $errorType == 'description'}
+ <small class="innerError">
+ {if $errorType == 'empty'}
+ {lang}wcf.global.form.error.empty{/lang}
+ {else}
+ {lang}{@$objectType->getProcessor()->getLangVarPrefix()}.description.error.{@$errorType}{/lang}
+ {/if}
+ </small>
+ {/if}
+ {hascontent}<small>{content}{lang __optional=true}{@$objectType->getProcessor()->getLangVarPrefix()}.description.description{/lang}{/content}</small>{/hascontent}
+ </dd>
+ </dl>
+
+ <dl{if $errorField == 'isDisabled'} class="formError"{/if}>
+ <dt class="reversed"><label for="isDisabled">{hascontent}{content}{lang __optional=true}{@$objectType->getProcessor()->getLangVarPrefix()}.isDisabled{/lang}{/content}{hascontentelse}{lang}wcf.category.isDisabled{/lang}{/hascontent}</label></dt>
+ <dd>
+ <input type="checkbox" id="isDisabled" name="isDisabled"{if $isDisabled} checked="checked"{/if} />
+ {hascontent}<small>{content}{lang __optional=true}{@$objectType->getProcessor()->getLangVarPrefix()}.isDisabled.description{/lang}{/content}</small>{/hascontent}
+ </dd>
+ </dl>
+
+ <dl{if $errorField == 'showOrder'} class="formError"{/if}>
+ <dt><label for="showOrder">{hascontent}{content}{lang __optional=true}{@$objectType->getProcessor()->getLangVarPrefix()}.showOrder{/lang}{/content}{hascontentelse}{lang}wcf.category.showOrder{/lang}{/hascontent}</label></dt>
+ <dd>
+ <input type="text" id="showOrder" name="showOrder" value="{$showOrder}" class="short" />
+ {if $errorField == 'title'}
+ <small class="innerError">
+ {lang}{@$objectType->getProcessor()->getLangVarPrefix()}.showOrder.error.{@$errorType}{/lang}
+ </small>
+ {/if}
+ {hascontent}<small>{content}{lang __optional=true}{@$objectType->getProcessor()->getLangVarPrefix()}.showOrder.description{/lang}{/content}</small>{/hascontent}
+ </dd>
+ </dl>
+
+ {if $aclObjectTypeID}
+ <dl id="groupPermissions">
+ <dt>{lang}wcf.acp.acl.permissions{/lang}</dt>
+ <dd></dd>
+ </dl>
+ {/if}
+
+ {event name='fields'}
+ </fieldset>
+
+ {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}
+ new WCF.Sortable.List('categoryList', 'wcf\\data\\category\\CategoryAction');
+ {/if}
+ {/if}
+ });
+ //]]>
+ </script>
+{/if}
+
+<header class="box48 boxHeadline">
+ <hgroup>
+ <h1>{hascontent}{content}{lang __optional=true}{@$objectType->getProcessor()->getLangVarPrefix()}.list{/lang}{/content}{hascontentelse}{lang}wcf.category.list{/lang}{/hascontent}</h1>
+ </hgroup>
+</header>
+
+{capture assign='addLangVar'}{lang __optional=true}{@$objectType->getProcessor()->getLangVarPrefix()}.add{/lang}{/capture}
+{if !$addLangVar}
+ {capture assign='addLangVar'}{lang}wcf.category.add{/lang}{/capture}
+{/if}
+
+{hascontent}
+ <div class="contentNavigation">
+ <nav>
+ <ul>
+ {content}
+ {if $objectType->getProcessor()->canAddCategory()}
+ <li><a href="{link controller=$addController}{/link}" title="{$addLangVar}" class="button"><img src="{@$__wcf->getPath()}icon/add.svg" alt="" class="icon24" /> <span>{@$addLangVar}</span></a></li>
+ {/if}
+
+ {event name='contentNavigationButtons'}
+ {/content}
+ </ul>
+ </nav>
+ </div>
+{/hascontent}
+
+{if $categoryNodeList|count}
+ <section id="categoryList" class="container containerPadding marginTop shadow{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}
+ {if $categoryNodeList->getDepth() < $oldDepth}
+ </ol></li>
+ {/if}
+
+ <li class="{if $objectType->getProcessor()->canEditCategory() && $categoryNodeList|count > 1}sortableNode {/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="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="{hascontent}{content}{lang __optional=true}{@$objectType->getProcessor()->getLangVarPrefix()}.delete.sure{/lang}{/content}{hascontentelse}{lang}wcf.category.delete.sure{/lang}{/hascontent}" />
+ {else}
+ <img src="{@$__wcf->getPath()}icon/delete.svg" alt="" title="{lang}wcf.global.button.delete{/lang}" class="icon16 disabled" />
+ {/if}
+
+ {if $objectType->getProcessor()->canEditCategory()}
+ <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>
+
+ <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="{$addLangVar}" class="button"><img src="{@$__wcf->getPath()}icon/add.svg" alt="" class="icon24" /> <span>{@$addLangVar}</span></a></li>
+ {/if}
+
+ {event name='contentNavigationButtons'}
+ {/content}
+ </ul>
+ </nav>
+ </div>
+ {/hascontent}
+{else}
+ <p class="warning">{hascontent}{content}{lang __optional=true}{@$objectType->getProcessor()->getLangVarPrefix()}.list.noneAvailable{/lang}{/content}{hascontentelse}{lang}wcf.category.list.noneAvailable{/lang}{/hascontent}</p>
+{/if}
+
+{include file='footer'}
\ No newline at end of file
--- /dev/null
+{foreach from=$categoryNodeList item=category}
+ <option value="{$category->objectTypeCategoryID}"{if $categoryID|isset && $categoryID == $category->objectTypeCategoryID} selected="selected"{/if}>{section name=i loop=$categoryNodeList->getDepth()} {/section}{$category->getTitle()}</option>
+{/foreach}
\ No newline at end of file
--- /dev/null
+<?php
+namespace wcf\acp\form;
+use wcf\data\category\CategoryAction;
+use wcf\data\category\CategoryEditor;
+use wcf\data\category\CategoryNodeList;
+use wcf\system\acl\ACLHandler;
+use wcf\system\category\CategoryHandler;
+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\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 ACPForm {
+ /**
+ * 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 = '';
+
+ /**
+ * 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::assignVariables()
+ */
+ public function __construct() {
+ $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::__construct();
+ }
+
+ /**
+ * @see wcf\page\IPage::assignVariables()
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ I18nHandler::getInstance()->assignVariables();
+
+ 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
+ ));
+ }
+
+ /**
+ * Reads the categories.
+ */
+ protected function readCategories() {
+ $this->categoryNodeList = new CategoryNodeList($this->objectType->objectTypeID, 0, true);
+ }
+
+ /**
+ * 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();
+ }
+ }
+
+ /**
+ * @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
+ if ($this->objectType->getProcessor()->getACLObjectTypeName()) {
+ $this->aclObjectTypeID = ACLHandler::getInstance()->getObjectTypeID($this->objectType->getProcessor()->getACLObjectTypeName());
+ }
+
+ // autoset package id
+ if (!$this->packageID) {
+ $this->packageID = $this->objectType->packageID;
+ }
+
+ parent::readData();
+
+ $this->readCategories();
+ }
+
+ /**
+ * @see wcf\page\IForm::readFormParameters()
+ */
+ public function readFormParameters() {
+ parent::readFormParameters();
+
+ I18nHandler::getInstance()->readValues();
+
+ if (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\IPage::readParameters()
+ */
+ public function readParameters() {
+ parent::readParameters();
+
+ I18nHandler::getInstance()->register('description');
+ I18nHandler::getInstance()->register('title');
+ }
+
+ /**
+ * @see wcf\page\IForm::save()
+ */
+ public function save() {
+ parent::save();
+
+ $this->objectAction = new CategoryAction(array(), 'create', array(
+ 'data' => array(
+ 'description' => $this->description,
+ 'isDisabled' => $this->isDisabled,
+ 'objectTypeID' => $this->objectType->objectTypeID,
+ 'parentCategoryID' => $this->parentCategoryID,
+ 'showOrder' => $this->showOrder,
+ 'title' => $this->title
+ )
+ ));
+ $this->objectAction->executeAction();
+ $returnValues = $this->objectAction->getReturnValues();
+
+ if (!I18nHandler::getInstance()->isPlainValue('description') || !I18nHandler::getInstance()->isPlainValue('title')) {
+ $categoryID = $returnValues['returnValues']->categoryID;
+
+ $updateData = array();
+ if (!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);
+ }
+
+ // 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();
+ }
+
+ /**
+ * Validates the parent category.
+ */
+ protected function validateParentCategory() {
+ if ($this->parentCategoryID) {
+ if (CategoryHandler::getInstance()->getCategory($this->objectType->objectTypeID, $this->parentCategoryID) === null) {
+ throw new UserInputException('parentCategoryID', 'invalid');
+ }
+ }
+ }
+}
--- /dev/null
+<?php
+namespace wcf\acp\form;
+use wcf\data\category\Category;
+use wcf\data\category\CategoryAction;
+use wcf\data\category\CategoryNodeList;
+use wcf\system\acl\ACLHandler;
+use wcf\system\category\CategoryHandler;
+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->objectTypeID, 0, true, array($this->category->objectTypeCategoryID));
+ }
+
+ /**
+ * @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)) {
+ 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->isDisabled = $this->category->isDisabled;
+ $this->parentCategoryID = $this->category->parentCategoryID;
+ $this->showOrder = $this->category->showOrder;
+ }
+ }
+
+ /**
+ * @see wcf\form\IForm::save()
+ */
+ public function save() {
+ ACPForm::save();
+
+ // handle description
+ $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(
+ '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);
+ }
+
+ // 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 {
+ /**
+ * active acp menu item
+ * @var string
+ */
+ public $activeMenuItem = '';
+
+ /**
+ * 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::assignVariables()
+ */
+ public function __construct() {
+ $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::__construct();
+ }
+
+ /**
+ * @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->objectTypeID, 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();
+
+ // get ids of collapsed category
+ $this->collapsibleObjectTypeID = UserCollapsibleContentHandler::getInstance()->getObjectTypeID($this->objectType->getProcessor()->getCollapsibleObjectTypeName());
+ if ($this->collapsibleObjectTypeID !== null) {
+ $this->collapsedCategoryIDs = UserCollapsibleContentHandler::getInstance()->getCollapsedContent($this->collapsibleObjectTypeID);
+ $this->collapsedCategoryIDs = array_flip($this->collapsedCategoryIDs);
+ }
+
+ parent::readData();
+ }
+
+ /**
+ * @see wcf\page\IPage::show()
+ */
+ public function show() {
+ if ($this->activeMenuItem) {
+ ACPMenu::getInstance()->setActiveMenuItem($this->activeMenuItem);
+ }
+
+ parent::show();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data;
+
+/**
+ * Every categorized object has to implement this interface.
+ *
+ * @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 Community Framework
+ */
+interface ICategorizedObject {
+ /**
+ * Returns the category this object belongs to.
+ *
+ * @return wcf\data\category\Category
+ */
+ public function getCategory();
+}
\ No newline at end of file
--- /dev/null
+<?php
+namespace wcf\data\category;
+use wcf\data\DatabaseObject;
+use wcf\system\category\CategoryHandler;
+use wcf\system\request\IRouteController;
+use wcf\system\WCF;
+
+/**
+ * Represents 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 data.category
+ * @category Community Framework
+ */
+class Category extends DatabaseObject implements IRouteController {
+ /**
+ * category type of this category
+ * @var wcf\system\category\ICategoryType
+ */
+ protected $categoryType = null;
+
+ /**
+ * list of all parent category generations of this category
+ * @var array<wcf\data\category\Category>
+ */
+ protected $parentCategories = null;
+
+ /**
+ * parent category of this category
+ * @var wcf\data\category\Category
+ */
+ protected $parentCategory = null;
+
+ /**
+ * @see wcf\data\DatabaseObject::$databaseTableIndexName
+ */
+ protected static $databaseTableIndexName = 'categoryID';
+
+ /**
+ * @see wcf\data\DatabaseObject::$databaseTableName
+ */
+ protected static $databaseTableName = 'category';
+
+ /**
+ * @see wcf\data\IStorableObject::__get()
+ */
+ public function __get($name) {
+ $value = parent::__get($name);
+
+ // check additional data
+ if ($value === null) {
+ if (isset($this->data['additionalData'][$name])) {
+ $value = $this->data['additionalData'][$name];
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * Returns the category type of this category.
+ *
+ * @return wcf\system\category\ICategoryType
+ */
+ public function getCategoryType() {
+ if ($this->categoryType === null) {
+ $this->categoryType = CategoryHandler::getInstance()->getObjectType($this->objectTypeID)->getProcessor();
+ }
+
+ return $this->categoryType;
+ }
+
+ /**
+ * @see wcf\system\request\IRouteController::getID()
+ */
+ public function getID() {
+ return $this->objectTypeCategoryID;
+ }
+
+ /**
+ * Returns the parent category of this category.
+ *
+ * @return wcf\data\category\Category
+ */
+ public function getParentCategory() {
+ if ($this->parentCategoryID && $this->parentCategory === null) {
+ $this->parentCategory = CategoryHandler::getInstance()->getCategory($this->objectTypeID, $this->parentCategoryID);
+ }
+
+ return $this->parentCategory;
+ }
+
+ /**
+ * Returns the parent categories of this category.
+ *
+ * @return array<wcf\data\category\Category>
+ */
+ public function getParentCategories() {
+ if ($this->parentCategories === null) {
+ $this->parentCategories = array();
+ $parentCaregory = $this;
+ while ($parentCaregory = $parentCaregory->getParentCategory()) {
+ $this->parentCategories[] = $parentCaregory;
+ }
+
+ $this->parentCategories = array_reverse($this->parentCategories);
+ }
+
+ return $this->parentCategories;
+ }
+
+ /**
+ * @see wcf\system\request\IRouteController::getTitle()
+ */
+ public function getTitle() {
+ return WCF::getLanguage()->get($this->title);
+ }
+
+ /**
+ * @see wcf\data\DatabaseObject::handleData()
+ */
+ protected function handleData($data) {
+ // handle additional data
+ $data['additionalData'] = @unserialize($data['additionalData']);
+ if (!is_array($data['additionalData'])) {
+ $data['additionalData'] = array();
+ }
+
+ parent::handleData($data);
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\category;
+use wcf\data\AbstractDatabaseObjectAction;
+use wcf\system\category\CategoryHandler;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\exception\ValidateActionException;
+use wcf\system\user\collapsible\content\UserCollapsibleContentHandler;
+use wcf\system\WCF;
+
+/**
+ * Executes category-related actions.
+ *
+ * @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 CategoryAction extends AbstractDatabaseObjectAction {
+ /**
+ * categorized object type
+ * @var wcf\data\object\type\ObjectType
+ */
+ protected $objectType = null;
+
+ /**
+ * @see wcf\data\AbstractDatabaseObjectAction::delete()
+ */
+ public function delete() {
+ $returnValue = parent::delete();
+
+ // call category types
+ foreach ($this->objects as $categoryEditor) {
+ $categoryEditor->getCategoryType()->afterDeletion($categoryEditor);
+ }
+
+ return $returnValue;
+ }
+
+ /**
+ * Toggles the activity status of categories.
+ */
+ public function toggle() {
+ foreach ($this->objects as $categoryEditor) {
+ $categoryEditor->update(array(
+ 'isDisabled' => 1 - $categoryEditor->isDisabled
+ ));
+ }
+ }
+
+ /**
+ * Toggles the collapse status of categories.
+ */
+ public function toggleContainer() {
+ $objectTypeID = UserCollapsibleContentHandler::getInstance()->getObjectTypeID($this->objects[0]->getCategoryType()->getCollapsibleObjectTypeName());
+ $collapsedCategories = UserCollapsibleContentHandler::getInstance()->getCollapsedContent($objectTypeID);
+
+ $categoryID = $this->objects[0]->categoryID;
+ if (array_search($categoryID, $collapsedCategories) !== false) {
+ UserCollapsibleContentHandler::getInstance()->markAsOpened($objectTypeID, $categoryID);
+ }
+ else {
+ UserCollapsibleContentHandler::getInstance()->markAsCollapsed($objectTypeID, $categoryID);
+ }
+ }
+
+ /**
+ * Updates the position of categories.
+ */
+ public function updatePosition() {
+ $showOrders = array();
+
+ WCF::getDB()->beginTransaction();
+ foreach ($this->parameters['data']['structure'] as $parentCategoryID => $categoryIDs) {
+ if (!isset($showOrders[$parentCategoryID])) {
+ $showOrders[$parentCategoryID] = 1;
+ }
+
+ foreach ($categoryIDs as $categoryID) {
+ $this->objects[$categoryID]->update(array(
+ 'parentCategoryID' => $parentCategoryID ? $this->objects[$parentCategoryID]->objectTypeCategoryID : 0,
+ 'showOrder' => $showOrders[$parentCategoryID]++
+ ));
+ }
+ }
+ WCF::getDB()->commitTransaction();
+ }
+
+ /**
+ * @see wcf\data\AbstractDatabaseObjectAction::validateDelete()
+ */
+ public function validateCreate() {
+ // validate permissions
+ if (count($this->permissionsCreate)) {
+ try {
+ WCF::getSession()->checkPermissions($this->permissionsCreate);
+ }
+ catch (PermissionDeniedException $e) {
+ throw new ValidateActionException('Insufficient permissions');
+ }
+ }
+
+ if (!isset($this->parameters['data']['objectTypeID'])) {
+ throw new ValidateActionException("Missing 'objectTypeID' data parameter");
+ }
+
+ $objectType = CategoryHandler::getInstance()->getObjectType($this->parameters['data']['objectTypeID']);
+ if ($objectType === null) {
+ throw new ValidateActionException("Unknown category object type with id '".$this->parameters['data']['objectTypeID']."'");
+ }
+ if (!$objectType->getProcessor()->canAddCategory()) {
+ throw new ValidateActionException('Insufficient permissions');
+ }
+ }
+
+ /**
+ * @see wcf\data\AbstractDatabaseObjectAction::validateDelete()
+ */
+ public function validateDelete() {
+ // validate permissions
+ if (count($this->permissionsDelete)) {
+ try {
+ WCF::getSession()->checkPermissions($this->permissionsDelete);
+ }
+ catch (PermissionDeniedException $e) {
+ throw new ValidateActionException('Insufficient permissions');
+ }
+ }
+
+ // read objects
+ if (!count($this->objects)) {
+ $this->readObjects();
+ }
+
+ if (!count($this->objects)) {
+ throw new ValidateActionException('Invalid object id');
+ }
+
+ foreach ($this->objects as $categoryEditor) {
+ if (!$categoryEditor->getCategoryType()->canAddCategory()) {
+ throw new ValidateActionException('Insufficient permissions');
+ }
+ }
+ }
+
+ /**
+ * Validates the 'toggle' action.
+ */
+ public function validateToggle() {
+ $this->validateUpdate();
+ }
+
+ /**
+ * Validates the 'toggleContainer' action.
+ */
+ public function validateToggleContainer() {
+ $this->validateUpdate();
+ }
+
+ /**
+ * @see wcf\data\AbstractDatabaseObjectAction::validateUpdate()
+ */
+ public function validateUpdate() {
+ // validate permissions
+ if (count($this->permissionsUpdate)) {
+ try {
+ WCF::getSession()->checkPermissions($this->permissionsUpdate);
+ }
+ catch (PermissionDeniedException $e) {
+ throw new ValidateActionException('Insufficient permissions');
+ }
+ }
+
+ // read objects
+ if (!count($this->objects)) {
+ $this->readObjects();
+ }
+
+ if (!count($this->objects)) {
+ throw new ValidateActionException('Invalid object id');
+ }
+
+ foreach ($this->objects as $categoryEditor) {
+ if (!$categoryEditor->getCategoryType()->canEditCategory()) {
+ throw new ValidateActionException('Insufficient permissions');
+ }
+ }
+ }
+
+ /**
+ * Validates the 'updatePosition' action.
+ */
+ public function validateUpdatePosition() {
+ // validate permissions
+ if (count($this->permissionsUpdate)) {
+ try {
+ WCF::getSession()->checkPermissions($this->permissionsUpdate);
+ }
+ catch (PermissionDeniedException $e) {
+ throw new ValidateActionException('Insufficient permissions');
+ }
+ }
+
+ // validate 'structure' parameter
+ if (!isset($this->parameters['data']['structure'])) {
+ throw new ValidateActionException("Missing 'structure' parameter");
+ }
+ if (!is_array($this->parameters['data']['structure'])) {
+ throw new ValidateActionException("'structure' parameter is no array");
+ }
+
+ // validate given category ids
+ foreach ($this->parameters['data']['structure'] as $parentCategoryID => $categoryIDs) {
+ if ($parentCategoryID) {
+ // validate category
+ $category = CategoryHandler::getInstance()->getCategoryByID($parentCategoryID);
+ if ($category === null) {
+ throw new ValidateActionException("Unknown category with id '".$parentCategoryID."'");
+ }
+
+ $this->objects[$category->categoryID] = new $this->className($category);
+
+ // validate permissions
+ if (!$category->getCategoryType()->canEditCategory()) {
+ throw new ValidateActionException('Insufficient permissions');
+ }
+ }
+
+ foreach ($categoryIDs as $categoryID) {
+ // validate category
+ $category = CategoryHandler::getInstance()->getCategoryByID($categoryID);
+ if ($category === null) {
+ throw new ValidateActionException("Unknown category with id '".$categoryID."'");
+ }
+
+ $this->objects[$category->categoryID] = new $this->className($category);
+
+ // validate permissions
+ if (!$category->getCategoryType()->canEditCategory()) {
+ throw new ValidateActionException('Insufficient permissions');
+ }
+ }
+ }
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\category;
+use wcf\data\object\type\ObjectTypeEditor;
+use wcf\data\DatabaseObjectEditor;
+use wcf\data\IEditableCachedObject;
+use wcf\system\cache\CacheHandler;
+use wcf\system\category\CategoryHandler;
+use wcf\system\exception\SystemException;
+use wcf\system\WCF;
+
+/**
+ * Provides functions to edit 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 data.category
+ * @category Community Framework
+ */
+class CategoryEditor extends DatabaseObjectEditor implements IEditableCachedObject {
+ /**
+ * @see wcf\data\DatabaseObjectDecorator::$baseClass
+ */
+ protected static $baseClass = 'wcf\data\category\Category';
+
+ /**
+ * @see wcf\data\IEditableObject::update()
+ */
+ public function update(array $parameters = array()) {
+ // update show order
+ if (isset($parameters['parentCategoryID']) || isset($parameters['showOrder'])) {
+ if (!isset($parameters['parentCategoryID'])) {
+ $parameters['parentCategoryID'] = $this->parentCategoryID;
+ }
+
+ if (!isset($parameters['showOrder'])) {
+ $parameters['showOrder'] = $this->showOrder;
+ }
+
+ $parameters['showOrder'] = $this->updateShowOrder($parameters['parentCategoryID'], $parameters['showOrder']);
+ }
+
+ parent::update($parameters);
+ }
+
+ /**
+ * Prepares the update of the show order of this category and return the
+ * correct new show order.
+ *
+ * @param integer $parentCategoryID
+ * @param integer $showOrder
+ * @return integer
+ */
+ protected function updateShowOrder($parentCategoryID, $showOrder) {
+ // correct invalid values
+ if ($showOrder <= 0) {
+ $showOrder = 1;
+ }
+
+ if ($parentCategoryID != $this->parentCategoryID) {
+ $sql = "UPDATE ".static::getDatabaseTableName()."
+ SET showOrder = showOrder - 1
+ WHERE showOrder > ?
+ AND parentCategoryID = ?
+ AND objectTypeID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array(
+ $this->showOrder,
+ $this->parentCategoryID,
+ $this->objectTypeID
+ ));
+
+ return static::getShowOrder($this->objectTypeID, $parentCategoryID, $showOrder);
+ }
+ else {
+ if ($showOrder < $this->showOrder) {
+ $sql = "UPDATE ".static::getDatabaseTableName()."
+ SET showOrder = showOrder + 1
+ WHERE showOrder >= ?
+ AND showOrder < ?
+ AND objectTypeID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array(
+ $showOrder,
+ $this->showOrder,
+ $this->objectTypeID
+ ));
+ }
+ else if ($showOrder > $this->showOrder) {
+ $sql = "SELECT MAX(showOrder) AS showOrder
+ FROM ".static::getDatabaseTableName()."
+ WHERE objectTypeID = ?
+ AND parentCategoryID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array(
+ $this->objectTypeID,
+ $this->parentCategoryID
+ ));
+ $row = $statement->fetchArray();
+ $maxShowOrder = 0;
+ if (!empty($row)) {
+ $maxShowOrder = intval($row['showOrder']);
+ }
+
+ if ($showOrder > $maxShowOrder) {
+ $showOrder = $maxShowOrder;
+ }
+
+ $sql = "UPDATE ".static::getDatabaseTableName()."
+ SET showOrder = showOrder - 1
+ WHERE showOrder <= ?
+ AND showOrder > ?
+ AND objectTypeID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array(
+ $showOrder,
+ $this->showOrder,
+ $this->objectTypeID
+ ));
+ }
+
+ return $showOrder;
+ }
+ }
+
+ /**
+ * @see wcf\data\IEditableObject::create()
+ */
+ public static function create(array $parameters = array()) {
+ // handle time
+ if (!isset($parameters['time'])) {
+ $parameters['time'] = TIME_NOW;
+ }
+
+ // handle show order
+ $parameters['showOrder'] = static::getShowOrder($parameters['objectTypeID'], $parameters['parentCategoryID'], $parameters['showOrder']);
+ $parameters['objectTypeCategoryID'] = static::getNextCategoryID($parameters['objectTypeID']);
+
+ // handle additionalData
+ if (!isset($parameters['additionalData'])) {
+ $parameters['additionalData'] = array();
+ }
+ $parameters['additionalData'] = serialize($parameters['additionalData']);
+
+ return parent::create($parameters);
+ }
+
+ /**
+ * @see wcf\data\IEditableObject::deleteAll()
+ */
+ public static function deleteAll(array $objectIDs = array()) {
+ // update positions
+ $sql = "UPDATE ".static::getDatabaseTableName()."
+ SET showOrder = showOrder - 1
+ WHERE parentCategoryID = ?
+ AND showOrder > ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+
+ foreach ($objectIDs as $categoryID) {
+ $category = CategoryHandler::getInstance()->getCategoryByID($categoryID);
+ $statement->execute(array($category->parentCategoryID, $category->showOrder));
+ }
+
+ return parent::deleteAll($objectIDs);
+ }
+
+ /**
+ * Returns the next category for the given object type id.
+ *
+ * @param integer $objectTypeID
+ * @return integer
+ */
+ protected static function getNextCategoryID($objectTypeID) {
+ $objectType = CategoryHandler::getInstance()->getObjectType($objectTypeID);
+ if ($objectType === null) {
+ throw new SystemException("Invalid category object type id '".$objectTypeID."'");
+ }
+
+ $nextCategoryID = 1;
+ if ($objectType->nextCategoryID !== null) {
+ $nextCategoryID = $objectType->nextCategoryID;
+ }
+
+ // update next category additional data
+ $objectTypeEditor = new ObjectTypeEditor($objectType);
+ $objectTypeEditor->update(array(
+ 'additionalData' => serialize(array_merge($objectType->additionalData, array(
+ 'nextCategoryID' => $nextCategoryID + 1
+ )))
+ ));
+
+ // reset object type cache
+ CacheHandler::getInstance()->clear(WCF_DIR.'cache/', 'cache.objectType-*.php');
+
+ return $nextCategoryID;
+ }
+
+ /**
+ * Returns the show order for a new category.
+ *
+ * @param integer $objectTypeID
+ * @param integer $parentCategoryID
+ * @param integer $showOrder
+ * @return integer
+ */
+ protected static function getShowOrder($objectTypeID, $parentCategoryID, $showOrder) {
+ // correct invalid values
+ if ($showOrder <= 0) {
+ $showOrder = 1;
+ }
+
+ $sql = "SELECT MAX(showOrder) AS showOrder
+ FROM ".static::getDatabaseTableName()."
+ WHERE objectTypeID = ?
+ AND parentCategoryID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array(
+ $objectTypeID,
+ $parentCategoryID
+ ));
+ $row = $statement->fetchArray();
+ $maxShowOrder = 0;
+ if (!empty($row)) {
+ $maxShowOrder = intval($row['showOrder']);
+ }
+
+ if ($maxShowOrder && $showOrder <= $maxShowOrder) {
+ $sql = "UPDATE ".static::getDatabaseTableName()."
+ SET showOrder = showOrder + 1
+ WHERE objectTypeID = ?
+ AND showOrder >= ?
+ AND parentCategoryID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array(
+ $objectTypeID,
+ $showOrder,
+ $parentCategoryID
+ ));
+
+ return $showOrder;
+ }
+
+ return $maxShowOrder + 1;
+ }
+
+ /**
+ * @see wcf\data\IEditableCachedObject::resetCache()
+ */
+ public static function resetCache() {
+ CacheHandler::getInstance()->clear(WCF_DIR.'cache/', 'cache.category-'.PACKAGE_ID.'.php');
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\category;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list 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 data.category
+ * @category Community Framework
+ */
+class CategoryList extends DatabaseObjectList { }
--- /dev/null
+<?php
+namespace wcf\data\category;
+use wcf\data\DatabaseObjectDecorator;
+use wcf\data\DatabaseObject;
+use wcf\system\category\CategoryHandler;
+
+/**
+ * Represents a 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 CategoryNode extends DatabaseObjectDecorator implements \RecursiveIterator, \Countable {
+ /**
+ * child category nodes
+ * @var array<wcf\data\category\CategoryNode>
+ */
+ protected $childCategories = array();
+
+ /**
+ * @see wcf\data\DatabaseObjectDecorator::$baseClass
+ */
+ protected static $baseClass = 'wcf\data\category\Category';
+
+ /**
+ * @see wcf\data\DatabaseObjectDecorator::__construct()
+ */
+ public function __construct(DatabaseObject $object, $inludeDisabledCategories = false, array $excludedObjectTypeCategoryIDs = array()) {
+ parent::__construct($object);
+
+ foreach (CategoryHandler::getInstance()->getChildCategories($this->getDecoratedObject()) as $category) {
+ if (!in_array($category->objectTypeCategoryID, $excludedObjectTypeCategoryIDs) && ($inludeDisabledCategories || !$category->isDisabled)) {
+ $this->childCategories[] = new CategoryNode($category, $inludeDisabledCategories, $excludedObjectTypeCategoryIDs);
+ }
+ }
+ }
+
+ /**
+ * @see \Countable::count()
+ */
+ public function count() {
+ return count($this->childCategories);
+ }
+
+ /**
+ * @see \Iterator::current()
+ */
+ public function current() {
+ return $this->childCategories[$this->index];
+ }
+
+ /**
+ * @see \RecursiveIterator::getChildren()
+ */
+ public function getChildren() {
+ return $this->childCategories[$this->index];
+ }
+
+ /**
+ * @see \RecursiveIterator::getChildren()
+ */
+ public function hasChildren() {
+ return !empty($this->childCategories);
+ }
+
+ /**
+ * @see \Iterator::key()
+ */
+ public function key() {
+ return $this->index;
+ }
+
+ /**
+ * @see \Iterator::next()
+ */
+ public function next() {
+ $this->index++;
+ }
+
+ /**
+ * @see \Iterator::rewind()
+ */
+ public function rewind() {
+ $this->index = 0;
+ }
+
+ /**
+ * @see \Iterator::valid()
+ */
+ public function valid() {
+ return isset($this->childCategories[$this->index]);
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\category;
+use wcf\system\exception\SystemException;
+use wcf\system\category\CategoryHandler;
+
+/**
+ * Represents a category node list.
+ *
+ * @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 CategoryNodeList extends \RecursiveIteratorIterator implements \Countable {
+ /**
+ * number of (real) category nodes in this list
+ * @var integer
+ */
+ protected $count = null;
+
+ /**
+ * id of the parent category
+ * @var integer
+ */
+ protected $parentCategoryID = 0;
+
+ /**
+ * Creates a new CategoryNodeList instance.
+ *
+ * @param integer $objectTypeID
+ * @param integer $parentCategoryID
+ * @param boolean $inludeDisabledCategories
+ * @param array<integer> $excludedCategoryIDs
+ */
+ public function __construct($objectTypeID, $parentCategoryID = 0, $inludeDisabledCategories = false, array $excludedObjectTypeCategoryIDs = array()) {
+ $this->parentCategoryID = $parentCategoryID;
+
+ // get parent category
+ if (!$this->parentCategoryID) {
+ // empty node
+ $parentCategory = new Category(null, array(
+ 'categoryID' => $this->parentCategoryID,
+ 'objectTypeID' => $objectTypeID,
+ 'objectTypeCategoryID' => $this->parentCategoryID
+ ));
+ }
+ else {
+ $parentCategory = CategoryHandler::getInstance()->getCategory($objectTypeID, $this->parentCategoryID);
+ if ($parentCategory === null) {
+ throw new SystemException("There is no category with id '".$this->parentCategoryID."' and object type id '".$objectTypeID."'");
+ }
+ }
+
+ parent::__construct(new CategoryNode($parentCategory, $inludeDisabledCategories, $excludedObjectTypeCategoryIDs), \RecursiveIteratorIterator::SELF_FIRST);
+ }
+
+ /**
+ * @see \Countable::count()
+ */
+ public function count() {
+ if ($this->count === null) {
+ $this->count = 0;
+ foreach ($this as $categoryNode) {
+ $this->count++;
+ }
+ }
+
+ return $this->count;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\cache\builder;
+use wcf\data\category\CategoryList;
+use wcf\system\package\PackageDependencyHandler;
+
+/**
+ * Caches the 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 CategoryCacheBuilder implements ICacheBuilder {
+ /**
+ * @see wcf\system\cache\ICacheBuilder::getData()
+ */
+ public function getData(array $cacheResource) {
+ list(, $packageID) = explode('-', $cacheResource['cache']);
+
+ $list = new CategoryList();
+ $list->sqlLimit = 0;
+ $list->sqlJoins = " LEFT JOIN wcf".WCF_N."_object_type object_type
+ ON (object_type.objectTypeID = category.objectTypeID)
+ LEFT JOIN wcf".WCF_N."_package_dependency package_dependency
+ ON (package_dependency.dependency = object_type.packageID)";
+ $list->getConditionBuilder()->add("package_dependency.packageID = ?", array($packageID));
+ $list->sqlOrderBy = "package_dependency.priority ASC, category.showOrder ASC";
+ $list->readObjects();
+
+ $data = array(
+ 'categories' => array(),
+ 'categoryIDs' => array()
+ );
+ foreach ($list as $category) {
+ if (!isset($data['categories'][$category->objectTypeID])) {
+ $data['categories'][$category->objectTypeID] = array();
+ }
+
+ $data['categories'][$category->objectTypeID][$category->objectTypeCategoryID] = $category;
+ $data['categoryIDs'][$category->categoryID] = array(
+ 'objectTypeID' => $category->objectTypeID,
+ 'objectTypeCategoryID' => $category->objectTypeCategoryID
+ );
+ }
+
+ return $data;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\category;
+use wcf\data\category\CategoryEditor;
+use wcf\system\SingletonFactory;
+use wcf\system\WCF;
+
+/**
+ * Abstract implementation of a category 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 system.category
+ * @category Community Framework
+ */
+abstract class AbstractCategoryType extends SingletonFactory implements ICategoryType {
+ /**
+ * name of the acl object type
+ * @var string
+ */
+ protected $aclObjectTypeName = '';
+
+ /**
+ * name of the collapsible object type
+ * @var string
+ */
+ protected $collapsibleObjectTypeName = '';
+
+ /**
+ * language category which contains the language variables of i18n values
+ * @var string
+ */
+ protected $i18nLangVarCategory = '';
+
+ /**
+ * prefix used for language variables in templates
+ * @var string
+ */
+ protected $langVarPrefix = 'wcf.category';
+
+ /**
+ * permission prefix for the add/delete/edit permissions
+ * @var string
+ */
+ protected $permissionPrefix = '';
+
+ /**
+ * @see wcf\system\category\ICategoryType::afterDeletion()
+ */
+ public function afterDeletion(CategoryEditor $categoryEditor) {
+ // move child categories to parent category
+ foreach (CategoryHandler::getInstance()->getChildCategories($categoryEditor->getDecoratedObject()) as $category) {
+ $__categoryEditor = new CategoryEditor($category);
+ $__categoryEditor->update(array(
+ 'parentCategoryID' => $categoryEditor->parentCategoryID
+ ));
+ }
+ }
+
+ /**
+ * @see wcf\system\category\ICategoryType::canAddCategory()
+ */
+ public function canAddCategory() {
+ return WCF::getSession()->getPermission($this->permissionPrefix.'.canAddCategory');
+ }
+
+ /**
+ * @see wcf\system\category\ICategoryType::canDeleteCategory()
+ */
+ public function canDeleteCategory() {
+ return WCF::getSession()->getPermission($this->permissionPrefix.'.canDeleteCategory');
+ }
+
+ /**
+ * @see wcf\system\category\ICategoryType::canEditCategory()
+ */
+ public function canEditCategory() {
+ return WCF::getSession()->getPermission($this->permissionPrefix.'.canEditCategory');
+ }
+
+ /**
+ * @see wcf\system\category\ICategoryType::getACLObjectTypeName()
+ */
+ public function getACLObjectTypeName() {
+ return $this->aclObjectTypeName ?: null;
+ }
+
+ /**
+ * @see wcf\system\category\ICategoryType::getCollapsibleObjectTypeName()
+ */
+ public function getCollapsibleObjectTypeName() {
+ return $this->aclObjectTypeName ?: null;
+ }
+
+ /**
+ * @see wcf\system\category\ICategoryType::getDescriptionLangVarCategory()
+ */
+ public function getDescriptionLangVarCategory() {
+ return $this->i18nLangVarCategory;
+ }
+
+ /**
+ * @see wcf\system\category\ICategoryType::getI18nLangVarPrefix()
+ */
+ public function getI18nLangVarPrefix() {
+ return $this->i18nLangVarCategory.'.category';
+ }
+
+ /**
+ * @see wcf\system\category\ICategoryType::getLangVarPrefix()
+ */
+ public function getLangVarPrefix() {
+ return $this->langVarPrefix;
+ }
+
+ /**
+ * @see wcf\system\category\ICategoryType::getTitleLangVarCategory()
+ */
+ public function getTitleLangVarCategory() {
+ return $this->i18nLangVarCategory;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\category;
+use wcf\data\category\Category;
+use wcf\data\category\CategoryEditor;
+use wcf\system\SingletonFactory;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\system\cache\CacheHandler;
+
+/**
+ * Handles 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.category
+ * @category Community Framework
+ */
+class CategoryHandler extends SingletonFactory {
+ /**
+ * cached categories
+ * @var array<wcf\data\category\Category>
+ */
+ protected $categories = array();
+
+ /**
+ * maps each category id to its object type id and object type category id
+ * @var array<array>
+ */
+ protected $categoryIDs = array();
+
+ /**
+ * mapes the names of the category object types to the object type ids
+ * @var array<integer>
+ */
+ protected $objectTypeIDs = array();
+
+ /**
+ * list of category object types
+ * @var array<wcf\data\object\type>
+ */
+ protected $objectTypes = array();
+
+ /**
+ * Returns all category objects with the given object type id.
+ *
+ * @param integer $objectTypeID
+ * @return array<wcf\data\category\Category>
+ */
+ public function getCategories($objectTypeID) {
+ if (isset($this->categories[$objectTypeID])) {
+ return $this->categories[$objectTypeID];
+ }
+
+ return array();
+ }
+
+ /**
+ * Returns the category object with the given object type id and object
+ * type category id.
+ *
+ * @param integer $objectTypeID
+ * @param integer $objectTypeCategoryID
+ * @return wcf\data\category\Category
+ */
+ public function getCategory($objectTypeID, $objectTypeCategoryID) {
+ if (isset($this->categories[$objectTypeID][$objectTypeCategoryID])) {
+ return $this->categories[$objectTypeID][$objectTypeCategoryID];
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the category object with the given category id.
+ *
+ * @param integer $categoryID
+ * @return wcf\data\category\Category
+ */
+ public function getCategoryByID($categoryID) {
+ if (isset($this->categoryIDs[$categoryID])) {
+ return $this->getCategory($this->categoryIDs[$categoryID]['objectTypeID'], $this->categoryIDs[$categoryID]['objectTypeCategoryID']);
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the child categories of the given category.
+ *
+ * @param wcf\data\category\Category $category
+ * @return array<wcf\data\category\Category>
+ */
+ public function getChildCategories(Category $category) {
+ $categories = array();
+
+ if (isset($this->categories[$category->objectTypeID])) {
+ foreach ($this->categories[$category->objectTypeID] as $__category) {
+ if ($__category->parentCategoryID == $category->objectTypeCategoryID) {
+ $categories[$__category->categoryID] = $__category;
+ }
+ }
+ }
+
+ return $categories;
+ }
+
+ /**
+ * Gets the object type with the given id.
+ *
+ * @param integer $objectTypeID
+ * @return wcf\data\object\type\ObjectType
+ */
+ public function getObjectType($objectTypeID) {
+ if (isset($this->objectTypeIDs[$objectTypeID])) {
+ return $this->getObjectTypeByName($this->objectTypeIDs[$objectTypeID]);
+ }
+
+ return null;
+ }
+
+ /**
+ * Gets the object type with the given name.
+ *
+ * @param string $objectTypeName
+ * @return wcf\data\object\type\ObjectType
+ */
+ public function getObjectTypeByName($objectTypeName) {
+ if (isset($this->objectTypes[$objectTypeName])) {
+ return $this->objectTypes[$objectTypeName];
+ }
+
+ return null;
+ }
+
+ /**
+ * @see wcf\system\SingletonFactory::init()
+ */
+ protected function init() {
+ $this->objectTypes = ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.category');
+ foreach ($this->objectTypes as $objectType) {
+ $this->objectTypeIDs[$objectType->objectTypeID] = $objectType->objectType;
+ }
+
+ $cacheName = 'category-'.PACKAGE_ID;
+ CacheHandler::getInstance()->addResource(
+ $cacheName,
+ WCF_DIR.'cache/cache.'.$cacheName.'.php',
+ 'wcf\system\cache\builder\CategoryCacheBuilder'
+ );
+ $this->categories = CacheHandler::getInstance()->get($cacheName, 'categories');
+ $this->categoryIDs = CacheHandler::getInstance()->get($cacheName, 'categoryIDs');
+ }
+
+ /**
+ * Reloads the category cache.
+ */
+ public function reloadCache() {
+ CacheHandler::getInstance()->clearResource('category-'.PACKAGE_ID);
+
+ $this->init();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\category;
+use wcf\data\category\CategoryEditor;
+
+/**
+ * Every category type has to implement this interface.
+ *
+ * @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
+ */
+interface ICategoryType {
+ /**
+ * Is called right after the given category is deleted.
+ *
+ * @param wcf\data\category\CategoryEditor $categoryEditor
+ */
+ public function afterDeletion(CategoryEditor $categoryEditor);
+
+ /**
+ * Returns true, if the active user can add a category of this type.
+ *
+ * @return boolean
+ */
+ public function canAddCategory();
+
+ /**
+ * Returns true, if the active user can delete a category of this type.
+ *
+ * @return boolean
+ */
+ public function canDeleteCategory();
+
+ /**
+ * Returns true, if the active user can edit a category of this type.
+ *
+ * @return boolean
+ */
+ public function canEditCategory();
+
+ /**
+ * Returns the name of the acl object type for categories of this type.
+ * Returns null if categories of this type don't support acl.
+ *
+ * @return string
+ */
+ public function getACLObjectTypeName();
+
+ /**
+ * Returns the name of the collapsible object type for categories of this
+ * type. Returns null if categories of this type don't support collapsing.
+ *
+ * @return string
+ */
+ public function getCollapsibleObjectTypeName();
+
+ /**
+ * Returns the language variable category for the description language
+ * variables of categories of this type.
+ *
+ * @return string
+ */
+ public function getDescriptionLangVarCategory();
+
+ /**
+ * Returns the prefix used for language variables of i18n values.
+ *
+ * @return string
+ */
+ public function getI18nLangVarPrefix();
+
+ /**
+ * Returns the prefix used for language variables in templates. If a custom
+ * prefix is used (not 'wcf.category'), a fallback to the default prefix
+ * ('wcf.category') is used if the relevant language variable doesn't exist
+ * in the custom category.
+ *
+ * @return string
+ */
+ public function getLangVarPrefix();
+
+ /**
+ * Returns the language variable category for the title language variables
+ * of categories of this type.
+ *
+ * @return string
+ */
+ public function getTitleLangVarCategory();
+}
<item name="wcf.acp.user.sendMail.text"><![CDATA[Nachricht]]></item>
</category>
+ <category name="wcf.category">
+ <item name="wcf.category.add"><![CDATA[Kategorie hinzufügen]]></item>
+ <item name="wcf.category.button.list"><![CDATA[Kategorien auflisten]]></item>
+ <item name="wcf.category.data"><![CDATA[Allgemeine Daten]]></item>
+ <item name="wcf.category.delete.sure"><![CDATA[Sind Sie sicher, dass Sie diese Kategorie löschen möchten? Alle sich in dieser Kategorie befindlichen Unterkategorien werden in die Elternkategorie dieser Kategorie verschoben.]]></item>
+ <item name="wcf.category.description"><![CDATA[Beschreibung]]></item>
+ <item name="wcf.category.edit"><![CDATA[Kategorie bearbeiten]]></item>
+ <item name="wcf.category.isDisabled"><![CDATA[deaktiviert]]></item>
+ <item name="wcf.category.list"><![CDATA[Kategorien]]></item>
+ <item name="wcf.category.list.noneAvailable"><![CDATA[Es wurde noch keine Kategorie hinzugefügt.]]></item>
+ <item name="wcf.category.parentCategoryID"><![CDATA[Übergeordnete Kategorie]]></item>
+ <item name="wcf.category.parentCategoryID.error.invalid"><![CDATA[Die ausgewählte Kategorie existiert nicht.]]></item>
+ <item name="wcf.category.showOrder"><![CDATA[Position]]></item>
+ <item name="wcf.category.title"><![CDATA[Titel]]></item>
+ </category>
+
<category name="wcf.clipboard">
<item name="wcf.clipboard.item.user.assignToGroup"><![CDATA[Benutzergruppe zuweisen]]></item>
<item name="wcf.clipboard.item.user.delete"><![CDATA[Benutzer löschen]]></item>
<item name="wcf.acp.user.sendMail.text"><![CDATA[Message]]></item>
</category>
+ <category name="wcf.category">
+ <item name="wcf.category.add"><![CDATA[Add category]]></item>
+ <item name="wcf.category.button.list"><![CDATA[List categories]]></item>
+ <item name="wcf.category.data"><![CDATA[General data]]></item>
+ <item name="wcf.category.delete.sure"><![CDATA[Are you sure that you would like to delete this category? All sub categories of this category will be moved to the parent category of this category.]]></item>
+ <item name="wcf.category.description"><![CDATA[Description]]></item>
+ <item name="wcf.category.edit"><![CDATA[Edit category]]></item>
+ <item name="wcf.category.isDisabled"><![CDATA[disabled]]></item>
+ <item name="wcf.category.list"><![CDATA[Categories]]></item>
+ <item name="wcf.category.list.noneAvailable"><![CDATA[No category has been added yet.]]></item>
+ <item name="wcf.category.parentCategoryID"><![CDATA[Parent category]]></item>
+ <item name="wcf.category.parentCategoryID.error.invalid"><![CDATA[The chosen category doesn't exist.]]></item>
+ <item name="wcf.category.showOrder"><![CDATA[Position]]></item>
+ <item name="wcf.category.title"><![CDATA[Title]]></item>
+ </category>
+
<category name="wcf.clipboard">
<item name="wcf.clipboard.item.user.assignToGroup"><![CDATA[Assign to user group]]></item>
<item name="wcf.clipboard.item.user.delete"><![CDATA[Delete users]]></item>
cacheResource VARCHAR(255) NOT NULL PRIMARY KEY
);
+DROP TABLE IF EXISTS wcf1_category;
+CREATE TABLE wcf1_category (
+ categoryID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ objectTypeID INT(10) NOT NULL,
+ objectTypeCategoryID INT(10) NOT NULL,
+ parentCategoryID INT(10) NOT NULL,
+ title VARCHAR(255) NOT NULL,
+ description TEXT,
+ showOrder INT(10) NOT NULL,
+ time INT(10) NOT NULL,
+ isDisabled TINYINT(1) NOT NULL DEFAULT 0,
+ additionalData TEXT,
+ UNIQUE KEY (objectTypeID, objectTypeCategoryID)
+);
+
DROP TABLE IF EXISTS wcf1_cleanup_listener;
CREATE TABLE wcf1_cleanup_listener (
listenerID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
ALTER TABLE wcf1_application ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
ALTER TABLE wcf1_application ADD FOREIGN KEY (groupID) REFERENCES wcf1_application_group (groupID) ON DELETE SET NULL;
+ALTER TABLE wcf1_category ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
+
ALTER TABLE wcf1_cleanup_listener ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
ALTER TABLE wcf1_cleanup_log ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;