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