Add the new variable `FileProcessorFormField::$bigPreview` with getter and setter.
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / data / AbstractDatabaseObjectAction.class.php
CommitLineData
11ade432 1<?php
a9229942 2
11ade432 3namespace wcf\data;
a9229942 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;
b6628fdc 9use wcf\system\request\RequestHandler;
11ade432 10use wcf\system\WCF;
857ed85d 11use wcf\util\ArrayUtil;
217e4987 12use wcf\util\JSON;
11ade432
AE
13use wcf\util\StringUtil;
14
15/**
16 * Default implementation for DatabaseObject-related actions.
a9229942
TD
17 *
18 * @author Alexander Ebert
19 * @copyright 2001-2020 WoltLab GmbH
20 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
11ade432 21 */
a9229942
TD
22abstract class AbstractDatabaseObjectAction implements IDatabaseObjectAction, IDeleteAction
23{
24 /**
25 * pending action
26 * @var string
27 */
28 protected $action = '';
29
30 /**
31 * object editor class name
32 * @var string
33 */
34 protected $className = '';
35
36 /**
37 * list of object ids
38 * @var int[]
39 */
40 protected $objectIDs = [];
41
42 /**
43 * list of object editors
44 * @var DatabaseObjectEditor[]
45 */
46 protected $objects = [];
47
48 /**
49 * multi-dimensional array of parameters required by an action
50 * @var array
51 */
52 protected $parameters = [];
53
54 /**
55 * list of permissions required to create objects
56 * @var string[]
57 */
58 protected $permissionsCreate = [];
59
60 /**
61 * list of permissions required to delete objects
62 * @var string[]
63 */
64 protected $permissionsDelete = [];
65
66 /**
67 * list of permissions required to update objects
68 * @var string[]
69 */
70 protected $permissionsUpdate = [];
71
72 /**
73 * disallow requests for specified methods if the origin is not the ACP
74 * @var string[]
75 */
76 protected $requireACP = [];
77
78 /**
79 * Resets cache if any of the listed actions is invoked
80 * @var string[]
81 */
82 protected $resetCache = ['create', 'delete', 'toggle', 'update', 'updatePosition'];
83
84 /**
85 * values returned by executed action
86 * @var mixed
87 */
88 protected $returnValues;
89
90 /**
91 * allows guest access for all specified methods, by default guest access
92 * is completely disabled
93 * @var string[]
94 */
95 protected $allowGuestAccess = [];
96
97 const TYPE_INTEGER = 1;
98
99 const TYPE_STRING = 2;
100
101 const TYPE_BOOLEAN = 3;
102
103 const TYPE_JSON = 4;
104
105 const STRUCT_FLAT = 1;
106
107 const STRUCT_ARRAY = 2;
108
109 /**
110 * Initialize a new DatabaseObject-related action.
111 *
112 * @param mixed[] $objects
113 * @param string $action
114 * @param array $parameters
115 * @throws SystemException
116 */
117 public function __construct(array $objects, $action, array $parameters = [])
118 {
119 // set class name
120 if (empty($this->className)) {
0befcb4a 121 $className = static::class;
a9229942
TD
122
123 if (\mb_substr($className, -6) == 'Action') {
124 $this->className = \mb_substr($className, 0, -6) . 'Editor';
125 }
126 }
127
128 $indexName = \call_user_func([$this->className, 'getDatabaseTableIndexName']);
129 $baseClass = \call_user_func([$this->className, 'getBaseClass']);
130
131 foreach ($objects as $object) {
132 if (\is_object($object)) {
133 if ($object instanceof $this->className) {
134 $this->objects[] = $object;
135 } elseif ($object instanceof $baseClass) {
136 $this->objects[] = new $this->className($object);
137 } else {
138 throw new SystemException('invalid value of parameter objects given');
139 }
140
141 /** @noinspection PhpVariableVariableInspection */
142 $this->objectIDs[] = $object->{$indexName};
143 } else {
144 $this->objectIDs[] = $object;
145 }
146 }
147
148 $this->action = $action;
149 $this->parameters = $parameters;
150
151 // initialize further settings
152 $this->__init($baseClass, $indexName);
153
154 // fire event action
155 EventHandler::getInstance()->fireAction($this, 'initializeAction');
156 }
157
158 /**
159 * This function can be overridden in children to perform custom initialization
160 * of a DBOAction before the 'initializeAction' event is fired.
161 *
162 * @param string $baseClass
163 * @param string $indexName
164 */
165 protected function __init($baseClass, $indexName)
166 {
167 // does nothing
168 }
169
170 /**
171 * @inheritDoc
172 */
173 public function validateAction()
174 {
175 // validate if user is logged in
176 if (!WCF::getUser()->userID && !\in_array($this->getActionName(), $this->allowGuestAccess)) {
177 throw new PermissionDeniedException();
178 } elseif (
179 !RequestHandler::getInstance()->isACPRequest() && \in_array(
180 $this->getActionName(),
181 $this->requireACP
182 )
183 ) {
184 // attempt to invoke method, but origin is not the ACP
185 throw new PermissionDeniedException();
186 }
187
188 // validate action name
189 if (!\method_exists($this, $this->getActionName())) {
190 throw new SystemException("unknown action '" . $this->getActionName() . "'");
191 }
192
193 $actionName = 'validate' . StringUtil::firstCharToUpperCase($this->getActionName());
194 if (!\method_exists($this, $actionName)) {
195 throw new PermissionDeniedException();
196 }
197
198 // validate action
199 $this->{$actionName}();
200
201 // fire event action
202 EventHandler::getInstance()->fireAction($this, 'validateAction');
203 }
204
205 /**
206 * @inheritDoc
207 */
208 public function executeAction()
209 {
210 // execute action
211 if (!\method_exists($this, $this->getActionName())) {
212 throw new SystemException("call to undefined function '" . $this->getActionName() . "'");
213 }
214
215 $this->returnValues = $this->{$this->getActionName()}();
216
217 // reset cache
218 if (\in_array($this->getActionName(), $this->resetCache)) {
219 $this->resetCache();
220 }
221
222 // fire event action
223 EventHandler::getInstance()->fireAction($this, 'finalizeAction');
224
225 return $this->getReturnValues();
226 }
227
228 /**
229 * Resets cache of database object.
230 */
231 protected function resetCache()
232 {
233 if (\is_subclass_of($this->className, IEditableCachedObject::class)) {
234 \call_user_func([$this->className, 'resetCache']);
235 }
236 }
237
238 /**
239 * @inheritDoc
240 */
241 public function getActionName()
242 {
243 return $this->action;
244 }
245
246 /**
247 * @inheritDoc
248 */
249 public function getObjectIDs()
250 {
251 return $this->objectIDs;
252 }
253
254 /**
255 * Sets the database objects.
256 *
257 * @param DatabaseObject[] $objects
258 */
259 public function setObjects(array $objects)
260 {
261 $this->objects = $objects;
262
263 // update object IDs
264 $this->objectIDs = [];
265 foreach ($this->getObjects() as $object) {
266 $this->objectIDs[] = $object->getObjectID();
267 }
268 }
269
270 /**
271 * @inheritDoc
272 */
273 public function getParameters()
274 {
275 return $this->parameters;
276 }
277
278 /**
279 * @inheritDoc
280 */
281 public function getReturnValues()
282 {
283 return [
284 'actionName' => $this->action,
285 'objectIDs' => $this->getObjectIDs(),
286 'returnValues' => $this->returnValues,
287 ];
288 }
289
290 /**
291 * Validates permissions and parameters.
292 */
293 public function validateCreate()
294 {
295 // validate permissions
296 if (\is_array($this->permissionsCreate) && !empty($this->permissionsCreate)) {
297 WCF::getSession()->checkPermissions($this->permissionsCreate);
298 } else {
299 throw new PermissionDeniedException();
300 }
301 }
302
303 /**
304 * @inheritDoc
305 */
306 public function validateDelete()
307 {
308 // validate permissions
309 if (\is_array($this->permissionsDelete) && !empty($this->permissionsDelete)) {
310 WCF::getSession()->checkPermissions($this->permissionsDelete);
311 } else {
312 throw new PermissionDeniedException();
313 }
314
315 // read objects
316 if (empty($this->objects)) {
317 $this->readObjects();
318
319 if (empty($this->objects)) {
320 throw new UserInputException('objectIDs');
321 }
322 }
323 }
324
325 /**
326 * Validates permissions and parameters.
327 */
328 public function validateUpdate()
329 {
330 // validate permissions
331 if (\is_array($this->permissionsUpdate) && !empty($this->permissionsUpdate)) {
332 WCF::getSession()->checkPermissions($this->permissionsUpdate);
333 } else {
334 throw new PermissionDeniedException();
335 }
336
337 // read objects
338 if (empty($this->objects)) {
339 $this->readObjects();
340
341 if (empty($this->objects)) {
342 throw new UserInputException('objectIDs');
343 }
344 }
345 }
346
347 /**
348 * Creates new database object.
349 *
350 * @return DatabaseObject
351 */
352 public function create()
353 {
354 return \call_user_func([$this->className, 'create'], $this->parameters['data']);
355 }
356
357 /**
358 * @inheritDoc
359 */
360 public function delete()
361 {
362 if (empty($this->objects)) {
363 $this->readObjects();
364 }
365
366 // get ids
367 $objectIDs = [];
368 foreach ($this->getObjects() as $object) {
369 $objectIDs[] = $object->getObjectID();
370 }
371
372 // execute action
373 return \call_user_func([$this->className, 'deleteAll'], $objectIDs);
374 }
375
376 /**
377 * Updates data.
378 */
379 public function update()
380 {
381 if (empty($this->objects)) {
382 $this->readObjects();
383 }
384
385 if (isset($this->parameters['data'])) {
386 foreach ($this->getObjects() as $object) {
387 $object->update($this->parameters['data']);
388 }
389 }
390
391 if (isset($this->parameters['counters'])) {
392 foreach ($this->getObjects() as $object) {
393 $object->updateCounters($this->parameters['counters']);
394 }
395 }
396 }
397
398 /**
399 * Reads data by data id.
400 */
401 protected function readObjects()
402 {
403 if (empty($this->objectIDs)) {
404 return;
405 }
406
407 // get base class
408 $baseClass = \call_user_func([$this->className, 'getBaseClass']);
409
410 // get db information
411 $tableName = \call_user_func([$this->className, 'getDatabaseTableName']);
412 $indexName = \call_user_func([$this->className, 'getDatabaseTableIndexName']);
413
414 // get objects
415 $sql = "SELECT *
416 FROM " . $tableName . "
417 WHERE " . $indexName . " IN (" . \str_repeat('?,', \count($this->objectIDs) - 1) . "?)";
418 $statement = WCF::getDB()->prepareStatement($sql);
419 $statement->execute($this->objectIDs);
420 while ($object = $statement->fetchObject($baseClass)) {
421 $this->objects[] = new $this->className($object);
422 }
423 }
424
425 /**
426 * Returns a single object and throws a UserInputException if no or more than one object is given.
427 *
054857eb 428 * @return DatabaseObjectEditor
a9229942
TD
429 * @throws UserInputException
430 */
431 protected function getSingleObject()
432 {
433 if (empty($this->objects)) {
434 $this->readObjects();
435 }
436
437 if (\count($this->objects) != 1) {
438 throw new UserInputException('objectIDs');
439 }
440
441 \reset($this->objects);
442
443 return \current($this->objects);
444 }
445
446 /**
447 * Reads an integer value and validates it.
448 *
449 * @param string $variableName
450 * @param bool $allowEmpty
451 * @param string $arrayIndex
452 */
453 protected function readInteger($variableName, $allowEmpty = false, $arrayIndex = '')
454 {
455 $this->readValue($variableName, $allowEmpty, $arrayIndex, self::TYPE_INTEGER, self::STRUCT_FLAT);
456 }
457
458 /**
459 * Reads an integer array and validates it.
460 *
461 * @param string $variableName
462 * @param bool $allowEmpty
463 * @param string $arrayIndex
464 * @since 3.0
465 */
466 protected function readIntegerArray($variableName, $allowEmpty = false, $arrayIndex = '')
467 {
468 $this->readValue($variableName, $allowEmpty, $arrayIndex, self::TYPE_INTEGER, self::STRUCT_ARRAY);
469 }
470
471 /**
472 * Reads a string value and validates it.
473 *
474 * @param string $variableName
475 * @param bool $allowEmpty
476 * @param string $arrayIndex
477 */
478 protected function readString($variableName, $allowEmpty = false, $arrayIndex = '')
479 {
480 $this->readValue($variableName, $allowEmpty, $arrayIndex, self::TYPE_STRING, self::STRUCT_FLAT);
481 }
482
483 /**
484 * Reads a string array and validates it.
485 *
486 * @param string $variableName
487 * @param bool $allowEmpty
488 * @param string $arrayIndex
489 * @since 3.0
490 */
491 protected function readStringArray($variableName, $allowEmpty = false, $arrayIndex = '')
492 {
493 $this->readValue($variableName, $allowEmpty, $arrayIndex, self::TYPE_STRING, self::STRUCT_ARRAY);
494 }
495
496 /**
497 * Reads a boolean value and validates it.
498 *
499 * @param string $variableName
500 * @param bool $allowEmpty
501 * @param string $arrayIndex
502 */
503 protected function readBoolean($variableName, $allowEmpty = false, $arrayIndex = '')
504 {
505 $this->readValue($variableName, $allowEmpty, $arrayIndex, self::TYPE_BOOLEAN, self::STRUCT_FLAT);
506 }
507
508 /**
509 * Reads a json-encoded value and validates it.
510 *
511 * @param string $variableName
512 * @param bool $allowEmpty
513 * @param string $arrayIndex
514 */
515 protected function readJSON($variableName, $allowEmpty = false, $arrayIndex = '')
516 {
517 $this->readValue($variableName, $allowEmpty, $arrayIndex, self::TYPE_JSON, self::STRUCT_FLAT);
518 }
519
520 /**
521 * Reads a value and validates it. If you set $allowEmpty to true, no exception will
522 * be thrown if the variable evaluates to 0 (int) or '' (string). Furthermore the
523 * variable will be always created with a sane value if it does not exist.
524 *
525 * @param string $variableName
526 * @param bool $allowEmpty
527 * @param string $arrayIndex
528 * @param int $type
529 * @param int $structure
530 * @throws SystemException
531 * @throws UserInputException
532 */
533 protected function readValue($variableName, $allowEmpty, $arrayIndex, $type, $structure)
534 {
535 if ($arrayIndex) {
536 if (!isset($this->parameters[$arrayIndex])) {
537 if ($allowEmpty) {
538 // Implicitly create the structure to permit implicitly defined values.
539 $this->parameters[$arrayIndex] = [];
540 } else {
541 throw new SystemException("Corrupt parameters, index '" . $arrayIndex . "' is missing");
542 }
543 }
544
545 $target = &$this->parameters[$arrayIndex];
546 } else {
547 $target = &$this->parameters;
548 }
549
550 switch ($type) {
551 case self::TYPE_INTEGER:
552 if (!isset($target[$variableName])) {
553 if ($allowEmpty) {
554 $target[$variableName] = ($structure === self::STRUCT_FLAT) ? 0 : [];
555 } else {
556 throw new UserInputException($variableName);
557 }
558 } else {
559 if ($structure === self::STRUCT_FLAT) {
560 $target[$variableName] = \intval($target[$variableName]);
561 if (!$allowEmpty && !$target[$variableName]) {
562 throw new UserInputException($variableName);
563 }
564 } else {
565 $target[$variableName] = ArrayUtil::toIntegerArray($target[$variableName]);
566 if (!\is_array($target[$variableName])) {
567 throw new UserInputException($variableName);
568 }
569
570 for ($i = 0, $length = \count($target[$variableName]); $i < $length; $i++) {
571 if ($target[$variableName][$i] === 0) {
572 throw new UserInputException($variableName);
573 }
574 }
575 }
576 }
577 break;
578
579 case self::TYPE_STRING:
580 if (!isset($target[$variableName])) {
581 if ($allowEmpty) {
582 $target[$variableName] = ($structure === self::STRUCT_FLAT) ? '' : [];
583 } else {
584 throw new UserInputException($variableName);
585 }
586 } else {
587 if ($structure === self::STRUCT_FLAT) {
2e687694
MW
588 if (!\is_string($target[$variableName])) {
589 throw new UserInputException($variableName);
590 }
a9229942
TD
591 $target[$variableName] = StringUtil::trim($target[$variableName]);
592 if (!$allowEmpty && $target[$variableName] === '') {
593 throw new UserInputException($variableName);
594 }
595 } else {
596 $target[$variableName] = ArrayUtil::trim($target[$variableName]);
597 if (!\is_array($target[$variableName])) {
598 throw new UserInputException($variableName);
599 }
600
601 for ($i = 0, $length = \count($target[$variableName]); $i < $length; $i++) {
602 if ($target[$variableName][$i] === '') {
603 throw new UserInputException($variableName);
604 }
605 }
606 }
607 }
608 break;
609
610 case self::TYPE_BOOLEAN:
611 if (!isset($target[$variableName])) {
612 if ($allowEmpty) {
613 $target[$variableName] = false;
614 } else {
615 throw new UserInputException($variableName);
616 }
617 } else {
618 if (\is_numeric($target[$variableName])) {
619 $target[$variableName] = (bool)$target[$variableName];
620 } else {
621 $target[$variableName] = $target[$variableName] != 'false';
622 }
623 }
624 break;
625
626 case self::TYPE_JSON:
627 if (!isset($target[$variableName])) {
628 if ($allowEmpty) {
629 $target[$variableName] = [];
630 } else {
631 throw new UserInputException($variableName);
632 }
633 } else {
634 try {
635 $target[$variableName] = JSON::decode($target[$variableName]);
636 } catch (SystemException $e) {
637 throw new UserInputException($variableName);
638 }
639
640 if (!$allowEmpty && empty($target[$variableName])) {
641 throw new UserInputException($variableName);
642 }
643 }
644 break;
645 }
646 }
647
648 /**
649 * Returns object class name.
650 *
651 * @return string
652 */
653 public function getClassName()
654 {
655 return $this->className;
656 }
657
658 /**
659 * Returns a list of currently loaded objects.
660 *
661 * @return DatabaseObjectEditor[]
662 */
663 public function getObjects()
664 {
665 return $this->objects;
666 }
11ade432 667}