xos_opal_Theme::generateCacheId()   A
last analyzed

Complexity

Conditions 6
Paths 5

Size

Total Lines 27
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 15
dl 0
loc 27
rs 9.2222
c 0
b 0
f 0
cc 6
nc 5
nop 2
1
<?php
2
/**
3
 * xos_opal_Theme component class file
4
 *
5
 * You may not change or alter any portion of this comment or credits
6
 * of supporting developers from this source code or any supporting source code
7
 * which is considered copyrighted (c) material of the original comment or credit authors.
8
 * This program is distributed in the hope that it will be useful,
9
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11
 *
12
 * @copyright       (c) 2000-2025 XOOPS Project (https://xoops.org)
13
 * @license             GNU GPL 2 (https://www.gnu.org/licenses/gpl-2.0.html)
14
 * @author              Skalpa Keo <[email protected]>
15
 * @author              Taiwen Jiang <[email protected]>
16
 * @since               2.3.0
17
 * @package             kernel
18
 * @subpackage          xos_opal_Theme
19
 */
20
21
use Xmf\Request;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Request. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
22
23
defined('XOOPS_ROOT_PATH') || exit('Restricted access');
24
25
/**
26
 * xos_opal_ThemeFactory
27
 *
28
 * @author     Skalpa Keo
29
 * @package    xos_opal
30
 * @subpackage xos_opal_Theme
31
 * @since      2.3.0
32
 */
33
class xos_opal_ThemeFactory
34
{
35
    public $xoBundleIdentifier = 'xos_opal_ThemeFactory';
36
    /**
37
     * Currently enabled themes (if empty, all the themes in themes/ are allowed)
38
     *
39
     * @var array
40
     */
41
    public $allowedThemes = [];
42
    /**
43
     * Default theme to instantiate if none specified
44
     *
45
     * @var string
46
     */
47
    public $defaultTheme = 'default';
48
    /**
49
     * If users are allowed to choose a custom theme
50
     *
51
     * @var bool
52
     */
53
    public $allowUserSelection = true;
54
55
    /**
56
     * Instantiate the specified theme
57
     * @param  array $options
58
     * @param  array $initArgs
59
     * @return null|xos_opal_Theme
60
     */
61
    public function createInstance($options = [], $initArgs = [])
62
    {
63
        // Grab the theme folder from request vars if present
64
        if (empty($options['folderName'])) {
65
            if (isset($_REQUEST['xoops_theme_select']) && ($req = $_REQUEST['xoops_theme_select']) && $this->isThemeAllowed($req)) {
66
                $options['folderName'] = $req;
67
                if (isset($_SESSION) && $this->allowUserSelection) {
68
                    $_SESSION[$this->xoBundleIdentifier]['defaultTheme'] = $req;
69
                }
70
            } elseif (isset($_SESSION[$this->xoBundleIdentifier]['defaultTheme'])) {
71
                $options['folderName'] = $_SESSION[$this->xoBundleIdentifier]['defaultTheme'];
72
            } elseif (empty($options['folderName']) || !$this->isThemeAllowed($options['folderName'])) {
73
                $options['folderName'] = $this->defaultTheme;
74
            }
75
            $GLOBALS['xoopsConfig']['theme_set'] = $options['folderName'];
76
        }
77
        $testPath = isset($options['themesPath'])
78
            ? XOOPS_ROOT_PATH . '/' . $options['themesPath'] . '/' . $options['folderName']
79
            : XOOPS_THEME_PATH . '/' . $options['folderName'];
80
        if (!(file_exists($testPath . '/theme.tpl')
81
            || file_exists($testPath . '/theme.html'))
82
        ) {
83
            trigger_error('Theme not found -- ' . $options['folderName']);
84
            $this->defaultTheme = 'default';
85
            $options['folderName'] = $this->defaultTheme;
86
            $GLOBALS['xoopsConfig']['theme_set'] = $options['folderName'];
87
        }
88
        $options['path'] = XOOPS_THEME_PATH . '/' . $options['folderName'];
89
        $inst            = new xos_opal_Theme();
90
        foreach ($options as $k => $v) {
91
            $inst->$k = $v;
92
        }
93
        $inst->xoInit();
94
95
        return $inst;
96
    }
97
98
    /**
99
     * Checks if the specified theme is enabled or not
100
     *
101
     * @param  string $name
102
     * @return bool
103
     */
104
    public function isThemeAllowed($name)
105
    {
106
        return (empty($this->allowedThemes) || in_array($name, $this->allowedThemes));
107
    }
108
}
109
110
/**
111
 * xos_opal_AdminThemeFactory
112
 *
113
 * @author     Andricq Nicolas (AKA MusS)
114
 * @author     trabis
115
 * @package    xos_opal
116
 * @subpackage xos_opal_Theme
117
 * @since      2.4.0
118
 */
