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>
21 * @package WoltLabSuite\Core\Data
23 abstract class AbstractDatabaseObjectAction
implements IDatabaseObjectAction
, IDeleteAction
29 protected $action = '';
32 * object editor class name
35 protected $className = '';
41 protected $objectIDs = [];
44 * list of object editors
45 * @var DatabaseObjectEditor[]
47 protected $objects = [];
50 * multi-dimensional array of parameters required by an action
53 protected $parameters = [];
56 * list of permissions required to create objects
59 protected $permissionsCreate = [];
62 * list of permissions required to delete objects
65 protected $permissionsDelete = [];
68 * list of permissions required to update objects
71 protected $permissionsUpdate = [];
74 * disallow requests for specified methods if the origin is not the ACP
77 protected $requireACP = [];
80 * Resets cache if any of the listed actions is invoked
83 protected $resetCache = ['create', 'delete', 'toggle', 'update', 'updatePosition'];
86 * values returned by executed action
89 protected $returnValues;
92 * allows guest access for all specified methods, by default guest access
93 * is completely disabled
96 protected $allowGuestAccess = [];
98 const TYPE_INTEGER
= 1;
100 const TYPE_STRING
= 2;
102 const TYPE_BOOLEAN
= 3;
106 const STRUCT_FLAT
= 1;
108 const STRUCT_ARRAY
= 2;
111 * Initialize a new DatabaseObject-related action.
113 * @param mixed[] $objects
114 * @param string $action
115 * @param array $parameters
116 * @throws SystemException
118 public function __construct(array $objects, $action, array $parameters = [])
121 if (empty($this->className
)) {
122 $className = \
get_called_class();
124 if (\
mb_substr($className, -6) == 'Action') {
125 $this->className
= \
mb_substr($className, 0, -6) . 'Editor';
129 $indexName = \
call_user_func([$this->className
, 'getDatabaseTableIndexName']);
130 $baseClass = \
call_user_func([$this->className
, 'getBaseClass']);
132 foreach ($objects as $object) {
133 if (\
is_object($object)) {
134 if ($object instanceof $this->className
) {
135 $this->objects
[] = $object;
136 } elseif ($object instanceof $baseClass) {
137 $this->objects
[] = new $this->className($object);
139 throw new SystemException('invalid value of parameter objects given');
142 /** @noinspection PhpVariableVariableInspection */
143 $this->objectIDs
[] = $object->{$indexName};
145 $this->objectIDs
[] = $object;
149 $this->action
= $action;
150 $this->parameters
= $parameters;
152 // initialize further settings
153 $this->__init($baseClass, $indexName);
156 EventHandler
::getInstance()->fireAction($this, 'initializeAction');
160 * This function can be overridden in children to perform custom initialization
161 * of a DBOAction before the 'initializeAction' event is fired.
163 * @param string $baseClass
164 * @param string $indexName
166 protected function __init($baseClass, $indexName)
174 public function validateAction()
176 // validate if user is logged in
177 if (!WCF
::getUser()->userID
&& !\
in_array($this->getActionName(), $this->allowGuestAccess
)) {
178 throw new PermissionDeniedException();
180 !RequestHandler
::getInstance()->isACPRequest() && \
in_array(
181 $this->getActionName(),
185 // attempt to invoke method, but origin is not the ACP
186 throw new PermissionDeniedException();
189 // validate action name
190 if (!\
method_exists($this, $this->getActionName())) {
191 throw new SystemException("unknown action '" . $this->getActionName() . "'");
194 $actionName = 'validate' . StringUtil
::firstCharToUpperCase($this->getActionName());
195 if (!\
method_exists($this, $actionName)) {
196 throw new PermissionDeniedException();
200 $this->{$actionName}();
203 EventHandler
::getInstance()->fireAction($this, 'validateAction');
209 public function executeAction()
212 if (!\
method_exists($this, $this->getActionName())) {
213 throw new SystemException("call to undefined function '" . $this->getActionName() . "'");
216 $this->returnValues
= $this->{$this->getActionName()}();
219 if (\
in_array($this->getActionName(), $this->resetCache
)) {
224 EventHandler
::getInstance()->fireAction($this, 'finalizeAction');
226 return $this->getReturnValues();
230 * Resets cache of database object.
232 protected function resetCache()
234 if (\
is_subclass_of($this->className
, IEditableCachedObject
::class)) {
235 \
call_user_func([$this->className
, 'resetCache']);
242 public function getActionName()
244 return $this->action
;
250 public function getObjectIDs()
252 return $this->objectIDs
;
256 * Sets the database objects.
258 * @param DatabaseObject[] $objects
260 public function setObjects(array $objects)
262 $this->objects
= $objects;
265 $this->objectIDs
= [];
266 foreach ($this->getObjects() as $object) {
267 $this->objectIDs
[] = $object->getObjectID();
274 public function getParameters()
276 return $this->parameters
;
282 public function getReturnValues()
285 'actionName' => $this->action
,
286 'objectIDs' => $this->getObjectIDs(),
287 'returnValues' => $this->returnValues
,
292 * Validates permissions and parameters.
294 public function validateCreate()
296 // validate permissions
297 if (\
is_array($this->permissionsCreate
) && !empty($this->permissionsCreate
)) {
298 WCF
::getSession()->checkPermissions($this->permissionsCreate
);
300 throw new PermissionDeniedException();
307 public function validateDelete()
309 // validate permissions
310 if (\
is_array($this->permissionsDelete
) && !empty($this->permissionsDelete
)) {
311 WCF
::getSession()->checkPermissions($this->permissionsDelete
);
313 throw new PermissionDeniedException();
317 if (empty($this->objects
)) {
318 $this->readObjects();
320 if (empty($this->objects
)) {
321 throw new UserInputException('objectIDs');
327 * Validates permissions and parameters.
329 public function validateUpdate()
331 // validate permissions
332 if (\
is_array($this->permissionsUpdate
) && !empty($this->permissionsUpdate
)) {
333 WCF
::getSession()->checkPermissions($this->permissionsUpdate
);
335 throw new PermissionDeniedException();
339 if (empty($this->objects
)) {
340 $this->readObjects();
342 if (empty($this->objects
)) {
343 throw new UserInputException('objectIDs');
349 * Creates new database object.
351 * @return DatabaseObject
353 public function create()
355 return \
call_user_func([$this->className
, 'create'], $this->parameters
['data']);
361 public function delete()
363 if (empty($this->objects
)) {
364 $this->readObjects();
369 foreach ($this->getObjects() as $object) {
370 $objectIDs[] = $object->getObjectID();
374 return \
call_user_func([$this->className
, 'deleteAll'], $objectIDs);
380 public function update()
382 if (empty($this->objects
)) {
383 $this->readObjects();
386 if (isset($this->parameters
['data'])) {
387 foreach ($this->getObjects() as $object) {
388 $object->update($this->parameters
['data']);
392 if (isset($this->parameters
['counters'])) {
393 foreach ($this->getObjects() as $object) {
394 $object->updateCounters($this->parameters
['counters']);
400 * Reads data by data id.
402 protected function readObjects()
404 if (empty($this->objectIDs
)) {
409 $baseClass = \
call_user_func([$this->className
, 'getBaseClass']);
411 // get db information
412 $tableName = \
call_user_func([$this->className
, 'getDatabaseTableName']);
413 $indexName = \
call_user_func([$this->className
, 'getDatabaseTableIndexName']);
417 FROM " . $tableName . "
418 WHERE " . $indexName . " IN (" . \
str_repeat('?,', \
count($this->objectIDs
) - 1) . "?)";
419 $statement = WCF
::getDB()->prepareStatement($sql);
420 $statement->execute($this->objectIDs
);
421 while ($object = $statement->fetchObject($baseClass)) {
422 $this->objects
[] = new $this->className($object);
427 * Returns a single object and throws a UserInputException if no or more than one object is given.
429 * @return DatabaseObject
430 * @throws UserInputException
432 protected function getSingleObject()
434 if (empty($this->objects
)) {
435 $this->readObjects();
438 if (\
count($this->objects
) != 1) {
439 throw new UserInputException('objectIDs');
442 \reset
($this->objects
);
444 return \
current($this->objects
);
448 * Reads an integer value and validates it.
450 * @param string $variableName
451 * @param bool $allowEmpty
452 * @param string $arrayIndex
454 protected function readInteger($variableName, $allowEmpty = false, $arrayIndex = '')
456 $this->readValue($variableName, $allowEmpty, $arrayIndex, self
::TYPE_INTEGER
, self
::STRUCT_FLAT
);
460 * Reads an integer array and validates it.
462 * @param string $variableName
463 * @param bool $allowEmpty
464 * @param string $arrayIndex
467 protected function readIntegerArray($variableName, $allowEmpty = false, $arrayIndex = '')
469 $this->readValue($variableName, $allowEmpty, $arrayIndex, self
::TYPE_INTEGER
, self
::STRUCT_ARRAY
);
473 * Reads a string value and validates it.
475 * @param string $variableName
476 * @param bool $allowEmpty
477 * @param string $arrayIndex
479 protected function readString($variableName, $allowEmpty = false, $arrayIndex = '')
481 $this->readValue($variableName, $allowEmpty, $arrayIndex, self
::TYPE_STRING
, self
::STRUCT_FLAT
);
485 * Reads a string array and validates it.
487 * @param string $variableName
488 * @param bool $allowEmpty
489 * @param string $arrayIndex
492 protected function readStringArray($variableName, $allowEmpty = false, $arrayIndex = '')
494 $this->readValue($variableName, $allowEmpty, $arrayIndex, self
::TYPE_STRING
, self
::STRUCT_ARRAY
);
498 * Reads a boolean value and validates it.
500 * @param string $variableName
501 * @param bool $allowEmpty
502 * @param string $arrayIndex
504 protected function readBoolean($variableName, $allowEmpty = false, $arrayIndex = '')
506 $this->readValue($variableName, $allowEmpty, $arrayIndex, self
::TYPE_BOOLEAN
, self
::STRUCT_FLAT
);
510 * Reads a json-encoded value and validates it.
512 * @param string $variableName
513 * @param bool $allowEmpty
514 * @param string $arrayIndex
516 protected function readJSON($variableName, $allowEmpty = false, $arrayIndex = '')
518 $this->readValue($variableName, $allowEmpty, $arrayIndex, self
::TYPE_JSON
, self
::STRUCT_FLAT
);
522 * Reads a value and validates it. If you set $allowEmpty to true, no exception will
523 * be thrown if the variable evaluates to 0 (int) or '' (string). Furthermore the
524 * variable will be always created with a sane value if it does not exist.
526 * @param string $variableName
527 * @param bool $allowEmpty
528 * @param string $arrayIndex
530 * @param int $structure
531 * @throws SystemException
532 * @throws UserInputException
534 protected function readValue($variableName, $allowEmpty, $arrayIndex, $type, $structure)
537 if (!isset($this->parameters
[$arrayIndex])) {
539 // Implicitly create the structure to permit implicitly defined values.
540 $this->parameters
[$arrayIndex] = [];
542 throw new SystemException("Corrupt parameters, index '" . $arrayIndex . "' is missing");
546 $target = &$this->parameters
[$arrayIndex];
548 $target = &$this->parameters
;
552 case self
::TYPE_INTEGER
:
553 if (!isset($target[$variableName])) {
555 $target[$variableName] = ($structure === self
::STRUCT_FLAT
) ?
0 : [];
557 throw new UserInputException($variableName);
560 if ($structure === self
::STRUCT_FLAT
) {
561 $target[$variableName] = \
intval($target[$variableName]);
562 if (!$allowEmpty && !$target[$variableName]) {
563 throw new UserInputException($variableName);
566 $target[$variableName] = ArrayUtil
::toIntegerArray($target[$variableName]);
567 if (!\
is_array($target[$variableName])) {
568 throw new UserInputException($variableName);
571 for ($i = 0, $length = \
count($target[$variableName]); $i < $length; $i++
) {
572 if ($target[$variableName][$i] === 0) {
573 throw new UserInputException($variableName);
580 case self
::TYPE_STRING
:
581 if (!isset($target[$variableName])) {
583 $target[$variableName] = ($structure === self
::STRUCT_FLAT
) ?
'' : [];
585 throw new UserInputException($variableName);
588 if ($structure === self
::STRUCT_FLAT
) {
589 $target[$variableName] = StringUtil
::trim($target[$variableName]);
590 if (!$allowEmpty && $target[$variableName] === '') {
591 throw new UserInputException($variableName);
594 $target[$variableName] = ArrayUtil
::trim($target[$variableName]);
595 if (!\
is_array($target[$variableName])) {
596 throw new UserInputException($variableName);
599 for ($i = 0, $length = \
count($target[$variableName]); $i < $length; $i++
) {
600 if ($target[$variableName][$i] === '') {
601 throw new UserInputException($variableName);
608 case self
::TYPE_BOOLEAN
:
609 if (!isset($target[$variableName])) {
611 $target[$variableName] = false;
613 throw new UserInputException($variableName);
616 if (\
is_numeric($target[$variableName])) {
617 $target[$variableName] = (bool)$target[$variableName];
619 $target[$variableName] = $target[$variableName] != 'false';
624 case self
::TYPE_JSON
:
625 if (!isset($target[$variableName])) {
627 $target[$variableName] = [];
629 throw new UserInputException($variableName);
633 $target[$variableName] = JSON
::decode($target[$variableName]);
634 } catch (SystemException
$e) {
635 throw new UserInputException($variableName);
638 if (!$allowEmpty && empty($target[$variableName])) {
639 throw new UserInputException($variableName);
647 * Returns object class name.
651 public function getClassName()
653 return $this->className
;
657 * Returns a list of currently loaded objects.
659 * @return DatabaseObjectEditor[]
661 public function getObjects()
663 return $this->objects
;