Use page object types to select page when creating page menu item
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / data / AbstractDatabaseObjectAction.class.php
CommitLineData
11ade432
AE
1<?php
2namespace wcf\data;
11ade432
AE
3use wcf\system\event\EventHandler;
4use wcf\system\exception\PermissionDeniedException;
4e877829 5use wcf\system\exception\SystemException;
429e91b8 6use wcf\system\exception\UserInputException;
b6628fdc 7use wcf\system\request\RequestHandler;
11ade432
AE
8use wcf\system\WCF;
9use wcf\util\ClassUtil;
10use wcf\util\StringUtil;
11
12/**
13 * Default implementation for DatabaseObject-related actions.
9f959ced 14 *
11ade432 15 * @author Alexander Ebert
ca4ba303 16 * @copyright 2001-2014 WoltLab GmbH
11ade432
AE
17 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
18 * @package com.woltlab.wcf
19 * @subpackage data
9f959ced 20 * @category Community Framework
11ade432 21 */
a427a8c8 22abstract class AbstractDatabaseObjectAction implements IDatabaseObjectAction, IDeleteAction {
11ade432
AE
23 /**
24 * pending action
11ade432
AE
25 * @var string
26 */
27 protected $action = '';
28
29 /**
30 * object editor class name
11ade432
AE
31 * @var string
32 */
33 protected $className = '';
34
35 /**
36 * list of object ids
11ade432
AE
37 * @var array<integer>
38 */
39 protected $objectIDs = array();
40
41 /**
832371b2 42 * list of object editors
0ad90fc3 43 * @var array<\wcf\data\DatabaseObjectEditor>
11ade432
AE
44 */
45 protected $objects = array();
46
47 /**
48 * multi-dimensional array of parameters required by an action
11ade432
AE
49 * @var array<array>
50 */
51 protected $parameters = array();
52
53 /**
54 * list of permissions required to create objects
832371b2 55 * @var array<string>
11ade432
AE
56 */
57 protected $permissionsCreate = array();
58
59 /**
60 * list of permissions required to delete objects
832371b2 61 * @var array<string>
11ade432
AE
62 */
63 protected $permissionsDelete = array();
64
65 /**
66 * list of permissions required to update objects
832371b2 67 * @var array<string>
11ade432
AE
68 */
69 protected $permissionsUpdate = array();
70
bae8dd1e
AE
71 /**
72 * disallow requests for specified methods if the origin is not the ACP
73 * @var array<string>
74 */
75 protected $requireACP = array();
76
22d94e94
AE
77 /**
78 * Resets cache if any of the listed actions is invoked
79 * @var array<string>
80 */
81 protected $resetCache = array('create', 'delete', 'toggle', 'update', 'updatePosition');
82
11ade432
AE
83 /**
84 * values returned by executed action
11ade432
AE
85 * @var mixed
86 */
87 protected $returnValues = null;
88
fca077a6 89 /**
9f959ced
MS
90 * allows guest access for all specified methods, by default guest access
91 * is completely disabled
878d0d80 92 * @var array<string>
fca077a6 93 */
878d0d80 94 protected $allowGuestAccess = array();
fca077a6 95
429e91b8
AE
96 const TYPE_INTEGER = 1;
97 const TYPE_STRING = 2;
976d79e3 98 const TYPE_BOOLEAN = 3;
429e91b8 99
11ade432 100 /**
a90028e5 101 * Initialize a new DatabaseObject-related action.
9f959ced 102 *
4e877829 103 * @param array<mixed> $objects
11ade432
AE
104 * @param string $action
105 * @param array $parameters
106 */
4e877829 107 public function __construct(array $objects, $action, array $parameters = array()) {
7703d974
MS
108 // set class name
109 if (empty($this->className)) {
110 $className = get_called_class();
111
838e315b
SG
112 if (mb_substr($className, -6) == 'Action') {
113 $this->className = mb_substr($className, 0, -6).'Editor';
7703d974
MS
114 }
115 }
116
4e877829
MW
117 $indexName = call_user_func(array($this->className, 'getDatabaseTableIndexName'));
118 $baseClass = call_user_func(array($this->className, 'getBaseClass'));
119
120 foreach ($objects as $object) {
121 if (is_object($object)) {
122 if ($object instanceof $this->className) {
123 $this->objects[] = $object;
124 }
125 else if ($object instanceof $baseClass) {
126 $this->objects[] = new $this->className($object);
127 }
128 else {
129 throw new SystemException('invalid value of parameter objects given');
130 }
131
132 $this->objectIDs[] = $object->$indexName;
133 }
4e877829 134 else {
f28efd50 135 $this->objectIDs[] = $object;
4e877829
MW
136 }
137 }
138
11ade432
AE
139 $this->action = $action;
140 $this->parameters = $parameters;
141
142 // fire event action
143 EventHandler::getInstance()->fireAction($this, 'initializeAction');
144 }
145
146 /**
0ad90fc3 147 * @see \wcf\data\IDatabaseObjectAction::validateAction()
11ade432
AE
148 */
149 public function validateAction() {
fca077a6 150 // validate if user is logged in
878d0d80 151 if (!WCF::getUser()->userID && !in_array($this->getActionName(), $this->allowGuestAccess)) {
f1a3caba 152 throw new PermissionDeniedException();
fca077a6 153 }
bae8dd1e
AE
154 else if (!RequestHandler::getInstance()->isACPRequest() && in_array($this->getActionName(), $this->requireACP)) {
155 // attempt to invoke method, but origin is not the ACP
156 throw new PermissionDeniedException();
157 }
fca077a6 158
11ade432
AE
159 // validate action name
160 if (!method_exists($this, $this->getActionName())) {
3631f7bd 161 throw new SystemException("unknown action '".$this->getActionName()."'");
11ade432
AE
162 }
163
164 $actionName = 'validate'.StringUtil::firstCharToUpperCase($this->getActionName());
165 if (!method_exists($this, $actionName)) {
3631f7bd 166 throw new PermissionDeniedException();
11ade432
AE
167 }
168
169 // execute action
170 call_user_func_array(array($this, $actionName), $this->getParameters());
171 }
172
173 /**
0ad90fc3 174 * @see \wcf\data\IDatabaseObjectAction::executeAction()
11ade432
AE
175 */
176 public function executeAction() {
177 // execute action
51772f85
MW
178 if (!method_exists($this, $this->getActionName())) {
179 throw new SystemException("call to undefined function '".$this->getActionName()."'");
351d836a
MS
180 }
181
51772f85 182 $this->returnValues = call_user_func(array($this, $this->getActionName()));
11ade432
AE
183
184 // reset cache
22d94e94
AE
185 if (in_array($this->getActionName(), $this->resetCache)) {
186 $this->resetCache();
11ade432
AE
187 }
188
189 // fire event action
190 EventHandler::getInstance()->fireAction($this, 'finalizeAction');
191
192 return $this->getReturnValues();
193 }
194
22d94e94
AE
195 /**
196 * Resets cache of database object.
197 */
198 protected function resetCache() {
199 if (ClassUtil::isInstanceOf($this->className, 'wcf\data\IEditableCachedObject')) {
200 call_user_func(array($this->className, 'resetCache'));
201 }
202 }
203
11ade432 204 /**
0ad90fc3 205 * @see \wcf\data\IDatabaseObjectAction::getActionName()
11ade432
AE
206 */
207 public function getActionName() {
208 return $this->action;
209 }
210
211 /**
0ad90fc3 212 * @see \wcf\data\IDatabaseObjectAction::getObjectIDs()
11ade432
AE
213 */
214 public function getObjectIDs() {
215 return $this->objectIDs;
216 }
217
e9aeabca
MW
218 /**
219 * Sets the database objects.
220 *
0ad90fc3 221 * @param array<\wcf\data\DatabaseObject> $objects
e9aeabca
MW
222 */
223 public function setObjects(array $objects) {
224 $this->objects = $objects;
225 }
226
11ade432 227 /**
0ad90fc3 228 * @see \wcf\data\IDatabaseObjectAction::getParameters()
11ade432
AE
229 */
230 public function getParameters() {
231 return $this->parameters;
232 }
233
234 /**
0ad90fc3 235 * @see \wcf\data\IDatabaseObjectAction::getReturnValues()
11ade432
AE
236 */
237 public function getReturnValues() {
238 return array(
a8f8312d 239 'actionName' => $this->action,
11ade432
AE
240 'objectIDs' => $this->getObjectIDs(),
241 'returnValues' => $this->returnValues
242 );
243 }
244
245 /**
246 * Validates permissions and parameters.
247 */
248 public function validateCreate() {
249 // validate permissions
15fa2802 250 if (is_array($this->permissionsCreate) && !empty($this->permissionsCreate)) {
3631f7bd 251 WCF::getSession()->checkPermissions($this->permissionsCreate);
11ade432
AE
252 }
253 else {
3631f7bd 254 throw new PermissionDeniedException();
11ade432
AE
255 }
256 }
257
258 /**
0ad90fc3 259 * @see \wcf\data\IDeleteAction::validateDelete()
11ade432
AE
260 */
261 public function validateDelete() {
262 // validate permissions
15fa2802 263 if (is_array($this->permissionsDelete) && !empty($this->permissionsDelete)) {
3631f7bd 264 WCF::getSession()->checkPermissions($this->permissionsDelete);
11ade432
AE
265 }
266 else {
3631f7bd 267 throw new PermissionDeniedException();
11ade432
AE
268 }
269
046f3d7b 270 // read objects
15fa2802 271 if (empty($this->objects)) {
e9aeabca 272 $this->readObjects();
15fa2802
MS
273
274 if (empty($this->objects)) {
3631f7bd 275 throw new UserInputException('objectIDs');
15fa2802 276 }
11ade432
AE
277 }
278 }
279
280 /**
281 * Validates permissions and parameters.
282 */
283 public function validateUpdate() {
284 // validate permissions
15fa2802 285 if (is_array($this->permissionsUpdate) && !empty($this->permissionsUpdate)) {
3631f7bd 286 WCF::getSession()->checkPermissions($this->permissionsUpdate);
11ade432
AE
287 }
288 else {
3631f7bd 289 throw new PermissionDeniedException();
11ade432
AE
290 }
291
046f3d7b 292 // read objects
15fa2802 293 if (empty($this->objects)) {
e9aeabca 294 $this->readObjects();
351d836a 295
15fa2802 296 if (empty($this->objects)) {
3631f7bd 297 throw new UserInputException('objectIDs');
15fa2802 298 }
11ade432
AE
299 }
300 }
301
302 /**
832371b2 303 * Creates new database object.
351d836a 304 *
0ad90fc3 305 * @return \wcf\data\DatabaseObject
11ade432
AE
306 */
307 public function create() {
11ade432
AE
308 return call_user_func(array($this->className, 'create'), $this->parameters['data']);
309 }
310
311 /**
0ad90fc3 312 * @see \wcf\data\IDeleteAction::delete()
11ade432
AE
313 */
314 public function delete() {
15fa2802 315 if (empty($this->objects)) {
11ade432
AE
316 $this->readObjects();
317 }
318
11ade432
AE
319 // get ids
320 $objectIDs = array();
321 foreach ($this->objects as $object) {
91a5aa24 322 $objectIDs[] = $object->getObjectID();
11ade432
AE
323 }
324
325 // execute action
326 return call_user_func(array($this->className, 'deleteAll'), $objectIDs);
327 }
328
329 /**
330 * Updates data.
331 */
332 public function update() {
15fa2802 333 if (empty($this->objects)) {
11ade432
AE
334 $this->readObjects();
335 }
336
046f3d7b
MS
337 if (isset($this->parameters['data'])) {
338 foreach ($this->objects as $object) {
339 $object->update($this->parameters['data']);
340 }
11ade432 341 }
575350ba
MS
342
343 if (isset($this->parameters['counters'])) {
344 foreach ($this->objects as $object) {
345 $object->updateCounters($this->parameters['counters']);
346 }
347 }
11ade432
AE
348 }
349
350 /**
351 * Reads data by data id.
352 */
353 protected function readObjects() {
15fa2802 354 if (empty($this->objectIDs)) {
11ade432
AE
355 return;
356 }
357
358 // get base class
359 $baseClass = call_user_func(array($this->className, 'getBaseClass'));
360
361 // get db information
362 $tableName = call_user_func(array($this->className, 'getDatabaseTableName'));
363 $indexName = call_user_func(array($this->className, 'getDatabaseTableIndexName'));
364
365 // get objects
366 $sql = "SELECT *
367 FROM ".$tableName."
368 WHERE ".$indexName." IN (".str_repeat('?,', count($this->objectIDs) - 1)."?)";
369 $statement = WCF::getDB()->prepareStatement($sql);
370 $statement->execute($this->objectIDs);
371 while ($object = $statement->fetchObject($baseClass)) {
372 $this->objects[] = new $this->className($object);
373 }
374 }
764fe46c 375
429e91b8 376 /**
56111b4d 377 * Returns a single object and throws an UserInputException if no or more than one object is given.
429e91b8 378 *
0ad90fc3 379 * @return \wcf\data\DatabaseObject
429e91b8
AE
380 */
381 protected function getSingleObject() {
382 if (empty($this->objects)) {
383 $this->readObjects();
384 }
385
386 if (count($this->objects) != 1) {
387 throw new UserInputException('objectIDs');
388 }
389
390 reset($this->objects);
391 return current($this->objects);
392 }
393
394 /**
395 * Reads an integer value and validates it.
396 *
397 * @param string $variableName
398 * @param boolean $allowEmpty
399 * @param string $arrayIndex
400 */
61582f5f 401 protected function readInteger($variableName, $allowEmpty = false, $arrayIndex = '') {
429e91b8
AE
402 $this->readValue($variableName, $allowEmpty, $arrayIndex, self::TYPE_INTEGER);
403 }
404
405 /**
406 * Reads a string value and validates it.
407 *
408 * @param string $variableName
409 * @param boolean $allowEmpty
410 * @param string $arrayIndex
411 */
412 protected function readString($variableName, $allowEmpty = false, $arrayIndex = '') {
413 $this->readValue($variableName, $allowEmpty, $arrayIndex, self::TYPE_STRING);
414 }
415
8795c633
MK
416 /**
417 * Reads a boolean value and validates it.
418 *
419 * @param string $variableName
420 * @param boolean $allowEmpty
421 * @param string $arrayIndex
8795c633 422 */
976d79e3
AE
423 protected function readBoolean($variableName, $allowEmpty = false, $arrayIndex = '') {
424 $this->readValue($variableName, $allowEmpty, $arrayIndex, self::TYPE_BOOLEAN);
429e91b8
AE
425 }
426
427 /**
428 * Reads a value and validates it. If you set $allowEmpty to true, no exception will
429 * be thrown if the variable evaluates to 0 (integer) or '' (string). Furthermore the
430 * variable will be always created with a sane value if it does not exist.
431 *
432 * @param string $variableName
433 * @param boolean $allowEmpty
434 * @param string $arrayIndex
435 * @param integer $type
436 */
437 protected function readValue($variableName, $allowEmpty, $arrayIndex, $type) {
438 if ($arrayIndex) {
439 if (!isset($this->parameters[$arrayIndex])) {
440 throw new SystemException("Corrupt parameters, index '".$arrayIndex."' is missing");
441 }
442
57d62a68 443 $target =& $this->parameters[$arrayIndex];
429e91b8
AE
444 }
445 else {
446 $target =& $this->parameters;
447 }
448
449 switch ($type) {
450 case self::TYPE_INTEGER:
451 if (!isset($target[$variableName])) {
452 if ($allowEmpty) {
453 $target[$variableName] = 0;
454 }
455 else {
456 throw new UserInputException($variableName);
457 }
458 }
459 else {
460 $target[$variableName] = intval($target[$variableName]);
461 if (!$allowEmpty && !$target[$variableName]) {
462 throw new UserInputException($variableName);
463 }
464 }
465 break;
466
467 case self::TYPE_STRING:
468 if (!isset($target[$variableName])) {
469 if ($allowEmpty) {
470 $target[$variableName] = '';
471 }
472 else {
473 throw new UserInputException($variableName);
474 }
475 }
476 else {
477 $target[$variableName] = StringUtil::trim($target[$variableName]);
478 if (!$allowEmpty && empty($target[$variableName])) {
479 throw new UserInputException($variableName);
480 }
481 }
482 break;
8795c633 483
976d79e3 484 case self::TYPE_BOOLEAN:
8795c633
MK
485 if (!isset($target[$variableName])) {
486 if ($allowEmpty) {
487 $target[$variableName] = false;
488 }
489 else {
490 throw new UserInputException($variableName);
491 }
492 }
493 else {
976d79e3
AE
494 if (is_numeric($target[$variableName])) {
495 $target[$variableName] = (bool) $target[$variableName];
921e346a
MK
496 }
497 else {
976d79e3 498 $target[$variableName] = $target[$variableName] != 'false';
6c611ea7 499 }
8795c633
MK
500 }
501 break;
429e91b8 502 }
429e91b8
AE
503 }
504
764fe46c
AE
505 /**
506 * Returns object class name.
507 *
508 * @return string
509 */
510 public function getClassName() {
511 return $this->className;
512 }
513
514 /**
515 * Returns a list of currently loaded objects.
516 *
0ad90fc3 517 * @return array<\wcf\data\IEditableObject>
764fe46c
AE
518 */
519 public function getObjects() {
520 return $this->objects;
521 }
11ade432 522}