119
class xos_opal_AdminThemeFactory extends xos_opal_ThemeFactory
120
{
121
    /**
122
     * @param array $options
123
     * @param array $initArgs
124
     *
125
     * @return null|xos_opal_Theme
126
     */
127
    public function &createInstance($options = [], $initArgs = [])
128
    {
129
        $options['plugins']      = [];
130
        $options['renderBanner'] = false;
131
        $inst                    = parent::createInstance($options, $initArgs);
132
        $inst->path              = XOOPS_ADMINTHEME_PATH . '/' . $inst->folderName;
133
        $inst->url               = XOOPS_ADMINTHEME_URL . '/' . $inst->folderName;
134
        $inst->template->assign(
135
            [
136
                'theme_path'  => $inst->path,
137
                'theme_tpl'   => $inst->path . '/xotpl',
138
                'theme_url'   => $inst->url,
139
                'theme_img'   => $inst->url . '/img',
140
                'theme_icons' => $inst->url . '/icons',
141
                'theme_css'   => $inst->url . '/css',
142
                'theme_js'    => $inst->url . '/js',
143
                'theme_lang'  => $inst->url . '/language',
144
            ],
145
        );
146
147
        return $inst;
148
    }
149
}
150
151
/**
152
 * Class xos_opal_Theme
153
 */
