1 <?php
  2 
  3 /**
  4  * Klasse regelt den Zugriff auf Artikelinhalte.
  5  * Alle benötigten Daten werden von der DB bezogen.
  6  *
  7  * @package redaxo\structure\content
  8  */
  9 class rex_article_content_base
 10 {
 11     public $warning;
 12     public $info;
 13     public $debug;
 14 
 15     public $template_id;
 16     public $template_attributes;
 17 
 18     protected $category_id;
 19     protected $article_id;
 20     protected $slice_id;
 21     protected $getSlice;
 22     protected $mode;
 23     protected $function;
 24 
 25     protected $ctype;
 26     protected $clang;
 27 
 28     protected $eval;
 29 
 30     protected $slice_revision;
 31 
 32     protected $ARTICLE;
 33 
 34     public function __construct($article_id = null, $clang = null)
 35     {
 36         $this->article_id = 0;
 37         $this->template_id = 0;
 38         $this->ctype = -1; // zeigt alles an
 39         $this->slice_id = 0;
 40         $this->getSlice = 0;
 41 
 42         $this->mode = 'view';
 43         $this->eval = false;
 44 
 45         $this->slice_revision = 0;
 46 
 47         $this->debug = false;
 48 
 49         if ($clang !== null) {
 50             $this->setCLang($clang);
 51         } else {
 52             $this->setClang(rex_clang::getCurrentId());
 53         }
 54 
 55         // ----- EXTENSION POINT
 56         rex_extension::registerPoint(new rex_extension_point('ART_INIT', '', [
 57             'article' => $this,
 58             'article_id' => $article_id,
 59             'clang' => $this->clang,
 60         ]));
 61 
 62         if ($article_id !== null) {
 63             $this->setArticleId($article_id);
 64         }
 65     }
 66 
 67     protected function getSqlInstance()
 68     {
 69         if (!is_object($this->ARTICLE)) {
 70             $this->ARTICLE = rex_sql::factory();
 71             if ($this->debug) {
 72                 $this->ARTICLE->setDebug();
 73             }
 74         }
 75         return $this->ARTICLE;
 76     }
 77 
 78     public function setSliceRevision($sr)
 79     {
 80         $this->slice_revision = (int) $sr;
 81     }
 82 
 83     // ----- Slice Id setzen für Editiermodus
 84     public function setSliceId($value)
 85     {
 86         $this->slice_id = $value;
 87     }
 88 
 89     public function setClang($value)
 90     {
 91         if (!rex_clang::exists($value)) {
 92             $value = rex_clang::getCurrentId();
 93         }
 94         $this->clang = $value;
 95     }
 96 
 97     public function getArticleId()
 98     {
 99         return $this->article_id;
100     }
101 
102     public function getClangId()
103     {
104         return $this->clang;
105     }
106 
107     /**
108      * @deprecated since redaxo 5.6, use getClangId() instead
109      */
110     public function getClang()
111     {
112         return $this->clang;
113     }
114 
115     public function setArticleId($article_id)
116     {
117         $article_id = (int) $article_id;
118         $this->article_id = $article_id;
119 
120         // ---------- select article
121         $sql = $this->getSqlInstance();
122         $sql->setQuery('SELECT * FROM ' . rex::getTablePrefix() . 'article WHERE ' . rex::getTablePrefix() . 'article.id=? AND clang_id=?', [$article_id, $this->clang]);
123 
124         if ($sql->getRows() == 1) {
125             $this->template_id = $this->getValue('template_id');
126             $this->category_id = $this->getValue('category_id');
127             return true;
128         }
129 
130         $this->article_id = 0;
131         $this->template_id = 0;
132         $this->category_id = 0;
133         return false;
134     }
135 
136     public function setTemplateId($template_id)
137     {
138         $this->template_id = $template_id;
139     }
140 
141     public function getTemplateId()
142     {
143         return $this->template_id;
144     }
145 
146     public function setMode($mode)
147     {
148         $this->mode = $mode;
149     }
150 
151     public function setFunction($function)
152     {
153         $this->function = $function;
154     }
155 
156     public function setEval($value)
157     {
158         if ($value) {
159             $this->eval = true;
160         } else {
161             $this->eval = false;
162         }
163     }
164 
165     protected function correctValue($value)
166     {
167         if ($value == 'category_id') {
168             if ($this->getValue('startarticle') != 1) {
169                 $value = 'parent_id';
170             } else {
171                 $value = 'id';
172             }
173         } elseif ($value == 'article_id') {
174             $value = 'id';
175         }
176 
177         return $value;
178     }
179 
180     protected function _getValue($value)
181     {
182         $value = $this->correctValue($value);
183 
184         return $this->getSqlInstance()->getValue($value);
185     }
186 
187     public function getValue($value)
188     {
189         // damit alte rex_article felder wie teaser, online_from etc
190         // noch funktionieren
191         // gleicher BC code nochmals in rex_structure_element::getValue
192         foreach (['', 'art_', 'cat_'] as $prefix) {
193             $val = $prefix . $value;
194             if ($this->hasValue($val)) {
195                 return $this->_getValue($val);
196             }
197         }
198         return '[' . $value . ' not found]';
199     }
200 
201     public function hasValue($value)
202     {
203         return $this->getSqlInstance()->hasValue($this->correctValue($value));
204     }
205 
206     /**
207      * Outputs a slice.
208      *
209      * @param rex_sql $artDataSql    A rex_sql instance containing all slice and module data
210      * @param int     $moduleIdToAdd The id of the module, which was selected using the ModuleSelect
211      *
212      * @return string
213      */
214     protected function outputSlice(rex_sql $artDataSql, $moduleIdToAdd)
215     {
216         $output = rex_extension::registerPoint(new rex_extension_point(
217             'SLICE_OUTPUT',
218             $artDataSql->getValue(rex::getTablePrefix() . 'module.output'),
219             [
220                 'article_id' => $this->article_id,
221                 'clang' => $this->clang,
222                 'slice_data' => $artDataSql,
223             ]
224         ));
225         $output = $this->replaceVars($artDataSql, $output);
226 
227         return $this->getStreamOutput('module/' . $artDataSql->getValue(rex::getTablePrefix() . 'module.id') . '/output', $output);
228     }
229 
230     /**
231      * Returns the content of the given slice-id.
232      *
233      * @param int $sliceId A article-slice id
234      *
235      * @return string
236      */
237     public function getSlice($sliceId)
238     {
239         $oldEval = $this->eval;
240         $this->setEval(true);
241 
242         $this->getSlice = $sliceId;
243         $sliceContent = $this->getArticle();
244         $this->getSlice = 0;
245 
246         $this->setEval($oldEval);
247         return $this->replaceLinks($sliceContent);
248     }
249 
250     /**
251      * Returns the content of the article of the given ctype. If no ctype is given, content of all ctypes is returned.
252      *
253      * @param int $curctype The ctype to fetch, or -1 for all ctypes
254      *
255      * @return string
256      */
257     public function getArticle($curctype = -1)
258     {
259         $this->ctype = $curctype;
260 
261         if ($this->article_id == 0 && $this->getSlice == 0) {
262             return rex_i18n::msg('no_article_available');
263         }
264 
265         $articleLimit = '';
266         if ($this->article_id != 0) {
267             $articleLimit = ' AND ' . rex::getTablePrefix() . 'article_slice.article_id=' . (int) $this->article_id;
268         }
269 
270         $sliceLimit = '';
271         if ($this->getSlice != 0) {
272             $sliceLimit = ' AND ' . rex::getTablePrefix() . "article_slice.id = '" . ((int) $this->getSlice) . "' ";
273         }
274 
275         // ----- start: article caching
276         ob_start();
277         ob_implicit_flush(0);
278         $module_id = rex_request('module_id', 'int');
279 
280         // ---------- alle teile/slices eines artikels auswaehlen
281         $query = 'SELECT ' . rex::getTablePrefix() . 'module.id, ' . rex::getTablePrefix() . 'module.name, ' . rex::getTablePrefix() . 'module.output, ' . rex::getTablePrefix() . 'module.input, ' . rex::getTablePrefix() . 'article_slice.*, ' . rex::getTablePrefix() . 'article.parent_id
282                         FROM
283                             ' . rex::getTablePrefix() . 'article_slice
284                         LEFT JOIN ' . rex::getTablePrefix() . 'module ON ' . rex::getTablePrefix() . 'article_slice.module_id=' . rex::getTablePrefix() . 'module.id
285                         LEFT JOIN ' . rex::getTablePrefix() . 'article ON ' . rex::getTablePrefix() . 'article_slice.article_id=' . rex::getTablePrefix() . 'article.id
286                         WHERE
287                             ' . rex::getTablePrefix() . "article_slice.clang_id='" . $this->clang . "' AND
288                             " . rex::getTablePrefix() . "article.clang_id='" . $this->clang . "' AND
289                             " . rex::getTablePrefix() . "article_slice.revision='" . $this->slice_revision . "'
290                             " . $articleLimit . '
291                             ' . $sliceLimit . '
292                             ORDER BY ' . rex::getTablePrefix() . 'article_slice.priority';
293 
294         $query = rex_extension::registerPoint(new rex_extension_point(
295             'ART_SLICES_QUERY',
296             $query,
297             ['article' => $this]
298         ));
299 
300         $artDataSql = rex_sql::factory();
301         $artDataSql->setDebug($this->debug);
302         $artDataSql->setQuery($query);
303 
304         // pre hook
305         $articleContent = '';
306         $articleContent = $this->preArticle($articleContent, $module_id);
307 
308         // ---------- SLICES AUSGEBEN
309 
310         $prevCtype = null;
311         $artDataSql->reset();
312         $rows = $artDataSql->getRows();
313         for ($i = 0; $i < $rows; ++$i) {
314             $sliceId = $artDataSql->getValue(rex::getTablePrefix() . 'article_slice.id');
315             $sliceCtypeId = $artDataSql->getValue(rex::getTablePrefix() . 'article_slice.ctype_id');
316             $sliceModuleId = $artDataSql->getValue(rex::getTablePrefix() . 'module.id');
317 
318             // ----- ctype unterscheidung
319             if ($this->mode != 'edit' && !$this->eval) {
320                 if (0 == $i) {
321                     $articleContent = "<?php if (\$this->ctype == '" . $sliceCtypeId . "' || (\$this->ctype == '-1')) { \n";
322                 } elseif (isset($prevCtype) && $sliceCtypeId != $prevCtype) {
323                     // ----- zwischenstand: ctype .. wenn ctype neu dann if
324                     $articleContent .= "\n } if(\$this->ctype == '" . $sliceCtypeId . "' || \$this->ctype == '-1'){ \n";
325                 }
326             }
327 
328             // ------------- EINZELNER SLICE - AUSGABE
329             $slice_content = $this->outputSlice(
330                 $artDataSql,
331                 $module_id
332             );
333             // --------------- ENDE EINZELNER SLICE
334 
335             // --------------- EP: SLICE_SHOW
336             $slice_content = rex_extension::registerPoint(new rex_extension_point(
337                 'SLICE_SHOW',
338                 $slice_content,
339                 [
340                     'article_id' => $this->article_id,
341                     'clang' => $this->clang,
342                     'ctype' => $sliceCtypeId,
343                     'module_id' => $sliceModuleId,
344                     'slice_id' => $sliceId,
345                     'function' => $this->function,
346                     'function_slice_id' => $this->slice_id,
347                     'sql' => $artDataSql,
348                 ]
349             ));
350 
351             // ---------- slice in ausgabe speichern wenn ctype richtig
352             if ($this->ctype == -1 || $this->ctype == $sliceCtypeId) {
353                 $articleContent .= $slice_content;
354             }
355 
356             $prevCtype = $sliceCtypeId;
357 
358             $artDataSql->flushValues();
359             $artDataSql->next();
360         }
361 
362         // ----- end: ctype unterscheidung
363         if ($this->mode != 'edit' && !$this->eval && $i > 0) {
364             $articleContent .= "\n } ?>";
365         }
366 
367         // ----- post hook
368         $articleContent = $this->postArticle($articleContent, $module_id);
369 
370         // -------------------------- schreibe content
371         echo $articleContent;
372 
373         // ----- end: article caching
374         $CONTENT = ob_get_clean();
375 
376         return $CONTENT;
377     }
378 
379     /**
380      * Method which gets called, before the slices of the article are processed.
381      *
382      * @param string $articleContent The content of the article
383      * @param int    $module_id      A module id
384      *
385      * @return string
386      */
387     protected function preArticle($articleContent, $module_id)
388     {
389         // nichts tun
390         return $articleContent;
391     }
392 
393     /**
394      * Method which gets called, after all slices have been processed.
395      *
396      * @param string $articleContent The content of the article
397      * @param int    $module_id      A module id
398      *
399      * @return string
400      */
401     protected function postArticle($articleContent, $module_id)
402     {
403         // nichts tun
404         return $articleContent;
405     }
406 
407     // ----- Template inklusive Artikel zurückgeben
408     public function getArticleTemplate()
409     {
410         if ($this->template_id != 0 && $this->article_id != 0) {
411             ob_start();
412             ob_implicit_flush(0);
413 
414             $TEMPLATE = new rex_template($this->template_id);
415             $tplContent = $this->replaceCommonVars($TEMPLATE->getTemplate());
416             require rex_stream::factory('template/' . $this->template_id, $tplContent);
417 
418             $CONTENT = ob_get_clean();
419 
420             $CONTENT = $this->replaceLinks($CONTENT);
421         } else {
422             $CONTENT = 'no template';
423         }
424 
425         return $CONTENT;
426     }
427 
428     protected function getStreamOutput($path, $content)
429     {
430         if (!$this->eval) {
431             $key = 'EOD_' . strtoupper(sha1(time()));
432             return "require rex_stream::factory('$path', \n<<<'$key'\n$content\n$key\n);\n";
433         }
434 
435         ob_start();
436         ob_implicit_flush(0);
437 
438         $__stream = rex_stream::factory($path, $content);
439 
440         $sandbox = function () use ($__stream) {
441             require $__stream;
442         };
443         $sandbox();
444 
445         $CONTENT = ob_get_clean();
446 
447         return $CONTENT;
448     }
449 
450     // ----- Modulvariablen werden ersetzt
451     protected function replaceVars(rex_sql $sql, $content)
452     {
453         $content = $this->replaceCommonVars($content);
454         $content = $this->replaceObjectVars($sql, $content);
455         $content = str_replace(
456             [
457                 'REX_MODULE_ID',
458                 'REX_SLICE_ID',
459                 'REX_CTYPE_ID',
460             ],
461             [
462                 (int) $sql->getValue('module_id'),
463                 (int) $sql->getValue(rex::getTable('article_slice') . '.id'),
464                 (int) $sql->getValue('ctype_id'),
465             ],
466             $content
467         );
468         return $content;
469     }
470 
471     // ----- REX_VAR Ersetzungen
472     protected function replaceObjectVars(rex_sql $sql, $content)
473     {
474         $sliceId = $sql->getValue(rex::getTablePrefix() . 'article_slice.id');
475 
476         if ($this->mode == 'edit') {
477             $env = rex_var::ENV_BACKEND;
478             if (($this->function == 'add' && $sliceId == null) || ($this->function == 'edit' && $sliceId == $this->slice_id)) {
479                 $env = $env | rex_var::ENV_INPUT;
480             }
481         } else {
482             $env = rex_var::ENV_FRONTEND;
483         }
484         $content = rex_var::parse($content, $env, 'module', $sql);
485 
486         return $content;
487     }
488 
489     // ---- Artikelweite globale variablen werden ersetzt
490     public function replaceCommonVars($content, $template_id = null)
491     {
492         static $user_id = null;
493         static $user_login = null;
494 
495         // UserId gibts nur im Backend
496         if ($user_id === null) {
497             if (rex::getUser()) {
498                 $user_id = rex::getUser()->getId();
499                 $user_login = rex::getUser()->getLogin();
500             } else {
501                 $user_id = '';
502                 $user_login = '';
503             }
504         }
505 
506         if (!$template_id) {
507             $template_id = $this->getTemplateId();
508         }
509 
510         static $search = [
511             'REX_ARTICLE_ID',
512             'REX_CATEGORY_ID',
513             'REX_CLANG_ID',
514             'REX_TEMPLATE_ID',
515             'REX_USER_ID',
516             'REX_USER_LOGIN',
517         ];
518 
519         $replace = [
520             $this->article_id,
521             $this->category_id,
522             $this->clang,
523             $template_id,
524             $user_id,
525             $user_login,
526         ];
527 
528         return str_replace($search, $replace, $content);
529     }
530 
531     protected function replaceLinks($content)
532     {
533         return preg_replace_callback(
534             '@redaxo://(\d+)(?:-(\d+))?/?@i',
535             function ($matches) {
536                 return rex_getUrl($matches[1], isset($matches[2]) ? $matches[2] : (int) $this->clang);
537             },
538             $content
539         );
540     }
541 }
542