Dotclear

source: inc/public/class.dc.template.php @ 3965:b4e408d85ae5

Revision 3965:b4e408d85ae5, 110.8 KB checked in by franck <carnet.franck.paul@…>, 6 years ago (diff)

Allow ?sub for category/categories attributes of tpl:EntryIf, and for url/urls attributes of fpl:CategoryIf, closes #2295

Line 
1<?php
2/**
3 * @package Dotclear
4 * @subpackage Public
5 *
6 * @copyright Olivier Meunier & Association Dotclear
7 * @copyright GPL-2.0-only
8 */
9
10class dcTemplate extends template
11{
12    private $core;
13    private $current_tag;
14
15    protected $unknown_value_handler = null;
16    protected $unknown_block_handler = null;
17
18    public function __construct($cache_dir, $self_name, $core)
19    {
20        parent::__construct($cache_dir, $self_name);
21
22        $this->remove_php = !$core->blog->settings->system->tpl_allow_php;
23        $this->use_cache  = $core->blog->settings->system->tpl_use_cache;
24
25        $this->tag_block = '<tpl:(\w+)(?:(\s+.*?)>|>)((?:[^<]|<(?!/?tpl:\1)|(?R))*)</tpl:\1>';
26        $this->tag_value = '{{tpl:(\w+)(\s(.*?))?}}';
27
28        $this->core = &$core;
29
30        # Transitional tags
31        $this->addValue('EntryTrackbackCount', [$this, 'EntryPingCount']);
32        $this->addValue('EntryTrackbackData', [$this, 'EntryPingData']);
33        $this->addValue('EntryTrackbackLink', [$this, 'EntryPingLink']);
34
35        # l10n
36        $this->addValue('lang', [$this, 'l10n']);
37
38        # Loops test tags
39        $this->addBlock('LoopPosition', [$this, 'LoopPosition']);
40        $this->addValue('LoopIndex', [$this, 'LoopIndex']);
41
42        # Archives
43        $this->addBlock('Archives', [$this, 'Archives']);
44        $this->addBlock('ArchivesHeader', [$this, 'ArchivesHeader']);
45        $this->addBlock('ArchivesFooter', [$this, 'ArchivesFooter']);
46        $this->addBlock('ArchivesYearHeader', [$this, 'ArchivesYearHeader']);
47        $this->addBlock('ArchivesYearFooter', [$this, 'ArchivesYearFooter']);
48        $this->addValue('ArchiveDate', [$this, 'ArchiveDate']);
49        $this->addBlock('ArchiveNext', [$this, 'ArchiveNext']);
50        $this->addBlock('ArchivePrevious', [$this, 'ArchivePrevious']);
51        $this->addValue('ArchiveEntriesCount', [$this, 'ArchiveEntriesCount']);
52        $this->addValue('ArchiveURL', [$this, 'ArchiveURL']);
53
54        # Blog
55        $this->addValue('BlogArchiveURL', [$this, 'BlogArchiveURL']);
56        $this->addValue('BlogCopyrightNotice', [$this, 'BlogCopyrightNotice']);
57        $this->addValue('BlogDescription', [$this, 'BlogDescription']);
58        $this->addValue('BlogEditor', [$this, 'BlogEditor']);
59        $this->addValue('BlogFeedID', [$this, 'BlogFeedID']);
60        $this->addValue('BlogFeedURL', [$this, 'BlogFeedURL']);
61        $this->addValue('BlogRSDURL', [$this, 'BlogRSDURL']);
62        $this->addValue('BlogName', [$this, 'BlogName']);
63        $this->addValue('BlogLanguage', [$this, 'BlogLanguage']);
64        $this->addValue('BlogThemeURL', [$this, 'BlogThemeURL']);
65        $this->addValue('BlogParentThemeURL', [$this, 'BlogParentThemeURL']);
66        $this->addValue('BlogUpdateDate', [$this, 'BlogUpdateDate']);
67        $this->addValue('BlogID', [$this, 'BlogID']);
68        $this->addValue('BlogURL', [$this, 'BlogURL']);
69        $this->addValue('BlogXMLRPCURL', [$this, 'BlogXMLRPCURL']);
70        $this->addValue('BlogPublicURL', [$this, 'BlogPublicURL']);
71        $this->addValue('BlogQmarkURL', [$this, 'BlogQmarkURL']);
72        $this->addValue('BlogMetaRobots', [$this, 'BlogMetaRobots']);
73        $this->addValue('BlogJsJQuery', [$this, 'BlogJsJQuery']);
74
75        # Categories
76        $this->addBlock('Categories', [$this, 'Categories']);
77        $this->addBlock('CategoriesHeader', [$this, 'CategoriesHeader']);
78        $this->addBlock('CategoriesFooter', [$this, 'CategoriesFooter']);
79        $this->addBlock('CategoryIf', [$this, 'CategoryIf']);
80        $this->addBlock('CategoryFirstChildren', [$this, 'CategoryFirstChildren']);
81        $this->addBlock('CategoryParents', [$this, 'CategoryParents']);
82        $this->addValue('CategoryFeedURL', [$this, 'CategoryFeedURL']);
83        $this->addValue('CategoryURL', [$this, 'CategoryURL']);
84        $this->addValue('CategoryShortURL', [$this, 'CategoryShortURL']);
85        $this->addValue('CategoryDescription', [$this, 'CategoryDescription']);
86        $this->addValue('CategoryTitle', [$this, 'CategoryTitle']);
87        $this->addValue('CategoryEntriesCount', [$this, 'CategoryEntriesCount']);
88
89        # Comments
90        $this->addBlock('Comments', [$this, 'Comments']);
91        $this->addValue('CommentAuthor', [$this, 'CommentAuthor']);
92        $this->addValue('CommentAuthorDomain', [$this, 'CommentAuthorDomain']);
93        $this->addValue('CommentAuthorLink', [$this, 'CommentAuthorLink']);
94        $this->addValue('CommentAuthorMailMD5', [$this, 'CommentAuthorMailMD5']);
95        $this->addValue('CommentAuthorURL', [$this, 'CommentAuthorURL']);
96        $this->addValue('CommentContent', [$this, 'CommentContent']);
97        $this->addValue('CommentDate', [$this, 'CommentDate']);
98        $this->addValue('CommentTime', [$this, 'CommentTime']);
99        $this->addValue('CommentEmail', [$this, 'CommentEmail']);
100        $this->addValue('CommentEntryTitle', [$this, 'CommentEntryTitle']);
101        $this->addValue('CommentFeedID', [$this, 'CommentFeedID']);
102        $this->addValue('CommentID', [$this, 'CommentID']);
103        $this->addBlock('CommentIf', [$this, 'CommentIf']);
104        $this->addValue('CommentIfFirst', [$this, 'CommentIfFirst']);
105        $this->addValue('CommentIfMe', [$this, 'CommentIfMe']);
106        $this->addValue('CommentIfOdd', [$this, 'CommentIfOdd']);
107        $this->addValue('CommentIP', [$this, 'CommentIP']);
108        $this->addValue('CommentOrderNumber', [$this, 'CommentOrderNumber']);
109        $this->addBlock('CommentsFooter', [$this, 'CommentsFooter']);
110        $this->addBlock('CommentsHeader', [$this, 'CommentsHeader']);
111        $this->addValue('CommentPostURL', [$this, 'CommentPostURL']);
112        $this->addBlock('IfCommentAuthorEmail', [$this, 'IfCommentAuthorEmail']);
113        $this->addValue('CommentHelp', [$this, 'CommentHelp']);
114
115        # Comment preview
116        $this->addBlock('IfCommentPreview', [$this, 'IfCommentPreview']);
117        $this->addBlock('IfCommentPreviewOptional', [$this, 'IfCommentPreviewOptional']);
118        $this->addValue('CommentPreviewName', [$this, 'CommentPreviewName']);
119        $this->addValue('CommentPreviewEmail', [$this, 'CommentPreviewEmail']);
120        $this->addValue('CommentPreviewSite', [$this, 'CommentPreviewSite']);
121        $this->addValue('CommentPreviewContent', [$this, 'CommentPreviewContent']);
122        $this->addValue('CommentPreviewCheckRemember', [$this, 'CommentPreviewCheckRemember']);
123
124        # Entries
125        $this->addBlock('DateFooter', [$this, 'DateFooter']);
126        $this->addBlock('DateHeader', [$this, 'DateHeader']);
127        $this->addBlock('Entries', [$this, 'Entries']);
128        $this->addBlock('EntriesFooter', [$this, 'EntriesFooter']);
129        $this->addBlock('EntriesHeader', [$this, 'EntriesHeader']);
130        $this->addValue('EntryAuthorCommonName', [$this, 'EntryAuthorCommonName']);
131        $this->addValue('EntryAuthorDisplayName', [$this, 'EntryAuthorDisplayName']);
132        $this->addValue('EntryAuthorEmail', [$this, 'EntryAuthorEmail']);
133        $this->addValue('EntryAuthorEmailMD5', [$this, 'EntryAuthorEmailMD5']);
134        $this->addValue('EntryAuthorID', [$this, 'EntryAuthorID']);
135        $this->addValue('EntryAuthorLink', [$this, 'EntryAuthorLink']);
136        $this->addValue('EntryAuthorURL', [$this, 'EntryAuthorURL']);
137        $this->addValue('EntryBasename', [$this, 'EntryBasename']);
138        $this->addValue('EntryCategory', [$this, 'EntryCategory']);
139        $this->addValue('EntryCategoryDescription', [$this, 'EntryCategoryDescription']);
140        $this->addBlock('EntryCategoriesBreadcrumb', [$this, 'EntryCategoriesBreadcrumb']);
141        $this->addValue('EntryCategoryID', [$this, 'EntryCategoryID']);
142        $this->addValue('EntryCategoryURL', [$this, 'EntryCategoryURL']);
143        $this->addValue('EntryCategoryShortURL', [$this, 'EntryCategoryShortURL']);
144        $this->addValue('EntryCommentCount', [$this, 'EntryCommentCount']);
145        $this->addValue('EntryContent', [$this, 'EntryContent']);
146        $this->addValue('EntryDate', [$this, 'EntryDate']);
147        $this->addValue('EntryExcerpt', [$this, 'EntryExcerpt']);
148        $this->addValue('EntryFeedID', [$this, 'EntryFeedID']);
149        $this->addValue('EntryFirstImage', [$this, 'EntryFirstImage']);
150        $this->addValue('EntryID', [$this, 'EntryID']);
151        $this->addBlock('EntryIf', [$this, 'EntryIf']);
152        $this->addBlock('EntryIfContentCut', [$this, 'EntryIfContentCut']);
153        $this->addValue('EntryIfFirst', [$this, 'EntryIfFirst']);
154        $this->addValue('EntryIfOdd', [$this, 'EntryIfOdd']);
155        $this->addValue('EntryIfSelected', [$this, 'EntryIfSelected']);
156        $this->addValue('EntryLang', [$this, 'EntryLang']);
157        $this->addBlock('EntryNext', [$this, 'EntryNext']);
158        $this->addValue('EntryPingCount', [$this, 'EntryPingCount']);
159        $this->addValue('EntryPingData', [$this, 'EntryPingData']);
160        $this->addValue('EntryPingLink', [$this, 'EntryPingLink']);
161        $this->addBlock('EntryPrevious', [$this, 'EntryPrevious']);
162        $this->addValue('EntryTitle', [$this, 'EntryTitle']);
163        $this->addValue('EntryTime', [$this, 'EntryTime']);
164        $this->addValue('EntryURL', [$this, 'EntryURL']);
165
166        # Languages
167        $this->addBlock('Languages', [$this, 'Languages']);
168        $this->addBlock('LanguagesHeader', [$this, 'LanguagesHeader']);
169        $this->addBlock('LanguagesFooter', [$this, 'LanguagesFooter']);
170        $this->addValue('LanguageCode', [$this, 'LanguageCode']);
171        $this->addBlock('LanguageIfCurrent', [$this, 'LanguageIfCurrent']);
172        $this->addValue('LanguageURL', [$this, 'LanguageURL']);
173
174        # Pagination
175        $this->addBlock('Pagination', [$this, 'Pagination']);
176        $this->addValue('PaginationCounter', [$this, 'PaginationCounter']);
177        $this->addValue('PaginationCurrent', [$this, 'PaginationCurrent']);
178        $this->addBlock('PaginationIf', [$this, 'PaginationIf']);
179        $this->addValue('PaginationURL', [$this, 'PaginationURL']);
180
181        # Trackbacks
182        $this->addValue('PingBlogName', [$this, 'PingBlogName']);
183        $this->addValue('PingContent', [$this, 'PingContent']);
184        $this->addValue('PingDate', [$this, 'PingDate']);
185        $this->addValue('PingEntryTitle', [$this, 'PingEntryTitle']);
186        $this->addValue('PingFeedID', [$this, 'PingFeedID']);
187        $this->addValue('PingID', [$this, 'PingID']);
188        $this->addValue('PingIfFirst', [$this, 'PingIfFirst']);
189        $this->addValue('PingIfOdd', [$this, 'PingIfOdd']);
190        $this->addValue('PingIP', [$this, 'PingIP']);
191        $this->addValue('PingNoFollow', [$this, 'PingNoFollow']);
192        $this->addValue('PingOrderNumber', [$this, 'PingOrderNumber']);
193        $this->addValue('PingPostURL', [$this, 'PingPostURL']);
194        $this->addBlock('Pings', [$this, 'Pings']);
195        $this->addBlock('PingsFooter', [$this, 'PingsFooter']);
196        $this->addBlock('PingsHeader', [$this, 'PingsHeader']);
197        $this->addValue('PingTime', [$this, 'PingTime']);
198        $this->addValue('PingTitle', [$this, 'PingTitle']);
199        $this->addValue('PingAuthorURL', [$this, 'PingAuthorURL']);
200
201        # System
202        $this->addValue('SysBehavior', [$this, 'SysBehavior']);
203        $this->addBlock('SysIf', [$this, 'SysIf']);
204        $this->addBlock('SysIfCommentPublished', [$this, 'SysIfCommentPublished']);
205        $this->addBlock('SysIfCommentPending', [$this, 'SysIfCommentPending']);
206        $this->addBlock('SysIfFormError', [$this, 'SysIfFormError']);
207        $this->addValue('SysFeedSubtitle', [$this, 'SysFeedSubtitle']);
208        $this->addValue('SysFormError', [$this, 'SysFormError']);
209        $this->addValue('SysPoweredBy', [$this, 'SysPoweredBy']);
210        $this->addValue('SysSearchString', [$this, 'SysSearchString']);
211        $this->addValue('SysSelfURI', [$this, 'SysSelfURI']);
212
213        # Generic
214        $this->addValue('else', [$this, 'GenericElse']);
215    }
216
217    public function getData($________)
218    {
219        # --BEHAVIOR-- tplBeforeData
220        if ($this->core->hasBehavior('tplBeforeData')) {
221            self::$_r = $this->core->callBehavior('tplBeforeData', $this->core);
222            if (self::$_r) {
223                return self::$_r;
224            }
225        }
226
227        parent::getData($________);
228
229        # --BEHAVIOR-- tplAfterData
230        if ($this->core->hasBehavior('tplAfterData')) {
231            $this->core->callBehavior('tplAfterData', $this->core, self::$_r);
232        }
233
234        return self::$_r;
235    }
236
237    public function compileBlockNode($tag, $attr, $content)
238    {
239        $this->current_tag = $tag;
240        $attr              = new ArrayObject($attr);
241        # --BEHAVIOR-- templateBeforeBlock
242        $res = $this->core->callBehavior('templateBeforeBlock', $this->core, $this->current_tag, $attr);
243
244        # --BEHAVIOR-- templateInsideBlock
245        $this->core->callBehavior('templateInsideBlock', $this->core, $this->current_tag, $attr, [ & $content]);
246
247        $res .= parent::compileBlockNode($this->current_tag, $attr, $content);
248
249        # --BEHAVIOR-- templateAfterBlock
250        $res .= $this->core->callBehavior('templateAfterBlock', $this->core, $this->current_tag, $attr);
251
252        return $res;
253    }
254
255    public function compileValueNode($tag, $attr, $str_attr)
256    {
257        $this->current_tag = $tag;
258
259        $attr = new ArrayObject($attr);
260        # --BEHAVIOR-- templateBeforeValue
261        $res = $this->core->callBehavior('templateBeforeValue', $this->core, $this->current_tag, $attr);
262
263        $res .= parent::compileValueNode($this->current_tag, $attr, $str_attr);
264
265        # --BEHAVIOR-- templateAfterValue
266        $res .= $this->core->callBehavior('templateAfterValue', $this->core, $this->current_tag, $attr);
267
268        return $res;
269    }
270
271    public function getFilters($attr, $default = [])
272    {
273        if (!is_array($attr) && !($attr instanceof arrayObject)) {
274            $attr = [];
275        }
276
277        $p = array_merge(
278            [
279                0             => null,
280                'encode_xml'  => 0,
281                'encode_html' => 0,
282                'cut_string'  => 0,
283                'lower_case'  => 0,
284                'upper_case'  => 0,
285                'encode_url'  => 0,
286                'remove_html' => 0,
287                'capitalize'  => 0,
288                'strip_tags'  => 0
289            ],
290            $default
291        );
292
293        foreach ($attr as $k => $v) {
294            // attributes names must follow this rule
295            $k = preg_filter('/[a-zA-Z0-9_]/', '$0', $k);
296            if ($k) {
297                // addslashes protect var_export, str_replace protect sprintf;
298                $p[$k] = str_replace('%', '%%', addslashes($v));
299            }
300        }
301
302        return "context::global_filters(%s," . var_export($p, true) . ",'" . addslashes($this->current_tag) . "')";
303    }
304
305    public static function getOperator($op)
306    {
307        switch (strtolower($op)) {
308            case 'or':
309            case '||':
310                return '||';
311            case 'and':
312            case '&&':
313            default:
314                return '&&';
315        }
316    }
317
318    public function getSortByStr($attr, $table = null)
319    {
320        $res = [];
321
322        $default_order = 'desc';
323
324        $default_alias = [
325            'post'    => [
326                'title'     => 'post_title',
327                'selected'  => 'post_selected',
328                'author'    => 'user_id',
329                'date'      => 'post_dt',
330                'id'        => 'post_id',
331                'comment'   => 'nb_comment',
332                'trackback' => 'nb_trackback'
333            ],
334            'comment' => [
335                'author' => 'comment_author',
336                'date'   => 'comment_dt',
337                'id'     => 'comment_id'
338            ]
339        ];
340
341        $alias = new ArrayObject();
342
343        # --BEHAVIOR-- templateCustomSortByAlias
344        $this->core->callBehavior('templateCustomSortByAlias', $alias);
345
346        $alias = $alias->getArrayCopy();
347
348        if (is_array($alias)) {
349            foreach ($alias as $k => $v) {
350                if (!is_array($v)) {
351                    $alias[$k] = [];
352                }
353                if (!isset($default_alias[$k]) || !is_array($default_alias[$k])) {
354                    $default_alias[$k] = [];
355                }
356                $default_alias[$k] = array_merge($default_alias[$k], $alias[$k]);
357            }
358        }
359
360        if (!array_key_exists($table, $default_alias)) {
361            return implode(', ', $res);
362        }
363
364        if (isset($attr['order']) && preg_match('/^(desc|asc)$/i', $attr['order'])) {
365            $default_order = $attr['order'];
366        }
367        if (isset($attr['sortby'])) {
368            $sorts = explode(',', $attr['sortby']);
369            foreach ($sorts as $k => $sort) {
370                $order = $default_order;
371                if (preg_match('/([a-z]*)\s*\?(desc|asc)$/i', $sort, $matches)) {
372                    $sort  = $matches[1];
373                    $order = $matches[2];
374                }
375                if (array_key_exists($sort, $default_alias[$table])) {
376                    array_push($res, $default_alias[$table][$sort] . ' ' . $order);
377                }
378            }
379        }
380
381        if (count($res) === 0) {
382            array_push($res, $default_alias[$table]['date'] . ' ' . $default_order);
383        }
384
385        return implode(', ', $res);
386    }
387
388    public static function getAge($attr)
389    {
390        if (isset($attr['age']) && preg_match('/^(\-[0-9]+|last).*$/i', $attr['age'])) {
391            if (($ts = strtotime($attr['age'])) !== false) {
392                return dt::str('%Y-%m-%d %H:%m:%S', $ts);
393            }
394        }
395        return '';
396    }
397
398    public function displayCounter($variable, $values, $attr, $count_only_by_default = false)
399    {
400        if (isset($attr['count_only'])) {
401            $count_only = ($attr['count_only'] == 1);
402        } else {
403            $count_only = $count_only_by_default;
404        }
405        if ($count_only) {
406            return "<?php echo " . $variable . "; ?>";
407        } else {
408            $v = $values;
409            if (isset($attr['none'])) {
410                $v['none'] = addslashes($attr['none']);
411            }
412            if (isset($attr['one'])) {
413                $v['one'] = addslashes($attr['one']);
414            }
415            if (isset($attr['more'])) {
416                $v['more'] = addslashes($attr['more']);
417            }
418            return
419                "<?php if (" . $variable . " == 0) {\n" .
420                "  printf(__('" . $v['none'] . "')," . $variable . ");\n" .
421                "} elseif (" . $variable . " == 1) {\n" .
422                "  printf(__('" . $v['one'] . "')," . $variable . ");\n" .
423                "} else {\n" .
424                "  printf(__('" . $v['more'] . "')," . $variable . ");\n" .
425                "} ?>";
426        }
427    }
428    /* TEMPLATE FUNCTIONS
429    ------------------------------------------------------- */
430
431    public function l10n($attr, $str_attr)
432    {
433        # Normalize content
434        $str_attr = preg_replace('/\s+/x', ' ', $str_attr);
435
436        return "<?php echo __('" . str_replace("'", "\\'", $str_attr) . "'); ?>";
437    }
438
439    public function LoopPosition($attr, $content)
440    {
441        $start  = isset($attr['start']) ? (integer) $attr['start'] : '0';
442        $length = isset($attr['length']) ? (integer) $attr['length'] : 'null';
443        $even   = isset($attr['even']) ? (integer) (boolean) $attr['even'] : 'null';
444        $modulo = isset($attr['modulo']) ? (integer) $attr['modulo'] : 'null';
445
446        if ($start > 0) {
447            $start--;
448        }
449
450        return
451            '<?php if ($_ctx->loopPosition(' . $start . ',' . $length . ',' . $even . ',' . $modulo . ')) : ?>' .
452            $content .
453            "<?php endif; ?>";
454    }
455
456    public function LoopIndex($attr)
457    {
458        $f = $this->getFilters($attr);
459        return '<?php echo ' . sprintf($f, '(!$_ctx->cur_loop ? 0 : $_ctx->cur_loop->index() + 1)') . '; ?>';
460    }
461
462    /* Archives ------------------------------------------- */
463    /*dtd
464    <!ELEMENT tpl:Archives - - -- Archives dates loop -->
465    <!ATTLIST tpl:Archives
466    type        (day|month|year)    #IMPLIED    -- Get days, months or years, default to month --
467    category    CDATA            #IMPLIED  -- Get dates of given category --
468    no_context (1|0)            #IMPLIED  -- Override context information
469    order    (asc|desc)        #IMPLIED  -- Sort asc or desc --
470    post_type    CDATA            #IMPLIED  -- Get dates of given type of entries, default to post --
471    post_lang    CDATA        #IMPLIED  -- Filter on the given language
472    >
473     */
474    public function Archives($attr, $content)
475    {
476        $p = "if (!isset(\$params)) \$params = [];\n";
477        $p .= "\$params['type'] = 'month';\n";
478        if (isset($attr['type'])) {
479            $p .= "\$params['type'] = '" . addslashes($attr['type']) . "';\n";
480        }
481
482        if (isset($attr['category'])) {
483            $p .= "\$params['cat_url'] = '" . addslashes($attr['category']) . "';\n";
484        }
485
486        if (isset($attr['post_type'])) {
487            $p .= "\$params['post_type'] = '" . addslashes($attr['post_type']) . "';\n";
488        }
489
490        if (isset($attr['post_lang'])) {
491            $p .= "\$params['post_lang'] = '" . addslashes($attr['post_lang']) . "';\n";
492        }
493
494        if (empty($attr['no_context']) && !isset($attr['category'])) {
495            $p .=
496                'if ($_ctx->exists("categories")) { ' .
497                "\$params['cat_id'] = \$_ctx->categories->cat_id; " .
498                "}\n";
499        }
500
501        $order = 'desc';
502        if (isset($attr['order']) && preg_match('/^(desc|asc)$/i', $attr['order'])) {
503            $p .= "\$params['order'] = '" . $attr['order'] . "';\n ";
504        }
505
506        $res = "<?php\n";
507        $res .= $p;
508        $res .= $this->core->callBehavior("templatePrepareParams",
509            ["tag" => "Archives", "method" => "blog::getDates"],
510            $attr, $content);
511        $res .= '$_ctx->archives = $core->blog->getDates($params); unset($params);' . "\n";
512        $res .= "?>\n";
513
514        $res .=
515            '<?php while ($_ctx->archives->fetch()) : ?>' . $content . '<?php endwhile; $_ctx->archives = null; ?>';
516
517        return $res;
518    }
519
520    /*dtd
521    <!ELEMENT tpl:ArchivesHeader - - -- First archives result container -->
522     */
523    public function ArchivesHeader($attr, $content)
524    {
525        return
526            "<?php if (\$_ctx->archives->isStart()) : ?>" .
527            $content .
528            "<?php endif; ?>";
529    }
530
531    /*dtd
532    <!ELEMENT tpl:ArchivesFooter - - -- Last archives result container -->
533     */
534    public function ArchivesFooter($attr, $content)
535    {
536        return
537            "<?php if (\$_ctx->archives->isEnd()) : ?>" .
538            $content .
539            "<?php endif; ?>";
540    }
541
542    /*dtd
543    <!ELEMENT tpl:ArchivesYearHeader - - -- First result of year in archives container -->
544     */
545    public function ArchivesYearHeader($attr, $content)
546    {
547        return
548            "<?php if (\$_ctx->archives->yearHeader()) : ?>" .
549            $content .
550            "<?php endif; ?>";
551    }
552
553    /*dtd
554    <!ELEMENT tpl:ArchivesYearFooter - - -- Last result of year in archives container -->
555     */
556    public function ArchivesYearFooter($attr, $content)
557    {
558        return
559            "<?php if (\$_ctx->archives->yearFooter()) : ?>" .
560            $content .
561            "<?php endif; ?>";
562    }
563
564    /*dtd
565    <!ELEMENT tpl:ArchiveDate - O -- Archive result date -->
566    <!ATTLIST tpl:ArchiveDate
567    format    CDATA    #IMPLIED  -- Date format (Default %B %Y) --
568    >
569     */
570    public function ArchiveDate($attr)
571    {
572        $format = '%B %Y';
573        if (!empty($attr['format'])) {
574            $format = addslashes($attr['format']);
575        }
576
577        $f = $this->getFilters($attr);
578        return '<?php echo ' . sprintf($f, "dt::dt2str('" . $format . "',\$_ctx->archives->dt)") . '; ?>';
579    }
580
581    /*dtd
582    <!ELEMENT tpl:ArchiveEntriesCount - O -- Current archive result number of entries -->
583     */
584    public function ArchiveEntriesCount($attr)
585    {
586        $f = $this->getFilters($attr);
587        return $this->displayCounter(
588            sprintf($f, '$_ctx->archives->nb_post'),
589            [
590                'none' => 'no archive',
591                'one'  => 'one archive',
592                'more' => '%d archives'
593            ],
594            $attr,
595            true
596        );
597    }
598
599    /*dtd
600    <!ELEMENT tpl:ArchiveNext - - -- Next archive result container -->
601    <!ATTLIST tpl:ArchiveNext
602    type        (day|month|year)    #IMPLIED    -- Get days, months or years, default to month --
603    post_type    CDATA            #IMPLIED  -- Get dates of given type of entries, default to post --
604    post_lang    CDATA        #IMPLIED  -- Filter on the given language
605    >
606     */
607    public function ArchiveNext($attr, $content)
608    {
609        $p = "if (!isset(\$params)) \$params = [];\n";
610        $p .= "\$params['type'] = 'month';\n";
611        if (isset($attr['type'])) {
612            $p .= "\$params['type'] = '" . addslashes($attr['type']) . "';\n";
613        }
614
615        if (isset($attr['post_type'])) {
616            $p .= "\$params['post_type'] = '" . addslashes($attr['post_type']) . "';\n";
617        }
618
619        if (isset($attr['post_lang'])) {
620            $p .= "\$params['post_lang'] = '" . addslashes($attr['post_lang']) . "';\n";
621        }
622
623        $p .= "\$params['next'] = \$_ctx->archives->dt;";
624
625        $res = "<?php\n";
626        $res .= $p;
627        $res .= $this->core->callBehavior("templatePrepareParams",
628            ["tag" => "ArchiveNext", "method" => "blog::getDates"],
629            $attr, $content);
630        $res .= '$_ctx->archives = $core->blog->getDates($params); unset($params);' . "\n";
631        $res .= "?>\n";
632
633        $res .=
634            '<?php while ($_ctx->archives->fetch()) : ?>' . $content . '<?php endwhile; $_ctx->archives = null; ?>';
635
636        return $res;
637    }
638
639    /*dtd
640    <!ELEMENT tpl:ArchivePrevious - - -- Previous archive result container -->
641    <!ATTLIST tpl:ArchivePrevious
642    type        (day|month|year)    #IMPLIED    -- Get days, months or years, default to month --
643    post_type    CDATA            #IMPLIED  -- Get dates of given type of entries, default to post --
644    post_lang    CDATA        #IMPLIED  -- Filter on the given language
645    >
646     */
647    public function ArchivePrevious($attr, $content)
648    {
649        $p = 'if (!isset($params)) $params = [];';
650        $p .= "\$params['type'] = 'month';\n";
651        if (isset($attr['type'])) {
652            $p .= "\$params['type'] = '" . addslashes($attr['type']) . "';\n";
653        }
654
655        if (isset($attr['post_type'])) {
656            $p .= "\$params['post_type'] = '" . addslashes($attr['post_type']) . "';\n";
657        }
658
659        if (isset($attr['post_lang'])) {
660            $p .= "\$params['post_lang'] = '" . addslashes($attr['post_lang']) . "';\n";
661        }
662
663        $p .= "\$params['previous'] = \$_ctx->archives->dt;";
664
665        $res = "<?php\n";
666        $res .= $this->core->callBehavior("templatePrepareParams",
667            ["tag" => "ArchivePrevious", "method" => "blog::getDates"],
668            $attr, $content);
669        $res .= $p;
670        $res .= '$_ctx->archives = $core->blog->getDates($params); unset($params);' . "\n";
671        $res .= "?>\n";
672
673        $res .=
674            '<?php while ($_ctx->archives->fetch()) : ?>' . $content . '<?php endwhile; $_ctx->archives = null; ?>';
675
676        return $res;
677    }
678
679    /*dtd
680    <!ELEMENT tpl:ArchiveURL - O -- Current archive result URL -->
681     */
682    public function ArchiveURL($attr)
683    {
684        $f = $this->getFilters($attr);
685        return '<?php echo ' . sprintf($f, '$_ctx->archives->url($core)') . '; ?>';
686    }
687
688    /* Blog ----------------------------------------------- */
689    /*dtd
690    <!ELEMENT tpl:BlogArchiveURL - O -- Blog Archives URL -->
691     */
692    public function BlogArchiveURL($attr)
693    {
694        $f = $this->getFilters($attr);
695        return '<?php echo ' . sprintf($f, '$core->blog->url.$core->url->getURLFor("archive")') . '; ?>';
696    }
697
698    /*dtd
699    <!ELEMENT tpl:BlogCopyrightNotice - O -- Blog copyrght notices -->
700     */
701    public function BlogCopyrightNotice($attr)
702    {
703        $f = $this->getFilters($attr);
704        return '<?php echo ' . sprintf($f, '$core->blog->settings->system->copyright_notice') . '; ?>';
705    }
706
707    /*dtd
708    <!ELEMENT tpl:BlogDescription - O -- Blog Description -->
709     */
710    public function BlogDescription($attr)
711    {
712        $f = $this->getFilters($attr);
713        return '<?php echo ' . sprintf($f, '$core->blog->desc') . '; ?>';
714    }
715
716    /*dtd
717    <!ELEMENT tpl:BlogEditor - O -- Blog Editor -->
718     */
719    public function BlogEditor($attr)
720    {
721        $f = $this->getFilters($attr);
722        return '<?php echo ' . sprintf($f, '$core->blog->settings->system->editor') . '; ?>';
723    }
724
725    /*dtd
726    <!ELEMENT tpl:BlogFeedID - O -- Blog Feed ID -->
727     */
728    public function BlogFeedID($attr)
729    {
730        $f = $this->getFilters($attr);
731        return '<?php echo ' . sprintf($f, '"urn:md5:".$core->blog->uid') . '; ?>';
732    }
733
734    /*dtd
735    <!ELEMENT tpl:BlogFeedURL - O -- Blog Feed URL -->
736    <!ATTLIST tpl:BlogFeedURL
737    type    (rss2|atom)    #IMPLIED    -- feed type (default : rss2)
738    >
739     */
740    public function BlogFeedURL($attr)
741    {
742        $type = !empty($attr['type']) ? $attr['type'] : 'atom';
743
744        if (!preg_match('#^(rss2|atom)$#', $type)) {
745            $type = 'atom';
746        }
747
748        $f = $this->getFilters($attr);
749        return '<?php echo ' . sprintf($f, '$core->blog->url.$core->url->getURLFor("feed","' . $type . '")') . '; ?>';
750    }
751
752    /*dtd
753    <!ELEMENT tpl:BlogName - O -- Blog Name -->
754     */
755    public function BlogName($attr)
756    {
757        $f = $this->getFilters($attr);
758        return '<?php echo ' . sprintf($f, '$core->blog->name') . '; ?>';
759    }
760
761    /*dtd
762    <!ELEMENT tpl:BlogLanguage - O -- Blog Language -->
763     */
764    public function BlogLanguage($attr)
765    {
766        $f = $this->getFilters($attr);
767        return '<?php echo ' . sprintf($f, '$core->blog->settings->system->lang') . '; ?>';
768    }
769
770    /*dtd
771    <!ELEMENT tpl:BlogThemeURL - O -- Blog's current Theme URL -->
772     */
773    public function BlogThemeURL($attr)
774    {
775        $f = $this->getFilters($attr);
776        return '<?php echo ' . sprintf($f, '$core->blog->settings->system->themes_url."/".$core->blog->settings->system->theme') . '; ?>';
777    }
778
779    /*dtd
780    <!ELEMENT tpl:BlogParentThemeURL - O -- Blog's current Theme's parent URL -->
781     */
782    public function BlogParentThemeURL($attr)
783    {
784        $f      = $this->getFilters($attr);
785        $parent = '$core->themes->moduleInfo($core->blog->settings->system->theme,\'parent\')';
786        return '<?php echo ' . sprintf($f, '$core->blog->settings->system->themes_url."/".(' . "$parent" . ' ? ' . "$parent" . ' : $core->blog->settings->system->theme)') . '; ?>';
787    }
788
789    /*dtd
790    <!ELEMENT tpl:BlogPublicURL - O -- Blog Public directory URL -->
791     */
792    public function BlogPublicURL($attr)
793    {
794        $f = $this->getFilters($attr);
795        return '<?php echo ' . sprintf($f, '$core->blog->settings->system->public_url') . '; ?>';
796    }
797
798    /*dtd
799    <!ELEMENT tpl:BlogUpdateDate - O -- Blog last update date -->
800    <!ATTLIST tpl:BlogUpdateDate
801    format    CDATA    #IMPLIED    -- date format (encoded in dc:str by default if iso8601 or rfc822 not specified)
802    iso8601    CDATA    #IMPLIED    -- if set, tells that date format is ISO 8601
803    rfc822    CDATA    #IMPLIED    -- if set, tells that date format is RFC 822
804    >
805     */
806    public function BlogUpdateDate($attr)
807    {
808        $format = '';
809        if (!empty($attr['format'])) {
810            $format = addslashes($attr['format']);
811        } else {
812            $format = '%Y-%m-%d %H:%M:%S';
813        }
814
815        $iso8601 = !empty($attr['iso8601']);
816        $rfc822  = !empty($attr['rfc822']);
817
818        $f = $this->getFilters($attr);
819
820        if ($rfc822) {
821            return '<?php echo ' . sprintf($f, "dt::rfc822(\$core->blog->upddt,\$core->blog->settings->system->blog_timezone)") . '; ?>';
822        } elseif ($iso8601) {
823            return '<?php echo ' . sprintf($f, "dt::iso8601(\$core->blog->upddt,\$core->blog->settings->system->blog_timezone)") . '; ?>';
824        } else {
825            return '<?php echo ' . sprintf($f, "dt::str('" . $format . "',\$core->blog->upddt)") . '; ?>';
826        }
827    }
828
829    /*dtd
830    <!ELEMENT tpl:BlogID - 0 -- Blog ID -->
831     */
832    public function BlogID($attr)
833    {
834        $f = $this->getFilters($attr);
835        return '<?php echo ' . sprintf($f, '$core->blog->id') . '; ?>';
836    }
837
838    /*dtd
839    <!ELEMENT tpl:BlogRSDURL - O -- Blog RSD URL -->
840     */
841    public function BlogRSDURL($attr)
842    {
843        $f = $this->getFilters($attr);
844        return '<?php echo ' . sprintf($f, '$core->blog->url.$core->url->getURLFor(\'rsd\')') . '; ?>';
845    }
846
847    /*dtd
848    <!ELEMENT tpl:BlogXMLRPCURL - O -- Blog XML-RPC URL -->
849     */
850    public function BlogXMLRPCURL($attr)
851    {
852        $f = $this->getFilters($attr);
853        return '<?php echo ' . sprintf($f, '$core->blog->url.$core->url->getURLFor(\'xmlrpc\',$core->blog->id)') . '; ?>';
854    }
855
856    /*dtd
857    <!ELEMENT tpl:BlogURL - O -- Blog URL -->
858     */
859    public function BlogURL($attr)
860    {
861        $f = $this->getFilters($attr);
862        return '<?php echo ' . sprintf($f, '$core->blog->url') . '; ?>';
863    }
864
865    /*dtd
866    <!ELEMENT tpl:BlogQmarkURL - O -- Blog URL, ending with a question mark -->
867     */
868    public function BlogQmarkURL($attr)
869    {
870        $f = $this->getFilters($attr);
871        return '<?php echo ' . sprintf($f, '$core->blog->getQmarkURL()') . '; ?>';
872    }
873
874    /*dtd
875    <!ELEMENT tpl:BlogMetaRobots - O -- Blog meta robots tag definition, overrides robots_policy setting -->
876    <!ATTLIST tpl:BlogMetaRobots
877    robots    CDATA    #IMPLIED    -- can be INDEX,FOLLOW,NOINDEX,NOFOLLOW,ARCHIVE,NOARCHIVE
878    >
879     */
880    public function BlogMetaRobots($attr)
881    {
882        $robots = isset($attr['robots']) ? addslashes($attr['robots']) : '';
883        return "<?php echo context::robotsPolicy(\$core->blog->settings->system->robots_policy,'" . $robots . "'); ?>";
884    }
885
886    /*dtd
887    <!ELEMENT gpl:BlogJsJQuery - 0 -- Blog Js jQuery version selected -->
888     */
889    public function BlogJsJQuery($attr)
890    {
891        $f = $this->getFilters($attr);
892        return '<?php echo ' . sprintf($f, '$core->blog->getJsJQuery()') . '; ?>';
893    }
894
895    /* Categories ----------------------------------------- */
896
897    /*dtd
898    <!ELEMENT tpl:Categories - - -- Categories loop -->
899     */
900    public function Categories($attr, $content)
901    {
902        $p = "if (!isset(\$params)) \$params = [];\n";
903
904        if (isset($attr['url'])) {
905            $p .= "\$params['cat_url'] = '" . addslashes($attr['url']) . "';\n";
906        }
907
908        if (!empty($attr['post_type'])) {
909            $p .= "\$params['post_type'] = '" . addslashes($attr['post_type']) . "';\n";
910        }
911
912        if (!empty($attr['level'])) {
913            $p .= "\$params['level'] = " . (integer) $attr['level'] . ";\n";
914        }
915
916        if (isset($attr['with_empty']) && ((boolean) $attr['with_empty'] == true)) {
917            $p .= '$params[\'without_empty\'] = false;';
918        }
919
920        $res = "<?php\n";
921        $res .= $p;
922        $res .= $this->core->callBehavior("templatePrepareParams",
923            ["tag" => "Categories", "method" => "blog::getCategories"],
924            $attr, $content);
925        $res .= '$_ctx->categories = $core->blog->getCategories($params);' . "\n";
926        $res .= "?>\n";
927        $res .= '<?php while ($_ctx->categories->fetch()) : ?>' . $content . '<?php endwhile; $_ctx->categories = null; unset($params); ?>';
928
929        return $res;
930    }
931
932    /*dtd
933    <!ELEMENT tpl:CategoriesHeader - - -- First Categories result container -->
934     */
935    public function CategoriesHeader($attr, $content)
936    {
937        return
938            "<?php if (\$_ctx->categories->isStart()) : ?>" .
939            $content .
940            "<?php endif; ?>";
941    }
942
943    /*dtd
944    <!ELEMENT tpl:CategoriesFooter - - -- Last Categories result container -->
945     */
946    public function CategoriesFooter($attr, $content)
947    {
948        return
949            "<?php if (\$_ctx->categories->isEnd()) : ?>" .
950            $content .
951            "<?php endif; ?>";
952    }
953
954    /*dtd
955    <!ELEMENT tpl:CategoryIf - - -- tests on current entry -->
956    <!ATTLIST tpl:CategoryIf
957    url        CDATA    #IMPLIED    -- category has given url
958    urls    CDATA    #IMPLIED    -- category has one of given urls
959    has_entries    (0|1)    #IMPLIED    -- post is the first post from list (value : 1) or not (value : 0)
960    has_description     (0|1)     #IMPLIED  -- category has description (value : 1) or not (value : 0)
961    >
962     */
963    public function CategoryIf($attr, $content)
964    {
965        $if       = new ArrayObject();
966        $operator = isset($attr['operator']) ? $this->getOperator($attr['operator']) : '&&';
967
968        if (isset($attr['url'])) {
969            $url  = addslashes(trim($attr['url']));
970            $args = preg_split('/\s*[?]\s*/', $url, -1, PREG_SPLIT_NO_EMPTY);
971            $url  = array_shift($args);
972            $args = array_flip($args);
973            if (substr($url, 0, 1) == '!') {
974                $url = substr($url, 1);
975                if (isset($args['sub'])) {
976                    $if[] = '(!$core->blog->IsInCatSubtree($_ctx->categories->cat_url, "' . $url . '"))';
977                } else {
978                    $if[] = '($_ctx->categories->cat_url != "' . $url . '")';
979                }
980            } else {
981                if (isset($args['sub'])) {
982                    $if[] = '($core->blog->IsInCatSubtree($_ctx->categories->cat_url, "' . $url . '"))';
983                } else {
984                    $if[] = '($_ctx->categories->cat_url == "' . $url . '")';
985                }
986            }
987        }
988
989        if (isset($attr['urls'])) {
990            $urls = explode(',', addslashes(trim($attr['urls'])));
991            if (is_array($urls) && count($urls)) {
992                foreach ($urls as $url) {
993                    $args = preg_split('/\s*[?]\s*/', trim($url), -1, PREG_SPLIT_NO_EMPTY);
994                    $url  = array_shift($args);
995                    $args = array_flip($args);
996                    if (substr($url, 0, 1) == '!') {
997                        $url = substr($url, 1);
998                        if (isset($args['sub'])) {
999                            $if[] = '(!$core->blog->IsInCatSubtree($_ctx->categories->cat_url, "' . $url . '"))';
1000                        } else {
1001                            $if[] = '($_ctx->categories->cat_url != "' . $url . '")';
1002                        }
1003                    } else {
1004                        if (isset($args['sub'])) {
1005                            $if[] = '($core->blog->IsInCatSubtree($_ctx->categories->cat_url, "' . $url . '"))';
1006                        } else {
1007                            $if[] = '($_ctx->categories->cat_url == "' . $url . '")';
1008                        }
1009                    }
1010                }
1011            }
1012        }
1013
1014        if (isset($attr['has_entries'])) {
1015            $sign = (boolean) $attr['has_entries'] ? '>' : '==';
1016            $if[] = '$_ctx->categories->nb_post ' . $sign . ' 0';
1017        }
1018
1019        if (isset($attr['has_description'])) {
1020            $sign = (boolean) $attr['has_description'] ? '!=' : '==';
1021            $if[] = '$_ctx->categories->cat_desc ' . $sign . ' ""';
1022        }
1023
1024        $this->core->callBehavior('tplIfConditions', 'CategoryIf', $attr, $content, $if);
1025
1026        if (count($if) != 0) {
1027            return '<?php if(' . implode(' ' . $operator . ' ', (array) $if) . ') : ?>' . $content . '<?php endif; ?>';
1028        } else {
1029            return $content;
1030        }
1031    }
1032
1033    /*dtd
1034    <!ELEMENT tpl:CategoryFirstChildren - - -- Current category first children loop -->
1035     */
1036    public function CategoryFirstChildren($attr, $content)
1037    {
1038        return
1039            "<?php\n" .
1040            '$_ctx->categories = $core->blog->getCategoryFirstChildren($_ctx->categories->cat_id);' . "\n" .
1041            'while ($_ctx->categories->fetch()) : ?>' . $content . '<?php endwhile; $_ctx->categories = null; ?>';
1042    }
1043
1044    /*dtd
1045    <!ELEMENT tpl:CategoryParents - - -- Current category parents loop -->
1046     */
1047    public function CategoryParents($attr, $content)
1048    {
1049        return
1050            "<?php\n" .
1051            '$_ctx->categories = $core->blog->getCategoryParents($_ctx->categories->cat_id);' . "\n" .
1052            'while ($_ctx->categories->fetch()) : ?>' . $content . '<?php endwhile; $_ctx->categories = null; ?>';
1053    }
1054
1055    /*dtd
1056    <!ELEMENT tpl:CategoryFeedURL - O -- Category feed URL -->
1057    <!ATTLIST tpl:CategoryFeedURL
1058    type    (rss2|atom)    #IMPLIED    -- feed type (default : rss2)
1059    >
1060     */
1061    public function CategoryFeedURL($attr)
1062    {
1063        $type = !empty($attr['type']) ? $attr['type'] : 'atom';
1064
1065        if (!preg_match('#^(rss2|atom)$#', $type)) {
1066            $type = 'atom';
1067        }
1068
1069        $f = $this->getFilters($attr);
1070        return '<?php echo ' . sprintf($f, '$core->blog->url.$core->url->getURLFor("feed","category/".' .
1071            '$_ctx->categories->cat_url."/' . $type . '")') . '; ?>';
1072    }
1073
1074    /*dtd
1075    <!ELEMENT tpl:CategoryURL - O -- Category URL (complete iabsolute URL, including blog URL) -->
1076     */
1077    public function CategoryURL($attr)
1078    {
1079        $f = $this->getFilters($attr);
1080        return '<?php echo ' . sprintf($f, '$core->blog->url.$core->url->getURLFor("category",' .
1081            '$_ctx->categories->cat_url)') . '; ?>';
1082    }
1083
1084    /*dtd
1085    <!ELEMENT tpl:CategoryShortURL - O -- Category short URL (relative URL, from /category/) -->
1086     */
1087    public function CategoryShortURL($attr)
1088    {
1089        $f = $this->getFilters($attr);
1090        return '<?php echo ' . sprintf($f, '$_ctx->categories->cat_url') . '; ?>';
1091    }
1092
1093    /*dtd
1094    <!ELEMENT tpl:CategoryDescription - O -- Category description -->
1095     */
1096    public function CategoryDescription($attr)
1097    {
1098        $f = $this->getFilters($attr);
1099        return '<?php echo ' . sprintf($f, '$_ctx->categories->cat_desc') . '; ?>';
1100    }
1101
1102    /*dtd
1103    <!ELEMENT tpl:CategoryTitle - O -- Category title -->
1104     */
1105    public function CategoryTitle($attr)
1106    {
1107        $f = $this->getFilters($attr);
1108        return '<?php echo ' . sprintf($f, '$_ctx->categories->cat_title') . '; ?>';
1109    }
1110
1111    /*dtd
1112    <!ELEMENT tpl:CategoryEntriesCount - O -- Category number of entries -->
1113     */
1114    public function CategoryEntriesCount($attr)
1115    {
1116        $f = $this->getFilters($attr);
1117        return $this->displayCounter(
1118            sprintf($f, '$_ctx->categories->nb_post'),
1119            [
1120                'none' => 'No post',
1121                'one'  => 'One post',
1122                'more' => '%d posts'
1123            ],
1124            $attr,
1125            true
1126        );
1127    }
1128
1129    /* Entries -------------------------------------------- */
1130    /*dtd
1131    <!ELEMENT tpl:Entries - - -- Blog Entries loop -->
1132    <!ATTLIST tpl:Entries
1133    lastn    CDATA    #IMPLIED    -- limit number of results to specified value
1134    author    CDATA    #IMPLIED    -- get entries for a given user id
1135    category    CDATA    #IMPLIED    -- get entries for specific categories only (multiple comma-separated categories can be specified. Use "!" as prefix to exclude a category)
1136    no_category    CDATA    #IMPLIED    -- get entries without category
1137    with_category    CDATA    #IMPLIED    -- get entries with category
1138    no_context (1|0)    #IMPLIED  -- Override context information
1139    sortby    (title|selected|author|date|id)    #IMPLIED    -- specify entries sort criteria (default : date) (multiple comma-separated sortby can be specified. Use "?asc" or "?desc" as suffix to provide an order for each sorby)
1140    order    (desc|asc)    #IMPLIED    -- specify entries order (default : desc)
1141    no_content    (0|1)    #IMPLIED    -- do not retrieve entries content
1142    selected    (0|1)    #IMPLIED    -- retrieve posts marked as selected only (value: 1) or not selected only (value: 0)
1143    url        CDATA    #IMPLIED    -- retrieve post by its url
1144    type        CDATA    #IMPLIED    -- retrieve post with given post_type (there can be many ones separated by comma)
1145    age        CDATA    #IMPLIED    -- retrieve posts by maximum age (ex: -2 days, last month, last week)
1146    ignore_pagination    (0|1)    #IMPLIED    -- ignore page number provided in URL (useful when using multiple tpl:Entries on the same page)
1147    >
1148     */
1149    public function Entries($attr, $content)
1150    {
1151        $lastn = -1;
1152        if (isset($attr['lastn'])) {
1153            $lastn = abs((integer) $attr['lastn']) + 0;
1154        }
1155
1156        $p = 'if (!isset($_page_number)) { $_page_number = 1; }' . "\n";
1157
1158        if ($lastn != 0) {
1159            // Set limit (aka nb of entries needed)
1160            if ($lastn > 0) {
1161                // nb of entries per page specified in template -> regular pagination
1162                $p .= "\$params['limit'] = " . $lastn . ";\n";
1163                $p .= "\$nb_entry_first_page = \$nb_entry_per_page = " . $lastn . ";\n";
1164            } else {
1165                // nb of entries per page not specified -> use ctx settings
1166                $p .= "\$nb_entry_first_page=\$_ctx->nb_entry_first_page; \$nb_entry_per_page = \$_ctx->nb_entry_per_page;\n";
1167                $p .= "if ((\$core->url->type == 'default') || (\$core->url->type == 'default-page')) {\n";
1168                $p .= "    \$params['limit'] = (\$_page_number == 1 ? \$nb_entry_first_page : \$nb_entry_per_page);\n";
1169                $p .= "} else {\n";
1170                $p .= "    \$params['limit'] = \$nb_entry_per_page;\n";
1171                $p .= "}\n";
1172            }
1173            // Set offset (aka index of first entry)
1174            if (!isset($attr['ignore_pagination']) || $attr['ignore_pagination'] == "0") {
1175                // standard pagination, set offset
1176                $p .= "if ((\$core->url->type == 'default') || (\$core->url->type == 'default-page')) {\n";
1177                $p .= "    \$params['limit'] = [(\$_page_number == 1 ? 0 : (\$_page_number - 2) * \$nb_entry_per_page + \$nb_entry_first_page),\$params['limit']];\n";
1178                $p .= "} else {\n";
1179                $p .= "    \$params['limit'] = [(\$_page_number - 1) * \$nb_entry_per_page,\$params['limit']];\n";
1180                $p .= "}\n";
1181            } else {
1182                // no pagination, get all posts from 0 to limit
1183                $p .= "\$params['limit'] = [0, \$params['limit']];\n";
1184            }
1185        }
1186
1187        if (isset($attr['author'])) {
1188            $p .= "\$params['user_id'] = '" . addslashes($attr['author']) . "';\n";
1189        }
1190
1191        if (isset($attr['category'])) {
1192            $p .= "\$params['cat_url'] = '" . addslashes($attr['category']) . "';\n";
1193            $p .= "context::categoryPostParam(\$params);\n";
1194        }
1195
1196        if (isset($attr['with_category']) && $attr['with_category']) {
1197            $p .= "@\$params['sql'] .= ' AND P.cat_id IS NOT NULL ';\n";
1198        }
1199
1200        if (isset($attr['no_category']) && $attr['no_category']) {
1201            $p .= "@\$params['sql'] .= ' AND P.cat_id IS NULL ';\n";
1202            $p .= "unset(\$params['cat_url']);\n";
1203        }
1204
1205        if (!empty($attr['type'])) {
1206            $p .= "\$params['post_type'] = preg_split('/\s*,\s*/','" . addslashes($attr['type']) . "',-1,PREG_SPLIT_NO_EMPTY);\n";
1207        }
1208
1209        if (!empty($attr['url'])) {
1210            $p .= "\$params['post_url'] = '" . addslashes($attr['url']) . "';\n";
1211        }
1212
1213        if (empty($attr['no_context'])) {
1214            if (!isset($attr['author'])) {
1215                $p .=
1216                    'if ($_ctx->exists("users")) { ' .
1217                    "\$params['user_id'] = \$_ctx->users->user_id; " .
1218                    "}\n";
1219            }
1220
1221            if (!isset($attr['category']) && (!isset($attr['no_category']) || !$attr['no_category'])) {
1222                $p .=
1223                    'if ($_ctx->exists("categories")) { ' .
1224                    "\$params['cat_id'] = \$_ctx->categories->cat_id.(\$core->blog->settings->system->inc_subcats?' ?sub':'');" .
1225                    "}\n";
1226            }
1227
1228            $p .=
1229                'if ($_ctx->exists("archives")) { ' .
1230                "\$params['post_year'] = \$_ctx->archives->year(); " .
1231                "\$params['post_month'] = \$_ctx->archives->month(); ";
1232            if (!isset($attr['lastn'])) {
1233                $p .= "unset(\$params['limit']); ";
1234            }
1235            $p .=
1236                "}\n";
1237
1238            $p .=
1239                'if ($_ctx->exists("langs")) { ' .
1240                "\$params['post_lang'] = \$_ctx->langs->post_lang; " .
1241                "}\n";
1242
1243            $p .=
1244                'if (isset($_search)) { ' .
1245                "\$params['search'] = \$_search; " .
1246                "}\n";
1247        }
1248
1249        $p .= "\$params['order'] = '" . $this->getSortByStr($attr, 'post') . "';\n";
1250
1251        if (isset($attr['no_content']) && $attr['no_content']) {
1252            $p .= "\$params['no_content'] = true;\n";
1253        }
1254
1255        if (isset($attr['selected'])) {
1256            $p .= "\$params['post_selected'] = " . (integer) (boolean) $attr['selected'] . ";";
1257        }
1258
1259        if (isset($attr['age'])) {
1260            $age = $this->getAge($attr);
1261            $p .= !empty($age) ? "@\$params['sql'] .= ' AND P.post_dt > \'" . $age . "\'';\n" : '';
1262        }
1263
1264        $res = "<?php\n";
1265        $res .= $p;
1266        $res .= $this->core->callBehavior("templatePrepareParams",
1267            ["tag" => "Entries", "method" => "blog::getPosts"],
1268            $attr, $content);
1269        $res .= '$_ctx->post_params = $params;' . "\n";
1270        $res .= '$_ctx->posts = $core->blog->getPosts($params); unset($params);' . "\n";
1271        $res .= "?>\n";
1272        $res .=
1273            '<?php while ($_ctx->posts->fetch()) : ?>' . $content . '<?php endwhile; ' .
1274            '$_ctx->posts = null; $_ctx->post_params = null; ?>';
1275
1276        return $res;
1277    }
1278
1279    /*dtd
1280    <!ELEMENT tpl:DateHeader - O -- Displays date, if post is the first post of the given day -->
1281     */
1282    public function DateHeader($attr, $content)
1283    {
1284        return
1285            "<?php if (\$_ctx->posts->firstPostOfDay()) : ?>" .
1286            $content .
1287            "<?php endif; ?>";
1288    }
1289
1290    /*dtd
1291    <!ELEMENT tpl:DateFooter - O -- Displays date,  if post is the last post of the given day -->
1292     */
1293    public function DateFooter($attr, $content)
1294    {
1295        return
1296            "<?php if (\$_ctx->posts->lastPostOfDay()) : ?>" .
1297            $content .
1298            "<?php endif; ?>";
1299    }
1300
1301    /*dtd
1302    <!ELEMENT tpl:EntryIf - - -- tests on current entry -->
1303    <!ATTLIST tpl:EntryIf
1304    type    CDATA    #IMPLIED    -- post has a given type (default: "post")
1305    category    CDATA    #IMPLIED    -- post has a given category
1306    categories    CDATA    #IMPLIED    -- post has a one of given categories
1307    first    (0|1)    #IMPLIED    -- post is the first post from list (value : 1) or not (value : 0)
1308    odd    (0|1)    #IMPLIED    -- post is in an odd position (value : 1) or not (value : 0)
1309    even    (0|1)    #IMPLIED    -- post is in an even position (value : 1) or not (value : 0)
1310    extended    (0|1)    #IMPLIED    -- post has an excerpt (value : 1) or not (value : 0)
1311    selected    (0|1)    #IMPLIED    -- post is selected (value : 1) or not (value : 0)
1312    has_category    (0|1)    #IMPLIED    -- post has a category (value : 1) or not (value : 0)
1313    has_attachment    (0|1)    #IMPLIED    -- post has attachments (value : 1) or not (value : 0) (see Attachment plugin for code)
1314    comments_active    (0|1)    #IMPLIED    -- comments are active for this post (value : 1) or not (value : 0)
1315    pings_active    (0|1)    #IMPLIED    -- trackbacks are active for this post (value : 1) or not (value : 0)
1316    show_comments    (0|1)    #IMPLIED    -- there are comments for this post (value : 1) or not (value : 0)
1317    show_pings    (0|1)    #IMPLIED    -- there are trackbacks for this post (value : 1) or not (value : 0)
1318    republished    (0|1)    #IMPLIED    -- post has been updated since publication (value : 1) or not (value : 0)
1319    operator    (and|or)    #IMPLIED    -- combination of conditions, if more than 1 specifiec (default: and)
1320    url        CDATA    #IMPLIED    -- post has given url
1321    >
1322     */
1323    public function EntryIf($attr, $content)
1324    {
1325        $if          = new ArrayObject();
1326        $extended    = null;
1327        $hascategory = null;
1328
1329        $operator = isset($attr['operator']) ? $this->getOperator($attr['operator']) : '&&';
1330
1331        if (isset($attr['type'])) {
1332            $type = trim($attr['type']);
1333            $type = !empty($type) ? $type : 'post';
1334            $if[] = '$_ctx->posts->post_type == "' . addslashes($type) . '"';
1335        }
1336
1337        if (isset($attr['url'])) {
1338            $url = trim($attr['url']);
1339            if (substr($url, 0, 1) == '!') {
1340                $url  = substr($url, 1);
1341                $if[] = '$_ctx->posts->post_url != "' . addslashes($url) . '"';
1342            } else {
1343                $if[] = '$_ctx->posts->post_url == "' . addslashes($url) . '"';
1344            }
1345        }
1346
1347        if (isset($attr['category'])) {
1348            $category = addslashes(trim($attr['category']));
1349            $args     = preg_split('/\s*[?]\s*/', $category, -1, PREG_SPLIT_NO_EMPTY);
1350            $category = array_shift($args);
1351            $args     = array_flip($args);
1352            if (substr($category, 0, 1) == '!') {
1353                $category = substr($category, 1);
1354                if (isset($args['sub'])) {
1355                    $if[] = '(!$_ctx->posts->underCat("' . $category . '"))';
1356                } else {
1357                    $if[] = '($_ctx->posts->cat_url != "' . $category . '")';
1358                }
1359            } else {
1360                if (isset($args['sub'])) {
1361                    $if[] = '($_ctx->posts->underCat("' . $category . '"))';
1362                } else {
1363                    $if[] = '($_ctx->posts->cat_url == "' . $category . '")';
1364                }
1365            }
1366        }
1367
1368        if (isset($attr['categories'])) {
1369            $categories = explode(',', addslashes(trim($attr['categories'])));
1370            if (is_array($categories) && count($categories)) {
1371                foreach ($categories as $category) {
1372                    $args     = preg_split('/\s*[?]\s*/', trim($category), -1, PREG_SPLIT_NO_EMPTY);
1373                    $category = array_shift($args);
1374                    $args     = array_flip($args);
1375                    if (substr($category, 0, 1) == '!') {
1376                        $category = substr($category, 1);
1377                        if (isset($args['sub'])) {
1378                            $if[] = '(!$_ctx->posts->underCat("' . $category . '"))';
1379                        } else {
1380                            $if[] = '($_ctx->posts->cat_url != "' . $category . '")';
1381                        }
1382                    } else {
1383                        if (isset($args['sub'])) {
1384                            $if[] = '($_ctx->posts->underCat("' . $category . '"))';
1385                        } else {
1386                            $if[] = '($_ctx->posts->cat_url == "' . $category . '")';
1387                        }
1388                    }
1389                }
1390            }
1391        }
1392
1393        if (isset($attr['first'])) {
1394            $sign = (boolean) $attr['first'] ? '=' : '!';
1395            $if[] = '$_ctx->posts->index() ' . $sign . '= 0';
1396        }
1397
1398        if (isset($attr['odd'])) {
1399            $sign = (boolean) $attr['odd'] ? '=' : '!';
1400            $if[] = '($_ctx->posts->index()+1)%2 ' . $sign . '= 1';
1401        }
1402
1403        if (isset($attr['extended'])) {
1404            $sign = (boolean) $attr['extended'] ? '' : '!';
1405            $if[] = $sign . '$_ctx->posts->isExtended()';
1406        }
1407
1408        if (isset($attr['selected'])) {
1409            $sign = (boolean) $attr['selected'] ? '' : '!';
1410            $if[] = $sign . '(boolean)$_ctx->posts->post_selected';
1411        }
1412
1413        if (isset($attr['has_category'])) {
1414            $sign = (boolean) $attr['has_category'] ? '' : '!';
1415            $if[] = $sign . '$_ctx->posts->cat_id';
1416        }
1417
1418        if (isset($attr['comments_active'])) {
1419            $sign = (boolean) $attr['comments_active'] ? '' : '!';
1420            $if[] = $sign . '$_ctx->posts->commentsActive()';
1421        }
1422
1423        if (isset($attr['pings_active'])) {
1424            $sign = (boolean) $attr['pings_active'] ? '' : '!';
1425            $if[] = $sign . '$_ctx->posts->trackbacksActive()';
1426        }
1427
1428        if (isset($attr['has_comment'])) {
1429            $sign = (boolean) $attr['has_comment'] ? '' : '!';
1430            $if[] = $sign . '$_ctx->posts->hasComments()';
1431        }
1432
1433        if (isset($attr['has_ping'])) {
1434            $sign = (boolean) $attr['has_ping'] ? '' : '!';
1435            $if[] = $sign . '$_ctx->posts->hasTrackbacks()';
1436        }
1437
1438        if (isset($attr['show_comments'])) {
1439            if ((boolean) $attr['show_comments']) {
1440                $if[] = '($_ctx->posts->hasComments() || $_ctx->posts->commentsActive())';
1441            } else {
1442                $if[] = '(!$_ctx->posts->hasComments() && !$_ctx->posts->commentsActive())';
1443            }
1444        }
1445
1446        if (isset($attr['show_pings'])) {
1447            if ((boolean) $attr['show_pings']) {
1448                $if[] = '($_ctx->posts->hasTrackbacks() || $_ctx->posts->trackbacksActive())';
1449            } else {
1450                $if[] = '(!$_ctx->posts->hasTrackbacks() && !$_ctx->posts->trackbacksActive())';
1451            }
1452        }
1453
1454        if (isset($attr['republished'])) {
1455            $sign = (boolean) $attr['republished'] ? '' : '!';
1456            $if[] = $sign . '(boolean)$_ctx->posts->isRepublished()';
1457        }
1458
1459        $this->core->callBehavior('tplIfConditions', 'EntryIf', $attr, $content, $if);
1460
1461        if (count($if) != 0) {
1462            return '<?php if(' . implode(' ' . $operator . ' ', (array) $if) . ') : ?>' . $content . '<?php endif; ?>';
1463        } else {
1464            return $content;
1465        }
1466    }
1467
1468    /*dtd
1469    <!ELEMENT tpl:EntryIfFirst - O -- displays value if entry is the first one -->
1470    <!ATTLIST tpl:EntryIfFirst
1471    return    CDATA    #IMPLIED    -- value to display in case of success (default: first)
1472    >
1473     */
1474    public function EntryIfFirst($attr)
1475    {
1476        $ret = isset($attr['return']) ? $attr['return'] : 'first';
1477        $ret = html::escapeHTML($ret);
1478
1479        return
1480        '<?php if ($_ctx->posts->index() == 0) { ' .
1481        "echo '" . addslashes($ret) . "'; } ?>";
1482    }
1483
1484    /*dtd
1485    <!ELEMENT tpl:EntryIfOdd - O -- displays value if entry is in an odd position -->
1486    <!ATTLIST tpl:EntryIfOdd
1487    return    CDATA    #IMPLIED    -- value to display in case of success (default: odd)
1488    >
1489     */
1490    public function EntryIfOdd($attr)
1491    {
1492        $ret = isset($attr['return']) ? $attr['return'] : 'odd';
1493        $ret = html::escapeHTML($ret);
1494
1495        return
1496        '<?php if (($_ctx->posts->index()+1)%2 == 1) { ' .
1497        "echo '" . addslashes($ret) . "'; } ?>";
1498    }
1499
1500    /*dtd
1501    <!ELEMENT tpl:EntryIfSelected - O -- displays value if entry is selected -->
1502    <!ATTLIST tpl:EntryIfSelected
1503    return    CDATA    #IMPLIED    -- value to display in case of success (default: selected)
1504    >
1505     */
1506    public function EntryIfSelected($attr)
1507    {
1508        $ret = isset($attr['return']) ? $attr['return'] : 'selected';
1509        $ret = html::escapeHTML($ret);
1510
1511        return
1512        '<?php if ($_ctx->posts->post_selected) { ' .
1513        "echo '" . addslashes($ret) . "'; } ?>";
1514    }
1515
1516    /*dtd
1517    <!ELEMENT tpl:EntryContent -  -- Entry content -->
1518    <!ATTLIST tpl:EntryContent
1519    absolute_urls    CDATA    #IMPLIED -- transforms local URLs to absolute one
1520    full            (1|0)    #IMPLIED -- returns full content with excerpt
1521    >
1522     */
1523    public function EntryContent($attr)
1524    {
1525        $urls = '0';
1526        if (!empty($attr['absolute_urls'])) {
1527            $urls = '1';
1528        }
1529
1530        $f = $this->getFilters($attr);
1531
1532        if (!empty($attr['full'])) {
1533            return '<?php echo ' . sprintf($f,
1534                '$_ctx->posts->getExcerpt(' . $urls . ')." ".$_ctx->posts->getContent(' . $urls . ')') . '; ?>';
1535        } else {
1536            return '<?php echo ' . sprintf($f, '$_ctx->posts->getContent(' . $urls . ')') . '; ?>';
1537        }
1538    }
1539
1540    /*dtd
1541    <!ELEMENT tpl:EntryIfContentCut - - -- Test if Entry content has been cut -->
1542    <!ATTLIST tpl:EntryIfContentCut
1543    absolute_urls    CDATA    #IMPLIED -- transforms local URLs to absolute one
1544    full            (1|0)    #IMPLIED -- test with full content and excerpt
1545    >
1546     */
1547    public function EntryIfContentCut($attr, $content)
1548    {
1549        if (empty($attr['cut_string']) || !empty($attr['full'])) {
1550            return '';
1551        }
1552
1553        $urls = '0';
1554        if (!empty($attr['absolute_urls'])) {
1555            $urls = '1';
1556        }
1557
1558        $short              = $this->getFilters($attr);
1559        $cut                = $attr['cut_string'];
1560        $attr['cut_string'] = 0;
1561        $full               = $this->getFilters($attr);
1562        $attr['cut_string'] = $cut;
1563
1564        return '<?php if (strlen(' . sprintf($full, '$_ctx->posts->getContent(' . $urls . ')') . ') > ' .
1565        'strlen(' . sprintf($short, '$_ctx->posts->getContent(' . $urls . ')') . ')) : ?>' .
1566            $content .
1567            '<?php endif; ?>';
1568    }
1569
1570    /*dtd
1571    <!ELEMENT tpl:EntryExcerpt - O -- Entry excerpt -->
1572    <!ATTLIST tpl:EntryExcerpt
1573    absolute_urls    CDATA    #IMPLIED -- transforms local URLs to absolute one
1574    >
1575     */
1576    public function EntryExcerpt($attr)
1577    {
1578        $urls = '0';
1579        if (!empty($attr['absolute_urls'])) {
1580            $urls = '1';
1581        }
1582
1583        $f = $this->getFilters($attr);
1584        return '<?php echo ' . sprintf($f, '$_ctx->posts->getExcerpt(' . $urls . ')') . '; ?>';
1585    }
1586
1587    /*dtd
1588    <!ELEMENT tpl:EntryAuthorCommonName - O -- Entry author common name -->
1589     */
1590    public function EntryAuthorCommonName($attr)
1591    {
1592        $f = $this->getFilters($attr);
1593        return '<?php echo ' . sprintf($f, '$_ctx->posts->getAuthorCN()') . '; ?>';
1594    }
1595
1596    /*dtd
1597    <!ELEMENT tpl:EntryAuthorDisplayName - O -- Entry author display name -->
1598     */
1599    public function EntryAuthorDisplayName($attr)
1600    {
1601        $f = $this->getFilters($attr);
1602        return '<?php echo ' . sprintf($f, '$_ctx->posts->user_displayname') . '; ?>';
1603    }
1604
1605    /*dtd
1606    <!ELEMENT tpl:EntryAuthorID - O -- Entry author ID -->
1607     */
1608    public function EntryAuthorID($attr)
1609    {
1610        $f = $this->getFilters($attr);
1611        return '<?php echo ' . sprintf($f, '$_ctx->posts->user_id') . '; ?>';
1612    }
1613
1614    /*dtd
1615    <!ELEMENT tpl:EntryAuthorEmail - O -- Entry author email -->
1616    <!ATTLIST tpl:EntryAuthorEmail
1617    spam_protected    (0|1)    #IMPLIED    -- protect email from spam (default: 1)
1618    >
1619     */
1620    public function EntryAuthorEmail($attr)
1621    {
1622        $p = 'true';
1623        if (isset($attr['spam_protected']) && !$attr['spam_protected']) {
1624            $p = 'false';
1625        }
1626
1627        $f = $this->getFilters($attr);
1628        return '<?php echo ' . sprintf($f, "\$_ctx->posts->getAuthorEmail(" . $p . ")") . '; ?>';
1629    }
1630
1631    /*dtd
1632    <!ELEMENT tpl:EntryAuthorEmailMD5 - O -- Entry author email MD5 sum -->
1633    >
1634     */
1635    public function EntryAuthorEmailMD5($attr)
1636    {
1637        $f = $this->getFilters($attr);
1638        return '<?php echo ' . sprintf($f, 'md5($_ctx->posts->getAuthorEmail(false))') . '; ?>';
1639    }
1640
1641    /*dtd
1642    <!ELEMENT tpl:EntryAuthorLink - O -- Entry author link -->
1643     */
1644    public function EntryAuthorLink($attr)
1645    {
1646        $f = $this->getFilters($attr);
1647        return '<?php echo ' . sprintf($f, '$_ctx->posts->getAuthorLink()') . '; ?>';
1648    }
1649
1650    /*dtd
1651    <!ELEMENT tpl:EntryAuthorURL - O -- Entry author URL -->
1652     */
1653    public function EntryAuthorURL($attr)
1654    {
1655        $f = $this->getFilters($attr);
1656        return '<?php echo ' . sprintf($f, '$_ctx->posts->user_url') . '; ?>';
1657    }
1658
1659    /*dtd
1660    <!ELEMENT tpl:EntryBasename - O -- Entry short URL (relative to /post) -->
1661     */
1662    public function EntryBasename($attr)
1663    {
1664        $f = $this->getFilters($attr);
1665        return '<?php echo ' . sprintf($f, '$_ctx->posts->post_url') . '; ?>';
1666    }
1667
1668    /*dtd
1669    <!ELEMENT tpl:EntryCategory - O -- Entry category (full name) -->
1670     */
1671    public function EntryCategory($attr)
1672    {
1673        $f = $this->getFilters($attr);
1674        return '<?php echo ' . sprintf($f, '$_ctx->posts->cat_title') . '; ?>';
1675    }
1676
1677    /*dtd
1678    <!ELEMENT tpl:EntryCategoryDescription - O -- Entry category description -->
1679     */
1680    public function EntryCategoryDescription($attr)
1681    {
1682        $f = $this->getFilters($attr);
1683        return '<?php echo ' . sprintf($f, '$_ctx->posts->cat_desc') . '; ?>';
1684    }
1685
1686    /*dtd
1687    <!ELEMENT tpl:EntryCategoriesBreadcrumb - - -- Current entry parents loop (without last one) -->
1688     */
1689    public function EntryCategoriesBreadcrumb($attr, $content)
1690    {
1691        return
1692            "<?php\n" .
1693            '$_ctx->categories = $core->blog->getCategoryParents($_ctx->posts->cat_id);' . "\n" .
1694            'while ($_ctx->categories->fetch()) : ?>' . $content . '<?php endwhile; $_ctx->categories = null; ?>';
1695    }
1696
1697    /*dtd
1698    <!ELEMENT tpl:EntryCategoryID - O -- Entry category ID -->
1699     */
1700    public function EntryCategoryID($attr)
1701    {
1702        $f = $this->getFilters($attr);
1703        return '<?php echo ' . sprintf($f, '$_ctx->posts->cat_id') . '; ?>';
1704    }
1705
1706    /*dtd
1707    <!ELEMENT tpl:EntryCategoryURL - O -- Entry category URL -->
1708     */
1709    public function EntryCategoryURL($attr)
1710    {
1711        $f = $this->getFilters($attr);
1712        return '<?php echo ' . sprintf($f, '$_ctx->posts->getCategoryURL()') . '; ?>';
1713    }
1714
1715    /*dtd
1716    <!ELEMENT tpl:EntryCategoryShortURL - O -- Entry category short URL (relative URL, from /category/) -->
1717     */
1718    public function EntryCategoryShortURL($attr)
1719    {
1720        $f = $this->getFilters($attr);
1721        return '<?php echo ' . sprintf($f, '$_ctx->posts->cat_url') . '; ?>';
1722    }
1723
1724    /*dtd
1725    <!ELEMENT tpl:EntryFeedID - O -- Entry feed ID -->
1726     */
1727    public function EntryFeedID($attr)
1728    {
1729        $f = $this->getFilters($attr);
1730        return '<?php echo ' . sprintf($f, '$_ctx->posts->getFeedID()') . '; ?>';
1731    }
1732
1733    /*dtd
1734    <!ELEMENT tpl:EntryFirstImage - O -- Extracts entry first image if exists -->
1735    <!ATTLIST tpl:EntryAuthorEmail
1736    size            (sq|t|s|m|o)    #IMPLIED    -- Image size to extract
1737    class        CDATA        #IMPLIED    -- Class to add on image tag
1738    with_category    (1|0)        #IMPLIED    -- Search in entry category description if present (default 0)
1739    no_tag    (1|0)    #IMPLIED    -- Return image URL without HTML tag (default 0)
1740    content_only    (1|0)        #IMPLIED    -- Search in content entry only, not in excerpt (default 0)
1741    cat_only    (1|0)        #IMPLIED    -- Search in category description only (default 0)
1742    >
1743     */
1744    public function EntryFirstImage($attr)
1745    {
1746        $size          = !empty($attr['size']) ? $attr['size'] : '';
1747        $class         = !empty($attr['class']) ? $attr['class'] : '';
1748        $with_category = !empty($attr['with_category']) ? 1 : 0;
1749        $no_tag        = !empty($attr['no_tag']) ? 1 : 0;
1750        $content_only  = !empty($attr['content_only']) ? 1 : 0;
1751        $cat_only      = !empty($attr['cat_only']) ? 1 : 0;
1752
1753        return "<?php echo context::EntryFirstImageHelper('" . addslashes($size) . "'," . $with_category . ",'" . addslashes($class) . "'," .
1754            $no_tag . "," . $content_only . "," . $cat_only . "); ?>";
1755    }
1756
1757    /*dtd
1758    <!ELEMENT tpl:EntryID - O -- Entry ID -->
1759     */
1760    public function EntryID($attr)
1761    {
1762        $f = $this->getFilters($attr);
1763        return '<?php echo ' . sprintf($f, '$_ctx->posts->post_id') . '; ?>';
1764    }
1765
1766    /*dtd
1767    <!ELEMENT tpl:EntryLang - O --  Entry language or blog lang if not defined -->
1768     */
1769    public function EntryLang($attr)
1770    {
1771        $f = $this->getFilters($attr);
1772        return
1773        '<?php if ($_ctx->posts->post_lang) { ' .
1774        'echo ' . sprintf($f, '$_ctx->posts->post_lang') . '; ' .
1775        '} else {' .
1776        'echo ' . sprintf($f, '$core->blog->settings->system->lang') . '; ' .
1777            '} ?>';
1778    }
1779
1780    /*dtd
1781    <!ELEMENT tpl:EntryNext - - -- Next entry block -->
1782    <!ATTLIST tpl:EntryNext
1783    restrict_to_category    (0|1)    #IMPLIED    -- find next post in the same category (default: 0)
1784    restrict_to_lang        (0|1)    #IMPLIED    -- find next post in the same language (default: 0)
1785    >
1786     */
1787    public function EntryNext($attr, $content)
1788    {
1789        $restrict_to_category = !empty($attr['restrict_to_category']) ? '1' : '0';
1790        $restrict_to_lang     = !empty($attr['restrict_to_lang']) ? '1' : '0';
1791
1792        return
1793            '<?php $next_post = $core->blog->getNextPost($_ctx->posts,1,' . $restrict_to_category . ',' . $restrict_to_lang . '); ?>' . "\n" .
1794            '<?php if ($next_post !== null) : ?>' .
1795
1796            '<?php $_ctx->posts = $next_post; unset($next_post);' . "\n" .
1797            'while ($_ctx->posts->fetch()) : ?>' .
1798            $content .
1799            '<?php endwhile; $_ctx->posts = null; ?>' .
1800            "<?php endif; ?>\n";
1801    }
1802
1803    /*dtd
1804    <!ELEMENT tpl:EntryPrevious - - -- Previous entry block -->
1805    <!ATTLIST tpl:EntryPrevious
1806    restrict_to_category    (0|1)    #IMPLIED    -- find previous post in the same category (default: 0)
1807    restrict_to_lang        (0|1)    #IMPLIED    -- find next post in the same language (default: 0)
1808    >
1809     */
1810    public function EntryPrevious($attr, $content)
1811    {
1812        $restrict_to_category = !empty($attr['restrict_to_category']) ? '1' : '0';
1813        $restrict_to_lang     = !empty($attr['restrict_to_lang']) ? '1' : '0';
1814
1815        return
1816            '<?php $prev_post = $core->blog->getNextPost($_ctx->posts,-1,' . $restrict_to_category . ',' . $restrict_to_lang . '); ?>' . "\n" .
1817            '<?php if ($prev_post !== null) : ?>' .
1818
1819            '<?php $_ctx->posts = $prev_post; unset($prev_post);' . "\n" .
1820            'while ($_ctx->posts->fetch()) : ?>' .
1821            $content .
1822            '<?php endwhile; $_ctx->posts = null; ?>' .
1823            "<?php endif; ?>\n";
1824    }
1825
1826    /*dtd
1827    <!ELEMENT tpl:EntryTitle - O -- Entry title -->
1828     */
1829    public function EntryTitle($attr)
1830    {
1831        $f = $this->getFilters($attr);
1832        return '<?php echo ' . sprintf($f, '$_ctx->posts->post_title') . '; ?>';
1833    }
1834
1835    /*dtd
1836    <!ELEMENT tpl:EntryURL - O -- Entry URL -->
1837     */
1838    public function EntryURL($attr)
1839    {
1840        $f = $this->getFilters($attr);
1841        return '<?php echo ' . sprintf($f, '$_ctx->posts->getURL()') . '; ?>';
1842    }
1843
1844    /*dtd
1845    <!ELEMENT tpl:EntryDate - O -- Entry date -->
1846    <!ATTLIST tpl:EntryDate
1847    format    CDATA    #IMPLIED    -- date format (encoded in dc:str by default if iso8601 or rfc822 not specified)
1848    iso8601    CDATA    #IMPLIED    -- if set, tells that date format is ISO 8601
1849    rfc822    CDATA    #IMPLIED    -- if set, tells that date format is RFC 822
1850    upddt    CDATA    #IMPLIED    -- if set, uses the post update time
1851    creadt    CDATA    #IMPLIED    -- if set, uses the post creation time
1852    >
1853     */
1854    public function EntryDate($attr)
1855    {
1856        $format = '';
1857        if (!empty($attr['format'])) {
1858            $format = addslashes($attr['format']);
1859        }
1860
1861        $iso8601 = !empty($attr['iso8601']);
1862        $rfc822  = !empty($attr['rfc822']);
1863        $type    = (!empty($attr['creadt']) ? 'creadt' : '');
1864        $type    = (!empty($attr['upddt']) ? 'upddt' : $type);
1865
1866        $f = $this->getFilters($attr);
1867
1868        if ($rfc822) {
1869            return '<?php echo ' . sprintf($f, "\$_ctx->posts->getRFC822Date('" . $type . "')") . '; ?>';
1870        } elseif ($iso8601) {
1871            return '<?php echo ' . sprintf($f, "\$_ctx->posts->getISO8601Date('" . $type . "')") . '; ?>';
1872        } else {
1873            return '<?php echo ' . sprintf($f, "\$_ctx->posts->getDate('" . $format . "','" . $type . "')") . '; ?>';
1874        }
1875    }
1876
1877    /*dtd
1878    <!ELEMENT tpl:EntryTime - O -- Entry date -->
1879    <!ATTLIST tpl:EntryTime
1880    format    CDATA    #IMPLIED    -- time format
1881    upddt    CDATA    #IMPLIED    -- if set, uses the post update time
1882    creadt    CDATA    #IMPLIED    -- if set, uses the post creation time
1883    >
1884     */
1885    public function EntryTime($attr)
1886    {
1887        $format = '';
1888        if (!empty($attr['format'])) {
1889            $format = addslashes($attr['format']);
1890        }
1891
1892        $type = (!empty($attr['creadt']) ? 'creadt' : '');
1893        $type = (!empty($attr['upddt']) ? 'upddt' : $type);
1894
1895        $f = $this->getFilters($attr);
1896        return '<?php echo ' . sprintf($f, "\$_ctx->posts->getTime('" . $format . "','" . $type . "')") . '; ?>';
1897    }
1898
1899    /*dtd
1900    <!ELEMENT tpl:EntriesHeader - - -- First entries result container -->
1901     */
1902    public function EntriesHeader($attr, $content)
1903    {
1904        return
1905            "<?php if (\$_ctx->posts->isStart()) : ?>" .
1906            $content .
1907            "<?php endif; ?>";
1908    }
1909
1910    /*dtd
1911    <!ELEMENT tpl:EntriesFooter - - -- Last entries result container -->
1912     */
1913    public function EntriesFooter($attr, $content)
1914    {
1915        return
1916            "<?php if (\$_ctx->posts->isEnd()) : ?>" .
1917            $content .
1918            "<?php endif; ?>";
1919    }
1920
1921    /*dtd
1922    <!ELEMENT tpl:EntryCommentCount - O -- Number of comments for entry -->
1923    <!ATTLIST tpl:EntryCommentCount
1924    none        CDATA    #IMPLIED    -- text to display for "no comments" (default: no comments)
1925    one        CDATA    #IMPLIED    -- text to display for "one comment" (default: one comment)
1926    more        CDATA    #IMPLIED    -- text to display for "more comments" (default: %s comments, %s is replaced by the number of comment)
1927    count_all    CDATA    #IMPLIED    -- count comments and trackbacks
1928    >
1929     */
1930    public function EntryCommentCount($attr)
1931    {
1932        if (empty($attr['count_all'])) {
1933            $operation = '$_ctx->posts->nb_comment';
1934        } else {
1935            $operation = '($_ctx->posts->nb_comment + $_ctx->posts->nb_trackback)';
1936        }
1937
1938        return $this->displayCounter(
1939            $operation,
1940            [
1941                'none' => 'no comments',
1942                'one'  => 'one comment',
1943                'more' => '%d comments'
1944            ],
1945            $attr,
1946            false
1947        );
1948    }
1949
1950    /*dtd
1951    <!ELEMENT tpl:EntryPingCount - O -- Number of trackbacks for entry -->
1952    <!ATTLIST tpl:EntryPingCount
1953    none    CDATA    #IMPLIED    -- text to display for "no pings" (default: no pings)
1954    one    CDATA    #IMPLIED    -- text to display for "one ping" (default: one ping)
1955    more    CDATA    #IMPLIED    -- text to display for "more pings" (default: %s trackbacks, %s is replaced by the number of pings)
1956    >
1957     */
1958    public function EntryPingCount($attr)
1959    {
1960        return $this->displayCounter(
1961            '$_ctx->posts->nb_trackback',
1962            [
1963                'none' => 'no trackbacks',
1964                'one'  => 'one trackback',
1965                'more' => '%d trackbacks'
1966            ],
1967            $attr,
1968            false
1969        );
1970    }
1971
1972    /*dtd
1973    <!ELEMENT tpl:EntryPingData - O -- Display trackback RDF information -->
1974     */
1975    public function EntryPingData($attr)
1976    {
1977        $format = !empty($attr['format']) && $attr['format'] == 'xml' ? 'xml' : 'html';
1978        return "<?php if (\$_ctx->posts->trackbacksActive()) { echo \$_ctx->posts->getTrackbackData('" . $format . "'); } ?>\n";
1979    }
1980
1981    /*dtd
1982    <!ELEMENT tpl:EntryPingLink - O -- Entry trackback link -->
1983     */
1984    public function EntryPingLink($attr)
1985    {
1986        return "<?php if (\$_ctx->posts->trackbacksActive()) { echo \$_ctx->posts->getTrackbackLink(); } ?>\n";
1987    }
1988
1989    /* Languages -------------------------------------- */
1990    /*dtd
1991    <!ELEMENT tpl:Languages - - -- Languages loop -->
1992    <!ATTLIST tpl:Languages
1993    lang    CDATA    #IMPLIED    -- restrict loop on given lang
1994    order    (desc|asc)    #IMPLIED    -- languages ordering (default: desc)
1995    >
1996     */
1997    public function Languages($attr, $content)
1998    {
1999        $p = "if (!isset(\$params)) \$params = [];\n";
2000
2001        if (isset($attr['lang'])) {
2002            $p = "\$params['lang'] = '" . addslashes($attr['lang']) . "';\n";
2003        }
2004
2005        $order = 'desc';
2006        if (isset($attr['order']) && preg_match('/^(desc|asc)$/i', $attr['order'])) {
2007            $p .= "\$params['order'] = '" . $attr['order'] . "';\n ";
2008        }
2009
2010        $res = "<?php\n";
2011        $res .= $p;
2012        $res .= $this->core->callBehavior("templatePrepareParams",
2013            ["tag" => "Languages", "method" => "blog::getLangs"],
2014            $attr, $content);
2015        $res .= '$_ctx->langs = $core->blog->getLangs($params); unset($params);' . "\n";
2016        $res .= "?>\n";
2017
2018        $res .=
2019            '<?php if ($_ctx->langs->count() > 1) : ' .
2020            'while ($_ctx->langs->fetch()) : ?>' . $content .
2021            '<?php endwhile; $_ctx->langs = null; endif; ?>';
2022
2023        return $res;
2024    }
2025
2026    /*dtd
2027    <!ELEMENT tpl:LanguagesHeader - - -- First languages result container -->
2028     */
2029    public function LanguagesHeader($attr, $content)
2030    {
2031        return
2032            "<?php if (\$_ctx->langs->isStart()) : ?>" .
2033            $content .
2034            "<?php endif; ?>";
2035    }
2036
2037    /*dtd
2038    <!ELEMENT tpl:LanguagesFooter - - -- Last languages result container -->
2039     */
2040    public function LanguagesFooter($attr, $content)
2041    {
2042        return
2043            "<?php if (\$_ctx->langs->isEnd()) : ?>" .
2044            $content .
2045            "<?php endif; ?>";
2046    }
2047
2048    /*dtd
2049    <!ELEMENT tpl:LanguageCode - O -- Language code -->
2050     */
2051    public function LanguageCode($attr)
2052    {
2053        $f = $this->getFilters($attr);
2054        return '<?php echo ' . sprintf($f, '$_ctx->langs->post_lang') . '; ?>';
2055    }
2056
2057    /*dtd
2058    <!ELEMENT tpl:LanguageIfCurrent - - -- tests if post language is current language -->
2059     */
2060    public function LanguageIfCurrent($attr, $content)
2061    {
2062        return
2063            "<?php if (\$_ctx->cur_lang == \$_ctx->langs->post_lang) : ?>" .
2064            $content .
2065            "<?php endif; ?>";
2066    }
2067
2068    /*dtd
2069    <!ELEMENT tpl:LanguageURL - O -- Language URL -->
2070     */
2071    public function LanguageURL($attr)
2072    {
2073        $f = $this->getFilters($attr);
2074        return '<?php echo ' . sprintf($f, '$core->blog->url.$core->url->getURLFor("lang",' .
2075            '$_ctx->langs->post_lang)') . '; ?>';
2076    }
2077
2078    /* Pagination ------------------------------------- */
2079    /*dtd
2080    <!ELEMENT tpl:Pagination - - -- Pagination container -->
2081    <!ATTLIST tpl:Pagination
2082    no_context    (0|1)    #IMPLIED    -- override test on posts count vs number of posts per page
2083    >
2084     */
2085    public function Pagination($attr, $content)
2086    {
2087        $p = "<?php\n";
2088        $p .= '$params = $_ctx->post_params;' . "\n";
2089        $p .= $this->core->callBehavior("templatePrepareParams",
2090            ["tag" => "Pagination", "method" => "blog::getPosts"],
2091            $attr, $content);
2092        $p .= '$_ctx->pagination = $core->blog->getPosts($params,true); unset($params);' . "\n";
2093        $p .= "?>\n";
2094
2095        if (isset($attr['no_context']) && $attr['no_context']) {
2096            return $p . $content;
2097        }
2098
2099        return
2100            $p .
2101            '<?php if ($_ctx->pagination->f(0) > $_ctx->posts->count()) : ?>' .
2102            $content .
2103            '<?php endif; ?>';
2104    }
2105
2106    /*dtd
2107    <!ELEMENT tpl:PaginationCounter - O -- Number of pages -->
2108     */
2109    public function PaginationCounter($attr)
2110    {
2111        $f = $this->getFilters($attr);
2112        return '<?php echo ' . sprintf($f, "context::PaginationNbPages()") . '; ?>';
2113    }
2114
2115    /*dtd
2116    <!ELEMENT tpl:PaginationCurrent - O -- current page -->
2117     */
2118    public function PaginationCurrent($attr)
2119    {
2120        $offset = 0;
2121        if (isset($attr['offset'])) {
2122            $offset = (integer) $attr['offset'];
2123        }
2124
2125        $f = $this->getFilters($attr);
2126        return '<?php echo ' . sprintf($f, "context::PaginationPosition(" . $offset . ")") . '; ?>';
2127    }
2128
2129    /*dtd
2130    <!ELEMENT tpl:PaginationIf - - -- pages tests -->
2131    <!ATTLIST tpl:PaginationIf
2132    start    (0|1)    #IMPLIED    -- test if we are at first page (value : 1) or not (value : 0)
2133    end    (0|1)    #IMPLIED    -- test if we are at last page (value : 1) or not (value : 0)
2134    >
2135     */
2136    public function PaginationIf($attr, $content)
2137    {
2138        $if = [];
2139
2140        if (isset($attr['start'])) {
2141            $sign = (boolean) $attr['start'] ? '' : '!';
2142            $if[] = $sign . 'context::PaginationStart()';
2143        }
2144
2145        if (isset($attr['end'])) {
2146            $sign = (boolean) $attr['end'] ? '' : '!';
2147            $if[] = $sign . 'context::PaginationEnd()';
2148        }
2149
2150        $this->core->callBehavior('tplIfConditions', 'PaginationIf', $attr, $content, $if);
2151
2152        if (count($if) != 0) {
2153            return '<?php if(' . implode(' && ', (array) $if) . ') : ?>' . $content . '<?php endif; ?>';
2154        } else {
2155            return $content;
2156        }
2157    }
2158
2159    /*dtd
2160    <!ELEMENT tpl:PaginationURL - O -- link to previoux/next page -->
2161    <!ATTLIST tpl:PaginationURL
2162    offset    CDATA    #IMPLIED    -- page offset (negative for previous pages), default: 0
2163    >
2164     */
2165    public function PaginationURL($attr)
2166    {
2167        $offset = 0;
2168        if (isset($attr['offset'])) {
2169            $offset = (integer) $attr['offset'];
2170        }
2171
2172        $f = $this->getFilters($attr);
2173        return '<?php echo ' . sprintf($f, "context::PaginationURL(" . $offset . ")") . '; ?>';
2174    }
2175
2176    /* Comments --------------------------------------- */
2177    /*dtd
2178    <!ELEMENT tpl:Comments - - -- Comments container -->
2179    <!ATTLIST tpl:Comments
2180    with_pings    (0|1)    #IMPLIED    -- include trackbacks in request
2181    lastn    CDATA    #IMPLIED    -- restrict the number of entries
2182    no_context (1|0)        #IMPLIED  -- Override context information
2183    sortby    (title|selected|author|date|id)    #IMPLIED    -- specify comments sort criteria (default : date) (multiple comma-separated sortby can be specified. Use "?asc" or "?desc" as suffix to provide an order for each sorby)
2184    order    (desc|asc)    #IMPLIED    -- result ordering (default: asc)
2185    age        CDATA    #IMPLIED    -- retrieve comments by maximum age (ex: -2 days, last month, last week)
2186    >
2187     */
2188    public function Comments($attr, $content)
2189    {
2190        $p = "";
2191        if (empty($attr['with_pings'])) {
2192            $p .= "\$params['comment_trackback'] = false;\n";
2193        }
2194
2195        $lastn = 0;
2196        if (isset($attr['lastn'])) {
2197            $lastn = abs((integer) $attr['lastn']) + 0;
2198        }
2199
2200        if ($lastn > 0) {
2201            $p .= "\$params['limit'] = " . $lastn . ";\n";
2202        } else {
2203            $p .= "if (\$_ctx->nb_comment_per_page !== null) { \$params['limit'] = \$_ctx->nb_comment_per_page; }\n";
2204        }
2205
2206        if (empty($attr['no_context'])) {
2207            $p .=
2208                "if (\$_ctx->posts !== null) { " .
2209                "\$params['post_id'] = \$_ctx->posts->post_id; " .
2210                "\$core->blog->withoutPassword(false);\n" .
2211                "}\n";
2212            $p .=
2213                'if ($_ctx->exists("categories")) { ' .
2214                "\$params['cat_id'] = \$_ctx->categories->cat_id; " .
2215                "}\n";
2216
2217            $p .=
2218                'if ($_ctx->exists("langs")) { ' .
2219                "\$params['sql'] = \"AND P.post_lang = '\".\$core->blog->con->escape(\$_ctx->langs->post_lang).\"' \"; " .
2220                "}\n";
2221        }
2222
2223        if (!isset($attr['order'])) {
2224            $attr['order'] = 'asc';
2225        }
2226
2227        $p .= "\$params['order'] = '" . $this->getSortByStr($attr, 'comment') . "';\n";
2228
2229        if (isset($attr['no_content']) && $attr['no_content']) {
2230            $p .= "\$params['no_content'] = true;\n";
2231        }
2232
2233        if (isset($attr['age'])) {
2234            $age = $this->getAge($attr);
2235            $p .= !empty($age) ? "@\$params['sql'] .= ' AND P.post_dt > \'" . $age . "\'';\n" : '';
2236        }
2237
2238        $res = "<?php\n";
2239        $res .= $this->core->callBehavior("templatePrepareParams",
2240            ["tag" => "Comments", "method" => "blog::getComments"],
2241            $attr, $content);
2242        $res .= $p;
2243        $res .= '$_ctx->comments = $core->blog->getComments($params); unset($params);' . "\n";
2244        $res .= "if (\$_ctx->posts !== null) { \$core->blog->withoutPassword(true);}\n";
2245
2246        if (!empty($attr['with_pings'])) {
2247            $res .= '$_ctx->pings = $_ctx->comments;' . "\n";
2248        }
2249
2250        $res .= "?>\n";
2251
2252        $res .=
2253            '<?php while ($_ctx->comments->fetch()) : ?>' . $content . '<?php endwhile; $_ctx->comments = null; ?>';
2254
2255        return $res;
2256    }
2257
2258    /*dtd
2259    <!ELEMENT tpl:CommentAuthor - O -- Comment author -->
2260     */
2261    public function CommentAuthor($attr)
2262    {
2263        $f = $this->getFilters($attr);
2264        return '<?php echo ' . sprintf($f, "\$_ctx->comments->comment_author") . '; ?>';
2265    }
2266
2267    /*dtd
2268    <!ELEMENT tpl:CommentAuthorDomain - O -- Comment author website domain -->
2269     */
2270    public function CommentAuthorDomain($attr)
2271    {
2272        return '<?php echo preg_replace("#^http(?:s?)://(.+?)/.*$#msu",\'$1\',$_ctx->comments->comment_site); ?>';
2273    }
2274
2275    /*dtd
2276    <!ELEMENT tpl:CommentAuthorLink - O -- Comment author link -->
2277     */
2278    public function CommentAuthorLink($attr)
2279    {
2280        $f = $this->getFilters($attr);
2281        return '<?php echo ' . sprintf($f, '$_ctx->comments->getAuthorLink()') . '; ?>';
2282    }
2283
2284    /*dtd
2285    <!ELEMENT tpl:CommentAuthorMailMD5 - O -- Comment author email MD5 sum -->
2286     */
2287    public function CommentAuthorMailMD5($attr)
2288    {
2289        return '<?php echo md5($_ctx->comments->comment_email) ; ?>';
2290    }
2291
2292    /*dtd
2293    <!ELEMENT tpl:CommentAuthorURL - O -- Comment author URL -->
2294     */
2295    public function CommentAuthorURL($attr)
2296    {
2297        $f = $this->getFilters($attr);
2298        return '<?php echo ' . sprintf($f, '$_ctx->comments->getAuthorURL()') . '; ?>';
2299    }
2300
2301    /*dtd
2302    <!ELEMENT tpl:CommentContent - O --  Comment content -->
2303    <!ATTLIST tpl:CommentContent
2304    absolute_urls    (0|1)    #IMPLIED    -- convert URLS to absolute urls
2305    >
2306     */
2307    public function CommentContent($attr)
2308    {
2309        $urls = '0';
2310        if (!empty($attr['absolute_urls'])) {
2311            $urls = '1';
2312        }
2313
2314        $f = $this->getFilters($attr);
2315        return '<?php echo ' . sprintf($f, '$_ctx->comments->getContent(' . $urls . ')') . '; ?>';
2316    }
2317
2318    /*dtd
2319    <!ELEMENT tpl:CommentDate - O -- Comment date -->
2320    <!ATTLIST tpl:CommentDate
2321    format    CDATA    #IMPLIED    -- date format (encoded in dc:str by default if iso8601 or rfc822 not specified)
2322    iso8601    CDATA    #IMPLIED    -- if set, tells that date format is ISO 8601
2323    rfc822    CDATA    #IMPLIED    -- if set, tells that date format is RFC 822
2324    upddt    CDATA    #IMPLIED    -- if set, uses the comment update time
2325    >
2326     */
2327    public function CommentDate($attr)
2328    {
2329        $format = '';
2330        if (!empty($attr['format'])) {
2331            $format = addslashes($attr['format']);
2332        }
2333
2334        $iso8601 = !empty($attr['iso8601']);
2335        $rfc822  = !empty($attr['rfc822']);
2336        $type    = (!empty($attr['upddt']) ? 'upddt' : '');
2337
2338        $f = $this->getFilters($attr);
2339
2340        if ($rfc822) {
2341            return '<?php echo ' . sprintf($f, "\$_ctx->comments->getRFC822Date('" . $type . "')") . '; ?>';
2342        } elseif ($iso8601) {
2343            return '<?php echo ' . sprintf($f, "\$_ctx->comments->getISO8601Date('" . $type . "')") . '; ?>';
2344        } else {
2345            return '<?php echo ' . sprintf($f, "\$_ctx->comments->getDate('" . $format . "','" . $type . "')") . '; ?>';
2346        }
2347    }
2348
2349    /*dtd
2350    <!ELEMENT tpl:CommentTime - O -- Comment date -->
2351    <!ATTLIST tpl:CommentTime
2352    format    CDATA    #IMPLIED    -- time format
2353    upddt    CDATA    #IMPLIED    -- if set, uses the comment update time
2354    >
2355     */
2356    public function CommentTime($attr)
2357    {
2358        $format = '';
2359        if (!empty($attr['format'])) {
2360            $format = addslashes($attr['format']);
2361        }
2362        $type = (!empty($attr['upddt']) ? 'upddt' : '');
2363
2364        $f = $this->getFilters($attr);
2365        return '<?php echo ' . sprintf($f, "\$_ctx->comments->getTime('" . $format . "','" . $type . "')") . '; ?>';
2366    }
2367
2368    /*dtd
2369    <!ELEMENT tpl:CommentEmail - O -- Comment author email -->
2370    <!ATTLIST tpl:CommentEmail
2371    spam_protected    (0|1)    #IMPLIED    -- protect email from spam (default: 1)
2372    >
2373     */
2374    public function CommentEmail($attr)
2375    {
2376        $p = 'true';
2377        if (isset($attr['spam_protected']) && !$attr['spam_protected']) {
2378            $p = 'false';
2379        }
2380
2381        $f = $this->getFilters($attr);
2382        return '<?php echo ' . sprintf($f, "\$_ctx->comments->getEmail(" . $p . ")") . '; ?>';
2383    }
2384
2385    /*dtd
2386    <!ELEMENT tpl:CommentEntryTitle - O -- Title of the comment entry -->
2387     */
2388    public function CommentEntryTitle($attr)
2389    {
2390        $f = $this->getFilters($attr);
2391        return '<?php echo ' . sprintf($f, '$_ctx->comments->post_title') . '; ?>';
2392    }
2393
2394    /*dtd
2395    <!ELEMENT tpl:CommentFeedID - O -- Comment feed ID -->
2396     */
2397    public function CommentFeedID($attr)
2398    {
2399        $f = $this->getFilters($attr);
2400        return '<?php echo ' . sprintf($f, '$_ctx->comments->getFeedID()') . '; ?>';
2401    }
2402
2403    /*dtd
2404    <!ELEMENT tpl:CommentID - O -- Comment ID -->
2405     */
2406    public function CommentID($attr)
2407    {
2408        return '<?php echo $_ctx->comments->comment_id; ?>';
2409    }
2410
2411    /*dtd
2412    <!ELEMENT tpl:CommentIf - - -- test container for comments -->
2413    <!ATTLIST tpl:CommentIf
2414    is_ping    (0|1)    #IMPLIED    -- test if comment is a trackback (value : 1) or not (value : 0)
2415    >
2416     */
2417    public function CommentIf($attr, $content)
2418    {
2419        $if      = [];
2420        $is_ping = null;
2421
2422        if (isset($attr['is_ping'])) {
2423            $sign = (boolean) $attr['is_ping'] ? '' : '!';
2424            $if[] = $sign . '$_ctx->comments->comment_trackback';
2425        }
2426
2427        $this->core->callBehavior('tplIfConditions', 'CommentIf', $attr, $content, $if);
2428
2429        if (count($if) != 0) {
2430            return '<?php if(' . implode(' && ', (array) $if) . ') : ?>' . $content . '<?php endif; ?>';
2431        } else {
2432            return $content;
2433        }
2434    }
2435
2436    /*dtd
2437    <!ELEMENT tpl:CommentIfFirst - O -- displays value if comment is the first one -->
2438    <!ATTLIST tpl:CommentIfFirst
2439    return    CDATA    #IMPLIED    -- value to display in case of success (default: first)
2440    >
2441     */
2442    public function CommentIfFirst($attr)
2443    {
2444        $ret = isset($attr['return']) ? $attr['return'] : 'first';
2445        $ret = html::escapeHTML($ret);
2446
2447        return
2448        '<?php if ($_ctx->comments->index() == 0) { ' .
2449        "echo '" . addslashes($ret) . "'; } ?>";
2450    }
2451
2452    /*dtd
2453    <!ELEMENT tpl:CommentIfMe - O -- displays value if comment is the from the entry author -->
2454    <!ATTLIST tpl:CommentIfMe
2455    return    CDATA    #IMPLIED    -- value to display in case of success (default: me)
2456    >
2457     */
2458    public function CommentIfMe($attr)
2459    {
2460        $ret = isset($attr['return']) ? $attr['return'] : 'me';
2461        $ret = html::escapeHTML($ret);
2462
2463        return
2464        '<?php if ($_ctx->comments->isMe()) { ' .
2465        "echo '" . addslashes($ret) . "'; } ?>";
2466    }
2467
2468    /*dtd
2469    <!ELEMENT tpl:CommentIfOdd - O -- displays value if comment is  at an odd position -->
2470    <!ATTLIST tpl:CommentIfOdd
2471    return    CDATA    #IMPLIED    -- value to display in case of success (default: odd)
2472    >
2473     */
2474    public function CommentIfOdd($attr)
2475    {
2476        $ret = isset($attr['return']) ? $attr['return'] : 'odd';
2477        $ret = html::escapeHTML($ret);
2478
2479        return
2480        '<?php if (($_ctx->comments->index()+1)%2) { ' .
2481        "echo '" . addslashes($ret) . "'; } ?>";
2482    }
2483
2484    /*dtd
2485    <!ELEMENT tpl:CommentIP - O -- Comment author IP -->
2486     */
2487    public function CommentIP($attr)
2488    {
2489        return '<?php echo $_ctx->comments->comment_ip; ?>';
2490    }
2491
2492    /*dtd
2493    <!ELEMENT tpl:CommentOrderNumber - O -- Comment order in page -->
2494     */
2495    public function CommentOrderNumber($attr)
2496    {
2497        return '<?php echo $_ctx->comments->index()+1; ?>';
2498    }
2499
2500    /*dtd
2501    <!ELEMENT tpl:CommentsFooter - - -- Last comments result container -->
2502     */
2503    public function CommentsFooter($attr, $content)
2504    {
2505        return
2506            "<?php if (\$_ctx->comments->isEnd()) : ?>" .
2507            $content .
2508            "<?php endif; ?>";
2509    }
2510
2511    /*dtd
2512    <!ELEMENT tpl:CommentsHeader - - -- First comments result container -->
2513     */
2514    public function CommentsHeader($attr, $content)
2515    {
2516        return
2517            "<?php if (\$_ctx->comments->isStart()) : ?>" .
2518            $content .
2519            "<?php endif; ?>";
2520    }
2521
2522    /*dtd
2523    <!ELEMENT tpl:CommentPostURL - O -- Comment Entry URL -->
2524     */
2525    public function CommentPostURL($attr)
2526    {
2527        $f = $this->getFilters($attr);
2528        return '<?php echo ' . sprintf($f, '$_ctx->comments->getPostURL()') . '; ?>';
2529    }
2530
2531    /*dtd
2532    <!ELEMENT tpl:IfCommentAuthorEmail - - -- Container displayed if comment author email is set -->
2533     */
2534    public function IfCommentAuthorEmail($attr, $content)
2535    {
2536        return
2537            "<?php if (\$_ctx->comments->comment_email) : ?>" .
2538            $content .
2539            "<?php endif; ?>";
2540    }
2541
2542    /*dtd
2543    <!ELEMENT tpl:CommentHelp - 0 -- Comment syntax mini help -->
2544     */
2545    public function CommentHelp($attr, $content)
2546    {
2547        return
2548            "<?php if (\$core->blog->settings->system->wiki_comments) {\n" .
2549            "  echo __('Comments can be formatted using a simple wiki syntax.');\n" .
2550            "} else {\n" .
2551            "  echo __('HTML code is displayed as text and web addresses are automatically converted.');\n" .
2552            "} ?>";
2553    }
2554
2555    /* Comment preview -------------------------------- */
2556    /*dtd
2557    <!ELEMENT tpl:IfCommentPreviewOptional - - -- Container displayed if comment preview is optional or currently previewed -->
2558     */
2559    public function IfCommentPreviewOptional($attr, $content)
2560    {
2561        return
2562            '<?php if ($core->blog->settings->system->comment_preview_optional || ($_ctx->comment_preview !== null && $_ctx->comment_preview["preview"])) : ?>' .
2563            $content .
2564            '<?php endif; ?>';
2565    }
2566
2567    /*dtd
2568    <!ELEMENT tpl:IfCommentPreview - - -- Container displayed if comment is being previewed -->
2569     */
2570    public function IfCommentPreview($attr, $content)
2571    {
2572        return
2573            '<?php if ($_ctx->comment_preview !== null && $_ctx->comment_preview["preview"]) : ?>' .
2574            $content .
2575            '<?php endif; ?>';
2576    }
2577
2578    /*dtd
2579    <!ELEMENT tpl:CommentPreviewName - O -- Author name for the previewed comment -->
2580     */
2581    public function CommentPreviewName($attr)
2582    {
2583        $f = $this->getFilters($attr);
2584        return '<?php echo ' . sprintf($f, '$_ctx->comment_preview["name"]') . '; ?>';
2585    }
2586
2587    /*dtd
2588    <!ELEMENT tpl:CommentPreviewEmail - O -- Author email for the previewed comment -->
2589     */
2590    public function CommentPreviewEmail($attr)
2591    {
2592        $f = $this->getFilters($attr);
2593        return '<?php echo ' . sprintf($f, '$_ctx->comment_preview["mail"]') . '; ?>';
2594    }
2595
2596    /*dtd
2597    <!ELEMENT tpl:CommentPreviewSite - O -- Author site for the previewed comment -->
2598     */
2599    public function CommentPreviewSite($attr)
2600    {
2601        $f = $this->getFilters($attr);
2602        return '<?php echo ' . sprintf($f, '$_ctx->comment_preview["site"]') . '; ?>';
2603    }
2604
2605    /*dtd
2606    <!ELEMENT tpl:CommentPreviewContent - O -- Content of the previewed comment -->
2607    <!ATTLIST tpl:CommentPreviewContent
2608    raw    (0|1)    #IMPLIED    -- display comment in raw content
2609    >
2610     */
2611    public function CommentPreviewContent($attr)
2612    {
2613        $f = $this->getFilters($attr);
2614
2615        if (!empty($attr['raw'])) {
2616            $co = '$_ctx->comment_preview["rawcontent"]';
2617        } else {
2618            $co = '$_ctx->comment_preview["content"]';
2619        }
2620
2621        return '<?php echo ' . sprintf($f, $co) . '; ?>';
2622    }
2623
2624    /*dtd
2625    <!ELEMENT tpl:CommentPreviewCheckRemember - O -- checkbox attribute for "remember me" (same value as before preview) -->
2626     */
2627    public function CommentPreviewCheckRemember($attr)
2628    {
2629        return
2630            "<?php if (\$_ctx->comment_preview['remember']) { echo ' checked=\"checked\"'; } ?>";
2631    }
2632
2633    /* Trackbacks ------------------------------------- */
2634    /*dtd
2635    <!ELEMENT tpl:PingBlogName - O -- Trackback blog name -->
2636     */
2637    public function PingBlogName($attr)
2638    {
2639        $f = $this->getFilters($attr);
2640        return '<?php echo ' . sprintf($f, '$_ctx->pings->comment_author') . '; ?>';
2641    }
2642
2643    /*dtd
2644    <!ELEMENT tpl:PingContent - O -- Trackback content -->
2645     */
2646    public function PingContent($attr)
2647    {
2648        $f = $this->getFilters($attr);
2649        return '<?php echo ' . sprintf($f, '$_ctx->pings->getTrackbackContent()') . '; ?>';
2650    }
2651
2652    /*dtd
2653    <!ELEMENT tpl:PingDate - O -- Trackback date -->
2654    <!ATTLIST tpl:PingDate
2655    format    CDATA    #IMPLIED    -- date format (encoded in dc:str by default if iso8601 or rfc822 not specified)
2656    iso8601    CDATA    #IMPLIED    -- if set, tells that date format is ISO 8601
2657    rfc822    CDATA    #IMPLIED    -- if set, tells that date format is RFC 822
2658    upddt    CDATA    #IMPLIED    -- if set, uses the comment update time
2659    >
2660     */
2661    public function PingDate($attr, $type = '')
2662    {
2663        $format = '';
2664        if (!empty($attr['format'])) {
2665            $format = addslashes($attr['format']);
2666        }
2667
2668        $iso8601 = !empty($attr['iso8601']);
2669        $rfc822  = !empty($attr['rfc822']);
2670        $type    = (!empty($attr['upddt']) ? 'upddt' : '');
2671
2672        $f = $this->getFilters($attr);
2673
2674        if ($rfc822) {
2675            return '<?php echo ' . sprintf($f, "\$_ctx->pings->getRFC822Date('" . $type . "')") . '; ?>';
2676        } elseif ($iso8601) {
2677            return '<?php echo ' . sprintf($f, "\$_ctx->pings->getISO8601Date('" . $type . "')") . '; ?>';
2678        } else {
2679            return '<?php echo ' . sprintf($f, "\$_ctx->pings->getDate('" . $format . "','" . $type . "')") . '; ?>';
2680        }
2681    }
2682
2683    /*dtd
2684    <!ELEMENT tpl:PingTime - O -- Trackback date -->
2685    <!ATTLIST tpl:PingTime
2686    format    CDATA    #IMPLIED    -- time format
2687    upddt    CDATA    #IMPLIED    -- if set, uses the comment update time
2688    >
2689     */
2690    public function PingTime($attr)
2691    {
2692        $format = '';
2693        if (!empty($attr['format'])) {
2694            $format = addslashes($attr['format']);
2695        }
2696        $type = (!empty($attr['upddt']) ? 'upddt' : '');
2697
2698        $f = $this->getFilters($attr);
2699        return '<?php echo ' . sprintf($f, "\$_ctx->pings->getTime('" . $format . "','" . $type . "')") . '; ?>';
2700    }
2701
2702    /*dtd
2703    <!ELEMENT tpl:PingEntryTitle - O -- Trackback entry title -->
2704     */
2705    public function PingEntryTitle($attr)
2706    {
2707        $f = $this->getFilters($attr);
2708        return '<?php echo ' . sprintf($f, '$_ctx->pings->post_title') . '; ?>';
2709    }
2710
2711    /*dtd
2712    <!ELEMENT tpl:PingFeedID - O -- Trackback feed ID -->
2713     */
2714    public function PingFeedID($attr)
2715    {
2716        $f = $this->getFilters($attr);
2717        return '<?php echo ' . sprintf($f, '$_ctx->pings->getFeedID()') . '; ?>';
2718    }
2719
2720    /*dtd
2721    <!ELEMENT tpl:PingID - O -- Trackback ID -->
2722     */
2723    public function PingID($attr)
2724    {
2725        return '<?php echo $_ctx->pings->comment_id; ?>';
2726    }
2727
2728    /*dtd
2729    <!ELEMENT tpl:PingIfFirst - O -- displays value if trackback is the first one -->
2730    <!ATTLIST tpl:PingIfFirst
2731    return    CDATA    #IMPLIED    -- value to display in case of success (default: first)
2732    >
2733     */
2734    public function PingIfFirst($attr)
2735    {
2736        $ret = isset($attr['return']) ? $attr['return'] : 'first';
2737        $ret = html::escapeHTML($ret);
2738
2739        return
2740        '<?php if ($_ctx->pings->index() == 0) { ' .
2741        "echo '" . addslashes($ret) . "'; } ?>";
2742    }
2743
2744    /*dtd
2745    <!ELEMENT tpl:PingIfOdd - O -- displays value if trackback is  at an odd position -->
2746    <!ATTLIST tpl:PingIfOdd
2747    return    CDATA    #IMPLIED    -- value to display in case of success (default: odd)
2748    >
2749     */
2750    public function PingIfOdd($attr)
2751    {
2752        $ret = isset($attr['return']) ? $attr['return'] : 'odd';
2753        $ret = html::escapeHTML($ret);
2754
2755        return
2756        '<?php if (($_ctx->pings->index()+1)%2) { ' .
2757        "echo '" . addslashes($ret) . "'; } ?>";
2758    }
2759
2760    /*dtd
2761    <!ELEMENT tpl:PingIP - O -- Trackback author IP -->
2762     */
2763    public function PingIP($attr)
2764    {
2765        return '<?php echo $_ctx->pings->comment_ip; ?>';
2766    }
2767
2768    /*dtd
2769    <!ELEMENT tpl:PingNoFollow - O -- displays 'rel="nofollow"' if set in blog -->
2770     */
2771    public function PingNoFollow($attr)
2772    {
2773        return
2774            '<?php if($core->blog->settings->system->comments_nofollow) { ' .
2775            'echo \' rel="nofollow"\';' .
2776            '} ?>';
2777    }
2778
2779    /*dtd
2780    <!ELEMENT tpl:PingOrderNumber - O -- Trackback order in page -->
2781     */
2782    public function PingOrderNumber($attr)
2783    {
2784        return '<?php echo $_ctx->pings->index()+1; ?>';
2785    }
2786
2787    /*dtd
2788    <!ELEMENT tpl:PingPostURL - O -- Trackback Entry URL -->
2789     */
2790    public function PingPostURL($attr)
2791    {
2792        $f = $this->getFilters($attr);
2793        return '<?php echo ' . sprintf($f, '$_ctx->pings->getPostURL()') . '; ?>';
2794    }
2795
2796    /*dtd
2797    <!ELEMENT tpl:Pings - - -- Trackbacks container -->
2798    <!ATTLIST tpl:Pings
2799    with_pings    (0|1)    #IMPLIED    -- include trackbacks in request
2800    lastn    CDATA        #IMPLIED    -- restrict the number of entries
2801    no_context (1|0)        #IMPLIED  -- Override context information
2802    order    (desc|asc)    #IMPLIED    -- result ordering (default: asc)
2803    >
2804     */
2805    public function Pings($attr, $content)
2806    {
2807        $p =
2808            "if (\$_ctx->posts !== null) { " .
2809            "\$params['post_id'] = \$_ctx->posts->post_id; " .
2810            "\$core->blog->withoutPassword(false);\n" .
2811            "}\n";
2812
2813        $p .= "\$params['comment_trackback'] = true;\n";
2814
2815        $lastn = 0;
2816        if (isset($attr['lastn'])) {
2817            $lastn = abs((integer) $attr['lastn']) + 0;
2818        }
2819
2820        if ($lastn > 0) {
2821            $p .= "\$params['limit'] = " . $lastn . ";\n";
2822        } else {
2823            $p .= "if (\$_ctx->nb_comment_per_page !== null) { \$params['limit'] = \$_ctx->nb_comment_per_page; }\n";
2824        }
2825
2826        if (empty($attr['no_context'])) {
2827            $p .=
2828                'if ($_ctx->exists("categories")) { ' .
2829                "\$params['cat_id'] = \$_ctx->categories->cat_id; " .
2830                "}\n";
2831
2832            $p .=
2833                'if ($_ctx->exists("langs")) { ' .
2834                "\$params['sql'] = \"AND P.post_lang = '\".\$core->blog->con->escape(\$_ctx->langs->post_lang).\"' \"; " .
2835                "}\n";
2836        }
2837
2838        $order = 'asc';
2839        if (isset($attr['order']) && preg_match('/^(desc|asc)$/i', $attr['order'])) {
2840            $order = $attr['order'];
2841        }
2842
2843        $p .= "\$params['order'] = 'comment_dt " . $order . "';\n";
2844
2845        if (isset($attr['no_content']) && $attr['no_content']) {
2846            $p .= "\$params['no_content'] = true;\n";
2847        }
2848
2849        $res = "<?php\n";
2850        $res .= $p;
2851        $res .= $this->core->callBehavior("templatePrepareParams",
2852            ["tag" => "Pings", "method" => "blog::getComments"],
2853            $attr, $content);
2854        $res .= '$_ctx->pings = $core->blog->getComments($params); unset($params);' . "\n";
2855        $res .= "if (\$_ctx->posts !== null) { \$core->blog->withoutPassword(true);}\n";
2856        $res .= "?>\n";
2857
2858        $res .=
2859            '<?php while ($_ctx->pings->fetch()) : ?>' . $content . '<?php endwhile; $_ctx->pings = null; ?>';
2860
2861        return $res;
2862    }
2863
2864    /*dtd
2865    <!ELEMENT tpl:PingsFooter - - -- Last trackbacks result container -->
2866     */
2867    public function PingsFooter($attr, $content)
2868    {
2869        return
2870            "<?php if (\$_ctx->pings->isEnd()) : ?>" .
2871            $content .
2872            "<?php endif; ?>";
2873    }
2874
2875    /*dtd
2876    <!ELEMENT tpl:PingsHeader - - -- First trackbacks result container -->
2877     */
2878    public function PingsHeader($attr, $content)
2879    {
2880        return
2881            "<?php if (\$_ctx->pings->isStart()) : ?>" .
2882            $content .
2883            "<?php endif; ?>";
2884    }
2885
2886    /*dtd
2887    <!ELEMENT tpl:PingTitle - O -- Trackback title -->
2888     */
2889    public function PingTitle($attr)
2890    {
2891        $f = $this->getFilters($attr);
2892        return '<?php echo ' . sprintf($f, '$_ctx->pings->getTrackbackTitle()') . '; ?>';
2893    }
2894
2895    /*dtd
2896    <!ELEMENT tpl:PingAuthorURL - O -- Trackback author URL -->
2897     */
2898    public function PingAuthorURL($attr)
2899    {
2900        $f = $this->getFilters($attr);
2901        return '<?php echo ' . sprintf($f, '$_ctx->pings->getAuthorURL()') . '; ?>';
2902    }
2903
2904    # System
2905    /*dtd
2906    <!ELEMENT tpl:SysBehavior - O -- Call a given behavior -->
2907    <!ATTLIST tpl:SysBehavior
2908    behavior    CDATA    #IMPLIED    -- behavior to call
2909    >
2910     */
2911    public function SysBehavior($attr, $raw)
2912    {
2913        if (!isset($attr['behavior'])) {
2914            return;
2915        }
2916
2917        $b = addslashes($attr['behavior']);
2918        return
2919            '<?php if ($core->hasBehavior(\'' . $b . '\')) { ' .
2920            '$core->callBehavior(\'' . $b . '\',$core,$_ctx);' .
2921            '} ?>';
2922    }
2923
2924    /*dtd
2925    <!ELEMENT tpl:SysIf - - -- System settings tester container -->
2926    <!ATTLIST tpl:SysIf
2927    categories        (0|1)    #IMPLIED    -- test if categories are set in current context (value : 1) or not (value : 0)
2928    posts            (0|1)    #IMPLIED    -- test if posts are set in current context (value : 1) or not (value : 0)
2929    blog_lang            CDATA    #IMPLIED    -- tests if blog language is the one given in parameter
2930    current_tpl        CDATA    #IMPLIED    -- tests if current template is the one given in paramater
2931    current_mode        CDATA    #IMPLIED    -- tests if current URL mode is the one given in parameter
2932    has_tpl            CDATA     #IMPLIED  -- tests if a named template exists
2933    has_tag            CDATA     #IMPLIED  -- tests if a named template block or value exists
2934    blog_id            CDATA     #IMPLIED  -- tests if current blog ID is the one given in parameter
2935    comments_active    (0|1)    #IMPLIED    -- test if comments are enabled blog-wide
2936    pings_active        (0|1)    #IMPLIED    -- test if trackbacks are enabled blog-wide
2937    wiki_comments        (0|1)    #IMPLIED    -- test if wiki syntax is enabled for comments
2938    operator            (and|or)    #IMPLIED    -- combination of conditions, if more than 1 specifiec (default: and)
2939    >
2940     */
2941    public function SysIf($attr, $content)
2942    {
2943        $if      = new ArrayObject();
2944        $is_ping = null;
2945
2946        $operator = isset($attr['operator']) ? $this->getOperator($attr['operator']) : '&&';
2947
2948        if (isset($attr['categories'])) {
2949            $sign = (boolean) $attr['categories'] ? '!' : '=';
2950            $if[] = '$_ctx->categories ' . $sign . '== null';
2951        }
2952
2953        if (isset($attr['posts'])) {
2954            $sign = (boolean) $attr['posts'] ? '!' : '=';
2955            $if[] = '$_ctx->posts ' . $sign . '== null';
2956        }
2957
2958        if (isset($attr['blog_lang'])) {
2959            $sign = '=';
2960            if (substr($attr['blog_lang'], 0, 1) == '!') {
2961                $sign              = '!';
2962                $attr['blog_lang'] = substr($attr['blog_lang'], 1);
2963            }
2964            $if[] = "\$core->blog->settings->system->lang " . $sign . "= '" . addslashes($attr['blog_lang']) . "'";
2965        }
2966
2967        if (isset($attr['current_tpl'])) {
2968            $sign = '=';
2969            if (substr($attr['current_tpl'], 0, 1) == '!') {
2970                $sign                = '!';
2971                $attr['current_tpl'] = substr($attr['current_tpl'], 1);
2972            }
2973            $if[] = "\$_ctx->current_tpl " . $sign . "= '" . addslashes($attr['current_tpl']) . "'";
2974        }
2975
2976        if (isset($attr['current_mode'])) {
2977            $sign = '=';
2978            if (substr($attr['current_mode'], 0, 1) == '!') {
2979                $sign                 = '!';
2980                $attr['current_mode'] = substr($attr['current_mode'], 1);
2981            }
2982            $if[] = "\$core->url->type " . $sign . "= '" . addslashes($attr['current_mode']) . "'";
2983        }
2984
2985        if (isset($attr['has_tpl'])) {
2986            $sign = '';
2987            if (substr($attr['has_tpl'], 0, 1) == '!') {
2988                $sign            = '!';
2989                $attr['has_tpl'] = substr($attr['has_tpl'], 1);
2990            }
2991            $if[] = $sign . "\$core->tpl->getFilePath('" . addslashes($attr['has_tpl']) . "') !== false";
2992        }
2993
2994        if (isset($attr['has_tag'])) {
2995            $sign = 'true';
2996            if (substr($attr['has_tag'], 0, 1) == '!') {
2997                $sign            = 'false';
2998                $attr['has_tag'] = substr($attr['has_tag'], 1);
2999            }
3000            $if[] = "\$core->tpl->tagExists('" . addslashes($attr['has_tag']) . "') === " . $sign;
3001        }
3002
3003        if (isset($attr['blog_id'])) {
3004            $sign = '';
3005            if (substr($attr['blog_id'], 0, 1) == '!') {
3006                $sign            = '!';
3007                $attr['blog_id'] = substr($attr['blog_id'], 1);
3008            }
3009            $if[] = $sign . "(\$core->blog->id == '" . addslashes($attr['blog_id']) . "')";
3010        }
3011
3012        if (isset($attr['comments_active'])) {
3013            $sign = (boolean) $attr['comments_active'] ? '' : '!';
3014            $if[] = $sign . '$core->blog->settings->system->allow_comments';
3015        }
3016
3017        if (isset($attr['pings_active'])) {
3018            $sign = (boolean) $attr['pings_active'] ? '' : '!';
3019            $if[] = $sign . '$core->blog->settings->system->allow_trackbacks';
3020        }
3021
3022        if (isset($attr['wiki_comments'])) {
3023            $sign = (boolean) $attr['wiki_comments'] ? '' : '!';
3024            $if[] = $sign . '$core->blog->settings->system->wiki_comments';
3025        }
3026
3027        if (isset($attr['search_count']) &&
3028            preg_match('/^((=|!|&gt;|&lt;)=|(&gt;|&lt;))\s*[0-9]+$/', trim($attr['search_count']))) {
3029            $if[] = '(isset($_search_count) && $_search_count ' . html::decodeEntities($attr['search_count']) . ')';
3030        }
3031
3032        $this->core->callBehavior('tplIfConditions', 'SysIf', $attr, $content, $if);
3033
3034        if (count($if) != 0) {
3035            return '<?php if(' . implode(' ' . $operator . ' ', (array) $if) . ') : ?>' . $content . '<?php endif; ?>';
3036        } else {
3037            return $content;
3038        }
3039    }
3040
3041    /*dtd
3042    <!ELEMENT tpl:SysIfCommentPublished - - -- Container displayed if comment has been published -->
3043     */
3044    public function SysIfCommentPublished($attr, $content)
3045    {
3046        return
3047            '<?php if (!empty($_GET[\'pub\'])) : ?>' .
3048            $content .
3049            '<?php endif; ?>';
3050    }
3051
3052    /*dtd
3053    <!ELEMENT tpl:SysIfCommentPending - - -- Container displayed if comment is pending after submission -->
3054     */
3055    public function SysIfCommentPending($attr, $content)
3056    {
3057        return
3058            '<?php if (isset($_GET[\'pub\']) && $_GET[\'pub\'] == 0) : ?>' .
3059            $content .
3060            '<?php endif; ?>';
3061    }
3062
3063    /*dtd
3064    <!ELEMENT tpl:SysFeedSubtitle - O -- Feed subtitle -->
3065     */
3066    public function SysFeedSubtitle($attr)
3067    {
3068        $f = $this->getFilters($attr);
3069        return '<?php if ($_ctx->feed_subtitle !== null) { echo ' . sprintf($f, '$_ctx->feed_subtitle') . ';} ?>';
3070    }
3071
3072    /*dtd
3073    <!ELEMENT tpl:SysIfFormError - O -- Container displayed if an error has been detected after form submission -->
3074     */
3075    public function SysIfFormError($attr, $content)
3076    {
3077        return
3078            '<?php if ($_ctx->form_error !== null) : ?>' .
3079            $content .
3080            '<?php endif; ?>';
3081    }
3082
3083    /*dtd
3084    <!ELEMENT tpl:SysFormError - O -- Form error -->
3085     */
3086    public function SysFormError($attr)
3087    {
3088        return
3089            '<?php if ($_ctx->form_error !== null) { echo $_ctx->form_error; } ?>';
3090    }
3091
3092    public function SysPoweredBy($attr)
3093    {
3094        return
3095            '<?php printf(__("Powered by %s"),"<a href=\"http://dotclear.org/\">Dotclear</a>"); ?>';
3096    }
3097
3098    public function SysSearchString($attr)
3099    {
3100        $s = isset($attr['string']) ? $attr['string'] : '%1$s';
3101
3102        $f = $this->getFilters($attr);
3103        return '<?php if (isset($_search)) { echo sprintf(__(\'' . $s . '\'),' . sprintf($f, '$_search') . ',$_search_count);} ?>';
3104    }
3105
3106    public function SysSelfURI($attr)
3107    {
3108        $f = $this->getFilters($attr);
3109        return '<?php echo ' . sprintf($f, 'http::getSelfURI()') . '; ?>';
3110    }
3111
3112    /*dtd
3113    <!ELEMENT tpl:else - O -- else: statement -->
3114     */
3115    public function GenericElse($attr)
3116    {
3117        return '<?php else: ?>';
3118    }
3119}
Note: See TracBrowser for help on using the repository browser.

Sites map