5 use wcf\system\event\EventHandler
;
6 use wcf\system\exception\PermissionDeniedException
;
7 use wcf\system\exception\SystemException
;
8 use wcf\system\exception\UserInputException
;
9 use wcf\system\request\RequestHandler
;
11 use wcf\util\ArrayUtil
;
13 use wcf\util\StringUtil
;
16 * Default implementation for DatabaseObject-related actions.
18 * @author Alexander Ebert
19 * @copyright 2001-2020 WoltLab GmbH
20 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
22 abstract class AbstractDatabaseObjectAction
implements IDatabaseObjectAction
, IDeleteAction
28 protected $action = '';
31 * object editor class name
34 protected $className = '';
40 protected $objectIDs = [];
43 * list of object editors
44 * @var DatabaseObjectEditor[]
46 protected $objects = [];
49 * multi-dimensional array of parameters required by an action
52 protected $parameters = [];
55 * list of permissions required to create objects
58 protected $permissionsCreate = [];
61 * list of permissions required to delete objects
64 protected $permissionsDelete = [];
67 * list of permissions required to update objects
70 protected $permissionsUpdate = [];
73 * disallow requests for specified methods if the origin is not the ACP
76 protected $requireACP = [];
79 * Resets cache if any of the listed actions is invoked
82 protected $resetCache = ['create', 'delete', 'toggle', 'update', 'updatePosition'];
85 * values returned by executed action
88 protected $returnValues;
91 * allows guest access for all specified methods, by default guest access
92 * is completely disabled
95 protected $allowGuestAccess = [];
97 const TYPE_INTEGER
= 1;
99 const TYPE_STRING
= 2;
101 const TYPE_BOOLEAN
= 3;
105 const STRUCT_FLAT
= 1;
107 const STRUCT_ARRAY
= 2;
110 * Initialize a new DatabaseObject-related action.
112 * @param mixed[] $objects
113 * @param string $action
114 * @param array $parameters
115 * @throws SystemException
117 public function __construct(array $objects, $action, array $parameters = [])
120 if (empty($this->className
)) {
121 $className = static::class;
123 if (\
mb_substr($className, -6) == 'Action') {
124 $this->className
= \
mb_substr($className, 0, -6) . 'Editor';
128 $indexName = \
call_user_func([$this->className
, 'getDatabaseTableIndexName']);
129 $baseClass = \
call_user_func([$this->className
, 'getBaseClass']);
131 foreach ($objects as $object) {
132 if (\
is_object($object)) {
133 if ($object instanceof $this->className
) {
134 $this->objects
[] = $object;
135 } elseif ($object instanceof $baseClass) {
136 $this->objects
[] = new $this->className($object);
138 throw new SystemException('invalid value of parameter objects given');
141 /** @noinspection PhpVariableVariableInspection */
142 $this->objectIDs
[] = $object->{$indexName};
144 $this->objectIDs
[] = $object;
148 $this->action
= $action;
149 $this->parameters
= $parameters;
151 // initialize further settings
152 $this->__init($baseClass, $indexName);
155 EventHandler
::getInstance()->fireAction($this, 'initializeAction');
159 * This function can be overridden in children to perform custom initialization
160 * of a DBOAction before the 'initializeAction' event is fired.
162 * @param string $baseClass
163 * @param string $indexName
165 protected function __init($baseClass, $indexName)
173 public function validateAction()
175 // validate if user is logged in
176 if (!WCF
::getUser()->userID
&& !\
in_array($this->getActionName(), $this->allowGuestAccess
)) {
177 throw new PermissionDeniedException();
179 !RequestHandler
::getInstance()->isACPRequest() && \
in_array(
180 $this->getActionName(),
184 // attempt to invoke method, but origin is not the ACP
185 throw new PermissionDeniedException();
188 // validate action name
189 if (!\
method_exists($this, $this->getActionName())) {
190 throw new SystemException("unknown action '" . $this->getActionName() . "'");
193 $actionName = 'validate' . StringUtil
::firstCharToUpperCase($this->getActionName());
194 if (!\
method_exists($this, $actionName)) {
195 throw new PermissionDeniedException();
199 $this->{$actionName}();
202 EventHandler
::getInstance()->fireAction($this, 'validateAction');
208 public function executeAction()
211 if (!\
method_exists($this, $this->getActionName())) {
212 throw new SystemException("call to undefined function '" . $this->getActionName() . "'");
215 $this->returnValues
= $this->{$this->getActionName()}();
218 if (\
in_array($this->getActionName(), $this->resetCache
)) {
223 EventHandler
::getInstance()->fireAction($this, 'finalizeAction');
225 return $this->getReturnValues();
229 * Resets cache of database object.
231 protected function resetCache()
233 if (\
is_subclass_of($this->className
, IEditableCachedObject
::class)) {
234 \
call_user_func([$this->className
, 'resetCache']);
241 public function getActionName()
243 return $this->action
;
249 public function getObjectIDs()
251 return $this->objectIDs
;
255 * Sets the database objects.
257 * @param DatabaseObject[] $objects
259 public function setObjects(array $objects)
261 $this->objects
= $objects;
264 $this->objectIDs
= [];
265 foreach ($this->getObjects() as $object) {
266 $this->objectIDs
[] = $object->getObjectID();
273 public function getParameters()
275 return $this->parameters
;
281 public function getReturnValues()
284 'actionName' => $this->action
,
285 'objectIDs' => $this->getObjectIDs(),
286 'returnValues' => $this->returnValues
,
291 * Validates permissions and parameters.
293 public function validateCreate()
295 // validate permissions
296 if (\
is_array($this->permissionsCreate
) && !empty($this->permissionsCreate
)) {
297 WCF
::getSession()->checkPermissions($this->permissionsCreate
);
299 throw new PermissionDeniedException();
306 public function validateDelete()
308 // validate permissions
309 if (\
is_array($this->permissionsDelete
) && !empty($this->permissionsDelete
)) {
310 WCF
::getSession()->checkPermissions($this->permissionsDelete
);
312 throw new PermissionDeniedException();
316 if (empty($this->objects
)) {
317 $this->readObjects();
319 if (empty($this->objects
)) {
320 throw new UserInputException('objectIDs');
326 * Validates permissions and parameters.
328 public function validateUpdate()
330 // validate permissions
331 if (\
is_array($this->permissionsUpdate
) && !empty($this->permissionsUpdate
)) {
332 WCF
::getSession()->checkPermissions($this->permissionsUpdate
);
334 throw new PermissionDeniedException();
338 if (empty($this->objects
)) {
339 $this->readObjects();
341 if (empty($this->objects
)) {
342 throw new UserInputException('objectIDs');
348 * Creates new database object.
350 * @return DatabaseObject
352 public function create()
354 return \
call_user_func([$this->className
, 'create'], $this->parameters
['data']);
360 public function delete()
362 if (empty($this->objects
)) {
363 $this->readObjects();
368 foreach ($this->getObjects() as $object) {
369 $objectIDs[] = $object->getObjectID();
373 return \
call_user_func([$this->className
, 'deleteAll'], $objectIDs);
379 public function update()
381 if (empty($this->objects
)) {
382 $this->readObjects();
385 if (isset($this->parameters
['data'])) {
386 foreach ($this->getObjects() as $object) {
387 $object->update($this->parameters
['data']);
391 if (isset($this->parameters
['counters'])) {
392 foreach ($this->getObjects() as $object) {
393 $object->updateCounters($this->parameters
['counters']);
399 * Reads data by data id.
401 protected function readObjects()
403 if (empty($this->objectIDs
)) {
408 $baseClass = \
call_user_func([$this->className
, 'getBaseClass']);
410 // get db information
411 $tableName = \
call_user_func([$this->className
, 'getDatabaseTableName']);
412 $indexName = \
call_user_func([$this->className
, 'getDatabaseTableIndexName']);
416 FROM " . $tableName . "
417 WHERE " . $indexName . " IN (" . \
str_repeat('?,', \
count($this->objectIDs
) - 1) . "?)";
418 $statement = WCF
::getDB()->prepareStatement($sql);
419 $statement->execute($this->objectIDs
);
420 while ($object = $statement->fetchObject($baseClass)) {
421 $this->objects
[] = new $this->className($object);
426 * Returns a single object and throws a UserInputException if no or more than one object is given.
428 * @return DatabaseObjectEditor
429 * @throws UserInputException
431 protected function getSingleObject()
433 if (empty($this->objects
)) {
434 $this->readObjects();
437 if (\
count($this->objects
) != 1) {
438 throw new UserInputException('objectIDs');
441 \reset
($this->objects
);
443 return \
current($this->objects
);
447 * Reads an integer value and validates it.
449 * @param string $variableName
450 * @param bool $allowEmpty
451 * @param string $arrayIndex
453 protected function readInteger($variableName, $allowEmpty = false, $arrayIndex = '')
455 $this->readValue($variableName, $allowEmpty, $arrayIndex, self
::TYPE_INTEGER
, self
::STRUCT_FLAT
);
459 * Reads an integer array and validates it.
461 * @param string $variableName
462 * @param bool $allowEmpty
463 * @param string $arrayIndex
466 protected function readIntegerArray($variableName, $allowEmpty = false, $arrayIndex = '')
468 $this->readValue($variableName, $allowEmpty, $arrayIndex, self
::TYPE_INTEGER
, self
::STRUCT_ARRAY
);
472 * Reads a string value and validates it.
474 * @param string $variableName
475 * @param bool $allowEmpty
476 * @param string $arrayIndex
478 protected function readString($variableName, $allowEmpty = false, $arrayIndex = '')
480 $this->readValue($variableName, $allowEmpty, $arrayIndex, self
::TYPE_STRING
, self
::STRUCT_FLAT
);
484 * Reads a string array and validates it.
486 * @param string $variableName
487 * @param bool $allowEmpty
488 * @param string $arrayIndex
491 protected function readStringArray($variableName, $allowEmpty = false, $arrayIndex = '')
493 $this->readValue($variableName, $allowEmpty, $arrayIndex, self
::TYPE_STRING
, self
::STRUCT_ARRAY
);
497 * Reads a boolean value and validates it.
499 * @param string $variableName
500 * @param bool $allowEmpty
501 * @param string $arrayIndex
503 protected function readBoolean($variableName, $allowEmpty = false, $arrayIndex = '')
505 $this->readValue($variableName, $allowEmpty, $arrayIndex, self
::TYPE_BOOLEAN
, self
::STRUCT_FLAT
);
509 * Reads a json-encoded value and validates it.
511 * @param string $variableName
512 * @param bool $allowEmpty
513 * @param string $arrayIndex
515 protected function readJSON($variableName, $allowEmpty = false, $arrayIndex = '')
517 $this->readValue($variableName, $allowEmpty, $arrayIndex, self
::TYPE_JSON
, self
::STRUCT_FLAT
);
521 * Reads a value and validates it. If you set $allowEmpty to true, no exception will
522 * be thrown if the variable evaluates to 0 (int) or '' (string). Furthermore the
523 * variable will be always created with a sane value if it does not exist.
525 * @param string $variableName
526 * @param bool $allowEmpty
527 * @param string $arrayIndex
529 * @param int $structure
530 * @throws SystemException
531 * @throws UserInputException
533 protected function readValue($variableName, $allowEmpty, $arrayIndex, $type, $structure)
536 if (!isset($this->parameters
[$arrayIndex])) {
538 // Implicitly create the structure to permit implicitly defined values.
539 $this->parameters
[$arrayIndex] = [];
541 throw new SystemException("Corrupt parameters, index '" . $arrayIndex . "' is missing");
545 $target = &$this->parameters
[$arrayIndex];
547 $target = &$this->parameters
;
551 case self
::TYPE_INTEGER
:
552 if (!isset($target[$variableName])) {
554 $target[$variableName] = ($structure === self
::STRUCT_FLAT
) ?
0 : [];
556 throw new UserInputException($variableName);
559 if ($structure === self
::STRUCT_FLAT
) {
560 $target[$variableName] = \
intval($target[$variableName]);
561 if (!$allowEmpty && !$target[$variableName]) {
562 throw new UserInputException($variableName);
565 $target[$variableName] = ArrayUtil
::toIntegerArray($target[$variableName]);
566 if (!\
is_array($target[$variableName])) {
567 throw new UserInputException($variableName);
570 for ($i = 0, $length = \
count($target[$variableName]); $i < $length; $i++
) {
571 if ($target[$variableName][$i] === 0) {
572 throw new UserInputException($variableName);
579 case self
::TYPE_STRING
:
580 if (!isset($target[$variableName])) {
582 $target[$variableName] = ($structure === self
::STRUCT_FLAT
) ?
'' : [];
584 throw new UserInputException($variableName);
587 if ($structure === self
::STRUCT_FLAT
) {
588 if (!\
is_string($target[$variableName])) {
589 throw new UserInputException($variableName);
591 $target[$variableName] = StringUtil
::trim($target[$variableName]);
592 if (!$allowEmpty && $target[$variableName] === '') {
593 throw new UserInputException($variableName);
596 $target[$variableName] = ArrayUtil
::trim($target[$variableName]);
597 if (!\
is_array($target[$variableName])) {
598 throw new UserInputException($variableName);
601 for ($i = 0, $length = \
count($target[$variableName]); $i < $length; $i++
) {
602 if ($target[$variableName][$i] === '') {
603 throw new UserInputException($variableName);
610 case self
::TYPE_BOOLEAN
:
611 if (!isset($target[$variableName])) {
613 $target[$variableName] = false;
615 throw new UserInputException($variableName);
618 if (\
is_numeric($target[$variableName])) {
619 $target[$variableName] = (bool)$target[$variableName];
621 $target[$variableName] = $target[$variableName] != 'false';
626 case self
::TYPE_JSON
:
627 if (!isset($target[$variableName])) {
629 $target[$variableName] = [];
631 throw new UserInputException($variableName);
635 $target[$variableName] = JSON
::decode($target[$variableName]);
636 } catch (SystemException
$e) {
637 throw new UserInputException($variableName);
640 if (!$allowEmpty && empty($target[$variableName])) {
641 throw new UserInputException($variableName);
649 * Returns object class name.
653 public function getClassName()
655 return $this->className
;
659 * Returns a list of currently loaded objects.
661 * @return DatabaseObjectEditor[]
663 public function getObjects()
665 return $this->objects
;