Add explicit `return null;` statements
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / data / box / Box.class.php
CommitLineData
11fd4618 1<?php
a9229942 2
11fd4618 3namespace wcf\data\box;
a9229942 4
2f273839 5use wcf\data\box\content\BoxContent;
91317611 6use wcf\data\condition\Condition;
a9229942 7use wcf\data\DatabaseObject;
56eb7314 8use wcf\data\media\ViewableMedia;
39abe192
AE
9use wcf\data\menu\Menu;
10use wcf\data\menu\MenuCache;
91317611 11use wcf\data\object\type\ObjectTypeCache;
a9229942
TD
12use wcf\data\page\Page;
13use wcf\data\page\PageCache;
98889e94 14use wcf\system\acl\simple\SimpleAclResolver;
8ff2cd79 15use wcf\system\box\IBoxController;
91317611
MS
16use wcf\system\box\IConditionBoxController;
17use wcf\system\condition\ConditionHandler;
ab2e1d13 18use wcf\system\exception\ImplementationException;
5636588a
MW
19use wcf\system\page\handler\ILookupPageHandler;
20use wcf\system\page\handler\IMenuPageHandler;
11fd4618
MW
21use wcf\system\WCF;
22
23/**
24 * Represents a box.
a9229942
TD
25 *
26 * @author Marcel Werk
27 * @copyright 2001-2019 WoltLab GmbH
28 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
29 * @package WoltLabSuite\Core\Data\Box
30 * @since 3.0
31 *
32 * @property-read int $boxID unique id of the box
33 * @property-read int|null $objectTypeID id of the box controller object type
34 * @property-read string $identifier unique textual identifier of the box
35 * @property-read string $name monolingual name of the box shown in the ACP
36 * @property-read string $boxType type of the box which determines the method of outputting its content (default box types are `text`, `html`, `tpl`, `system`)
37 * @property-read string $position name of the position on the page at which the box is shown
38 * @property-read int $showOrder position of the box in relation to its siblings
39 * @property-read int $visibleEverywhere is `1` if the box is visible on every page, otherwise `0`
40 * @property-read int $isMultilingual is `1` if the box content is available in multiple languages, otherwise `0`
41 * @property-read int $lastUpdateTime timestamp at which the box has been updated the last time
42 * @property-read string $cssClassName css class name(s) of the box
43 * @property-read int $showHeader is `1` if the box header will be shown, otherwise `0`
44 * @property-read int $originIsSystem is `1` if the box has been delivered by a package, otherwise `0` (i.e. the box has been created in the ACP)
45 * @property-read int $packageID id of the package which delivers the box or `1` if it has been created in the ACP
46 * @property-read int|null $menuID id of the menu whose menu items are shown in the contents if `$boxType = menu`, otherwise `null`
47 * @property-read int|null $linkPageID id of the (internal) page the box image and box title are linking to or `null` if no internal page is linked
48 * @property-read int $linkPageObjectID id of the object the (internal) page links refers to or `0` if no internal link is used or no specific object is linked
49 * @property-read string $externalURL external link used to for the box image and box title or empty if no external link is set
50 * @property-read array $additionalData array with additional data of the box
51 * @property-read int|null $limit number of objects shown in the box for `AbstractDatabaseObjectListBoxController` controllers or `null` otherwise
52 * @property-read string|null $sortField sort field of the objects shown in the box for `AbstractDatabaseObjectListBoxController` controllers or `null` otherwise
53 * @property-read string|null $sortOrder sort order of the objects shown in the box for `AbstractDatabaseObjectListBoxController` controllers or `null` otherwise
54 * @property-read int $isDisabled is `1` if the box is disabled and thus is not displayed, otherwise `0`
33bd967c 55 * @property-read bool $invertPermissions is `1` if the permissions are inverted
11fd4618 56 */
a9229942
TD
57class Box extends DatabaseObject
58{
59 /**
60 * image media object
61 * @var ViewableMedia
62 */
63 protected $image;
64
65 /**
66 * available box types
67 * @var string[]
68 */
69 public static $availableBoxTypes = ['text', 'html', 'tpl', 'system'];
70
71 /**
72 * available box positions
73 * @var string[]
74 */
75 public static $availablePositions = [
76 'hero',
77 'headerBoxes',
78 'top',
79 'sidebarLeft',
80 'contentTop',
81 'sidebarRight',
82 'contentBottom',
83 'bottom',
84 'footerBoxes',
85 'footer',
86 ];
87
88 /**
89 * available menu positions
90 * @var string[]
91 */
92 public static $availableMenuPositions = ['top', 'sidebarLeft', 'sidebarRight', 'bottom', 'footer'];
93
94 /**
95 * menu object
96 * @var Menu
97 */
98 protected $menu;
99
100 /**
101 * box to page assignments
102 * @var int[]
103 */
104 protected $pageIDs;
105
106 /**
107 * box controller
108 * @var IBoxController
109 */
110 protected $controller;
111
112 /**
113 * box content grouped by language id
114 * @var BoxContent[]
115 */
116 public $boxContents;
117
118 /**
119 * @var IMenuPageHandler
120 */
121 protected $linkPageHandler;
122
123 /**
124 * page object
125 * @var Page
126 */
127 protected $linkPage;
128
129 /**
130 * virtual show order of this box
131 * @var int
132 */
133 public $virtualShowOrder = -1;
134
135 /**
136 * list of positions that support the edit button
137 * @var string[]
138 */
139 public $editButtonPositions = [
140 'headerBoxes',
141 'sidebarLeft',
142 'contentTop',
143 'sidebarRight',
144 'contentBottom',
145 'footerBoxes',
146 'footer',
147 ];
148
149 /**
150 * @inheritDoc
151 */
152 public function __get($name)
153 {
154 $value = parent::__get($name);
155
156 if ($value === null && isset($this->data['additionalData'][$name])) {
157 $value = $this->data['additionalData'][$name];
158 }
159
160 return $value;
161 }
162
163 /**
164 * @inheritDoc
165 */
166 protected function handleData($data)
167 {
168 parent::handleData($data);
169
170 // handle condition data
171 if (isset($data['additionalData'])) {
172 $this->data['additionalData'] = @\unserialize($data['additionalData'] ?: '');
173
174 if (!\is_array($this->data['additionalData'])) {
175 $this->data['additionalData'] = [];
176 }
177 } else {
178 $this->data['additionalData'] = [];
179 }
180 }
181
182 /**
183 * Returns true if the active user can delete this box.
184 *
185 * @return bool
186 */
187 public function canDelete()
188 {
189 if (WCF::getSession()->getPermission('admin.content.cms.canManageBox') && !$this->originIsSystem) {
190 return true;
191 }
192
193 return false;
194 }
195
196 /**
197 * Returns the box's content.
198 *
199 * @return BoxContent[]
200 */
201 public function getBoxContents()
202 {
203 if ($this->boxContents === null) {
204 $this->boxContents = [];
205
206 $sql = "SELECT *
207 FROM wcf" . WCF_N . "_box_content
208 WHERE boxID = ?";
209 $statement = WCF::getDB()->prepareStatement($sql);
210 $statement->execute([$this->boxID]);
211 while ($row = $statement->fetchArray()) {
212 $this->boxContents[$row['languageID'] ?: 0] = new BoxContent(null, $row);
213 }
214 }
215
216 return $this->boxContents;
217 }
218
219 /**
220 * Sets the box's content.
221 *
222 * @param BoxContent[] $boxContents
223 */
224 public function setBoxContents($boxContents)
225 {
226 $this->boxContents = $boxContents;
227 }
228
229 /**
230 * Returns the title of the box as set in the box content database table.
231 *
232 * @return string
233 */
234 public function getBoxContentTitle()
235 {
236 $this->getBoxContents();
237 if ($this->isMultilingual || $this->boxType === 'system') {
238 if ($this->boxType === 'system' && $this->getController()->getTitle()) {
239 return $this->getController()->getTitle();
240 }
241
242 if (isset($this->boxContents[WCF::getLanguage()->languageID])) {
243 return $this->boxContents[WCF::getLanguage()->languageID]->title;
244 }
245 } elseif (isset($this->boxContents[0])) {
246 return $this->boxContents[0]->title;
247 }
248
249 return '';
250 }
251
252 /**
253 * Returns the title for the rendered version of this box.
254 *
255 * @return string
256 */
257 public function getTitle()
258 {
259 if ($this->boxType == 'menu') {
260 return $this->getMenu()->getTitle();
261 }
262
263 return $this->getBoxContentTitle();
264 }
265
266 /**
267 * Returns the content for the rendered version of this box.
268 *
269 * @return string
270 */
271 public function getContent()
272 {
273 if ($this->boxType == 'system') {
274 return $this->getController()->getContent();
275 } elseif ($this->boxType == 'menu') {
276 return $this->getMenu()->getContent();
277 }
278
279 $this->getBoxContents();
280 $boxContent = null;
281 if ($this->isMultilingual) {
282 if (isset($this->boxContents[WCF::getLanguage()->languageID])) {
283 $boxContent = $this->boxContents[WCF::getLanguage()->languageID];
284 }
285 } else {
286 if (isset($this->boxContents[0])) {
287 $boxContent = $this->boxContents[0];
288 }
289 }
290
291 if ($boxContent !== null) {
292 if ($this->boxType == 'text') {
293 return $boxContent->getFormattedContent();
294 } elseif ($this->boxType == 'html') {
295 return $boxContent->getParsedContent();
296 } elseif ($this->boxType == 'tpl') {
297 return $boxContent->getParsedTemplate($this->getTplName(WCF::getLanguage()->languageID));
298 }
299 }
300
301 return '';
302 }
303
304 /**
305 * Returns the rendered version of this box.
306 *
307 * @return string
308 */
309 public function render()
310 {
311 if (!$this->hasContent()) {
312 return '';
313 }
314
315 WCF::getTPL()->assign([
316 'box' => $this,
317 ]);
318
319 return WCF::getTPL()->fetch('__box');
320 }
321
322 /**
323 * Returns false if this box has no content.
324 *
325 * @return bool
326 */
327 public function hasContent()
328 {
329 if ($this->boxType == 'system') {
330 return $this->getController()->hasContent();
331 } elseif ($this->boxType == 'menu') {
332 return $this->getMenu()->hasContent();
333 }
334
335 $this->getBoxContents();
336 $content = '';
337 if ($this->isMultilingual) {
338 if (isset($this->boxContents[WCF::getLanguage()->languageID])) {
339 $content = $this->boxContents[WCF::getLanguage()->languageID]->content;
340 }
341 } else {
342 if (isset($this->boxContents[0])) {
343 $content = $this->boxContents[0]->content;
344 }
345 }
346
347 return !empty($content);
348 }
349
350 /**
351 * Returns the box controller.
352 *
353 * @return IBoxController
354 */
355 public function getController()
356 {
357 if ($this->controller === null && $this->objectTypeID) {
358 $className = ObjectTypeCache::getInstance()->getObjectType($this->objectTypeID)->className;
359
360 $this->controller = new $className();
361 $this->controller->setBox($this);
362 }
363
364 return $this->controller;
365 }
366
367 /**
368 * Returns the menu shown in the box.
369 *
370 * @return Menu
371 */
372 public function getMenu()
373 {
374 if ($this->menu === null) {
375 $this->menu = MenuCache::getInstance()->getMenuByID($this->menuID);
376 }
377
378 return $this->menu;
379 }
380
381 /**
382 * Returns the image of this box or `null` if the box has no image.
383 *
384 * @return ViewableMedia|null
385 */
386 public function getImage()
387 {
388 if ($this->boxType === 'menu') {
c0b28aa2 389 return null;
a9229942
TD
390 }
391
392 if ($this->image === null) {
393 if ($this->boxType === 'system') {
394 $this->image = $this->getController()->getImage();
395 } else {
396 $this->getBoxContents();
397 if ($this->isMultilingual) {
398 if (isset($this->boxContents[WCF::getLanguage()->languageID]) && $this->boxContents[WCF::getLanguage()->languageID]->imageID) {
399 $this->image = $this->boxContents[WCF::getLanguage()->languageID]->getImage();
400 }
401 } elseif (isset($this->boxContents[0]) && $this->boxContents[0]->imageID) {
402 $this->image = $this->boxContents[0]->getImage();
403 }
404 }
405 }
406
407 if ($this->image === null || !$this->image->isAccessible()) {
c0b28aa2 408 return null;
a9229942
TD
409 }
410
411 return $this->image;
412 }
413
414 /**
415 * Returns the URL of this box.
416 *
417 * @return string
418 */
419 public function getLink()
420 {
421 if ($this->boxType == 'system') {
422 return $this->getController()->getLink();
423 } elseif ($this->boxType == 'menu') {
424 return '';
425 }
426
427 if ($this->linkPageObjectID) {
428 $handler = $this->getLinkPageHandler();
429 if ($handler && $handler instanceof ILookupPageHandler) {
430 return $handler->getLink($this->linkPageObjectID);
431 }
432 }
433
434 if ($this->linkPageID) {
435 return $this->getLinkPage()->getLink();
436 } else {
437 return $this->externalURL;
438 }
439 }
440
441 /**
442 * Returns true if this box has a link.
443 *
444 * @return bool
445 */
446 public function hasLink()
447 {
448 if ($this->boxType == 'system') {
449 return $this->getController()->hasLink();
450 } elseif ($this->boxType == 'menu') {
451 return false;
452 }
453
454 return $this->linkPageID || !empty($this->externalURL);
455 }
456
457 /**
458 * Returns the IMenuPageHandler of the linked page.
459 *
460 * @return IMenuPageHandler|null
461 * @throws ImplementationException
462 */
463 protected function getLinkPageHandler()
464 {
465 $page = $this->getLinkPage();
466 if ($page !== null && $page->handler) {
467 if ($this->linkPageHandler === null) {
468 $className = $page->handler;
469 $this->linkPageHandler = new $className();
470 if (!($this->linkPageHandler instanceof IMenuPageHandler)) {
471 throw new ImplementationException(\get_class($this->linkPageHandler), IMenuPageHandler::class);
472 }
473 }
474 }
475
476 return $this->linkPageHandler;
477 }
478
479 /**
480 * Returns the page that is linked by this box.
481 *
482 * @return Page|null
483 */
484 public function getLinkPage()
485 {
486 if ($this->linkPage === null && $this->linkPageID) {
487 $this->linkPage = PageCache::getInstance()->getPage($this->linkPageID);
488 }
489
490 return $this->linkPage;
491 }
492
493 /**
494 * Returns the template name of this box.
495 *
496 * @param int $languageID
497 * @return string
498 */
499 public function getTplName($languageID = null)
500 {
501 if ($this->boxType == 'tpl') {
502 if ($this->isMultilingual) {
503 return '__cms_box_' . $this->boxID . '_' . $languageID;
504 }
505
506 return '__cms_box_' . $this->boxID;
507 }
508
509 return '';
510 }
511
512 /**
513 * Returns box to page assignments.
514 *
515 * @return int[]
516 */
517 public function getPageIDs()
518 {
519 if ($this->pageIDs === null) {
520 $sql = "SELECT pageID
521 FROM wcf" . WCF_N . "_box_to_page
522 WHERE boxID = ?";
523 $statement = WCF::getDB()->prepareStatement($sql);
524 $statement->execute([$this->boxID]);
525
526 $this->pageIDs = $statement->fetchAll(\PDO::FETCH_COLUMN);
527 }
528
529 return $this->pageIDs;
530 }
531
532 /**
533 * Returns the conditions of the notice.
534 *
535 * @return Condition[]
536 */
537 public function getConditions()
538 {
539 /** @noinspection PhpUndefinedMethodInspection */
540 if ($this->boxType === 'system' && $this->getController() instanceof IConditionBoxController && $this->getController()->getConditionDefinition()) {
541 /** @noinspection PhpUndefinedMethodInspection */
542 return ConditionHandler::getInstance()->getConditions(
543 $this->getController()->getConditionDefinition(),
544 $this->boxID
545 );
546 }
547
548 return [];
549 }
550
551 /**
552 * Returns true if this box is accessible by current user.
553 *
554 * @return bool
555 */
556 public function isAccessible()
557 {
33bd967c 558 $canAccess = SimpleAclResolver::getInstance()->canAccess('com.woltlab.wcf.box', $this->boxID);
559
560 if ($this->invertPermissions) {
561 $canAccess = !$canAccess;
562 }
563
564 return $canAccess;
a9229942
TD
565 }
566
567 /**
568 * Sets the virtual show order of this box.
569 *
570 * @param int $virtualShowOrder
571 */
572 public function setVirtualShowOrder($virtualShowOrder)
573 {
574 $this->virtualShowOrder = $virtualShowOrder;
575 }
576
577 /**
578 * Returns true if an edit button should be displayed for this box.
579 *
580 * @return bool
581 * @since 5.2
582 */
583 public function showEditButton()
584 {
585 if (
586 WCF::getSession()->getPermission('admin.content.cms.canManageBox')
587 && $this->boxType !== 'menu'
588 && \in_array($this->position, $this->editButtonPositions)
589 ) {
590 return true;
591 }
592
593 return false;
594 }
595
596 /**
597 * Returns the box with the given identifier.
598 *
599 * @param string $identifier
600 * @return Box
601 */
602 public static function getBoxByIdentifier($identifier)
603 {
604 $sql = "SELECT *
605 FROM wcf" . WCF_N . "_box
606 WHERE identifier = ?";
607 $statement = WCF::getDB()->prepareStatement($sql);
608 $statement->execute([$identifier]);
609
610 return $statement->fetchObject(self::class);
611 }
612
613 /**
614 * Returns the box with the given name.
615 *
616 * @param string $name
617 * @return Box
618 */
619 public static function getBoxByName($name)
620 {
621 $sql = "SELECT *
622 FROM wcf" . WCF_N . "_box
623 WHERE name = ?";
624 $statement = WCF::getDB()->prepareStatement($sql);
625 $statement->execute([$name]);
626
627 return $statement->fetchObject(self::class);
628 }
629
630 /**
631 * Returns the box with the menu id.
632 *
633 * @param int $menuID
634 * @return Box
635 */
636 public static function getBoxByMenuID($menuID)
637 {
638 $sql = "SELECT *
639 FROM wcf" . WCF_N . "_box
640 WHERE menuID = ?";
641 $statement = WCF::getDB()->prepareStatement($sql);
642 $statement->execute([$menuID]);
643
644 return $statement->fetchObject(self::class);
645 }
11fd4618 646}