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