Commit | Line | Data |
---|---|---|
11ade432 AE |
1 | <?php |
2 | namespace wcf\data; | |
11ade432 AE |
3 | use wcf\system\event\EventHandler; |
4 | use wcf\system\exception\PermissionDeniedException; | |
4e877829 | 5 | use wcf\system\exception\SystemException; |
429e91b8 | 6 | use wcf\system\exception\UserInputException; |
b6628fdc | 7 | use wcf\system\request\RequestHandler; |
11ade432 AE |
8 | use wcf\system\WCF; |
9 | use wcf\util\ClassUtil; | |
10 | use 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 | 22 | abstract 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 | } |