1 <?php
  2 
  3 /**
  4  * Object Oriented Framework: Basisklasse für die Strukturkomponenten.
  5  *
  6  * @package redaxo\structure
  7  */
  8 abstract class rex_structure_element
  9 {
 10     use rex_instance_pool_trait;
 11     use rex_instance_list_pool_trait;
 12 
 13     /*
 14      * these vars get read out
 15      */
 16     protected $id = '';
 17     protected $parent_id = '';
 18     protected $clang_id = '';
 19     protected $name = '';
 20     protected $catname = '';
 21     protected $template_id = '';
 22     protected $path = '';
 23     protected $priority = '';
 24     protected $catpriority = '';
 25     protected $startarticle = '';
 26     protected $status = '';
 27     protected $updatedate = '';
 28     protected $createdate = '';
 29     protected $updateuser = '';
 30     protected $createuser = '';
 31 
 32     protected static $classVars;
 33 
 34     /**
 35      * Constructor.
 36      *
 37      * @param array $params
 38      */
 39     protected function __construct(array $params)
 40     {
 41         foreach (self::getClassVars() as $var) {
 42             if (isset($params[$var])) {
 43                 $this->$var = $params[$var];
 44             }
 45         }
 46     }
 47 
 48     /**
 49      * Returns Object Value.
 50      *
 51      * @param string $value
 52      *
 53      * @return string
 54      */
 55     public function getValue($value)
 56     {
 57         // damit alte rex_article felder wie teaser, online_from etc
 58         // noch funktionieren
 59         // gleicher BC code nochmals in article::getValue
 60         foreach (['', 'art_', 'cat_'] as $prefix) {
 61             $val = $prefix . $value;
 62             if (isset($this->$val)) {
 63                 return $this->$val;
 64             }
 65         }
 66         return null;
 67     }
 68 
 69     /**
 70      * @param string $value
 71      * @param array  $prefixes
 72      *
 73      * @return bool
 74      */
 75     protected static function _hasValue($value, array $prefixes = [])
 76     {
 77         $values = self::getClassVars();
 78 
 79         if (in_array($value, $values)) {
 80             return true;
 81         }
 82 
 83         foreach ($prefixes as $prefix) {
 84             if (in_array($prefix . $value, $values)) {
 85                 return true;
 86             }
 87         }
 88 
 89         return false;
 90     }
 91 
 92     /**
 93      * Returns an Array containing article field names.
 94      *
 95      * @return string[]
 96      */
 97     public static function getClassVars()
 98     {
 99         if (empty(self::$classVars)) {
100             self::$classVars = [];
101 
102             $startId = rex_article::getSiteStartArticleId();
103             $file = rex_path::addonCache('structure', $startId . '.1.article');
104             if (!rex::isBackend() && file_exists($file)) {
105                 // da getClassVars() eine statische Methode ist, können wir hier nicht mit $this->getId() arbeiten!
106                 $genVars = rex_file::getCache($file);
107                 unset($genVars['last_update_stamp']);
108                 foreach ($genVars as $name => $value) {
109                     self::$classVars[] = $name;
110                 }
111             } else {
112                 // Im Backend die Spalten aus der DB auslesen / via EP holen
113                 $sql = rex_sql::factory();
114                 $sql->setQuery('SELECT * FROM ' . rex::getTablePrefix() . 'article LIMIT 0');
115                 foreach ($sql->getFieldnames() as $field) {
116                     self::$classVars[] = $field;
117                 }
118             }
119         }
120 
121         return self::$classVars;
122     }
123 
124     public static function resetClassVars()
125     {
126         self::$classVars = null;
127     }
128 
129     /**
130      * Return an rex_structure_element object based on an id.
131      * The instance will be cached in an instance-pool and therefore re-used by a later call.
132      *
133      * @param int $id    the article id
134      * @param int $clang the clang id
135      *
136      * @return static|null A rex_structure_element instance typed to the late-static binding type of the caller
137      */
138     public static function get($id, $clang = null)
139     {
140         $id = (int) $id;
141 
142         if ($id <= 0) {
143             return null;
144         }
145 
146         if (!$clang) {
147             $clang = rex_clang::getCurrentId();
148         }
149 
150         $class = static::class;
151         return static::getInstance([$id, $clang], function ($id, $clang) use ($class) {
152             $article_path = rex_path::addonCache('structure', $id . '.' . $clang . '.article');
153 
154             // load metadata from cache
155             $metadata = rex_file::getCache($article_path);
156 
157             // generate cache if not exists
158             if (!$metadata) {
159                 rex_article_cache::generateMeta($id, $clang);
160                 $metadata = rex_file::getCache($article_path);
161             }
162 
163             // if cache does not exist after generation, the article id is invalid
164             if (!$metadata) {
165                 return null;
166             }
167 
168             // don't allow to retrieve non-categories (startarticle=0) as rex_category
169             if (!$metadata['startarticle'] && (rex_category::class === static::class || is_subclass_of(static::class, rex_category::class))) {
170                 return null;
171             }
172 
173             return new $class($metadata);
174         });
175     }
176 
177     /**
178      * @param int    $parentId
179      * @param string $listType
180      * @param bool   $ignoreOfflines
181      * @param int    $clang
182      *
183      * @return static[]
184      */
185     protected static function getChildElements($parentId, $listType, $ignoreOfflines = false, $clang = null)
186     {
187         $parentId = (int) $parentId;
188         // for $parentId=0 root elements will be returned, so abort here for $parentId<0 only
189         if (0 > $parentId) {
190             return [];
191         }
192         if (!$clang) {
193             $clang = rex_clang::getCurrentId();
194         }
195 
196         $class = static::class;
197         return static::getInstanceList(
198             // list key
199             [$parentId, $listType],
200             // callback to get an instance for a given ID, status will be checked if $ignoreOfflines==true
201             function ($id) use ($class, $ignoreOfflines, $clang) {
202                 if ($instance = $class::get($id, $clang)) {
203                     return !$ignoreOfflines || $instance->isOnline() ? $instance : null;
204                 }
205                 return null;
206             },
207             // callback to create the list of IDs
208             function ($parentId, $listType) {
209                 $listFile = rex_path::addonCache('structure', $parentId . '.' . $listType);
210 
211                 $list = rex_file::getCache($listFile);
212                 if (!$list) {
213                     rex_article_cache::generateLists($parentId);
214                     $list = rex_file::getCache($listFile);
215                 }
216                 return $list;
217             }
218         );
219     }
220 
221     /**
222      * Returns the clang of the category.
223      *
224      * @return int
225      *
226      * @deprecated since redaxo 5.6, use getClangId() instead
227      */
228     public function getClang()
229     {
230         return $this->clang_id;
231     }
232 
233     /**
234      * Returns the clang of the category.
235      *
236      * @return int
237      */
238     public function getClangId()
239     {
240         return $this->clang_id;
241     }
242 
243     /**
244      * Returns a url for linking to this article.
245      *
246      * @param array  $params
247      * @param string $divider
248      *
249      * @return string
250      */
251     public function getUrl(array $params = [], $divider = '&amp;')
252     {
253         return rex_getUrl($this->getId(), $this->getClang(), $params, $divider);
254     }
255 
256     /**
257      * Returns the id of the article.
258      *
259      * @return int
260      */
261     public function getId()
262     {
263         return $this->id;
264     }
265 
266     /**
267      * Returns the parent_id of the article.
268      *
269      * @return int
270      */
271     public function getParentId()
272     {
273         return $this->parent_id;
274     }
275 
276     /**
277      * Returns the path of the category/article.
278      *
279      * @return string
280      */
281     abstract public function getPath();
282 
283     /**
284      * Returns the path ids of the category/article as an array.
285      *
286      * @return int[]
287      */
288     public function getPathAsArray()
289     {
290         $path = explode('|', $this->getPath());
291         return array_values(array_map('intval', array_filter($path)));
292     }
293 
294     /**
295      * Returns the parent category.
296      *
297      * @return self
298      */
299     abstract public function getParent();
300 
301     /**
302      * Returns the name of the article.
303      *
304      * @return string
305      */
306     public function getName()
307     {
308         return $this->name;
309     }
310 
311     /**
312      * Returns the article priority.
313      *
314      * @return int
315      */
316     public function getPriority()
317     {
318         return $this->priority;
319     }
320 
321     /**
322      * Returns the last update user.
323      *
324      * @return string
325      */
326     public function getUpdateUser()
327     {
328         return $this->updateuser;
329     }
330 
331     /**
332      * Returns the last update date.
333      *
334      * @return int
335      */
336     public function getUpdateDate()
337     {
338         return $this->updatedate;
339     }
340 
341     /**
342      * Returns the creator.
343      *
344      * @return string
345      */
346     public function getCreateUser()
347     {
348         return $this->createuser;
349     }
350 
351     /**
352      * Returns the creation date.
353      *
354      * @return int
355      */
356     public function getCreateDate()
357     {
358         return $this->createdate;
359     }
360 
361     /**
362      * Returns true if article is online.
363      *
364      * @return bool
365      */
366     public function isOnline()
367     {
368         return $this->status == 1;
369     }
370 
371     /**
372      * Returns the template id.
373      *
374      * @return int
375      */
376     public function getTemplateId()
377     {
378         return $this->template_id;
379     }
380 
381     /**
382      * Returns true if article has a template.
383      *
384      * @return bool
385      */
386     public function hasTemplate()
387     {
388         return $this->template_id > 0;
389     }
390 
391     /**
392      * Returns whether the element is permitted.
393      *
394      * @return bool
395      */
396     abstract public function isPermitted();
397 
398     /**
399      * Returns a link to this article.
400      *
401      * @param array  $params             Parameter für den Link
402      * @param array  $attributes         Attribute die dem Link hinzugefügt werden sollen. Default: array
403      * @param string $sorroundTag        HTML-Tag-Name mit dem der Link umgeben werden soll, z.b. 'li', 'div'. Default: null
404      * @param array  $sorroundAttributes Attribute die Umgebenden-Element hinzugefügt werden sollen. Default: array
405      *
406      * @return string
407      */
408     public function toLink(array $params = [], array $attributes = [], $sorroundTag = null, array $sorroundAttributes = [])
409     {
410         $name = $this->getName();
411         $link = '<a href="' . $this->getUrl($params) . '"' . $this->_toAttributeString($attributes) . ' title="' . rex_escape($name) . '">' . rex_escape($name) . '</a>';
412 
413         if ($sorroundTag !== null && is_string($sorroundTag)) {
414             $link = '<' . $sorroundTag . $this->_toAttributeString($sorroundAttributes) . '>' . $link . '</' . $sorroundTag . '>';
415         }
416 
417         return $link;
418     }
419 
420     /**
421      * @param array $attributes
422      *
423      * @return string
424      */
425     protected function _toAttributeString(array $attributes)
426     {
427         $attr = '';
428 
429         if ($attributes !== null && is_array($attributes)) {
430             foreach ($attributes as $name => $value) {
431                 $attr .= ' ' . $name . '="' . $value . '"';
432             }
433         }
434 
435         return $attr;
436     }
437 
438     /**
439      * Get an array of all parentCategories.
440      * Returns an array of rex_structure_element objects.
441      *
442      * @return rex_category[]
443      */
444     public function getParentTree()
445     {
446         $return = [];
447 
448         if ($this->path) {
449             if ($this->isStartArticle()) {
450                 $explode = explode('|', $this->path . $this->id . '|');
451             } else {
452                 $explode = explode('|', $this->path);
453             }
454 
455             if (is_array($explode)) {
456                 foreach ($explode as $var) {
457                     if ($var != '') {
458                         $return[] = rex_category::get($var, $this->clang_id);
459                     }
460                 }
461             }
462         }
463 
464         return $return;
465     }
466 
467     /**
468      * Checks if $anObj is in the parent tree of the object.
469      *
470      * @param self $anObj
471      *
472      * @return bool
473      */
474     public function inParentTree(self $anObj)
475     {
476         $tree = $this->getParentTree();
477         foreach ($tree as $treeObj) {
478             if ($treeObj == $anObj) {
479                 return true;
480             }
481         }
482         return false;
483     }
484 
485     /**
486      * Returns true if this Article is the Startpage for the category.
487      *
488      * @return bool
489      */
490     public function isStartArticle()
491     {
492         return $this->startarticle;
493     }
494 
495     /**
496      * Returns true if this Article is the Startpage for the entire site.
497      *
498      * @return bool
499      */
500     public function isSiteStartArticle()
501     {
502         return $this->id == rex_article::getSiteStartArticleId();
503     }
504 
505     /**
506      * Returns  true if this Article is the not found article.
507      *
508      * @return bool
509      */
510     public function isNotFoundArticle()
511     {
512         return $this->id == rex_article::getNotfoundArticleId();
513     }
514 }
515