154
class xos_opal_Theme
155
{
156
    /**
157
     * Should we render banner? Not for redirect pages or admin side
158
     *
159
     * @var bool
160
     */
161
    public $renderBanner = true;
162
    /**
163
     * The name of this theme
164
     *
165
     * @var string
166
     */
167
    public $folderName = '';
168
    /**
169
     * Physical path of this theme folder
170
     *
171
     * @var string
172
     */
173
    public $path = '';
174
    public $url  = '';
175
176
    /**
177
     * Whether or not the theme engine should include the output generated by PHP
178
     *
179
     * @var string
180
     */
181
    public $bufferOutput = true;
182
    /**
183
     * Canvas-level template to use
184
     *
185
     * @var string
186
     */
187
    public $canvasTemplate = 'theme.tpl';
188
189
    /**
190
     * Theme folder path
191
     *
192
     * @var string
193
     */
194
    public $themesPath = 'themes';
195
196
    /**
197
     * Content-level template to use
198
     *
199
     * @var string
200
     */
201
    public $contentTemplate = '';
202
203
    public $contentCacheLifetime = 0;
204
    public $contentCacheId;
205
206
    /**
207
     * Text content to display right after the contentTemplate output
208
     *
209
     * @var string
210
     */
211
    public $content = '';
212
    /**
213
     * Page construction plug-ins to use
214
     *
215
     * @var array
216
     * @access public
217
     */
218
    public $plugins     = [
219
        'xos_logos_PageBuilder',
220
    ];
221
    public $renderCount = 0;
222
    /**
223
     * Pointer to the theme template engine
224
     *
225
     * @var XoopsTpl
226
     */
227
    public $template = false;
228
229
    /**
230
     * Array containing the document meta-information
231
     *
232
     * @var array
233
     */
234
    public $metas = [
235
        //'http' => array(
236
        //    'Content-Script-Type' => 'text/javascript' ,
237
        //    'Content-Style-Type' => 'text/css') ,
238
        'meta'   => [],
239
        'link'   => [],
240
        'script' => [],
241
    ];
242
243
    /**
244
     * Array of strings to be inserted in the head tag of HTML documents
245
     *
246
     * @var array
247
     */
248
    public $htmlHeadStrings = [];
249
    /**
250
     * Custom variables that will always be assigned to the template
251
     *
252
     * @var array
253
     */
254
    public $templateVars = [];
255
256
    /**
257
     * User extra information for cache id, like language, user groups
258
     *
259
     * @var boolean
260
     */
261
    public $use_extra_cache_id = true;
262
263
    /**
264
     * *#@-
265
     */
266
267
    /**
268
     * *#@+
269
     *
270
     * @tasktype 10 Initialization
271
     */
272
    /**
273
     * Initializes this theme
274
     *
275
     * Upon initialization, the theme creates its template engine and instantiates the
276
     * plug-ins from the specified {@link $plugins} list. If the theme is a 2.0 theme, that does not
277
     * display redirection messages, the HTTP redirections system is disabled to ensure users will
278
     * see the redirection screen.
279
     *
280
     * @param  array $options
281
     * @return bool
282
     */
283
    public function xoInit($options = [])
0 ignored issues
show
Unused Code introduced by
The parameter $options is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

283
    public function xoInit(/** @scrutinizer ignore-unused */ $options = [])

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
284
    {
285
        /** @var XoopsConfigHandler $configHandler */
286
        $configHandler = xoops_getHandler('config');
287
288
        $this->path                   = XOOPS_THEME_PATH . '/' . $this->folderName;
289
        $this->url                    = XOOPS_THEME_URL . '/' . $this->folderName;
290
        $this->template               = null;
291
        $this->template               = new XoopsTpl();
292
        $this->template->currentTheme = $this;
293
        $this->template->assignByRef('xoTheme', $this);
294
        $GLOBALS['xoTheme']  = $this;
295
        $GLOBALS['xoopsTpl'] = $this->template;
296
        $tempPath = str_replace('\\', '/', realpath(XOOPS_ROOT_PATH) . '/');
297
        $tempName = str_replace('\\', '/', realpath($_SERVER['SCRIPT_FILENAME']));
298
        $xoops_page = str_replace($tempPath, '', $tempName);
299
        if (strpos($xoops_page, 'modules') !== false) {
300
            $xoops_page = str_replace('modules/', '', $xoops_page);
301
        }
302
        $tempScriptname = str_replace('\\', '/', $_SERVER['SCRIPT_NAME']);
303
        $tempRequesturi = str_replace('\\', '/', Request::getString('REQUEST_URI', '', 'SERVER'));
304
        if (strlen($tempRequesturi) > strlen($tempScriptname)) {
305
            $xoops_modulepage =  $xoops_page . str_replace($tempScriptname, '', $tempRequesturi);
306
        } else {
307
            $xoops_modulepage =  '';
308
        }
309
        $xoops_page = str_replace('.php', '', $xoops_page);
310
        if (isset($GLOBALS['xoopsConfig']['startpage'])) {
311
            $xoops_startpage = $GLOBALS['xoopsConfig']['startpage'];
312
            if ($xoops_startpage == '--') {
313
                $xoops_startpage = 'system';
314
            }
315
        } else {
316
            $xoops_startpage = 'system';
317
        }
318
        // call the theme_autorun.php if the theme has one
319
        if (file_exists($this->path . "/theme_autorun.php")) {
320
            include_once($this->path . "/theme_autorun.php");
321
        }
322
323
        $searchConfig = $configHandler->getConfigsByCat(XOOPS_CONF_SEARCH);
324
        $xoops_search = (bool) (isset($searchConfig['enable_search']) && $searchConfig['enable_search'] === 1);
325
        $this->template->assign(
326
            [
327
                'xoops_theme'      => $GLOBALS['xoopsConfig']['theme_set'],
328
                'xoops_imageurl'   => XOOPS_THEME_URL . '/' . $GLOBALS['xoopsConfig']['theme_set'] . '/',
329
                'xoops_themecss'   => xoops_getcss($GLOBALS['xoopsConfig']['theme_set']),
330
                'xoops_requesturi' => htmlspecialchars($_SERVER['REQUEST_URI'], ENT_QUOTES | ENT_HTML5),
331
                'xoops_sitename'   => htmlspecialchars($GLOBALS['xoopsConfig']['sitename'], ENT_QUOTES | ENT_HTML5),
332
                'xoops_slogan'     => htmlspecialchars($GLOBALS['xoopsConfig']['slogan'], ENT_QUOTES | ENT_HTML5),
333
                'xoops_dirname'    => isset($GLOBALS['xoopsModule']) && is_object($GLOBALS['xoopsModule'])
334
                    ? $GLOBALS['xoopsModule']->getVar('dirname') : 'system',
335
                'xoops_page'       => $xoops_page,
336
                'xoops_startpage'  => $xoops_startpage,
337
                'xoops_modulepage' => $xoops_modulepage,
338
                'xoops_banner'     => ($GLOBALS['xoopsConfig']['banners'] && $this->renderBanner)
339
                    ? xoops_getbanner() : '&nbsp;',
340
                'xoops_pagetitle'  => isset($GLOBALS['xoopsModule']) && is_object($GLOBALS['xoopsModule'])
341
                    ? $GLOBALS['xoopsModule']->getVar('name')
342
                    : htmlspecialchars($GLOBALS['xoopsConfig']['slogan'], ENT_QUOTES | ENT_HTML5),
343
                'xoops_search'     => $xoops_search,
344
            ],
345
        );
346
        if (isset($GLOBALS['xoopsUser']) && is_object($GLOBALS['xoopsUser'])) {
347
            $this->template->assign(
348
                [
349
                    'xoops_isuser'     => true,
350
                    'xoops_avatar'     => XOOPS_UPLOAD_URL . '/' . $GLOBALS['xoopsUser']->getVar('user_avatar'),
351
                    'xoops_userid'     => $GLOBALS['xoopsUser']->getVar('uid'),
352
                    'xoops_uname'      => $GLOBALS['xoopsUser']->getVar('uname'),
353
                    'xoops_name'       => $GLOBALS['xoopsUser']->getVar('name'),
354
                    'xoops_isadmin'    => $GLOBALS['xoopsUserIsAdmin'],
355
                    'xoops_usergroups' => $GLOBALS['xoopsUser']->getGroups(),
356
                ],
357
            );
358
        } else {
359
            $this->template->assign(
360
                [
361
                    'xoops_isuser'     => false,
362
                    'xoops_isadmin'    => false,
363
                    'xoops_usergroups' => [XOOPS_GROUP_ANONYMOUS],
364
                ],
365
            );
366
        }
367
368
        // Meta tags
369
        $criteria       = new CriteriaCompo(new Criteria('conf_modid', 0));
370
        $criteria->add(new Criteria('conf_catid', XOOPS_CONF_METAFOOTER));
371
        $config = $configHandler->getConfigs($criteria, true);
372
        foreach (array_keys($config) as $i) {
373
            $name = $config[$i]->getVar('conf_name', 'n');
374
            $value = $config[$i]->getVar('conf_value', 'n');
375
            // limited substitutions for {X_SITEURL} and {X_YEAR}
376
            if ($name === 'footer' || $name === 'meta_copyright') {
377
                $value = str_replace('{X_SITEURL}', XOOPS_URL . '/', $value);
378
                $value = str_replace('{X_YEAR}', date('Y', time()), $value);
379
            }
380
            if (substr($name, 0, 5) === 'meta_') {
381
                $this->addMeta('meta', substr($name, 5), $value);
382
            } else {
383
                // prefix each tag with 'xoops_'
384
                $this->template->assign("xoops_$name", $value);
385
            }
386
        }
387
        // Load global javascript
388
        $this->addScript('include/xoops.js');
389
        $this->loadLocalization();
390
391
        if ($this->bufferOutput) {
392
            ob_start();
393
        }
394
        // Instantiate and initialize all the theme plugins
395
        foreach ($this->plugins as $k => $bundleId) {
396
            if (!is_object($bundleId)) {
397
                $this->plugins[$bundleId]        = null;
398
                $this->plugins[$bundleId]        = new $bundleId();
399
                $this->plugins[$bundleId]->theme = & $this;
400
                $this->plugins[$bundleId]->xoInit();
401
                unset($this->plugins[$k]);
402
            }
403
        }
404
405
        return true;
406
    }
407
408
    /**
409
     * Generate cache id based on extra information of language and user groups
410
     *
411
     * User groups other than anonymous should be detected to avoid disclosing group sensitive contents
412
     *
413
     * @param  string $cache_id    raw cache id
414
     * @param  string $extraString extra string
415
     * @return string complete cache id
416
     */
417
    public function generateCacheId($cache_id, $extraString = '')
418
    {
419
        static $extra_string;
420
        if (!$this->use_extra_cache_id) {
421
            return $cache_id;
422
        }
423
424
        if (empty($extraString)) {
425
            if (empty($extra_string)) {
426
                // Generate language section
427
                $extra_string = $GLOBALS['xoopsConfig']['language'];
428
                // Generate group section
429
                if (!isset($GLOBALS['xoopsUser']) || !is_object($GLOBALS['xoopsUser'])) {
430
                    $extra_string .= '-' . XOOPS_GROUP_ANONYMOUS;
431
                } else {
432
                    $groups = $GLOBALS['xoopsUser']->getGroups();
433
                    sort($groups);
434
                    // Generate group string for non-anonymous groups,
435
                    // XOOPS_DB_PASS and XOOPS_DB_NAME (before we find better variables) are used to protect group sensitive contents
436
                    $extra_string .= '-' . substr(md5(implode('-', $groups)), 0, 8) . '-' . substr(md5(XOOPS_DB_PASS . XOOPS_DB_NAME . XOOPS_DB_USER), 0, 8);
437
                }
438
            }
439
            $extraString = $extra_string;
440
        }
441
        $cache_id .= '-' . $extraString;
442
443
        return $cache_id;
444
    }
445
446
    /**
447
     * xos_opal_Theme::checkCache()
448
     *
449
     * @return bool
450
     */
451
    public function checkCache()
452
    {
453
        if ($_SERVER['REQUEST_METHOD'] !== 'POST' && $this->contentCacheLifetime) {
454
            $template                       = $this->contentTemplate ?: 'db:system_dummy.tpl';
455
            $this->template->caching        = 2;
456
            $this->template->cache_lifetime = $this->contentCacheLifetime;
457
            $uri                            = str_replace(XOOPS_URL, '', $_SERVER['REQUEST_URI']);
458
459
            if (session_id() && strpos($uri, session_id())) {
460
                $uri = preg_replace("/([\?&])(" . session_id() . "$|" . session_id() . '&)/', "\\1", $uri);
461
            }
462
            $this->contentCacheId = $this->generateCacheId('page_' . substr(md5($uri), 0, 8));
463
            if ($this->template->isCached($template, $this->contentCacheId)) {
464
                $xoopsLogger = XoopsLogger::getInstance();
465
                $xoopsLogger->addExtra($template, sprintf('Cached (regenerates every %d seconds)', $this->contentCacheLifetime));
466
                $this->render(null, null, $template);
467
468
                return true;
469
            }
470
        }
471
472
        return false;
473
    }
474
475
    /**
476
     * Render the page
477
     *
478
     * The theme engine builds pages from 2 templates: canvas and content.
479
     *
480
     * A module can call this method directly and specify what templates the theme engine must use.
481
     * If render() hasn't been called before, the theme defaults will be used for the canvas and
482
     * page template (and xoopsOption['template_main'] for the content).
483
     *
484
     * @param string $canvasTpl  The canvas template, if different from the theme default
485
     * @param string $pageTpl    The page template, if different from the theme default (unsupported, 2.3+ only)
486
     * @param string $contentTpl The content template
487
     * @param array  $vars       Template variables to send to the template engine
488
     *
489
     * @return bool
490
     */
491
    public function render($canvasTpl = null, $pageTpl = null, $contentTpl = null, $vars = [])
492
    {
493
        if ($this->renderCount) {
494
            return false;
495
        }
496
        $xoopsLogger = XoopsLogger::getInstance();
497
        $xoopsLogger->startTime('Page rendering');
498
499
        xoops_load('xoopscache');
500
        $cache = XoopsCache::getInstance();
501
502
        //Get meta information for cached pages
503
        if ($this->contentCacheLifetime && $this->contentCacheId && $content = $cache->read($this->contentCacheId)) {
504
            //we need to merge metas set by blocks, with the module cached meta
505
            $this->htmlHeadStrings = array_merge($this->htmlHeadStrings, $content['htmlHeadStrings']);
506
            foreach ($content['metas'] as $type => $value) {
507
                $this->metas[$type] = array_merge($this->metas[$type], $content['metas'][$type]);
508
            }
509
            $GLOBALS['xoopsOption']['xoops_pagetitle']     = $content['xoops_pagetitle'];
510
            $GLOBALS['xoopsOption']['xoops_module_header'] = $content['header'];
511
        }
512
        /** if cache was not found, define $content[] */
513
        if (!isset($content) || false === $content) {
514
            $content = [];
515
        }
516
        if (!empty($GLOBALS['xoopsOption']['xoops_pagetitle'])) {
517
            $this->template->assign('xoops_pagetitle', $GLOBALS['xoopsOption']['xoops_pagetitle']);
518
        }
519
        $header = empty($GLOBALS['xoopsOption']['xoops_module_header']) ? $this->template->getTemplateVars('xoops_module_header') : $GLOBALS['xoopsOption']['xoops_module_header'];
520
521
        //save meta information of cached pages
522
        if ($this->contentCacheLifetime && $this->contentCacheId && !$contentTpl) {
523
            $content['htmlHeadStrings'] = $this->htmlHeadStrings;
524
            $content['metas']           = $this->metas;
525
            $content['xoops_pagetitle'] = $this->template->getTemplateVars('xoops_pagetitle');
526
            $content['header']          = $header;
527
            $cache->write($this->contentCacheId, $content);
528
        }
529
530
        //  @internal : Lame fix to ensure the metas specified in the xoops config page don't appear twice
531
        $old = [
532
            'robots',
533
            'keywords',
534
            'description',
535
            'rating',
536
            'author',
537
            'copyright',
538
        ];
539
        foreach ($this->metas['meta'] as $name => $value) {
540
            if (in_array($name, $old)) {
541
                $this->template->assign("xoops_meta_$name", htmlspecialchars($value, ENT_QUOTES | ENT_HTML5));
542
                unset($this->metas['meta'][$name]);
543
            }
544
        }
545
546
        // We assume no overlap between $GLOBALS['xoopsOption']['xoops_module_header'] and $this->template->getTemplateVars( 'xoops_module_header' ) ?
547
        $this->template->assign('xoops_module_header', $this->renderMetas(null, true) . "\n" . $header);
0 ignored issues
show
Bug introduced by
Are you sure $header of type array|mixed|null can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

547
        $this->template->assign('xoops_module_header', $this->renderMetas(null, true) . "\n" . /** @scrutinizer ignore-type */ $header);
Loading history...
548
549
        if ($canvasTpl) {
550
            $this->canvasTemplate = $canvasTpl;
551
        }
552
        if ($contentTpl) {
553
            $this->contentTemplate = $contentTpl;
554
        }
555
        if (!empty($vars)) {
556
            $this->template->assign($vars);
557
        }
558
        if ($this->contentTemplate) {
559
            $this->content = $this->template->fetch($this->contentTemplate, $this->contentCacheId);
560
        }
561
        if ($this->bufferOutput) {
562
            $this->content .= ob_get_contents();
563
            ob_end_clean();
564
        }
565
566
        $this->template->assignByRef('xoops_contents', $this->content);
567
568
        // Do not cache the main (theme.html) template output
569
        $this->template->caching = 0;
570
        if (file_exists($this->path . '/' . $this->canvasTemplate)) {
571
            $this->template->display($this->path . '/' . $this->canvasTemplate);
572
        } else {
573
            $this->template->display($this->path . '/theme.html');
574
        }
575
        $this->renderCount++;
576
        $xoopsLogger->stopTime('Page rendering');
577
578
        return true;
579
    }
580
581
    /**
582
     * Load localization information
583
     *
584
     * Folder structure for localization:
585
     * <ul>themes/themefolder/english
586
     *     <li>main.php - language definitions</li>
587
     *     <li>style.css - localization stylesheet</li>
588
     *     <li>script.js - localization script</li>
589
     * </ul>
590
     * @param  string $type
591
     * @return bool
592
     */
593
    public function loadLocalization($type = 'main')
594
    {
595
        $language = $GLOBALS['xoopsConfig']['language'];
596
        // Load global localization stylesheet if available
597
        if (file_exists($GLOBALS['xoops']->path('language/' . $language . '/style.css'))) {
598
            $this->addStylesheet($GLOBALS['xoops']->url('language/' . $language . '/style.css'));
599
        }
600
        $this->addLanguage($type, $language);
601
        // Load theme localization stylesheet and scripts if available
602
        if (file_exists($this->path . '/language/' . $language . '/script.js')) {
603
            $this->addScript($this->url . '/language/' . $language . '/script.js');
604
        }
605
        if (file_exists($this->path . '/language/' . $language . '/style.css')) {
606
            $this->addStylesheet($this->url . '/language/' . $language . '/style.css');
607
        }
608
609
        return true;
610
    }
611
612
    /**
613
     * Load theme specific language constants
614
     *
615
     * @param string $type     language type, like 'main', 'admin'; Needs to be declared in theme xo-info.php
616
     * @param string $language specific language
617
     *
618
     * @return bool|mixed
619
     */
620
    public function addLanguage($type = 'main', $language = null)
621
    {
622
        $language ??= $GLOBALS['xoopsConfig']['language'];
623
        if (!file_exists($fileinc = $this->path . "/language/{$language}/{$type}.php")) {
624
            if (!file_exists($fileinc = $this->path . "/language/english/{$type}.php")) {
625
                return false;
626
            }
627
        }
628
        $ret = include_once $fileinc;
629
630
        return $ret;
631
    }
632
633
    /**
634
     * *#@+
635
     *
636
     * @tasktype 20 Manipulating page meta-information
637
     */
638
    /**
639
     * Adds script code to the document head
640
     *
641
     * This methods allows the insertion of an external script file (if $src is provided), or
642
     * of a script snippet. The file URI is parsed to take benefit of the theme resource
643
     * overloading system.
644
     *
645
     * The $attributes parameter allows you to specify the attributes that will be added to the
646
     * inserted <script> tag. If unspecified, the <var>type</var> attribute value will default to
647
     * 'text/javascript'.
648
     *
649
     * <code>
650
     * // Add an external script using a physical path
651
     * $theme->addScript( 'www/script.js', null, '' );
652
     * $theme->addScript( 'modules/newbb/script.js', null, '' );
653
     * // Specify attributes for the <script> tag
654
     * $theme->addScript( 'mod_xoops_SiteManager#common.js', array( 'type' => 'application/x-javascript' ), '', 'mod_xoops_Sitemanager' );
655
     * // Insert a code snippet
656
     * $theme->addScript( null, array( 'type' => 'application/x-javascript' ), 'window.open("Hello world");', 'hello' );
657
     * </code>
658
     *
659
     * @param  string $src        path to an external script file
660
     * @param  array  $attributes hash of attributes to add to the <script> tag
661
     * @param  string $content    Code snippet to output within the <script> tag
662
     * @param  string $name       Element Name in array scripts are stored in.
663
     * @return void
664
     */
665
    public function addScript($src = '', $attributes = [], $content = '', $name = '')
666
    {
667
        if (empty($attributes)) {
668
            $attributes = [];
669
        }
670
        if (!empty($src)) {
671
            $src               = $GLOBALS['xoops']->url($this->resourcePath($src));
672
            $attributes['src'] = $src;
673
        }
674
        if (!empty($content)) {
675
            $attributes['_'] = $content;
676
        }
677
        if (!isset($attributes['type'])) {
678
            $attributes['type'] = 'text/javascript';
679
        }
680
        if (empty($name)) {
681
            $name = md5(serialize($attributes));
682
        }
683
        $this->addMeta('script', $name, $attributes);
0 ignored issues
show
Bug introduced by
$attributes of type array is incompatible with the type string expected by parameter $value of xos_opal_Theme::addMeta(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

683
        $this->addMeta('script', $name, /** @scrutinizer ignore-type */ $attributes);
Loading history...
684
    }
685
686
    /**
687
     * Add StyleSheet or CSS code to the document head
688
     *
689
     * @param  string $src        path to .css file
690
     * @param  array  $attributes name => value paired array of attributes such as title
691
     * @param  string $content    CSS code to output between the <style> tags (in case $src is empty)
692
     * @param  string $name       Element Name in array stylesheets are stored in.
693
     * @return void
694
     */
695
    public function addStylesheet($src = '', $attributes = [], $content = '', $name = '')
696
    {
697
        if (empty($attributes)) {
698
            $attributes = [];
699
        }
700
        if (!empty($src)) {
701
            $src                = $GLOBALS['xoops']->url($this->resourcePath($src));
702
            $attributes['href'] = $src;
703
        }
704
        if (!isset($attributes['type'])) {
705
            $attributes['type'] = 'text/css';
706
        }
707
        if (!empty($content)) {
708
            $attributes['_'] = $content;
709
        }
710
        if (empty($name)) {
711
            $name = md5(serialize($attributes));
712
        }
713
        $this->addMeta('stylesheet', $name, $attributes);
0 ignored issues
show
Bug introduced by
$attributes of type array is incompatible with the type string expected by parameter $value of xos_opal_Theme::addMeta(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

713
        $this->addMeta('stylesheet', $name, /** @scrutinizer ignore-type */ $attributes);
Loading history...
714
    }
715
716
    /**
717
     * Add a <link> to the header
718
     *
719
     * @param string $rel        Relationship from the current doc to the anchored one
720
     * @param string $href       URI of the anchored document
721
     * @param array  $attributes Additional attributes to add to the <link> element
722
     * @param string $name       Element Name in array links are stored in.
723
     */
724
    public function addLink($rel, $href = '', $attributes = [], $name = '')
725
    {
726
        if (empty($attributes)) {
727
            $attributes = [];
728
        }
729
        if (!empty($href)) {
730
            $attributes['href'] = $href;
731
        }
732
        $attributes['rel'] = $rel;
733
        if (empty($name)) {
734
            $name = md5(serialize($attributes));
735
        }
736
        $this->addMeta('link', $name, $attributes);
0 ignored issues
show
Bug introduced by
$attributes of type array is incompatible with the type string expected by parameter $value of xos_opal_Theme::addMeta(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

736
        $this->addMeta('link', $name, /** @scrutinizer ignore-type */ $attributes);
Loading history...
737
    }
738
739
    /**
740
     * Set a meta http-equiv value
741
     * @param         $name
742
     * @param  null   $value
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $value is correct as it would always require null to be passed?
Loading history...
743
     * @return string
744
     */
745
    public function addHttpMeta($name, $value = null)
746
    {
747
        if (isset($value)) {
748
            return $this->addMeta('http', $name, $value);
749
        }
750
        unset($this->metas['http'][$name]);
751
        return null;
752
    }
753
754
    /**
755
     * Change output page meta-information
756
     * @param  string $type
757
     * @param  string $name
758
     * @param  string $value
759
     * @return string
760
     */
761
    public function addMeta($type = 'meta', $name = '', $value = '')
762
    {
763
        if (!isset($this->metas[$type])) {
764
            $this->metas[$type] = [];
765
        }
766
        if (!empty($name)) {
767
            $this->metas[$type][$name] = $value;
768
        } else {
769
            $this->metas[$type][md5(serialize([$value]))] = $value;
770
        }
771
772
        return $value;
773
    }
774
775
    /**
776
     * xos_opal_Theme::headContent()
777
     *
778
     * @param mixed $params
779
     * @param mixed $content
780
     * @param mixed $smarty
781
     * @param mixed $repeat
782
     *
783
     * @return void
784
     */
785
    public function headContent($params, $content, &$smarty, $repeat)
0 ignored issues
show
Unused Code introduced by
The parameter $smarty is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

785
    public function headContent($params, $content, /** @scrutinizer ignore-unused */ &$smarty, $repeat)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $params is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

785
    public function headContent(/** @scrutinizer ignore-unused */ $params, $content, &$smarty, $repeat)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
786
    {
787
        if (!$repeat) {
788
            $this->htmlHeadStrings[] = $content;
789
        }
790
    }
791
792
    /**
793
     * xos_opal_Theme::renderMetas()
794
     *
795
     * @param  mixed $type
796
     * @param  mixed $return
797
     * @return bool|string
798
     */
799
    public function renderMetas($type = null, $return = false)
800
    {
801
        $str = '';
802
        if (!isset($type)) {
803
            foreach (array_keys($this->metas) as $type) {
804
                $str .= $this->renderMetas($type, true);
805
            }
806
            $str .= implode("\n", $this->htmlHeadStrings);
807
        } else {
808
            switch ($type) {
809
                case 'script':
810
                    foreach ($this->metas[$type] as $attrs) {
811
                        $str .= '<script' . $this->renderAttributes($attrs) . '>';
812
                        if (isset($attrs['_'])) {
813
                            $str .= "\n//<![CDATA[\n" . $attrs['_'] . "\n//]]>";
814
                        }
815
                        $str .= "</script>\n";
816
                    }
817
                    break;
818
                case 'link':
819
                    foreach ($this->metas[$type] as $attrs) {
820
                        $rel = $attrs['rel'];
821
                        unset($attrs['rel']);
822
                        $str .= '<link rel="' . $rel . '"' . $this->renderAttributes($attrs) . " />\n";
823
                    }
824
                    break;
825
                case 'stylesheet':
826
                    foreach ($this->metas[$type] as $attrs) {
827
                        if (isset($attrs['_'])) {
828
                            $str .= '<style' . $this->renderAttributes($attrs) . ">\n/* <![CDATA[ */\n" . ($attrs['_'] ?? '') . "\n/* //]]> */\n</style>";
829
                        } else {
830
                            $str .= '<link rel="stylesheet"' . $this->renderAttributes($attrs) . " />\n";
831
                        }
832
                    }
833
                    break;
834
                case 'http':
835
                    foreach ($this->metas[$type] as $name => $content) {
836
                        $str .= '<meta http-equiv="' . htmlspecialchars($name, ENT_QUOTES | ENT_HTML5) . '" content="' . htmlspecialchars($content, ENT_QUOTES | ENT_HTML5) . "\" />\n";
837
                    }
838
                    break;
839
                default:
840
                    foreach ($this->metas[$type] as $name => $content) {
841
                        $str .= '<meta name="' . htmlspecialchars($name, ENT_QUOTES | ENT_HTML5) . '" content="' . htmlspecialchars($content, ENT_QUOTES | ENT_HTML5) . "\" />\n";
842
                    }
843
                    break;
844
            }
845
        }
846
        if ($return) {
847
            return $str;
848
        }
849
        echo $str;
850
851
        return true;
852
    }
853
854
    /**
855
     * Generates a unique element ID
856
     *
857
     * @param  string $tagName
858
     * @return string
859
     */
860
    public function genElementId($tagName = 'xos')
861
    {
862
        static $cache = [];
863
        if (!isset($cache[$tagName])) {
864
            $cache[$tagName] = 1;
865
        }
866
867
        return $tagName . '-' . $cache[$tagName]++;
868
    }
869
870
    /**
871
     * Transform an attribute collection to an XML string
872
     *
873
     * @param  array $coll
874
     * @return string
875
     */
876
    public function renderAttributes($coll)
877
    {
878
        $str = '';
879
        foreach ($coll as $name => $val) {
880
            if ($name !== '_') {
881
                $str .= ' ' . $name . '="' . htmlspecialchars($val, ENT_QUOTES | ENT_HTML5) . '"';
882
            }
883
        }
884
885
        return $str;
886
    }
887
888
    /**
889
     * Return a themeable file resource path
890
     *
891
     * @param  string $path
892
     * @return string
893
     */
894
    public function resourcePath($path)
895
    {
896
        $path = (string) $path;
897
        if (substr($path, 0, 1) === '/') {
898
            $path = substr($path, 1);
899
        }
900
901
        if (file_exists(XOOPS_ROOT_PATH . "/{$this->themesPath}/{$this->folderName}/{$path}")) {
902
            return "{$this->themesPath}/{$this->folderName}/{$path}";
903
        }
904
905
        if (file_exists(XOOPS_ROOT_PATH . "/themes/{$this->folderName}/{$path}")) {
906
            return "themes/{$this->folderName}/{$path}";
907
        }
908
909
        return $path;
910
    }
911
}
912