3 use wcf\system\event\EventHandler
;
4 use wcf\system\exception\PermissionDeniedException
;
5 use wcf\system\exception\SystemException
;
6 use wcf\system\exception\UserInputException
;
7 use wcf\system\request\RequestHandler
;
9 use wcf\util\ClassUtil
;
11 use wcf\util\StringUtil
;
14 * Default implementation for DatabaseObject-related actions.
16 * @author Alexander Ebert
17 * @copyright 2001-2014 WoltLab GmbH
18 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
19 * @package com.woltlab.wcf
21 * @category Community Framework
23 abstract class AbstractDatabaseObjectAction
implements IDatabaseObjectAction
, IDeleteAction
{
28 protected $action = '';
31 * object editor class name
34 protected $className = '';
40 protected $objectIDs = array();
43 * list of object editors
44 * @var array<\wcf\data\DatabaseObjectEditor>
46 protected $objects = array();
49 * multi-dimensional array of parameters required by an action
52 protected $parameters = array();
55 * list of permissions required to create objects
58 protected $permissionsCreate = array();
61 * list of permissions required to delete objects
64 protected $permissionsDelete = array();
67 * list of permissions required to update objects
70 protected $permissionsUpdate = array();
73 * disallow requests for specified methods if the origin is not the ACP
76 protected $requireACP = array();
79 * Resets cache if any of the listed actions is invoked
82 protected $resetCache = array('create', 'delete', 'toggle', 'update', 'updatePosition');
85 * values returned by executed action
88 protected $returnValues = null;
91 * allows guest access for all specified methods, by default guest access
92 * is completely disabled
95 protected $allowGuestAccess = array();
97 const TYPE_INTEGER
= 1;
98 const TYPE_STRING
= 2;
99 const TYPE_BOOLEAN
= 3;
103 * Initialize a new DatabaseObject-related action.
105 * @param array<mixed> $objects
106 * @param string $action
107 * @param array $parameters
109 public function __construct(array $objects, $action, array $parameters = array()) {
111 if (empty($this->className
)) {
112 $className = get_called_class();
114 if (mb_substr($className, -6) == 'Action') {
115 $this->className
= mb_substr($className, 0, -6).'Editor';
119 $indexName = call_user_func(array($this->className
, 'getDatabaseTableIndexName'));
120 $baseClass = call_user_func(array($this->className
, 'getBaseClass'));
122 foreach ($objects as $object) {
123 if (is_object($object)) {
124 if ($object instanceof $this->className
) {
125 $this->objects
[] = $object;
127 else if ($object instanceof $baseClass) {
128 $this->objects
[] = new $this->className($object);
131 throw new SystemException('invalid value of parameter objects given');
134 $this->objectIDs
[] = $object->$indexName;
137 $this->objectIDs
[] = $object;
141 $this->action
= $action;
142 $this->parameters
= $parameters;
145 EventHandler
::getInstance()->fireAction($this, 'initializeAction');
149 * @see \wcf\data\IDatabaseObjectAction::validateAction()
151 public function validateAction() {
152 // validate if user is logged in
153 if (!WCF
::getUser()->userID
&& !in_array($this->getActionName(), $this->allowGuestAccess
)) {
154 throw new PermissionDeniedException();
156 else if (!RequestHandler
::getInstance()->isACPRequest() && in_array($this->getActionName(), $this->requireACP
)) {
157 // attempt to invoke method, but origin is not the ACP
158 throw new PermissionDeniedException();
161 // validate action name
162 if (!method_exists($this, $this->getActionName())) {
163 throw new SystemException("unknown action '".$this->getActionName()."'");
166 $actionName = 'validate'.StringUtil
::firstCharToUpperCase($this->getActionName());
167 if (!method_exists($this, $actionName)) {
168 throw new PermissionDeniedException();
172 call_user_func_array(array($this, $actionName), $this->getParameters());
176 * @see \wcf\data\IDatabaseObjectAction::executeAction()
178 public function executeAction() {
180 if (!method_exists($this, $this->getActionName())) {
181 throw new SystemException("call to undefined function '".$this->getActionName()."'");
184 $this->returnValues
= call_user_func(array($this, $this->getActionName()));
187 if (in_array($this->getActionName(), $this->resetCache
)) {
192 EventHandler
::getInstance()->fireAction($this, 'finalizeAction');
194 return $this->getReturnValues();
198 * Resets cache of database object.
200 protected function resetCache() {
201 if (ClassUtil
::isInstanceOf($this->className
, 'wcf\data\IEditableCachedObject')) {
202 call_user_func(array($this->className
, 'resetCache'));
207 * @see \wcf\data\IDatabaseObjectAction::getActionName()
209 public function getActionName() {
210 return $this->action
;
214 * @see \wcf\data\IDatabaseObjectAction::getObjectIDs()
216 public function getObjectIDs() {
217 return $this->objectIDs
;
221 * Sets the database objects.
223 * @param array<\wcf\data\DatabaseObject> $objects
225 public function setObjects(array $objects) {
226 $this->objects
= $objects;
230 * @see \wcf\data\IDatabaseObjectAction::getParameters()
232 public function getParameters() {
233 return $this->parameters
;
237 * @see \wcf\data\IDatabaseObjectAction::getReturnValues()
239 public function getReturnValues() {
241 'actionName' => $this->action
,
242 'objectIDs' => $this->getObjectIDs(),
243 'returnValues' => $this->returnValues
248 * Validates permissions and parameters.
250 public function validateCreate() {
251 // validate permissions
252 if (is_array($this->permissionsCreate
) && !empty($this->permissionsCreate
)) {
253 WCF
::getSession()->checkPermissions($this->permissionsCreate
);
256 throw new PermissionDeniedException();
261 * @see \wcf\data\IDeleteAction::validateDelete()
263 public function validateDelete() {
264 // validate permissions
265 if (is_array($this->permissionsDelete
) && !empty($this->permissionsDelete
)) {
266 WCF
::getSession()->checkPermissions($this->permissionsDelete
);
269 throw new PermissionDeniedException();
273 if (empty($this->objects
)) {
274 $this->readObjects();
276 if (empty($this->objects
)) {
277 throw new UserInputException('objectIDs');
283 * Validates permissions and parameters.
285 public function validateUpdate() {
286 // validate permissions
287 if (is_array($this->permissionsUpdate
) && !empty($this->permissionsUpdate
)) {
288 WCF
::getSession()->checkPermissions($this->permissionsUpdate
);
291 throw new PermissionDeniedException();
295 if (empty($this->objects
)) {
296 $this->readObjects();
298 if (empty($this->objects
)) {
299 throw new UserInputException('objectIDs');
305 * Creates new database object.
307 * @return \wcf\data\DatabaseObject
309 public function create() {
310 return call_user_func(array($this->className
, 'create'), $this->parameters
['data']);
314 * @see \wcf\data\IDeleteAction::delete()
316 public function delete() {
317 if (empty($this->objects
)) {
318 $this->readObjects();
322 $objectIDs = array();
323 foreach ($this->objects
as $object) {
324 $objectIDs[] = $object->getObjectID();
328 return call_user_func(array($this->className
, 'deleteAll'), $objectIDs);
334 public function update() {
335 if (empty($this->objects
)) {
336 $this->readObjects();
339 if (isset($this->parameters
['data'])) {
340 foreach ($this->objects
as $object) {
341 $object->update($this->parameters
['data']);
345 if (isset($this->parameters
['counters'])) {
346 foreach ($this->objects
as $object) {
347 $object->updateCounters($this->parameters
['counters']);
353 * Reads data by data id.
355 protected function readObjects() {
356 if (empty($this->objectIDs
)) {
361 $baseClass = call_user_func(array($this->className
, 'getBaseClass'));
363 // get db information
364 $tableName = call_user_func(array($this->className
, 'getDatabaseTableName'));
365 $indexName = call_user_func(array($this->className
, 'getDatabaseTableIndexName'));
370 WHERE ".$indexName." IN (".str_repeat('?,', count($this->objectIDs
) - 1)."?)";
371 $statement = WCF
::getDB()->prepareStatement($sql);
372 $statement->execute($this->objectIDs
);
373 while ($object = $statement->fetchObject($baseClass)) {
374 $this->objects
[] = new $this->className($object);
379 * Returns a single object and throws an UserInputException if no or more than one object is given.
381 * @return \wcf\data\DatabaseObject
383 protected function getSingleObject() {
384 if (empty($this->objects
)) {
385 $this->readObjects();
388 if (count($this->objects
) != 1) {
389 throw new UserInputException('objectIDs');
392 reset($this->objects
);
393 return current($this->objects
);
397 * Reads an integer value and validates it.
399 * @param string $variableName
400 * @param boolean $allowEmpty
401 * @param string $arrayIndex
403 protected function readInteger($variableName, $allowEmpty = false, $arrayIndex = '') {
404 $this->readValue($variableName, $allowEmpty, $arrayIndex, self
::TYPE_INTEGER
);
408 * Reads a string value and validates it.
410 * @param string $variableName
411 * @param boolean $allowEmpty
412 * @param string $arrayIndex
414 protected function readString($variableName, $allowEmpty = false, $arrayIndex = '') {
415 $this->readValue($variableName, $allowEmpty, $arrayIndex, self
::TYPE_STRING
);
419 * Reads a boolean value and validates it.
421 * @param string $variableName
422 * @param boolean $allowEmpty
423 * @param string $arrayIndex
425 protected function readBoolean($variableName, $allowEmpty = false, $arrayIndex = '') {
426 $this->readValue($variableName, $allowEmpty, $arrayIndex, self
::TYPE_BOOLEAN
);
430 * Reads a json-encoded value and validates it.
432 * @param string $variableName
433 * @param boolean $allowEmpty
434 * @param string $arrayIndex
436 protected function readJSON($variableName, $allowEmpty = false, $arrayIndex = '') {
437 $this->readValue($variableName, $allowEmpty, $arrayIndex, self
::TYPE_JSON
);
441 * Reads a value and validates it. If you set $allowEmpty to true, no exception will
442 * be thrown if the variable evaluates to 0 (integer) or '' (string). Furthermore the
443 * variable will be always created with a sane value if it does not exist.
445 * @param string $variableName
446 * @param boolean $allowEmpty
447 * @param string $arrayIndex
448 * @param integer $type
450 protected function readValue($variableName, $allowEmpty, $arrayIndex, $type) {
452 if (!isset($this->parameters
[$arrayIndex])) {
453 throw new SystemException("Corrupt parameters, index '".$arrayIndex."' is missing");
456 $target =& $this->parameters
[$arrayIndex];
459 $target =& $this->parameters
;
463 case self
::TYPE_INTEGER
:
464 if (!isset($target[$variableName])) {
466 $target[$variableName] = 0;
469 throw new UserInputException($variableName);
473 $target[$variableName] = intval($target[$variableName]);
474 if (!$allowEmpty && !$target[$variableName]) {
475 throw new UserInputException($variableName);
480 case self
::TYPE_STRING
:
481 if (!isset($target[$variableName])) {
483 $target[$variableName] = '';
486 throw new UserInputException($variableName);
490 $target[$variableName] = StringUtil
::trim($target[$variableName]);
491 if (!$allowEmpty && empty($target[$variableName])) {
492 throw new UserInputException($variableName);
497 case self
::TYPE_BOOLEAN
:
498 if (!isset($target[$variableName])) {
500 $target[$variableName] = false;
503 throw new UserInputException($variableName);
507 if (is_numeric($target[$variableName])) {
508 $target[$variableName] = (bool) $target[$variableName];
511 $target[$variableName] = $target[$variableName] != 'false';
516 case self
::TYPE_JSON
:
517 if (!isset($target[$variableName])) {
519 $target[$variableName] = array();
522 throw new UserInputException($variableName);
527 $target[$variableName] = JSON
::decode($target[$variableName]);
529 catch (SystemException
$e) {
530 throw new UserInputException($variableName);
533 if (!$allowEmpty && empty($target[$variableName])) {
534 throw new UserInputException($variableName);
542 * Returns object class name.
546 public function getClassName() {
547 return $this->className
;
551 * Returns a list of currently loaded objects.
553 * @return array<\wcf\data\IEditableObject>
555 public function getObjects() {
556 return $this->objects
;