1 <?php
  2 
  3 /**
  4  * Backend Page Class.
  5  *
  6  * @package redaxo\core\backend
  7  */
  8 class rex_be_page
  9 {
 10     private $key;
 11     private $fullKey;
 12     private $title;
 13 
 14     private $popup = null;
 15     private $href;
 16     private $itemAttr = [];
 17     private $linkAttr = [];
 18     private $path;
 19     private $subPath;
 20 
 21     /** @var self|null */
 22     private $parent;
 23 
 24     /** @var self[] */
 25     private $subpages = [];
 26 
 27     private $isActive = null;
 28     private $hidden = false;
 29     private $hasLayout = true;
 30     private $hasNavigation = true;
 31     private $pjax;
 32     private $icon;
 33     private $requiredPermissions = [];
 34 
 35     /**
 36      * Constructor.
 37      *
 38      * @param string $key
 39      * @param string $title
 40      *
 41      * @throws InvalidArgumentException
 42      */
 43     public function __construct($key, $title)
 44     {
 45         if (!is_string($key)) {
 46             throw new InvalidArgumentException('Expecting $key to be a string, ' . gettype($key) . ' given!');
 47         }
 48         if (!is_string($title)) {
 49             throw new InvalidArgumentException('Expecting $title to be a string, ' . gettype($title) . ' given!');
 50         }
 51 
 52         $this->key = $key;
 53         $this->fullKey = $key;
 54         $this->title = $title;
 55     }
 56 
 57     /**
 58      * Returns the page key.
 59      *
 60      * @return string
 61      */
 62     public function getKey()
 63     {
 64         return $this->key;
 65     }
 66 
 67     /**
 68      * Returns the full page path.
 69      *
 70      * @return string
 71      */
 72     public function getFullKey()
 73     {
 74         return $this->fullKey;
 75     }
 76 
 77     /**
 78      * Returns the title.
 79      *
 80      * @returns string
 81      */
 82     public function getTitle()
 83     {
 84         return $this->title;
 85     }
 86 
 87     /**
 88      * Sets whether the page is a popup page.
 89      *
 90      * The method adds (or removes) also the rex-popup CSS class and sets hasNavigation to false (true).
 91      * If $popup is a string, the variable will be used for the onclick attribute.
 92      *
 93      * @param bool|string $popup
 94      *
 95      * @return $this
 96      */
 97     public function setPopup($popup)
 98     {
 99         if ($popup) {
100             $this->popup = true;
101             $this->setHasNavigation(false);
102             $this->addItemClass('rex-popup');
103             $this->addLinkClass('rex-popup');
104             if (is_string($popup)) {
105                 $this->setLinkAttr('onclick', $popup);
106             }
107         } else {
108             $this->popup = false;
109             $this->setHasNavigation(true);
110             $this->removeItemClass('rex-popup');
111             $this->removeLinkClass('rex-popup');
112             $this->removeLinkAttr('onclick');
113         }
114 
115         return $this;
116     }
117 
118     /**
119      * Returns whether the page is a popup.
120      *
121      * @return bool
122      */
123     public function isPopup()
124     {
125         if (null !== $this->popup) {
126             return $this->popup;
127         }
128 
129         return $this->parent && $this->parent->isPopup();
130     }
131 
132     /**
133      * Sets the page href.
134      *
135      * @param string|array $href Href string or array of params
136      *
137      * @return $this
138      */
139     public function setHref($href)
140     {
141         if (is_array($href)) {
142             $href = rex_url::backendController($href, false);
143         }
144         $this->href = $href;
145 
146         return $this;
147     }
148 
149     /**
150      * Returns whether the page has a custom href.
151      *
152      * @return bool
153      */
154     public function hasHref()
155     {
156         return (bool) $this->href;
157     }
158 
159     /**
160      * Returns the page href.
161      *
162      * @return string
163      */
164     public function getHref()
165     {
166         if ($this->href) {
167             return $this->href;
168         }
169         return rex_url::backendPage($this->getFirstSubpagesLeaf()->getFullKey(), [], false);
170     }
171 
172     /**
173      * Sets an item attribute.
174      *
175      * @param string $name
176      * @param string $value
177      *
178      * @throws InvalidArgumentException
179      *
180      * @return $this
181      */
182     public function setItemAttr($name, $value)
183     {
184         if (!is_string($name)) {
185             throw new InvalidArgumentException('Expecting $name to be a string, ' . gettype($name) . 'given!');
186         }
187         if (!is_scalar($value)) {
188             throw new InvalidArgumentException('Expecting $value to be a scalar, ' . gettype($value) . 'given!');
189         }
190         $this->itemAttr[$name] = $value;
191 
192         return $this;
193     }
194 
195     /**
196      * Returns an item attribute or all item attributes.
197      *
198      * @param string|null $name
199      * @param string      $default
200      *
201      * @return string|array Attribute value for given `$name` or attribute array if `$name` is `null`
202      */
203     public function getItemAttr($name, $default = '')
204     {
205         // return all attributes if null is passed as name
206         if ($name === null) {
207             return $this->itemAttr;
208         }
209 
210         return isset($this->itemAttr[$name]) ? $this->itemAttr[$name] : $default;
211     }
212 
213     /**
214      * Removes an item attribute.
215      *
216      * @param string $name
217      */
218     public function removeItemAttr($name)
219     {
220         unset($this->itemAttr[$name]);
221     }
222 
223     /**
224      * Adds an item class.
225      *
226      * @param string $class
227      *
228      * @throws InvalidArgumentException
229      *
230      * @return $this
231      */
232     public function addItemClass($class)
233     {
234         if (!is_string($class)) {
235             throw new InvalidArgumentException('Expecting $class to be a string, ' . gettype($class) . 'given!');
236         }
237         $classAttr = $this->getItemAttr('class');
238         if (!preg_match('/\b' . preg_quote($class, '/') . '\b/', $classAttr)) {
239             $this->setItemAttr('class', ltrim($classAttr . ' ' . $class));
240         }
241 
242         return $this;
243     }
244 
245     /**
246      * Removes an item class.
247      *
248      * @param string $class
249      */
250     public function removeItemClass($class)
251     {
252         $this->setItemAttr('class', preg_replace('/\b' . preg_quote($class, '/') . '\b/', '', $this->getItemAttr('class')));
253     }
254 
255     /**
256      * Sets an link attribute.
257      *
258      * @param string $name
259      * @param string $value
260      *
261      * @throws InvalidArgumentException
262      *
263      * @return $this
264      */
265     public function setLinkAttr($name, $value)
266     {
267         if (!is_string($name)) {
268             throw new InvalidArgumentException('Expecting $name to be a string, ' . gettype($name) . 'given!');
269         }
270         if (!is_scalar($value)) {
271             throw new InvalidArgumentException('Expecting $value to be a scalar, ' . gettype($value) . 'given!');
272         }
273         $this->linkAttr[$name] = $value;
274 
275         return $this;
276     }
277 
278     /**
279      * Removes an link attribute.
280      *
281      * @param string $name
282      */
283     public function removeLinkAttr($name)
284     {
285         unset($this->linkAttr[$name]);
286     }
287 
288     /**
289      * Returns an link attribute or all link attributes.
290      *
291      * @param string|null $name
292      * @param string      $default
293      *
294      * @return string|array Attribute value for given `$name` or attribute array if `$name` is `null`
295      */
296     public function getLinkAttr($name, $default = '')
297     {
298         // return all attributes if null is passed as name
299         if ($name === null) {
300             return $this->linkAttr;
301         }
302 
303         return isset($this->linkAttr[$name]) ? $this->linkAttr[$name] : $default;
304     }
305 
306     /**
307      * Adds an link class.
308      *
309      * @param string $class
310      *
311      * @throws InvalidArgumentException
312      *
313      * @return $this
314      */
315     public function addLinkClass($class)
316     {
317         if (!is_string($class)) {
318             throw new InvalidArgumentException('Expecting $class to be a string, ' . gettype($class) . 'given!');
319         }
320         $classAttr = $this->getLinkAttr('class');
321         if (!preg_match('/\b' . preg_quote($class, '/') . '\b/', $classAttr)) {
322             $this->setLinkAttr('class', ltrim($classAttr . ' ' . $class));
323         }
324 
325         return $this;
326     }
327 
328     /**
329      * Removes an link class.
330      *
331      * @param string $class
332      */
333     public function removeLinkClass($class)
334     {
335         $this->setLinkAttr('class', preg_replace('/\b' . preg_quote($class, '/') . '\b/', '', $this->getLinkAttr('class')));
336     }
337 
338     /**
339      * Set the page path which will be included directly by the core.
340      *
341      * @param string $path
342      *
343      * @return $this
344      */
345     public function setPath($path)
346     {
347         $this->path = $path;
348 
349         return $this;
350     }
351 
352     /**
353      * Returns whether a path is set.
354      *
355      * @return bool
356      */
357     public function hasPath()
358     {
359         return !empty($this->path) || $this->parent && $this->parent->hasPath();
360     }
361 
362     /**
363      * Returns the path which will be included directly by the core.
364      *
365      * @return string
366      */
367     public function getPath()
368     {
369         if (!empty($this->path)) {
370             return $this->path;
371         }
372         return $this->parent ? $this->parent->getPath() : null;
373     }
374 
375     /**
376      * Set the page subpath which should be used by the packages to include this page inside their main page.
377      *
378      * @param string $subPath
379      *
380      * @return $this
381      */
382     public function setSubPath($subPath)
383     {
384         $this->subPath = $subPath;
385 
386         return $this;
387     }
388 
389     /**
390      * Returns whether a subpath is set.
391      *
392      * @return bool
393      */
394     public function hasSubPath()
395     {
396         return !empty($this->subPath);
397     }
398 
399     /**
400      * Returns the subpath which should by used by packages to include this page inside their main page.
401      *
402      * @return string
403      */
404     public function getSubPath()
405     {
406         return $this->subPath;
407     }
408 
409     /**
410      * Adds a subpage.
411      *
412      * @param self $subpage
413      *
414      * @return $this
415      */
416     public function addSubpage(self $subpage)
417     {
418         $this->subpages[$subpage->getKey()] = $subpage;
419         $subpage->parent = $this;
420         $subpage->setParentKey($this->getFullKey());
421 
422         return $this;
423     }
424 
425     /**
426      * @param string $key
427      */
428     private function setParentKey($key)
429     {
430         $this->fullKey = $key . '/' . $this->key;
431         foreach ($this->subpages as $subpage) {
432             $subpage->setParentKey($this->fullKey);
433         }
434     }
435 
436     /**
437      * Sets all subpages.
438      *
439      * @param self[] $subpages
440      *
441      * @return $this
442      */
443     public function setSubpages(array $subpages)
444     {
445         $this->subpages = [];
446         array_walk($subpages, [$this, 'addSubpage']);
447 
448         return $this;
449     }
450 
451     /**
452      * Returns the subpage for the given key.
453      *
454      * @param string $key
455      *
456      * @return self
457      */
458     public function getSubpage($key)
459     {
460         return isset($this->subpages[$key]) ? $this->subpages[$key] : null;
461     }
462 
463     /**
464      * Returns all subpages.
465      *
466      * @return self[]
467      */
468     public function getSubpages()
469     {
470         return $this->subpages;
471     }
472 
473     /**
474      * Returns the first leaf of the subpages tree.
475      *
476      * @return self
477      */
478     public function getFirstSubpagesLeaf()
479     {
480         $page = $this;
481         while ($subpages = $page->getSubpages()) {
482             $page = reset($subpages);
483         }
484         return $page;
485     }
486 
487     /**
488      * Sets whether the page is active.
489      *
490      * @param bool $isActive
491      *
492      * @return $this
493      */
494     public function setIsActive($isActive = true)
495     {
496         $this->isActive = $isActive;
497 
498         return $this;
499     }
500 
501     /**
502      * Returns whether the page is active.
503      *
504      * @return bool
505      */
506     public function isActive()
507     {
508         if ($this->isActive !== null) {
509             return $this->isActive;
510         }
511         $page = rex_be_controller::getCurrentPageObject();
512         do {
513             if ($page === $this) {
514                 return true;
515             }
516         } while ($page = $page->getParent());
517         return false;
518     }
519 
520     /**
521      * Returns the parent page object.
522      *
523      * @return self|null
524      */
525     public function getParent()
526     {
527         return $this->parent;
528     }
529 
530     /**
531      * Sets whether the page is hidden.
532      *
533      * @param bool $hidden
534      *
535      * @return $this
536      */
537     public function setHidden($hidden = true)
538     {
539         $this->hidden = $hidden;
540 
541         return $this;
542     }
543 
544     /**
545      * Returns whether the page is hidden.
546      *
547      * @return bool
548      */
549     public function isHidden()
550     {
551         return $this->hidden;
552     }
553 
554     /**
555      * Sets whether the page has layout.
556      *
557      * @param bool $hasLayout
558      *
559      * @return $this
560      */
561     public function setHasLayout($hasLayout)
562     {
563         $this->hasLayout = $hasLayout;
564 
565         return $this;
566     }
567 
568     /**
569      * Returns whether tha page has layout.
570      *
571      * @return bool
572      */
573     public function hasLayout()
574     {
575         return $this->hasLayout && (!$this->parent || $this->parent->hasLayout());
576     }
577 
578     /**
579      * Sets whether the page has a navigation.
580      *
581      * @param bool $hasNavigation
582      *
583      * @return $this
584      */
585     public function setHasNavigation($hasNavigation)
586     {
587         $this->hasNavigation = $hasNavigation;
588 
589         return $this;
590     }
591 
592     /**
593      * Returns whether the page has a navigation.
594      *
595      * @return bool
596      */
597     public function hasNavigation()
598     {
599         return $this->hasNavigation && (!$this->parent || $this->parent->hasNavigation());
600     }
601 
602     /**
603      * Sets whether the page allows pjax.
604      *
605      * @param bool $pjax
606      *
607      * @return $this
608      */
609     public function setPjax($pjax = true)
610     {
611         $this->pjax = $pjax;
612 
613         return $this;
614     }
615 
616     /**
617      * Returns whether the page allows pjax.
618      *
619      * @return bool
620      */
621     public function allowsPjax()
622     {
623         if (null !== $this->pjax) {
624             return $this->pjax;
625         }
626         if ($this->parent) {
627             return $this->parent->allowsPjax();
628         }
629         return false;
630     }
631 
632     /**
633      * Sets whether the page has an icon.
634      *
635      * @param string $icon
636      *
637      * @return $this
638      */
639     public function setIcon($icon)
640     {
641         $this->icon = $icon;
642 
643         return $this;
644     }
645 
646     /**
647      * Returns the icon.
648      *
649      * @returns string
650      */
651     public function getIcon()
652     {
653         return $this->icon;
654     }
655 
656     /**
657      * Returns whether the page has an icon.
658      *
659      * @return bool
660      */
661     public function hasIcon()
662     {
663         return !empty($this->icon);
664     }
665 
666     /**
667      * Sets the required permissions.
668      *
669      * @param array|string $perm
670      *
671      * @return $this
672      */
673     public function setRequiredPermissions($perm)
674     {
675         $this->requiredPermissions = (array) $perm;
676 
677         return $this;
678     }
679 
680     /**
681      * Returns the required permission.
682      *
683      * @return array
684      */
685     public function getRequiredPermissions()
686     {
687         return $this->requiredPermissions;
688     }
689 
690     /**
691      * Checks whether the given user has permission for the page.
692      *
693      * @param rex_user $rexUser
694      *
695      * @return bool
696      */
697     public function checkPermission(rex_user $rexUser)
698     {
699         foreach ($this->requiredPermissions as $perm) {
700             if (!$rexUser->hasPerm($perm)) {
701                 return false;
702             }
703         }
704         if ($parent = $this->getParent()) {
705             return $parent->checkPermission($rexUser);
706         }
707         return true;
708     }
709 }
710