Merge branch '5.3' into 5.4
authorTim Düsterhus <duesterhus@woltlab.com>
Thu, 26 Aug 2021 08:32:39 +0000 (10:32 +0200)
committerTim Düsterhus <duesterhus@woltlab.com>
Thu, 26 Aug 2021 08:32:39 +0000 (10:32 +0200)
1  2 
wcfsetup/install/files/lib/data/AbstractDatabaseObjectAction.class.php

index 0bdf307df134acaa3906592bc07f9a24da4a8455,8227e0f4c2113ba7265ee02932187c77bd72719b..80169782c8d229b08fbfa050702df7cec29b4ac8
@@@ -14,652 -12,635 +14,652 @@@ use wcf\util\StringUtil
  
  /**
   * Default implementation for DatabaseObject-related actions.
 - * 
 - * @author    Alexander Ebert
 - * @copyright 2001-2020 WoltLab GmbH
 - * @license   GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
 - * @package   WoltLabSuite\Core\Data
 + *
 + * @author  Alexander Ebert
 + * @copyright   2001-2020 WoltLab GmbH
 + * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
 + * @package WoltLabSuite\Core\Data
   */
 -abstract class AbstractDatabaseObjectAction implements IDatabaseObjectAction, IDeleteAction {
 -      /**
 -       * pending action
 -       * @var string
 -       */
 -      protected $action = '';
 -      
 -      /**
 -       * object editor class name
 -       * @var string
 -       */
 -      protected $className = '';
 -      
 -      /**
 -       * list of object ids
 -       * @var integer[]
 -       */
 -      protected $objectIDs = [];
 -      
 -      /**
 -       * list of object editors
 -       * @var DatabaseObjectEditor[]
 -       */
 -      protected $objects = [];
 -      
 -      /**
 -       * multi-dimensional array of parameters required by an action
 -       * @var array
 -       */
 -      protected $parameters = [];
 -      
 -      /**
 -       * list of permissions required to create objects
 -       * @var string[]
 -       */
 -      protected $permissionsCreate = [];
 -      
 -      /**
 -       * list of permissions required to delete objects
 -       * @var string[]
 -       */
 -      protected $permissionsDelete = [];
 -      
 -      /**
 -       * list of permissions required to update objects
 -       * @var string[]
 -       */
 -      protected $permissionsUpdate = [];
 -      
 -      /**
 -       * disallow requests for specified methods if the origin is not the ACP
 -       * @var string[]
 -       */
 -      protected $requireACP = [];
 -      
 -      /**
 -       * Resets cache if any of the listed actions is invoked
 -       * @var string[]
 -       */
 -      protected $resetCache = ['create', 'delete', 'toggle', 'update', 'updatePosition'];
 -      
 -      /**
 -       * values returned by executed action
 -       * @var mixed
 -       */
 -      protected $returnValues = null;
 -      
 -      /**
 -       * allows guest access for all specified methods, by default guest access
 -       * is completely disabled
 -       * @var string[]
 -       */
 -      protected $allowGuestAccess = [];
 -      
 -      const TYPE_INTEGER = 1;
 -      const TYPE_STRING = 2;
 -      const TYPE_BOOLEAN = 3;
 -      const TYPE_JSON = 4;
 -      
 -      const STRUCT_FLAT = 1;
 -      const STRUCT_ARRAY = 2;
 -      
 -      /**
 -       * Initialize a new DatabaseObject-related action.
 -       * 
 -       * @param       mixed[]         $objects
 -       * @param       string          $action
 -       * @param       array           $parameters
 -       * @throws      SystemException
 -       */
 -      public function __construct(array $objects, $action, array $parameters = []) {
 -              // set class name
 -              if (empty($this->className)) {
 -                      $className = get_called_class();
 -                      
 -                      if (mb_substr($className, -6) == 'Action') {
 -                              $this->className = mb_substr($className, 0, -6).'Editor';
 -                      }
 -              }
 -              
 -              $indexName = call_user_func([$this->className, 'getDatabaseTableIndexName']);
 -              $baseClass = call_user_func([$this->className, 'getBaseClass']);
 -              
 -              foreach ($objects as $object) {
 -                      if (is_object($object)) {
 -                              if ($object instanceof $this->className) {
 -                                      $this->objects[] = $object;
 -                              }
 -                              else if ($object instanceof $baseClass) {
 -                                      $this->objects[] = new $this->className($object);
 -                              }
 -                              else {
 -                                      throw new SystemException('invalid value of parameter objects given');
 -                              }
 -                              
 -                              /** @noinspection PhpVariableVariableInspection */
 -                              $this->objectIDs[] = $object->$indexName;
 -                      }
 -                      else {
 -                              $this->objectIDs[] = $object;
 -                      }
 -              }
 -              
 -              $this->action = $action;
 -              $this->parameters = $parameters;
 -              
 -              // initialize further settings
 -              $this->__init($baseClass, $indexName);
 -              
 -              // fire event action
 -              EventHandler::getInstance()->fireAction($this, 'initializeAction');
 -      }
 -      
 -      /**
 -       * This function can be overridden in children to perform custom initialization
 -       * of a DBOAction before the 'initializeAction' event is fired.
 -       * 
 -       * @param       string          $baseClass
 -       * @param       string          $indexName
 -       */
 -      protected function __init($baseClass, $indexName) {
 -              // does nothing
 -      }
 -      
 -      /**
 -       * @inheritDoc
 -       */
 -      public function validateAction() {
 -              // validate if user is logged in
 -              if (!WCF::getUser()->userID && !in_array($this->getActionName(), $this->allowGuestAccess)) {
 -                      throw new PermissionDeniedException();
 -              }
 -              else if (!RequestHandler::getInstance()->isACPRequest() && in_array($this->getActionName(), $this->requireACP)) {
 -                      // attempt to invoke method, but origin is not the ACP
 -                      throw new PermissionDeniedException();
 -              }
 -              
 -              // validate action name
 -              if (!method_exists($this, $this->getActionName())) {
 -                      throw new SystemException("unknown action '".$this->getActionName()."'");
 -              }
 -              
 -              $actionName = 'validate'.StringUtil::firstCharToUpperCase($this->getActionName());
 -              if (!method_exists($this, $actionName)) {
 -                      throw new PermissionDeniedException();
 -              }
 -              
 -              // validate action
 -              $this->{$actionName}();
 -              
 -              // fire event action
 -              EventHandler::getInstance()->fireAction($this, 'validateAction');
 -      }
 -      
 -      /**
 -       * @inheritDoc
 -       */
 -      public function executeAction() {
 -              // execute action
 -              if (!method_exists($this, $this->getActionName())) {
 -                      throw new SystemException("call to undefined function '".$this->getActionName()."'");
 -              }
 -              
 -              $this->returnValues = $this->{$this->getActionName()}();
 -              
 -              // reset cache
 -              if (in_array($this->getActionName(), $this->resetCache)) {
 -                      $this->resetCache();
 -              }
 -              
 -              // fire event action
 -              EventHandler::getInstance()->fireAction($this, 'finalizeAction');
 -              
 -              return $this->getReturnValues();
 -      }
 -      
 -      /**
 -       * Resets cache of database object.
 -       */
 -      protected function resetCache() {
 -              if (is_subclass_of($this->className, IEditableCachedObject::class)) {
 -                      call_user_func([$this->className, 'resetCache']);
 -              }
 -      }
 -      
 -      /**
 -       * @inheritDoc
 -       */
 -      public function getActionName() {
 -              return $this->action;
 -      }
 -      
 -      /**
 -       * @inheritDoc
 -       */
 -      public function getObjectIDs() {
 -              return $this->objectIDs;
 -      }
 -      
 -      /**
 -       * Sets the database objects.
 -       * 
 -       * @param       DatabaseObject[]        $objects
 -       */
 -      public function setObjects(array $objects) {
 -              $this->objects = $objects;
 -              
 -              // update object IDs
 -              $this->objectIDs = [];
 -              foreach ($this->getObjects() as $object) {
 -                      $this->objectIDs[] = $object->getObjectID();
 -              }
 -      }
 -      
 -      /**
 -       * @inheritDoc
 -       */
 -      public function getParameters() {
 -              return $this->parameters;
 -      }
 -      
 -      /**
 -       * @inheritDoc
 -       */
 -      public function getReturnValues() {
 -              return [
 -                      'actionName' => $this->action,
 -                      'objectIDs' => $this->getObjectIDs(),
 -                      'returnValues' => $this->returnValues
 -              ];
 -      }
 -      
 -      /**
 -       * Validates permissions and parameters.
 -       */
 -      public function validateCreate() {
 -              // validate permissions
 -              if (is_array($this->permissionsCreate) && !empty($this->permissionsCreate)) {
 -                      WCF::getSession()->checkPermissions($this->permissionsCreate);
 -              }
 -              else {
 -                      throw new PermissionDeniedException();
 -              }
 -      }
 -      
 -      /**
 -       * @inheritDoc
 -       */
 -      public function validateDelete() {
 -              // validate permissions
 -              if (is_array($this->permissionsDelete) && !empty($this->permissionsDelete)) {
 -                      WCF::getSession()->checkPermissions($this->permissionsDelete);
 -              }
 -              else {
 -                      throw new PermissionDeniedException();
 -              }
 -              
 -              // read objects
 -              if (empty($this->objects)) {
 -                      $this->readObjects();
 -                      
 -                      if (empty($this->objects)) {
 -                              throw new UserInputException('objectIDs');
 -                      }
 -              }
 -      }
 -      
 -      /**
 -       * Validates permissions and parameters.
 -       */
 -      public function validateUpdate() {
 -              // validate permissions
 -              if (is_array($this->permissionsUpdate) && !empty($this->permissionsUpdate)) {
 -                      WCF::getSession()->checkPermissions($this->permissionsUpdate);
 -              }
 -              else {
 -                      throw new PermissionDeniedException();
 -              }
 -              
 -              // read objects
 -              if (empty($this->objects)) {
 -                      $this->readObjects();
 -                      
 -                      if (empty($this->objects)) {
 -                              throw new UserInputException('objectIDs');
 -                      }
 -              }
 -      }
 -      
 -      /**
 -       * Creates new database object.
 -       * 
 -       * @return      DatabaseObject
 -       */
 -      public function create() {
 -              return call_user_func([$this->className, 'create'], $this->parameters['data']);
 -      }
 -      
 -      /**
 -       * @inheritDoc
 -       */
 -      public function delete() {
 -              if (empty($this->objects)) {
 -                      $this->readObjects();
 -              }
 -              
 -              // get ids
 -              $objectIDs = [];
 -              foreach ($this->getObjects() as $object) {
 -                      $objectIDs[] = $object->getObjectID();
 -              }
 -              
 -              // execute action
 -              return call_user_func([$this->className, 'deleteAll'], $objectIDs);
 -      }
 -      
 -      /**
 -       * Updates data.
 -       */
 -      public function update() {
 -              if (empty($this->objects)) {
 -                      $this->readObjects();
 -              }
 -              
 -              if (isset($this->parameters['data'])) {
 -                      foreach ($this->getObjects() as $object) {
 -                              $object->update($this->parameters['data']);
 -                      }
 -              }
 -              
 -              if (isset($this->parameters['counters'])) {
 -                      foreach ($this->getObjects() as $object) {
 -                              $object->updateCounters($this->parameters['counters']);
 -                      }
 -              }
 -      }
 -      
 -      /**
 -       * Reads data by data id.
 -       */
 -      protected function readObjects() {
 -              if (empty($this->objectIDs)) {
 -                      return;
 -              }
 -              
 -              // get base class
 -              $baseClass = call_user_func([$this->className, 'getBaseClass']);
 -              
 -              // get db information
 -              $tableName = call_user_func([$this->className, 'getDatabaseTableName']);
 -              $indexName = call_user_func([$this->className, 'getDatabaseTableIndexName']);
 -              
 -              // get objects
 -              $sql = "SELECT  *
 -                      FROM    ".$tableName."
 -                      WHERE   ".$indexName." IN (".str_repeat('?,', count($this->objectIDs) - 1)."?)";
 -              $statement = WCF::getDB()->prepareStatement($sql);
 -              $statement->execute($this->objectIDs);
 -              while ($object = $statement->fetchObject($baseClass)) {
 -                      $this->objects[] = new $this->className($object);
 -              }
 -      }
 -      
 -      /**
 -       * Returns a single object and throws a UserInputException if no or more than one object is given.
 -       * 
 -       * @return      DatabaseObjectEditor
 -       * @throws      UserInputException
 -       */
 -      protected function getSingleObject() {
 -              if (empty($this->objects)) {
 -                      $this->readObjects();
 -              }
 -              
 -              if (count($this->objects) != 1) {
 -                      throw new UserInputException('objectIDs');
 -              }
 -              
 -              reset($this->objects);
 -              return current($this->objects);
 -      }
 -      
 -      /**
 -       * Reads an integer value and validates it.
 -       * 
 -       * @param       string          $variableName
 -       * @param       boolean         $allowEmpty
 -       * @param       string          $arrayIndex
 -       */
 -      protected function readInteger($variableName, $allowEmpty = false, $arrayIndex = '') {
 -              $this->readValue($variableName, $allowEmpty, $arrayIndex, self::TYPE_INTEGER, self::STRUCT_FLAT);
 -      }
 -      
 -      /**
 -       * Reads an integer array and validates it.
 -       * 
 -       * @param       string          $variableName
 -       * @param       boolean         $allowEmpty
 -       * @param       string          $arrayIndex
 -       * @since       3.0
 -       */
 -      protected function readIntegerArray($variableName, $allowEmpty = false, $arrayIndex = '') {
 -              $this->readValue($variableName, $allowEmpty, $arrayIndex, self::TYPE_INTEGER, self::STRUCT_ARRAY);
 -      }
 -      
 -      /**
 -       * Reads a string value and validates it.
 -       * 
 -       * @param       string          $variableName
 -       * @param       boolean         $allowEmpty
 -       * @param       string          $arrayIndex
 -       */
 -      protected function readString($variableName, $allowEmpty = false, $arrayIndex = '') {
 -              $this->readValue($variableName, $allowEmpty, $arrayIndex, self::TYPE_STRING, self::STRUCT_FLAT);
 -      }
 -      
 -      /**
 -       * Reads a string array and validates it.
 -       * 
 -       * @param       string          $variableName
 -       * @param       boolean         $allowEmpty
 -       * @param       string          $arrayIndex
 -       * @since       3.0
 -       */
 -      protected function readStringArray($variableName, $allowEmpty = false, $arrayIndex = '') {
 -              $this->readValue($variableName, $allowEmpty, $arrayIndex, self::TYPE_STRING, self::STRUCT_ARRAY);
 -      }
 -      
 -      /**
 -       * Reads a boolean value and validates it.
 -       * 
 -       * @param       string          $variableName
 -       * @param       boolean         $allowEmpty
 -       * @param       string          $arrayIndex
 -       */
 -      protected function readBoolean($variableName, $allowEmpty = false, $arrayIndex = '') {
 -              $this->readValue($variableName, $allowEmpty, $arrayIndex, self::TYPE_BOOLEAN, self::STRUCT_FLAT);
 -      }
 -      
 -      /**
 -       * Reads a json-encoded value and validates it.
 -       * 
 -       * @param       string          $variableName
 -       * @param       boolean         $allowEmpty
 -       * @param       string          $arrayIndex
 -       */
 -      protected function readJSON($variableName, $allowEmpty = false, $arrayIndex = '') {
 -              $this->readValue($variableName, $allowEmpty, $arrayIndex, self::TYPE_JSON, self::STRUCT_FLAT);
 -      }
 -      
 -      /**
 -       * Reads a value and validates it. If you set $allowEmpty to true, no exception will
 -       * be thrown if the variable evaluates to 0 (integer) or '' (string). Furthermore the
 -       * variable will be always created with a sane value if it does not exist.
 -       * 
 -       * @param       string          $variableName
 -       * @param       boolean         $allowEmpty
 -       * @param       string          $arrayIndex
 -       * @param       integer         $type
 -       * @param       integer         $structure
 -       * @throws      SystemException
 -       * @throws      UserInputException
 -       */
 -      protected function readValue($variableName, $allowEmpty, $arrayIndex, $type, $structure) {
 -              if ($arrayIndex) {
 -                      if (!isset($this->parameters[$arrayIndex])) {
 -                              if ($allowEmpty) {
 -                                      // Implicitly create the structure to permit implicitly defined values.
 -                                      $this->parameters[$arrayIndex] = [];
 -                              }
 -                              else {
 -                                      throw new SystemException("Corrupt parameters, index '" . $arrayIndex . "' is missing");
 -                              }
 -                      }
 -                      
 -                      $target =& $this->parameters[$arrayIndex];
 -              }
 -              else {
 -                      $target =& $this->parameters;
 -              }
 -              
 -              switch ($type) {
 -                      case self::TYPE_INTEGER:
 -                              if (!isset($target[$variableName])) {
 -                                      if ($allowEmpty) {
 -                                              $target[$variableName] = ($structure === self::STRUCT_FLAT) ? 0 : [];
 -                                      }
 -                                      else {
 -                                              throw new UserInputException($variableName);
 -                                      }
 -                              }
 -                              else {
 -                                      if ($structure === self::STRUCT_FLAT) {
 -                                              $target[$variableName] = intval($target[$variableName]);
 -                                              if (!$allowEmpty && !$target[$variableName]) {
 -                                                      throw new UserInputException($variableName);
 -                                              }
 -                                      }
 -                                      else {
 -                                              $target[$variableName] = ArrayUtil::toIntegerArray($target[$variableName]);
 -                                              if (!is_array($target[$variableName])) {
 -                                                      throw new UserInputException($variableName);
 -                                              }
 -                                              
 -                                              for ($i = 0, $length = count($target[$variableName]); $i < $length; $i++) {
 -                                                      if ($target[$variableName][$i] === 0) {
 -                                                              throw new UserInputException($variableName);
 -                                                      }
 -                                              }
 -                                      }
 -                              }
 -                      break;
 -                      
 -                      case self::TYPE_STRING:
 -                              if (!isset($target[$variableName])) {
 -                                      if ($allowEmpty) {
 -                                              $target[$variableName] = ($structure === self::STRUCT_FLAT) ? '' : [];
 -                                      }
 -                                      else {
 -                                              throw new UserInputException($variableName);
 -                                      }
 -                              }
 -                              else {
 -                                      if ($structure === self::STRUCT_FLAT) {
 -                                              $target[$variableName] = StringUtil::trim($target[$variableName]);
 -                                              if (!$allowEmpty && $target[$variableName] === '') {
 -                                                      throw new UserInputException($variableName);
 -                                              }
 -                                      }
 -                                      else {
 -                                              $target[$variableName] = ArrayUtil::trim($target[$variableName]);
 -                                              if (!is_array($target[$variableName])) {
 -                                                      throw new UserInputException($variableName);
 -                                              }
 -                                              
 -                                              for ($i = 0, $length = count($target[$variableName]); $i < $length; $i++) {
 -                                                      if ($target[$variableName][$i] === '') {
 -                                                              throw new UserInputException($variableName);
 -                                                      }
 -                                              }
 -                                      }
 -                              }
 -                      break;
 -                      
 -                      case self::TYPE_BOOLEAN:
 -                              if (!isset($target[$variableName])) {
 -                                      if ($allowEmpty) {
 -                                              $target[$variableName] = false;
 -                                      }
 -                                      else {
 -                                              throw new UserInputException($variableName);
 -                                      }
 -                              }
 -                              else {
 -                                      if (is_numeric($target[$variableName])) {
 -                                              $target[$variableName] = (bool) $target[$variableName];
 -                                      }
 -                                      else {
 -                                              $target[$variableName] = $target[$variableName] != 'false';
 -                                      }
 -                              }
 -                      break;
 -                      
 -                      case self::TYPE_JSON:
 -                              if (!isset($target[$variableName])) {
 -                                      if ($allowEmpty) {
 -                                              $target[$variableName] = [];
 -                                      }
 -                                      else {
 -                                              throw new UserInputException($variableName);
 -                                      }
 -                              }
 -                              else {
 -                                      try {
 -                                              $target[$variableName] = JSON::decode($target[$variableName]);
 -                                      }
 -                                      catch (SystemException $e) {
 -                                              throw new UserInputException($variableName);
 -                                      }
 -                                      
 -                                      if (!$allowEmpty && empty($target[$variableName])) {
 -                                              throw new UserInputException($variableName);
 -                                      }
 -                              }
 -                      break;
 -              }
 -      }
 -      
 -      /**
 -       * Returns object class name.
 -       * 
 -       * @return      string
 -       */
 -      public function getClassName() {
 -              return $this->className;
 -      }
 -      
 -      /**
 -       * Returns a list of currently loaded objects.
 -       * 
 -       * @return      DatabaseObjectEditor[]
 -       */
 -      public function getObjects() {
 -              return $this->objects;
 -      }
 +abstract class AbstractDatabaseObjectAction implements IDatabaseObjectAction, IDeleteAction
 +{
 +    /**
 +     * pending action
 +     * @var string
 +     */
 +    protected $action = '';
 +
 +    /**
 +     * object editor class name
 +     * @var string
 +     */
 +    protected $className = '';
 +
 +    /**
 +     * list of object ids
 +     * @var int[]
 +     */
 +    protected $objectIDs = [];
 +
 +    /**
 +     * list of object editors
 +     * @var DatabaseObjectEditor[]
 +     */
 +    protected $objects = [];
 +
 +    /**
 +     * multi-dimensional array of parameters required by an action
 +     * @var array
 +     */
 +    protected $parameters = [];
 +
 +    /**
 +     * list of permissions required to create objects
 +     * @var string[]
 +     */
 +    protected $permissionsCreate = [];
 +
 +    /**
 +     * list of permissions required to delete objects
 +     * @var string[]
 +     */
 +    protected $permissionsDelete = [];
 +
 +    /**
 +     * list of permissions required to update objects
 +     * @var string[]
 +     */
 +    protected $permissionsUpdate = [];
 +
 +    /**
 +     * disallow requests for specified methods if the origin is not the ACP
 +     * @var string[]
 +     */
 +    protected $requireACP = [];
 +
 +    /**
 +     * Resets cache if any of the listed actions is invoked
 +     * @var string[]
 +     */
 +    protected $resetCache = ['create', 'delete', 'toggle', 'update', 'updatePosition'];
 +
 +    /**
 +     * values returned by executed action
 +     * @var mixed
 +     */
 +    protected $returnValues;
 +
 +    /**
 +     * allows guest access for all specified methods, by default guest access
 +     * is completely disabled
 +     * @var string[]
 +     */
 +    protected $allowGuestAccess = [];
 +
 +    const TYPE_INTEGER = 1;
 +
 +    const TYPE_STRING = 2;
 +
 +    const TYPE_BOOLEAN = 3;
 +
 +    const TYPE_JSON = 4;
 +
 +    const STRUCT_FLAT = 1;
 +
 +    const STRUCT_ARRAY = 2;
 +
 +    /**
 +     * Initialize a new DatabaseObject-related action.
 +     *
 +     * @param mixed[] $objects
 +     * @param string $action
 +     * @param array $parameters
 +     * @throws  SystemException
 +     */
 +    public function __construct(array $objects, $action, array $parameters = [])
 +    {
 +        // set class name
 +        if (empty($this->className)) {
 +            $className = \get_called_class();
 +
 +            if (\mb_substr($className, -6) == 'Action') {
 +                $this->className = \mb_substr($className, 0, -6) . 'Editor';
 +            }
 +        }
 +
 +        $indexName = \call_user_func([$this->className, 'getDatabaseTableIndexName']);
 +        $baseClass = \call_user_func([$this->className, 'getBaseClass']);
 +
 +        foreach ($objects as $object) {
 +            if (\is_object($object)) {
 +                if ($object instanceof $this->className) {
 +                    $this->objects[] = $object;
 +                } elseif ($object instanceof $baseClass) {
 +                    $this->objects[] = new $this->className($object);
 +                } else {
 +                    throw new SystemException('invalid value of parameter objects given');
 +                }
 +
 +                /** @noinspection PhpVariableVariableInspection */
 +                $this->objectIDs[] = $object->{$indexName};
 +            } else {
 +                $this->objectIDs[] = $object;
 +            }
 +        }
 +
 +        $this->action = $action;
 +        $this->parameters = $parameters;
 +
 +        // initialize further settings
 +        $this->__init($baseClass, $indexName);
 +
 +        // fire event action
 +        EventHandler::getInstance()->fireAction($this, 'initializeAction');
 +    }
 +
 +    /**
 +     * This function can be overridden in children to perform custom initialization
 +     * of a DBOAction before the 'initializeAction' event is fired.
 +     *
 +     * @param string $baseClass
 +     * @param string $indexName
 +     */
 +    protected function __init($baseClass, $indexName)
 +    {
 +        // does nothing
 +    }
 +
 +    /**
 +     * @inheritDoc
 +     */
 +    public function validateAction()
 +    {
 +        // validate if user is logged in
 +        if (!WCF::getUser()->userID && !\in_array($this->getActionName(), $this->allowGuestAccess)) {
 +            throw new PermissionDeniedException();
 +        } elseif (
 +            !RequestHandler::getInstance()->isACPRequest() && \in_array(
 +                $this->getActionName(),
 +                $this->requireACP
 +            )
 +        ) {
 +            // attempt to invoke method, but origin is not the ACP
 +            throw new PermissionDeniedException();
 +        }
 +
 +        // validate action name
 +        if (!\method_exists($this, $this->getActionName())) {
 +            throw new SystemException("unknown action '" . $this->getActionName() . "'");
 +        }
 +
 +        $actionName = 'validate' . StringUtil::firstCharToUpperCase($this->getActionName());
 +        if (!\method_exists($this, $actionName)) {
 +            throw new PermissionDeniedException();
 +        }
 +
 +        // validate action
 +        $this->{$actionName}();
 +
 +        // fire event action
 +        EventHandler::getInstance()->fireAction($this, 'validateAction');
 +    }
 +
 +    /**
 +     * @inheritDoc
 +     */
 +    public function executeAction()
 +    {
 +        // execute action
 +        if (!\method_exists($this, $this->getActionName())) {
 +            throw new SystemException("call to undefined function '" . $this->getActionName() . "'");
 +        }
 +
 +        $this->returnValues = $this->{$this->getActionName()}();
 +
 +        // reset cache
 +        if (\in_array($this->getActionName(), $this->resetCache)) {
 +            $this->resetCache();
 +        }
 +
 +        // fire event action
 +        EventHandler::getInstance()->fireAction($this, 'finalizeAction');
 +
 +        return $this->getReturnValues();
 +    }
 +
 +    /**
 +     * Resets cache of database object.
 +     */
 +    protected function resetCache()
 +    {
 +        if (\is_subclass_of($this->className, IEditableCachedObject::class)) {
 +            \call_user_func([$this->className, 'resetCache']);
 +        }
 +    }
 +
 +    /**
 +     * @inheritDoc
 +     */
 +    public function getActionName()
 +    {
 +        return $this->action;
 +    }
 +
 +    /**
 +     * @inheritDoc
 +     */
 +    public function getObjectIDs()
 +    {
 +        return $this->objectIDs;
 +    }
 +
 +    /**
 +     * Sets the database objects.
 +     *
 +     * @param DatabaseObject[] $objects
 +     */
 +    public function setObjects(array $objects)
 +    {
 +        $this->objects = $objects;
 +
 +        // update object IDs
 +        $this->objectIDs = [];
 +        foreach ($this->getObjects() as $object) {
 +            $this->objectIDs[] = $object->getObjectID();
 +        }
 +    }
 +
 +    /**
 +     * @inheritDoc
 +     */
 +    public function getParameters()
 +    {
 +        return $this->parameters;
 +    }
 +
 +    /**
 +     * @inheritDoc
 +     */
 +    public function getReturnValues()
 +    {
 +        return [
 +            'actionName' => $this->action,
 +            'objectIDs' => $this->getObjectIDs(),
 +            'returnValues' => $this->returnValues,
 +        ];
 +    }
 +
 +    /**
 +     * Validates permissions and parameters.
 +     */
 +    public function validateCreate()
 +    {
 +        // validate permissions
 +        if (\is_array($this->permissionsCreate) && !empty($this->permissionsCreate)) {
 +            WCF::getSession()->checkPermissions($this->permissionsCreate);
 +        } else {
 +            throw new PermissionDeniedException();
 +        }
 +    }
 +
 +    /**
 +     * @inheritDoc
 +     */
 +    public function validateDelete()
 +    {
 +        // validate permissions
 +        if (\is_array($this->permissionsDelete) && !empty($this->permissionsDelete)) {
 +            WCF::getSession()->checkPermissions($this->permissionsDelete);
 +        } else {
 +            throw new PermissionDeniedException();
 +        }
 +
 +        // read objects
 +        if (empty($this->objects)) {
 +            $this->readObjects();
 +
 +            if (empty($this->objects)) {
 +                throw new UserInputException('objectIDs');
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Validates permissions and parameters.
 +     */
 +    public function validateUpdate()
 +    {
 +        // validate permissions
 +        if (\is_array($this->permissionsUpdate) && !empty($this->permissionsUpdate)) {
 +            WCF::getSession()->checkPermissions($this->permissionsUpdate);
 +        } else {
 +            throw new PermissionDeniedException();
 +        }
 +
 +        // read objects
 +        if (empty($this->objects)) {
 +            $this->readObjects();
 +
 +            if (empty($this->objects)) {
 +                throw new UserInputException('objectIDs');
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Creates new database object.
 +     *
 +     * @return  DatabaseObject
 +     */
 +    public function create()
 +    {
 +        return \call_user_func([$this->className, 'create'], $this->parameters['data']);
 +    }
 +
 +    /**
 +     * @inheritDoc
 +     */
 +    public function delete()
 +    {
 +        if (empty($this->objects)) {
 +            $this->readObjects();
 +        }
 +
 +        // get ids
 +        $objectIDs = [];
 +        foreach ($this->getObjects() as $object) {
 +            $objectIDs[] = $object->getObjectID();
 +        }
 +
 +        // execute action
 +        return \call_user_func([$this->className, 'deleteAll'], $objectIDs);
 +    }
 +
 +    /**
 +     * Updates data.
 +     */
 +    public function update()
 +    {
 +        if (empty($this->objects)) {
 +            $this->readObjects();
 +        }
 +
 +        if (isset($this->parameters['data'])) {
 +            foreach ($this->getObjects() as $object) {
 +                $object->update($this->parameters['data']);
 +            }
 +        }
 +
 +        if (isset($this->parameters['counters'])) {
 +            foreach ($this->getObjects() as $object) {
 +                $object->updateCounters($this->parameters['counters']);
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Reads data by data id.
 +     */
 +    protected function readObjects()
 +    {
 +        if (empty($this->objectIDs)) {
 +            return;
 +        }
 +
 +        // get base class
 +        $baseClass = \call_user_func([$this->className, 'getBaseClass']);
 +
 +        // get db information
 +        $tableName = \call_user_func([$this->className, 'getDatabaseTableName']);
 +        $indexName = \call_user_func([$this->className, 'getDatabaseTableIndexName']);
 +
 +        // get objects
 +        $sql = "SELECT  *
 +                FROM    " . $tableName . "
 +                WHERE   " . $indexName . " IN (" . \str_repeat('?,', \count($this->objectIDs) - 1) . "?)";
 +        $statement = WCF::getDB()->prepareStatement($sql);
 +        $statement->execute($this->objectIDs);
 +        while ($object = $statement->fetchObject($baseClass)) {
 +            $this->objects[] = new $this->className($object);
 +        }
 +    }
 +
 +    /**
 +     * Returns a single object and throws a UserInputException if no or more than one object is given.
 +     *
-      * @return  DatabaseObject
++     * @return  DatabaseObjectEditor
 +     * @throws  UserInputException
 +     */
 +    protected function getSingleObject()
 +    {
 +        if (empty($this->objects)) {
 +            $this->readObjects();
 +        }
 +
 +        if (\count($this->objects) != 1) {
 +            throw new UserInputException('objectIDs');
 +        }
 +
 +        \reset($this->objects);
 +
 +        return \current($this->objects);
 +    }
 +
 +    /**
 +     * Reads an integer value and validates it.
 +     *
 +     * @param string $variableName
 +     * @param bool $allowEmpty
 +     * @param string $arrayIndex
 +     */
 +    protected function readInteger($variableName, $allowEmpty = false, $arrayIndex = '')
 +    {
 +        $this->readValue($variableName, $allowEmpty, $arrayIndex, self::TYPE_INTEGER, self::STRUCT_FLAT);
 +    }
 +
 +    /**
 +     * Reads an integer array and validates it.
 +     *
 +     * @param string $variableName
 +     * @param bool $allowEmpty
 +     * @param string $arrayIndex
 +     * @since   3.0
 +     */
 +    protected function readIntegerArray($variableName, $allowEmpty = false, $arrayIndex = '')
 +    {
 +        $this->readValue($variableName, $allowEmpty, $arrayIndex, self::TYPE_INTEGER, self::STRUCT_ARRAY);
 +    }
 +
 +    /**
 +     * Reads a string value and validates it.
 +     *
 +     * @param string $variableName
 +     * @param bool $allowEmpty
 +     * @param string $arrayIndex
 +     */
 +    protected function readString($variableName, $allowEmpty = false, $arrayIndex = '')
 +    {
 +        $this->readValue($variableName, $allowEmpty, $arrayIndex, self::TYPE_STRING, self::STRUCT_FLAT);
 +    }
 +
 +    /**
 +     * Reads a string array and validates it.
 +     *
 +     * @param string $variableName
 +     * @param bool $allowEmpty
 +     * @param string $arrayIndex
 +     * @since   3.0
 +     */
 +    protected function readStringArray($variableName, $allowEmpty = false, $arrayIndex = '')
 +    {
 +        $this->readValue($variableName, $allowEmpty, $arrayIndex, self::TYPE_STRING, self::STRUCT_ARRAY);
 +    }
 +
 +    /**
 +     * Reads a boolean value and validates it.
 +     *
 +     * @param string $variableName
 +     * @param bool $allowEmpty
 +     * @param string $arrayIndex
 +     */
 +    protected function readBoolean($variableName, $allowEmpty = false, $arrayIndex = '')
 +    {
 +        $this->readValue($variableName, $allowEmpty, $arrayIndex, self::TYPE_BOOLEAN, self::STRUCT_FLAT);
 +    }
 +
 +    /**
 +     * Reads a json-encoded value and validates it.
 +     *
 +     * @param string $variableName
 +     * @param bool $allowEmpty
 +     * @param string $arrayIndex
 +     */
 +    protected function readJSON($variableName, $allowEmpty = false, $arrayIndex = '')
 +    {
 +        $this->readValue($variableName, $allowEmpty, $arrayIndex, self::TYPE_JSON, self::STRUCT_FLAT);
 +    }
 +
 +    /**
 +     * Reads a value and validates it. If you set $allowEmpty to true, no exception will
 +     * be thrown if the variable evaluates to 0 (int) or '' (string). Furthermore the
 +     * variable will be always created with a sane value if it does not exist.
 +     *
 +     * @param string $variableName
 +     * @param bool $allowEmpty
 +     * @param string $arrayIndex
 +     * @param int $type
 +     * @param int $structure
 +     * @throws  SystemException
 +     * @throws  UserInputException
 +     */
 +    protected function readValue($variableName, $allowEmpty, $arrayIndex, $type, $structure)
 +    {
 +        if ($arrayIndex) {
 +            if (!isset($this->parameters[$arrayIndex])) {
 +                if ($allowEmpty) {
 +                    // Implicitly create the structure to permit implicitly defined values.
 +                    $this->parameters[$arrayIndex] = [];
 +                } else {
 +                    throw new SystemException("Corrupt parameters, index '" . $arrayIndex . "' is missing");
 +                }
 +            }
 +
 +            $target = &$this->parameters[$arrayIndex];
 +        } else {
 +            $target = &$this->parameters;
 +        }
 +
 +        switch ($type) {
 +            case self::TYPE_INTEGER:
 +                if (!isset($target[$variableName])) {
 +                    if ($allowEmpty) {
 +                        $target[$variableName] = ($structure === self::STRUCT_FLAT) ? 0 : [];
 +                    } else {
 +                        throw new UserInputException($variableName);
 +                    }
 +                } else {
 +                    if ($structure === self::STRUCT_FLAT) {
 +                        $target[$variableName] = \intval($target[$variableName]);
 +                        if (!$allowEmpty && !$target[$variableName]) {
 +                            throw new UserInputException($variableName);
 +                        }
 +                    } else {
 +                        $target[$variableName] = ArrayUtil::toIntegerArray($target[$variableName]);
 +                        if (!\is_array($target[$variableName])) {
 +                            throw new UserInputException($variableName);
 +                        }
 +
 +                        for ($i = 0, $length = \count($target[$variableName]); $i < $length; $i++) {
 +                            if ($target[$variableName][$i] === 0) {
 +                                throw new UserInputException($variableName);
 +                            }
 +                        }
 +                    }
 +                }
 +                break;
 +
 +            case self::TYPE_STRING:
 +                if (!isset($target[$variableName])) {
 +                    if ($allowEmpty) {
 +                        $target[$variableName] = ($structure === self::STRUCT_FLAT) ? '' : [];
 +                    } else {
 +                        throw new UserInputException($variableName);
 +                    }
 +                } else {
 +                    if ($structure === self::STRUCT_FLAT) {
 +                        $target[$variableName] = StringUtil::trim($target[$variableName]);
 +                        if (!$allowEmpty && $target[$variableName] === '') {
 +                            throw new UserInputException($variableName);
 +                        }
 +                    } else {
 +                        $target[$variableName] = ArrayUtil::trim($target[$variableName]);
 +                        if (!\is_array($target[$variableName])) {
 +                            throw new UserInputException($variableName);
 +                        }
 +
 +                        for ($i = 0, $length = \count($target[$variableName]); $i < $length; $i++) {
 +                            if ($target[$variableName][$i] === '') {
 +                                throw new UserInputException($variableName);
 +                            }
 +                        }
 +                    }
 +                }
 +                break;
 +
 +            case self::TYPE_BOOLEAN:
 +                if (!isset($target[$variableName])) {
 +                    if ($allowEmpty) {
 +                        $target[$variableName] = false;
 +                    } else {
 +                        throw new UserInputException($variableName);
 +                    }
 +                } else {
 +                    if (\is_numeric($target[$variableName])) {
 +                        $target[$variableName] = (bool)$target[$variableName];
 +                    } else {
 +                        $target[$variableName] = $target[$variableName] != 'false';
 +                    }
 +                }
 +                break;
 +
 +            case self::TYPE_JSON:
 +                if (!isset($target[$variableName])) {
 +                    if ($allowEmpty) {
 +                        $target[$variableName] = [];
 +                    } else {
 +                        throw new UserInputException($variableName);
 +                    }
 +                } else {
 +                    try {
 +                        $target[$variableName] = JSON::decode($target[$variableName]);
 +                    } catch (SystemException $e) {
 +                        throw new UserInputException($variableName);
 +                    }
 +
 +                    if (!$allowEmpty && empty($target[$variableName])) {
 +                        throw new UserInputException($variableName);
 +                    }
 +                }
 +                break;
 +        }
 +    }
 +
 +    /**
 +     * Returns object class name.
 +     *
 +     * @return  string
 +     */
 +    public function getClassName()
 +    {
 +        return $this->className;
 +    }
 +
 +    /**
 +     * Returns a list of currently loaded objects.
 +     *
 +     * @return  DatabaseObjectEditor[]
 +     */
 +    public function getObjects()
 +    {
 +        return $this->objects;
 +    }
  }