Moved option handling into external class.
authorAlexander Ebert <ebert@woltlab.com>
Wed, 16 Nov 2011 16:46:00 +0000 (17:46 +0100)
committerAlexander Ebert <ebert@woltlab.com>
Wed, 16 Nov 2011 16:46:00 +0000 (17:46 +0100)
Options were handled within the Form-derived classes itself, causing non-accessible code if calling from outside (e.g. using an AJAX-request).

wcfsetup/install/files/lib/acp/form/AbstractOptionListForm.class.php
wcfsetup/install/files/lib/acp/form/OptionForm.class.php
wcfsetup/install/files/lib/data/option/Option.class.php
wcfsetup/install/files/lib/system/option/IOptionHandler.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/option/OptionHandler.class.php [new file with mode: 0644]

index 679953a46f771128cad3887640d42c0ec02badb5..84fed98c797214413b12d45e4081eccc63b48bb7 100755 (executable)
@@ -1,14 +1,8 @@
 <?php
 namespace wcf\acp\form;
-use wcf\data\option\category\OptionCategory;
-use wcf\data\option\Option;
 use wcf\form\AbstractForm;
-use wcf\system\cache\CacheHandler;
-use wcf\system\exception\SystemException;
 use wcf\system\exception\UserInputException;
-use wcf\system\language\I18nHandler;
-use wcf\util\ClassUtil;
-use wcf\util\StringUtil;
+use wcf\system\option\OptionHandler;
 
 /**
  * This class provides default implementations for a list of options.
@@ -30,54 +24,18 @@ abstract class AbstractOptionListForm extends AbstractForm {
         * @see wcf\form\AbstractForm::$errorType
         */
        public $errorType = array();
-
+       
        /**
         * cache name
         * @var string
         */
-       public $cacheName = 'option-';
+       public $cacheName = 'option';
        
        /**
         * cache class name
         * @var string
         */
        public $cacheClass = 'wcf\system\cache\builder\OptionCacheBuilder';
-
-       /**
-        * list of all option categories
-        * @var array<wcf\data\option\category\OptionCategory>
-        */
-       public $cachedCategories = array();
-       
-       /**
-        * list of all options
-        * @var array<wcf\data\option\Option>
-        */
-       public $cachedOptions = array();
-       
-       /**
-        * category structure
-        * @var array
-        */
-       public $cachedCategoryStructure = array();
-       
-       /**
-        * option structure
-        * @var array
-        */
-       public $cachedOptionToCategories = array();
-       
-       /**
-        * raw option values
-        * @var array<mixed>
-        */
-       public $rawValues = array();
-       
-       /**
-        * option values
-        * @var array<mixed>
-        */
-       public $optionValues = array();
        
        /**
         * Name of the active option category.
@@ -86,62 +44,39 @@ abstract class AbstractOptionListForm extends AbstractForm {
        public $categoryName = '';
        
        /**
-        * Options of the active category.
-        * @var array<Option>
+        * language item pattern
+        * @var string
         */
-       public $options = array();
+       protected $languageItemPattern = '';
        
        /**
-        * Type object cache.
-        * @var array
+        * option handler object
+        * @var wcf\system\option\IOptionHandler
         */
-       public $typeObjects = array();
+       public $optionHandler = null;
        
        /**
-        * language item pattern
+        * option handler class name
         * @var string
         */
-       protected $languageItemPattern = '';
-               
+       public $optionHandlerClassName = 'wcf\system\option\OptionHandler';
+       
        /**
-        * @see wcf\form\IForm::readFormParameters()
+        * @see wcf\page\Page::readParameters()
         */
-       public function readFormParameters() {
-               parent::readFormParameters();
-               
-               if (isset($_POST['values']) && is_array($_POST['values'])) $this->rawValues = $_POST['values'];
+       public function readParameters() {
+               parent::readParameters();
                
-               foreach ($this->options as $option) {
-                       if ($option->supportI18n) {
-                               I18nHandler::getInstance()->register($option->optionName);
-                               I18nHandler::getInstance()->setOptions($option->optionName, $option->packageID, $option->optionValue, $this->languageItemPattern);
-                       }
-               }
-               I18nHandler::getInstance()->readValues();
+               $this->optionHandler = new $this->optionHandlerClassName($this->cacheName, $this->cacheClass, $this->languageItemPattern, $this->categoryName);
        }
-
+       
        /**
-        * Returns an object of the requested option type.
-        * 
-        * @param       string                  $type
-        * @return      wcf\system\option\IOptionType
+        * @see wcf\form\IForm::readFormParameters()
         */
-       protected function getTypeObject($type) {
-               if (!isset($this->typeObjects[$type])) {
-                       $className = 'wcf\system\option\\'.StringUtil::firstCharToUpperCase($type).'OptionType';
-                       
-                       // validate class
-                       if (!class_exists($className)) {
-                               throw new SystemException("unable to find class '".$className."'");
-                       }
-                       if (!ClassUtil::isInstanceOf($className, 'wcf\system\option\IOptionType')) {
-                               throw new SystemException("'".$className."' should implement wcf\system\option\IOptionType");
-                       }
-                       // create instance
-                       $this->typeObjects[$type] = new $className();
-               }
+       public function readFormParameters() {
+               parent::readFormParameters();
                
-               return $this->typeObjects[$type];
+               $this->optionHandler->readUserInput($_POST);
        }
        
        /**
@@ -150,260 +85,18 @@ abstract class AbstractOptionListForm extends AbstractForm {
        public function validate() {
                parent::validate();
                
-               foreach ($this->options as $option) {
-                       try {
-                               $this->validateOption($option);
-                       }
-                       catch (UserInputException $e) {
-                               $this->errorType[$e->getField()] = $e->getType();
-                       }
-               }
+               $this->errorType = $this->optionHandler->validate();
                
                if (count($this->errorType) > 0) {
                        throw new UserInputException('options', $this->errorType);
                }
        }
        
-       /**
-        * Validates an option.
-        * 
-        * @param       Option          $option
-        */
-       protected function validateOption(Option $option) {
-               // get type object
-               $typeObj = $this->getTypeObject($option->optionType);
-               
-               // get new value
-               $newValue = isset($this->rawValues[$option->optionName]) ? $this->rawValues[$option->optionName] : null;
-                               
-               // get save value
-               $this->optionValues[$option->optionName] = $typeObj->getData($option, $newValue);
-                               
-               // validate with pattern
-               if ($option->validationPattern) {
-                       if (!preg_match('~'.$option->validationPattern.'~', $this->optionValues[$option->optionName])) {
-                               throw new UserInputException($option->optionName, 'validationFailed');
-                       }
-               }
-               
-               // validate by type object
-               $typeObj->validate($option, $newValue);
-       }
-       
-       /**
-        * Gets all options and option categories from cache.
-        */
-       protected function readCache() {
-               // init cache
-               $cacheName = $this->cacheName.PACKAGE_ID;
-               CacheHandler::getInstance()->addResource($cacheName, WCF_DIR.'cache/cache.'.$cacheName.'.php', $this->cacheClass);
-               
-               // get cache contents
-               $this->cachedCategories = CacheHandler::getInstance()->get($cacheName, 'categories');
-               $this->cachedOptions = CacheHandler::getInstance()->get($cacheName, 'options');
-               $this->cachedCategoryStructure = CacheHandler::getInstance()->get($cacheName, 'categoryStructure');
-               $this->cachedOptionToCategories = CacheHandler::getInstance()->get($cacheName, 'optionToCategories');
-               
-               // get active options
-               $this->loadActiveOptions($this->categoryName);
-       }
-       
-       /**
-        * Creates a list of all active options.
-        * 
-        * @param       string          $parentCategoryName
-        */
-       protected function loadActiveOptions($parentCategoryName) {
-               if (!isset($this->cachedCategories[$parentCategoryName]) || static::checkCategory($this->cachedCategories[$parentCategoryName])) {
-                       if (isset($this->cachedOptionToCategories[$parentCategoryName])) {
-                               foreach ($this->cachedOptionToCategories[$parentCategoryName] as $optionName) {
-                                       if (static::checkOption($this->cachedOptions[$optionName])) {
-                                               $this->options[$optionName] = $this->cachedOptions[$optionName];
-                                       }
-                               }
-                       }
-                       if (isset($this->cachedCategoryStructure[$parentCategoryName])) {
-                               foreach ($this->cachedCategoryStructure[$parentCategoryName] as $categoryName) {
-                                       $this->loadActiveOptions($categoryName);
-                               }
-                       }
-               }
-       }
-       
-       /**
-        * Checks the required permissions and options of a category.
-        * 
-        * @param       OptionCategory          $category
-        * @return      boolean
-        */
-       protected static function checkCategory(OptionCategory $category) {
-               if ($category->permissions) {
-                       $hasPermission = false;
-                       $permissions = explode(',', $category->permissions);
-                       foreach ($permissions as $permission) {
-                               if (WCF::getSession()->getPermission($permission)) {
-                                       $hasPermission = true;
-                                       break;
-                               }
-                       }
-                       
-                       if (!$hasPermission) return false;
-                       
-               }
-               if ($category->options) {
-                       $hasEnabledOption = false;
-                       $options = explode(',', strtoupper($category->options));
-                       foreach ($options as $option) {
-                               if (defined($option) && constant($option)) {
-                                       $hasEnabledOption = true;
-                                       break;
-                               }
-                       }
-                       
-                       if (!$hasEnabledOption) return false;
-               }
-               
-               return true;
-       }
-       
-       /**
-        * @see wcf\system\option\IOptionType::getFormElement()
-        */
-       protected function getFormElement($type, Option $option) {
-               return $this->getTypeObject($type)->getFormElement($option, (isset($this->optionValues[$option->optionName]) ? $this->optionValues[$option->optionName] : null));
-       }
-       
-       /**
-        * Checks the required permissions and options of an option.
-        * 
-        * @param       Option          $option
-        * @return      boolean
-        */
-       protected static function checkOption(Option $option) {
-               if ($option->permissions) {
-                       $hasPermission = false;
-                       $permissions = explode(',', $option->permissions);
-                       foreach ($permissions as $permission) {
-                               if (WCF::getSession()->getPermission($permission)) {
-                                       $hasPermission = true;
-                                       break;
-                               }
-                       }
-                       
-                       if (!$hasPermission) return false;
-                       
-               }
-               if ($option->options) {
-                       $hasEnabledOption = false;
-                       $options = explode(',', strtoupper($option->options));
-                       foreach ($options as $option) {
-                               if (defined($option) && constant($option)) {
-                                       $hasEnabledOption = true;
-                                       break;
-                               }
-                       }
-                       
-                       if (!$hasEnabledOption) return false;
-               }
-               
-               return true;
-       }
-       
-       /**
-        * Returns the tree of options.
-        * 
-        * @param       string          $parentCategoryName
-        * @param       integer         $level
-        * @return      array
-        */
-       protected function getOptionTree($parentCategoryName = '', $level = 0) {
-               $tree = array();
-               
-               if (isset($this->cachedCategoryStructure[$parentCategoryName])) {
-                       // get super categories
-                       foreach ($this->cachedCategoryStructure[$parentCategoryName] as $superCategoryName) {
-                               $superCategoryObject = $this->cachedCategories[$superCategoryName];
-                               $superCategory = array(
-                                       'object' => $superCategoryObject,
-                                       'categories' => array(),
-                                       'options' => array()
-                               );
-                               
-                               if (static::checkCategory($superCategoryObject)) {
-                                       if ($level <= 1) {
-                                               $superCategory['categories'] = $this->getOptionTree($superCategoryName, $level + 1);
-                                       }
-                                       if ($level > 1 || count($superCategory['categories']) == 0) {
-                                               $superCategory['options'] = $this->getCategoryOptions($superCategoryName);
-                                       }
-                                       else {
-                                               $superCategory['options'] = $this->getCategoryOptions($superCategoryName, false);
-                                       }
-                                       
-                                       if (count($superCategory['categories']) > 0 || count($superCategory['options']) > 0) {
-                                               $tree[] = $superCategory;
-                                       }
-                               }
-                       }
-               }
-               
-               return $tree;
-       }
-       
-       /**
-        * Returns a list with the options of a specific option category.
-        * 
-        * @param       string          $categoryName
-        * @param       boolean         $inherit
-        * @return      array
-        */
-       protected function getCategoryOptions($categoryName = '', $inherit = true) {
-               $children = array();
-               
-               // get sub categories
-               if ($inherit && isset($this->cachedCategoryStructure[$categoryName])) {
-                       foreach ($this->cachedCategoryStructure[$categoryName] as $subCategoryName) {
-                               $children = array_merge($children, $this->getCategoryOptions($subCategoryName));
-                       }
-               }
-               
-               // get options
-               if (isset($this->cachedOptionToCategories[$categoryName])) {
-                       $i = 0;
-                       $last = count($this->cachedOptionToCategories[$categoryName]) - 1;
-                       foreach ($this->cachedOptionToCategories[$categoryName] as $optionName) {
-                               if (!isset($this->options[$optionName]) || !$this->checkOption($this->options[$optionName])) continue;
-                               
-                               // get option object
-                               $option = $this->options[$optionName];
-                               
-                               // get form element html
-                               $html = $this->getFormElement($option->optionType, $option);
-                               
-                               // add option to list
-                               $children[] = array(
-                                       'object' => $option,
-                                       'html' => $html,
-                                       'cssClassName' => $this->getTypeObject($option->optionType)->getCSSClassName()
-                               );
-                               
-                               $i++;
-                       }
-               }
-               
-               return $children;
-       }
-       
        public function readData() {
                parent::readData();
                
                if (!count($_POST)) {
-                       foreach ($this->options as $option) {
-                               if ($option->supportI18n) {
-                                       I18nHandler::getInstance()->register($option->optionName);
-                                       I18nHandler::getInstance()->setOptions($option->optionName, $option->packageID, $option->optionValue, $this->languageItemPattern);
-                               }
-                       }
+                       $this->optionHandler->readData();
                }
        }
 }
index a6b56fd4bc67fe29f84397967cf557c0752a1d66..1f2074025128ea4f91ecdce6094949c36136fb6b 100644 (file)
@@ -52,20 +52,20 @@ class OptionForm extends AbstractOptionListForm {
        /**
         * @see wcf\acp\form\AbstractOptionListForm::$languageItemPattern
         */
-       protected $languageItemPattern = 'wcf.option.option\d+';
+       protected $languageItemPattern = 'wcf.acp.option.option\d+';
        
        /**
         * @see wcf\page\IPage::readParameters()
         */
        public function readParameters() {
-               parent::readParameters();
-               
                if (isset($_REQUEST['id'])) $this->categoryID = intval($_REQUEST['id']);
                $this->category = new OptionCategory($this->categoryID);
                if (!isset($this->category->categoryID)) {
                        throw new IllegalLinkException();
                }
                $this->categoryName = $this->category->categoryName;
+               
+               parent::readParameters();
        }
        
        /**
@@ -84,23 +84,7 @@ class OptionForm extends AbstractOptionListForm {
                parent::save();
                
                // save options
-               $saveOptions = array();
-               foreach ($this->options as $option) {
-                       // handle i18n support
-                       if ($option->supportI18n) {
-                               if (I18nHandler::getInstance()->isPlainValue($option->optionName)) {
-                                       I18nHandler::getInstance()->remove('wcf.option.option' . $option->optionID, $option->packageID);
-                                       $saveOptions[$option->optionID] = I18nHandler::getInstance()->getValue($option->optionName);
-                               }
-                               else {
-                                       I18nHandler::getInstance()->save($option->optionName, 'wcf.option.option' . $option->optionID, 'wcf.option', $option->packageID);
-                                       $saveOptions[$option->optionID] = 'wcf.option.option' . $option->optionID;
-                               }
-                       }
-                       else {
-                               $saveOptions[$option->optionID] = $this->optionValues[$option->optionName];
-                       }
-               }
+               $saveOptions = $this->optionHandler->save('wcf.acp.option', 'wcf.acp.option.option');
                $optionAction = new OptionAction(array(), 'updateAll', array('data' => $saveOptions));
                $optionAction->executeAction();
                $this->saved();
@@ -115,15 +99,8 @@ class OptionForm extends AbstractOptionListForm {
        public function readData() {
                parent::readData();
                
-               if (!count($_POST)) {
-                       // get option values
-                       foreach ($this->options as $option) {
-                               $this->optionValues[$option->optionName] = $option->optionValue;
-                       }
-               }
-               
                // load option tree
-               $this->optionTree = $this->getOptionTree($this->category->categoryName);
+               $this->optionTree = $this->optionHandler->getOptionTree($this->category->categoryName);
                
                if (!count($_POST)) {
                        $this->activeTabMenuItem = $this->optionTree[0]['object']->categoryName;
@@ -158,9 +135,6 @@ class OptionForm extends AbstractOptionListForm {
                        WCFACP::checkMasterPassword();
                }
                
-               // get options and categories from cache
-               $this->readCache();
-               
                // show form
                parent::show();
        }
index 92035ee3cf66c96f4a6c38a2f82559aa9d5f918b..7cc1277fd5107e7244d854c6763711b26773db2e 100644 (file)
@@ -163,4 +163,13 @@ class Option extends DatabaseObject {
                
                return $result;
        }
+       
+       /**
+        * Returns true, if option is visible
+        * 
+        * @return      boolean
+        */
+       public function isVisible() {
+               return !$this->hidden;
+       }
 }
diff --git a/wcfsetup/install/files/lib/system/option/IOptionHandler.class.php b/wcfsetup/install/files/lib/system/option/IOptionHandler.class.php
new file mode 100644 (file)
index 0000000..ef6bdf6
--- /dev/null
@@ -0,0 +1,70 @@
+<?php
+namespace wcf\system\option;
+
+/**
+ * Default interface for option handlers.
+ *
+ * @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.option
+ * @category   Community Framework
+ */
+interface IOptionHandler {
+       /**
+        * Creates a new option handler instance.
+        * 
+        * @param       string          $cacheName
+        * @param       string          $cacheClass
+        * @param       string          $languageItemPattern
+        * @param       string          $categoryName
+        */
+       public function __construct($cacheName, $cacheClass, $languageItemPattern = '', $categoryName = '');
+       
+       /**
+        * Reads user input from given source array.
+        * 
+        * @param       array           $source
+        */
+       public function readUserInput(array &$source);
+       
+       /**
+        * Validates user input, returns an array with all occured errors.
+        * 
+        * @return      array
+        */
+       public function validate();
+       
+       /**
+        * Returns the tree of options.
+        * 
+        * @param       string          $parentCategoryName
+        * @param       integer         $level
+        * @return      array
+        */
+       public function getOptionTree($parentCategoryName = '', $level = 0);
+       
+       /**
+        * Returns a list with the options of a specific option category.
+        * 
+        * @param       string          $categoryName
+        * @param       boolean         $inherit
+        * @return      array
+        */
+       public function getCategoryOptions($categoryName = '', $inherit = true);
+       
+       /**
+        * Initializes i18n support.
+        */
+       public function readData();
+       
+       /**
+        * Saves i18n variables and returns the updated option values.
+        * 
+        * @param       string          $categoryName
+        * @param       string          $optionPrefix
+        * @return      array
+        */
+       public function save($categoryName, $optionPrefix);
+}
diff --git a/wcfsetup/install/files/lib/system/option/OptionHandler.class.php b/wcfsetup/install/files/lib/system/option/OptionHandler.class.php
new file mode 100644 (file)
index 0000000..170802a
--- /dev/null
@@ -0,0 +1,432 @@
+<?php
+namespace wcf\system\option;
+use wcf\data\option\category\OptionCategory;
+use wcf\data\option\Option;
+use wcf\system\cache\CacheHandler;
+use wcf\system\exception\UserInputException;
+use wcf\system\language\I18nHandler;
+use wcf\util\ClassUtil;
+use wcf\util\StringUtil;
+
+/**
+ * Default implementation for option lists.
+ *
+ * @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.option
+ * @category   Community Framework
+ */
+class OptionHandler implements IOptionHandler {
+       /**
+        * cache name
+        * @var string
+        */
+       public $cacheName = '';
+       
+       /**
+        * cache class name
+        * @var string
+        */
+       public $cacheClass = '';
+       
+       /**
+        * list of all option categories
+        * @var array<wcf\data\option\category\OptionCategory>
+        */
+       public $cachedCategories = null;
+       
+       /**
+        * list of all options
+        * @var array<wcf\data\option\Option>
+        */
+       public $cachedOptions = null;
+       
+       /**
+        * category structure
+        * @var array
+        */
+       public $cachedCategoryStructure = null;
+       
+       /**
+        * option structure
+        * @var array
+        */
+       protected $cachedOptionToCategories = null;
+       
+       /**
+        * Name of the active option category.
+        * @var string
+        */
+       public $categoryName = '';
+       
+       /**
+        * Options of the active category.
+        * @var array<Option>
+        */
+       public $options = array();
+       
+       /**
+        * Type object cache.
+        * @var array<wcf\system\option\ITypeObject>
+        */
+       public $typeObjects = array();
+       
+       /**
+        * language item pattern
+        * @var string
+        */
+       public $languageItemPattern = '';
+       
+       /**
+        * option values
+        * @var array<mixed>
+        */
+       public $optionValues = array();
+       
+       /**
+        * raw option values
+        * @var array<mixed>
+        */
+       public $rawValues = array();
+       
+       /**
+        * @see wcf\system\option\IOptionHandler::__construct()
+        */
+       public function __construct($cacheName, $cacheClass, $languageItemPattern = '', $categoryName = '') {
+               $this->cacheName = $cacheName;
+               $this->cacheClass = $cacheClass;
+               $this->categoryName = $categoryName;
+               $this->languageItemPattern = $languageItemPattern;
+               
+               // load cache on init
+               $this->readCache();
+       }
+       
+       /**
+        * @see wcf\system\option\IOptionHandler::readUserInput()
+        */
+       public function readUserInput(array &$source) {
+               if (isset($source['values']) && is_array($source['values'])) $this->rawValues = $source['values'];
+               
+               foreach ($this->options as $option) {
+                       if ($option->supportI18n) {
+                               I18nHandler::getInstance()->register($option->optionName);
+                               I18nHandler::getInstance()->setOptions($option->optionName, $option->packageID, $option->optionValue, $this->languageItemPattern);
+                       }
+               }
+               I18nHandler::getInstance()->readValues();
+       }
+       
+       /**
+        * @see wcf\system\option\IOptionHandler::validate()
+        */
+       public function validate() {
+               $errors = array();
+               
+               foreach ($this->options as $option) {
+                       try {
+                               $this->validateOption($option);
+                       }
+                       catch (UserInputException $e) {
+                               $errors[$e->getField()] = $e->getType();
+                       }
+               }
+               
+               return $errors;
+       }
+       
+       /**
+        * @see wcf\system\option\IOptionHandler::getOptionTree()
+        */
+       public function getOptionTree($parentCategoryName = '', $level = 0) {
+               $tree = array();
+               
+               if (isset($this->cachedCategoryStructure[$parentCategoryName])) {
+                       // get super categories
+                       foreach ($this->cachedCategoryStructure[$parentCategoryName] as $superCategoryName) {
+                               $superCategoryObject = $this->cachedCategories[$superCategoryName];
+                               $superCategory = array(
+                                       'object' => $superCategoryObject,
+                                       'categories' => array(),
+                                       'options' => array()
+                               );
+                               
+                               if (static::checkCategory($superCategoryObject)) {
+                                       if ($level <= 1) {
+                                               $superCategory['categories'] = $this->getOptionTree($superCategoryName, $level + 1);
+                                       }
+                                       if ($level > 1 || count($superCategory['categories']) == 0) {
+                                               $superCategory['options'] = $this->getCategoryOptions($superCategoryName);
+                                       }
+                                       else {
+                                               $superCategory['options'] = $this->getCategoryOptions($superCategoryName, false);
+                                       }
+                                       
+                                       if (count($superCategory['categories']) > 0 || count($superCategory['options']) > 0) {
+                                               $tree[] = $superCategory;
+                                       }
+                               }
+                       }
+               }
+               
+               return $tree;
+       }
+       
+       /**
+        * @see wcf\system\option\IOptionHandler::getCategoryOptions()
+        */
+       public function getCategoryOptions($categoryName = '', $inherit = true) {
+               $children = array();
+               
+               // get sub categories
+               if ($inherit && isset($this->cachedCategoryStructure[$categoryName])) {
+                       foreach ($this->cachedCategoryStructure[$categoryName] as $subCategoryName) {
+                               $children = array_merge($children, $this->getCategoryOptions($subCategoryName));
+                       }
+               }
+               
+               // get options
+               if (isset($this->cachedOptionToCategories[$categoryName])) {
+                       $i = 0;
+                       $last = count($this->cachedOptionToCategories[$categoryName]) - 1;
+                       foreach ($this->cachedOptionToCategories[$categoryName] as $optionName) {
+                               if (!isset($this->options[$optionName]) || !$this->checkOption($this->options[$optionName])) continue;
+                               
+                               // get option object
+                               $option = $this->options[$optionName];
+                               
+                               // get form element html
+                               $html = $this->getFormElement($option->optionType, $option);
+                               
+                               // add option to list
+                               $children[] = array(
+                                       'object' => $option,
+                                       'html' => $html,
+                                       'cssClassName' => $this->getTypeObject($option->optionType)->getCSSClassName()
+                               );
+                               
+                               $i++;
+                       }
+               }
+               
+               return $children;
+       }
+       
+       /**
+        * @see wcf\system\option\IOptionHandler::readData()
+        */
+       public function readData() {
+               foreach ($this->options as $option) {
+                       if ($option->supportI18n) {
+                               I18nHandler::getInstance()->register($option->optionName);
+                               I18nHandler::getInstance()->setOptions($option->optionName, $option->packageID, $option->optionValue, $this->languageItemPattern);
+                       }
+                       
+                       $this->optionValues[$option->optionName] = $option->optionValue;
+               }
+       }
+       
+       /**
+        * @see wcf\system\option\IOptionHandler::save()
+        */
+       public function save($categoryName, $optionPrefix) {
+               $saveOptions = array();
+               
+               foreach ($this->options as $option) {
+                       // handle i18n support
+                       if ($option->supportI18n) {
+                               if (I18nHandler::getInstance()->isPlainValue($option->optionName)) {
+                                       I18nHandler::getInstance()->remove($optionPrefix . $option->optionID, $option->packageID);
+                                       $saveOptions[$option->optionID] = I18nHandler::getInstance()->getValue($option->optionName);
+                               }
+                               else {
+                                       I18nHandler::getInstance()->save($option->optionName, $optionPrefix . $option->optionID, $categoryName, $option->packageID);
+                                       $saveOptions[$option->optionID] = $optionPrefix . $option->optionID;
+                               }
+                       }
+                       else {
+                               $saveOptions[$option->optionID] = $this->optionValues[$option->optionName];
+                       }
+               }
+               
+               return $saveOptions;
+       }
+       
+       /**
+        * Validates an option.
+        * 
+        * @param       wcf\data\option\Option          $option
+        */
+       protected function validateOption(Option $option) {
+               // get type object
+               $typeObj = $this->getTypeObject($option->optionType);
+               
+               // get new value
+               $newValue = isset($this->rawValues[$option->optionName]) ? $this->rawValues[$option->optionName] : null;
+               
+               // get save value
+               $this->optionValues[$option->optionName] = $typeObj->getData($option, $newValue);
+               
+               // validate with pattern
+               if ($option->validationPattern) {
+                       if (!preg_match('~'.$option->validationPattern.'~', $this->optionValues[$option->optionName])) {
+                               throw new UserInputException($option->optionName, 'validationFailed');
+                       }
+               }
+               
+               // validate by type object
+               $typeObj->validate($option, $newValue);
+       }
+       
+       /**
+        * @see wcf\system\option\IOptionType::getFormElement()
+        */
+       protected function getFormElement($type, Option $option) {
+               return $this->getTypeObject($type)->getFormElement($option, (isset($this->optionValues[$option->optionName]) ? $this->optionValues[$option->optionName] : null));
+       }
+       
+       /**
+        * Returns an object of the requested option type.
+        * 
+        * @param       string                  $type
+        * @return      wcf\system\option\IOptionType
+        */
+       protected function getTypeObject($type) {
+               if (!isset($this->typeObjects[$type])) {
+                       $className = 'wcf\system\option\\'.ucfirst($type).'OptionType';
+                       
+                       // validate class
+                       if (!class_exists($className)) {
+                               throw new SystemException("unable to find class '".$className."'");
+                       }
+                       if (!ClassUtil::isInstanceOf($className, 'wcf\system\option\IOptionType')) {
+                               throw new SystemException("'".$className."' should implement wcf\system\option\IOptionType");
+                       }
+                       
+                       // create instance
+                       $this->typeObjects[$type] = new $className();
+               }
+               
+               return $this->typeObjects[$type];
+       }
+       
+       /**
+        * Gets all options and option categories from cache.
+        */
+       protected function readCache() {
+               $cacheName = $this->cacheName . '-' . PACKAGE_ID;
+               CacheHandler::getInstance()->addResource($cacheName, WCF_DIR.'cache/cache.'.$cacheName.'.php', $this->cacheClass);
+               
+               // get cache contents
+               $this->cachedCategories = CacheHandler::getInstance()->get($cacheName, 'categories');
+               $this->cachedOptions = CacheHandler::getInstance()->get($cacheName, 'options');
+               $this->cachedCategoryStructure = CacheHandler::getInstance()->get($cacheName, 'categoryStructure');
+               $this->cachedOptionToCategories = CacheHandler::getInstance()->get($cacheName, 'optionToCategories');
+               
+               // get active options
+               $this->loadActiveOptions($this->categoryName);
+       }
+       
+       /**
+        * Creates a list of all active options.
+        * 
+        * @param       string          $parentCategoryName
+        */
+       protected function loadActiveOptions($parentCategoryName) {
+               if (!isset($this->cachedCategories[$parentCategoryName]) || $this->checkCategory($this->cachedCategories[$parentCategoryName])) {
+                       if (isset($this->cachedOptionToCategories[$parentCategoryName])) {
+                               foreach ($this->cachedOptionToCategories[$parentCategoryName] as $optionName) {
+                                       if ($this->checkOption($this->cachedOptions[$optionName])) {
+                                               $this->options[$optionName] = $this->cachedOptions[$optionName];
+                                       }
+                               }
+                       }
+                       
+                       if (isset($this->cachedCategoryStructure[$parentCategoryName])) {
+                               foreach ($this->cachedCategoryStructure[$parentCategoryName] as $categoryName) {
+                                       $this->loadActiveOptions($categoryName);
+                               }
+                       }
+               }
+       }
+       
+       /**
+        * Checks the required permissions and options of a category.
+        * 
+        * @param       wcf\data\option\category\OptionCategory         $category
+        * @return      boolean
+        */
+       protected function checkCategory(OptionCategory $category) {
+               if ($category->permissions) {
+                       $hasPermission = false;
+                       $permissions = explode(',', $category->permissions);
+                       foreach ($permissions as $permission) {
+                               if (WCF::getSession()->getPermission($permission)) {
+                                       $hasPermission = true;
+                                       break;
+                               }
+                       }
+                       
+                       if (!$hasPermission) return false;
+                       
+               }
+               
+               if ($category->options) {
+                       $hasEnabledOption = false;
+                       $options = explode(',', strtoupper($category->options));
+                       foreach ($options as $option) {
+                               if (defined($option) && constant($option)) {
+                                       $hasEnabledOption = true;
+                                       break;
+                               }
+                       }
+                       
+                       if (!$hasEnabledOption) return false;
+               }
+               
+               return true;
+       }
+       
+       /**
+        * Checks the required permissions and options of an option.
+        * 
+        * @param       wcf\data\option\Option          $option
+        * @return      boolean
+        */
+       protected function checkOption(Option $option) {
+               if ($option->permissions) {
+                       $hasPermission = false;
+                       $permissions = explode(',', $option->permissions);
+                       foreach ($permissions as $permission) {
+                               if (WCF::getSession()->getPermission($permission)) {
+                                       $hasPermission = true;
+                                       break;
+                               }
+                       }
+                       
+                       if (!$hasPermission) return false;
+                       
+               }
+               
+               if ($option->options) {
+                       $hasEnabledOption = false;
+                       $options = explode(',', strtoupper($option->options));
+                       foreach ($options as $option) {
+                               if (defined($option) && constant($option)) {
+                                       $hasEnabledOption = true;
+                                       break;
+                               }
+                       }
+                       
+                       if (!$hasEnabledOption) return false;
+               }
+               
+               if (!$option->isVisible()) {
+                       return false;
+               }
+               
+               return true;
+       }
+}