Design::softwareComment()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 46 and the first side effect is on line 14.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
/**
3
 * @title            Design Class
4
 * @desc             File containing HTML for display management.
5
 *
6
 * @author           Pierre-Henry Soria <[email protected]>
7
 * @copyright        (c) 2012-2019, Pierre-Henry Soria. All Rights Reserved.
8
 * @license          CC-BY License - http://creativecommons.org/licenses/by/3.0/
9
 * @package          PH7 / Framework / Layout / Html
10
 */
11
12
namespace PH7\Framework\Layout\Html;
13
14
defined('PH7') or exit('Restricted access');
15
16
use PH7\AdminCore;
17
use PH7\AffiliateCore;
18
use PH7\DbTableName;
19
use PH7\Framework\Benchmark\Benchmark;
20
use PH7\Framework\Cache\Cache;
21
use PH7\Framework\Core\Kernel;
22
use PH7\Framework\File\File;
23
use PH7\Framework\Geo\Ip\Geo;
24
use PH7\Framework\Geo\Misc\Country;
25
use PH7\Framework\Http\Http;
26
use PH7\Framework\Ip\Ip;
27
use PH7\Framework\Module\Various as SysMod;
28
use PH7\Framework\Mvc\Model\DbConfig;
29
use PH7\Framework\Mvc\Model\Engine\Db;
30
use PH7\Framework\Mvc\Request\Http as HttpRequest;
31
use PH7\Framework\Mvc\Router\Uri;
32
use PH7\Framework\Navigation\Browser;
33
use PH7\Framework\Navigation\Page;
34
use PH7\Framework\Navigation\Pagination;
35
use PH7\Framework\Parse\Url as UrlParser;
36
use PH7\Framework\Registry\Registry;
37
use PH7\Framework\Security\Validate\Validate;
38
use PH7\Framework\Session\Session;
39
use PH7\Framework\Str\Str;
40
use PH7\Framework\Translate\Lang;
41
use PH7\Framework\Url\Url;
42
use PH7\GenderTypeUserCore;
43
use PH7\UserCore;
44
use PH7\UserCoreModel;
45
46
class Design
47
{
48
    const CACHE_GROUP = 'str/design';
49
    const CACHE_AVATAR_GROUP = 'str/design/avatar/'; // We put a slash for after creating a directory for each username
50
    const CACHE_AVATAR_LIFETIME = 3600;
51
52
    const NONE_FLAG_FILENAME = 'none.gif';
53
    const FLAG_ICON_EXT = '.gif';
54
    const AVATAR_IMG_EXT = '.svg';
55
56
    const SUCCESS_TYPE = 'success';
57
    const ERROR_TYPE = 'error';
58
    const WARNING_TYPE = 'warning';
59
    const INFO_TYPE = 'info';
60
61
    const MESSAGE_TYPES = [
62
        self::SUCCESS_TYPE,
63
        self::ERROR_TYPE,
64
        self::WARNING_TYPE,
65
        self::INFO_TYPE
66
    ];
67
68
    const FLASH_MSG = 'flash_msg';
69
    const FLASH_TYPE = 'flash_type';
70
71
    const DEFAULT_REDIRECTION_DELAY = 3; // In secs
72
    const MAX_MESSAGE_LENGTH_SHOWN = 300;
73
    const MAX_IP_LENGTH_SHOWN = 15;
74
75
    /** @var bool */
76
    protected $bIsDiv = false;
77
78
    /** @var Str */
79
    protected $oStr;
80
81
    /** @var Session */
82
    protected $oSession;
83
84
    /** @var HttpRequest */
85
    protected $oHttpRequest;
86
87
    /** @var array */
88
    protected $aCssDir = [];
89
90
    /** @var array */
91
    protected $aCssFiles = [];
92
93
    /** @var array */
94
    protected $aCssMedia = [];
95
96
    /** @var array */
97
    protected $aJsDir = [];
98
99
    /** @var array */
100
    protected $aJsFiles = [];
101
102
    /** @var array */
103
    protected $aMessages = [];
104
105
    /** @var array */
106
    protected $aErrors = [];
107
108
    public function __construct()
109
    {
110
        /** Instance objects for the class **/
111
        $this->oStr = new Str;
112
        $this->oSession = new Session;
113
        $this->oHttpRequest = new HttpRequest;
114
    }
115
116
    public function langList()
117
    {
118
        $sCurrentPage = Page::cleanDynamicUrl('l');
119
        //$aLangs = (new File)->getDirList(Registry::getInstance()->path_module_lang);
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
120
        $aLangs = (new File)->getDirList(PH7_PATH_APP_LANG);
121
122
        foreach ($aLangs as $sLang) {
123
            if ($sLang === PH7_LANG_NAME) {
124
                // Skip the current lang
125
                continue;
126
            }
127
128
            // Get the first|last two-letter country code
129
            $sAbbrLang = Lang::getIsoCode($sLang, Lang::FIRST_ISO_CODE);
130
            $sFlagCountryCode = Lang::getIsoCode($sLang, Lang::LAST_ISO_CODE);
131
132
            echo '<a href="', $sCurrentPage, $sLang, '" hreflang="', $sAbbrLang, '"><img src="', PH7_URL_STATIC, PH7_IMG, 'flag/s/', $sFlagCountryCode, self::FLAG_ICON_EXT, '" alt="', t($sAbbrLang), '" title="', t($sAbbrLang), '" /></a>&nbsp;';
133
        }
134
135
        unset($aLangs);
136
    }
137
138
    /**
139
     * For better SEO optimization for multilingual sites. Ref: https://support.google.com/webmasters/answer/189077
140
     *
141
     * @return void
142
     */
143
    public function regionalUrls()
144
    {
145
        $sCurrentPage = Page::cleanDynamicUrl('l');
146
        $aLangs = (new File)->getDirList(PH7_PATH_APP_LANG);
147
148
        echo '<link rel="alternate" hreflang="x-default" href="', PH7_URL_ROOT, '">'; // For pages that aren't specifically targeted
149
        foreach ($aLangs as $sLang) {
150
            // Get only the two-letter country code
151
            $sAbbrLang = Lang::getIsoCode($sLang);
152
            echo '<link rel="alternate" hreflang="', $sAbbrLang, '" href="', $sCurrentPage, $sLang, '" />';
153
        }
154
155
        unset($aLangs, $sCurrentPage);
156
    }
157
158
    /**
159
     * Set an information message.
160
     *
161
     * @param string $sMsg
162
     *
163
     * @return void
164
     */
165
    public function setMessage($sMsg)
166
    {
167
        $this->aMessages[] = $sMsg;
168
    }
169
170
    /**
171
     * Display the information message.
172
     *
173
     * @return void
174
     */
175
    public function message()
176
    {
177
        if ($this->oHttpRequest->getExists('msg')) {
178
            $this->aMessages[] = substr($this->oHttpRequest->get('msg'), 0, self::MAX_MESSAGE_LENGTH_SHOWN);
179
        }
180
181
        $iMsgNum = count($this->aMessages);
182
        /** Check if there are any messages in $aMessages array **/
183
        if ($iMsgNum > 0) {
184
            $this->staticFiles('js', PH7_STATIC . PH7_JS, 'jquery/apprise.js');
185
186
            echo '<script>$(function(){Apprise(\'';
187
188
            if ($iMsgNum > 1) {
189
                echo '<strong>', t('You have'), ' <em>', $iMsgNum, '</em> ', nt('message:', 'messages:', $iMsgNum), '</strong><br />';
190
            }
191
192
            for ($iKey = 0; $iKey < $iMsgNum; $iKey++) {
193
                echo $this->oStr->upperFirst(str_replace('-', ' ', $this->aMessages[$iKey])), '<br />';
194
            }
195
196
            echo '\')});</script>';
197
        }
198
199
        unset($this->aMessages);
200
    }
201
202
    /**
203
     * Set an error message.
204
     *
205
     * @param string $sErr
206
     *
207
     * @return void
208
     */
209
    public function setError($sErr)
210
    {
211
        $this->aErrors[] = $sErr;
212
    }
213
214
    /**
215
     * Display the error message.
216
     *
217
     * @return void
218
     */
219
    public function error()
220
    {
221
        if ($this->oHttpRequest->getExists('err')) {
222
            $this->aErrors[] = substr($this->oHttpRequest->get('err'), 0, self::MAX_MESSAGE_LENGTH_SHOWN);
223
        }
224
225
        $iErrNum = count($this->aErrors);
226
        /** Check if there are any errors in $aErrors array **/
227
        if ($iErrNum > 0) {
228
            $this->staticFiles('js', PH7_STATIC . PH7_JS, 'jquery/apprise.js');
229
230
            echo '<script>$(function(){Apprise(\'';
231
            echo '<strong>', t('You have'), ' <em>', $iErrNum, '</em> ', nt('error:', 'errors:', $iErrNum), '</strong><br />';
232
233
            for ($iKey = 0; $iKey < $iErrNum; $iKey++) {
234
                echo $this->oStr->upperFirst(str_replace('-', ' ', $this->aErrors[$iKey])), '<br />';
235
            }
236
237
            echo '\')});</script>';
238
        }
239
240
        unset($this->aErrors);
241
    }
242
243
    /**
244
     * Redirect Page using Refresh with Header.
245
     *
246
     * @param string $sUrl If NULL, the URL will be the current page. Default NULL
247
     * @param string $sMsg , Optional, display a message after redirect of the page.
248
     * @param string $sType Type of message: "success", "info", "warning" or "error". Default: "success".
249
     * @param int $iTime Optional, a time. Default: "3" seconds.
250
     *
251
     * @return void
252
     */
253
    public function setRedirect($sUrl = null, $sMsg = null, $sType = self::SUCCESS_TYPE, $iTime = self::DEFAULT_REDIRECTION_DELAY)
254
    {
255
        if ($sMsg !== null) {
256
            $this->setFlashMsg($sMsg, $sType);
257
        }
258
259
        $sUrl = $sUrl !== null ? $sUrl : $this->oHttpRequest->currentUrl();
260
261
        header('Refresh: ' . $iTime . '; URL=' . $this->oHttpRequest->pH7Url($sUrl));
262
    }
263
264
    /**
265
     * Get stats from the benchmark.
266
     *
267
     * @return void HTML output.
268
     */
269
    public function stat()
270
    {
271
        $iCountQueries = Db::queryCount();
272
        $sRequest = nt('Query', 'Queries', $iCountQueries);
273
274
        $sMicrotime = microtime(true) - Registry::getInstance()->start_time;
0 ignored issues
show
Documentation introduced by
The property start_time does not exist on object<PH7\Framework\Registry\Registry>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
275
        $sTime = Benchmark::readableElapsedTime($sMicrotime);
276
        $iMemory = Benchmark::readableSize(memory_get_usage(true));
277
278
        echo t('Queries time %0% | %1% %2% | Generated in %3% | Memory allocated %4%', Db::time(), $iCountQueries, $sRequest, $sTime, $iMemory);
279
    }
280
281
    /**
282
     * Display accurate homepage URL.
283
     *
284
     * @return void The homepage URL output.
285
     */
286
    public function homePageUrl()
287
    {
288
        if (UserCore::auth()) {
289
            if (SysMod::isEnabled('user-dashboard')) {
290
                $this->url('user-dashboard', 'main', 'index');
291
            } else {
292
                $this->url('user', 'browse', 'index');
293
            }
294
        } elseif (AdminCore::auth()) {
295
            $this->url(PH7_ADMIN_MOD, 'main', 'index');
296
        } elseif (AffiliateCore::auth()) {
297
            $this->url('affiliate', 'account', 'index');
298
        } else {
299
            echo PH7_URL_ROOT;
300
        }
301
    }
302
303
    /**
304
     * @param string $sModule
305
     * @param string $sController
306
     * @param string $sAction
307
     * @param null|string $sVars
308
     * @param bool $bClear
309
     *
310
     * @return void
311
     */
312
    public function url($sModule, $sController, $sAction, $sVars = null, $bClear = true)
313
    {
314
        $sUrl = Uri::get($sModule, $sController, $sAction, $sVars, $bClear);
315
316
        echo Url::clean($sUrl); // For the URL parameters to avoid invalid HTML code
317
    }
318
319
    /**
320
     * Create a link of to display a popup confirmation for a CRUD action (http://en.wikipedia.org/wiki/Create,_read,_update_and_delete).
321
     *
322
     * @param string $sLabel
323
     * @param string $sMod
324
     * @param string $sCtrl
325
     * @param string $sAct
326
     * @param int|string $mId Content ID
327
     * @param string $sClass Add a CSS class
328
     *
329
     * @return void HTML output.
330
     */
331
    public function popupLinkConfirm($sLabel, $sMod, $sCtrl, $sAct, $mId, $sClass = null)
332
    {
333
        $sClass = $sClass !== null ? ' class="' . $sClass . '" ' : ' ';
334
335
        $aHttpParams = [
336
            'label' => Url::encode($sLabel),
337
            'mod' => $sMod,
338
            'ctrl' => $sCtrl,
339
            'act' => $sAct,
340
            'id' => Url::encode($mId)
341
        ];
342
343
        echo '<a', $sClass, 'href="', PH7_URL_ROOT, 'asset/ajax/popup/confirm/?', Url::httpBuildQuery($aHttpParams), '" data-popup="classic">', $sLabel, '</a>';
344
    }
345
346
    /**
347
     * @param string $sCountryCode The Country Code (e.g., US = United States).
348
     *
349
     * @return void Output the Flag Icon Url.
350
     */
351
    public function getSmallFlagIcon($sCountryCode)
352
    {
353
        $sIcon = $this->oStr->lower($sCountryCode) . self::FLAG_ICON_EXT;
354
        $sDir = PH7_URL_STATIC . PH7_IMG . 'flag/s/';
355
356
        echo is_file(PH7_PATH_STATIC . PH7_IMG . 'flag/s/' . $sIcon) ? $sDir . $sIcon : $sDir . self::NONE_FLAG_FILENAME;
357
    }
358
359
    /**
360
     * @return string
361
     */
362
    final public function softwareComment()
363
    {
364
        echo PageDna::generateHtmlComment();
365
    }
366
367
    /**
368
     * Provide a "Powered By" link.
369
     *
370
     * @param bool $bLink To include a link to pH7CMS or pH7Framework.
371
     * @param bool $bSoftwareName
372
     * @param bool $bVersion To include the version being used.
373
     * @param bool $bComment HTML comment.
374
     * @param bool $bEmailContext Is it for email content or not.
375
     *
376
     * @return void
377
     */
378
    final public function link($bLink = true, $bSoftwareName = true, $bVersion = false, $bComment = true, $bEmailContext = false)
379
    {
380
        if (!$bEmailContext && (bool)DbConfig::getSetting('displayPoweredByLink')) {
381
            if ($bLink) {
382
                $bSoftwareName = true;
383
            }
384
385
            echo ($bSoftwareName ? '<span class="italic">' . t('Big thanks to') : ''), ' <strong>', ($bLink ? '<a class="underline" href="' . Kernel::SOFTWARE_WEBSITE . '" title="' . Kernel::SOFTWARE_DESCRIPTION . '">' : ''), ($bSoftwareName ? Kernel::SOFTWARE_NAME : ''), ($bVersion ? ' ' . Kernel::SOFTWARE_VERSION : ''), ($bLink ? '</a>' : ''), ($bSoftwareName ? '</strong><span role="img" aria-label="love">❤️</span></span>' : '');
386
        }
387
388
        if ($bComment) {
389
            echo '
390
                <!-- ', sprintf(Kernel::SOFTWARE_COPYRIGHT, date('Y')), ' -->
391
                <!-- Powered by ', Kernel::SOFTWARE_NAME, ' ', Kernel::SOFTWARE_VERSION, ', Build ', Kernel::SOFTWARE_BUILD, ' -->
392
                <!-- This notice cannot be removed in any case.
393
                This open source software is distributed for free and you must respect the thousands of days, months and several years it took to develop it.
394
                Think to the developer who worked hard for years coding what you use.
395
                All rights reserved to ', Kernel::SOFTWARE_NAME, ', ', Kernel::SOFTWARE_COMPANY, '
396
                You can never claim that you own the code, developed or helped the software if it is not the case -->';
397
        }
398
399
        echo '<!-- "Powered by ', Kernel::SOFTWARE_NAME, ', ', Kernel::SOFTWARE_VERSION_NAME, ', ', Kernel::SOFTWARE_VERSION, ', Build ', Kernel::SOFTWARE_BUILD, ' -->';
400
    }
401
402
    /**
403
     * Provide a small "Powered By" link (e.g., for sitemap.xsl.tpl).
404
     *
405
     * @return void
406
     */
407
    final public function smallLink()
408
    {
409
        echo '<strong>', t('THANKS to'), ' <a href="', Kernel::SOFTWARE_WEBSITE, '" title="', Kernel::SOFTWARE_DESCRIPTION, '">', Kernel::SOFTWARE_NAME, '</a> ', Kernel::SOFTWARE_VERSION, '!</strong> <span role="img" aria-label="love">❤️</span>';
410
    }
411
412
    /**
413
     * @return void Output the relevant link based on the client browser's language.
414
     */
415
    final public function smartLink()
416
    {
417
        // Get Client's Language Code
418
        $sLangCode = (new Browser)->getLanguage(true);
419
420
        // Default links, set to English
421
        $aSites = [
422
            ['title' => 'Free Dating CMS', 'link' => Kernel::SOFTWARE_GIT_REPO_URL],
423
            ['title' => 'Flirt Hot Girls', 'link' => 'http://01script.com/p/dooba'],
424
            ['title' => 'Romance Dating', 'link' => 'http://love-rencontre.wekiss.net'],
425
            ['title' => 'Date your Friends', 'link' => 'http://01script.com/p/dooba']
426
        ];
427
428
        if ($sLangCode === 'en-ie') {
429
            $aSites[] = ['title' => 'FREE Flirt in Dublin City', 'link' => 'http://01script.com/p/dooba'];
430
            $aSites[] = ['title' => 'Date Dubs in the Town!', 'link' => 'http://01script.com/p/dooba'];
431
        } elseif ($sLangCode === 'en-gb') {
432
            $aSites[] = ['title' => 'Date Brits near from YOU', 'link' => 'http://01script.com/p/dooba'];
433
            $aSites[] = ['title' => 'Date Londoners', 'link' => 'http://01script.com/p/dooba'];
434
        } elseif (strpos($sLangCode, 'fr') !== false) {
435
            /**
436
             * Reset the array since we don't want to mix it up with different langs (default one is English, not French)
437
             */
438
            $aSites = [
439
                ['title' => 'Rencontre d\'un soir', 'link' => 'http://01script.com/p/dooba'],
440
                ['title' => 'Flirt Coquin', 'link' => 'http://01script.com/p/dooba'],
441
                ['title' => 'Rencontre amoureuse', 'link' => 'http://love-rencontre.wekiss.net']
442
            ];
443
        }
444
445
        $aSite = $aSites[array_rand($aSites)];
446
        echo '<a href="', $aSite['link'], '">', $aSite['title'], '</a>';
447
    }
448
449
    /**
450
     * @param string $sType (js or css).
451
     * @param string $sDir
452
     * @param string $sFiles
453
     * @param string $sCssMedia Only works for CSS files. The CSS Media type (e.g., screen,handheld,tv,projection). Default "all". Leave blank ('' or null) not to use the media attribute.
454
     *
455
     * @return void
456
     */
457
    public function staticFiles($sType, $sDir, $sFiles, $sCssMedia = 'all')
458
    {
459
        if ($sType === 'js') {
460
            echo $this->externalJsFile(PH7_RELATIVE . 'asset/gzip/?t=js&amp;d=' . $sDir . '&amp;f=' . $sFiles);
461
        } else {
462
            echo $this->externalCssFile(PH7_RELATIVE . 'asset/gzip/?t=css&amp;d=' . $sDir . '&amp;f=' . $sFiles, $sCssMedia);
463
        }
464
    }
465
466
    /**
467
     * @param string $sDir The CSS folder.
468
     * @param string $sFiles The CSS files.
469
     * @param string $sCssMedia CSS Media type (e.g., screen,handheld,tv,projection). Default "all". Leave blank ('' or null) not to use the media attribute.
470
     *
471
     * @return void
472
     */
473
    public function addCss($sDir, $sFiles, $sCssMedia = 'all')
474
    {
475
        $this->aCssDir[] = $sDir;
476
        $this->aCssFiles[] = $sFiles;
477
        $this->aCssMedia[] = $sCssMedia;
478
    }
479
480
    /**
481
     * @param string $sDir The JavaScript folder.
482
     * @param string $sFiles The JavaScript files.
483
     *
484
     * @return void
485
     */
486
    public function addJs($sDir, $sFiles)
487
    {
488
        $this->aJsDir[] = $sDir;
489
        $this->aJsFiles[] = $sFiles;
490
    }
491
492
    /**
493
     * @return void
494
     */
495
    public function css()
496
    {
497
        for ($iKey = 0, $iCount = count($this->aCssDir); $iKey < $iCount; $iKey++) {
498
            $this->staticFiles('css', $this->aCssDir[$iKey], $this->aCssFiles[$iKey], $this->aCssMedia[$iKey]);
499
        }
500
501
        unset($this->aCssDir, $this->aCssFiles, $this->aCssMedia);
502
    }
503
504
    /**
505
     * @return void
506
     */
507
    public function js()
508
    {
509
        for ($iKey = 0, $iCount = count($this->aJsDir); $iKey < $iCount; $iKey++) {
510
            $this->staticFiles('js', $this->aJsDir[$iKey], $this->aJsFiles[$iKey]);
511
        }
512
513
        unset($this->aJsDir, $this->aJsFiles);
514
    }
515
516
    /**
517
     * Set flash message.
518
     *
519
     * @param string $sMessage
520
     * @param string $sType Type of message: "Design::SUCCESS_TYPE", "Design::INFO_TYPE", "Design::WARNING_TYPE" or "Design::ERROR_TYPE"
521
     *
522
     * @return void
523
     */
524
    public function setFlashMsg($sMessage, $sType = self::SUCCESS_TYPE)
525
    {
526
        /** Check the type of message, otherwise it's the default one **/
527
        $sType = in_array($sType, self::MESSAGE_TYPES, true) ? $sType : self::SUCCESS_TYPE;
528
        $sType = $sType === self::ERROR_TYPE ? 'danger' : $sType; // Now the "error" CSS class has become "danger", so we have to convert it
529
        $this->oSession->set(
530
            [
531
                self::FLASH_MSG => $sMessage,
532
                self::FLASH_TYPE => $sType
533
            ]
534
        );
535
    }
536
537
    /**
538
     * Flash displays the message defined in the method setFlash.
539
     *
540
     * @return void The message text with CSS layout depending on the type of message.
541
     */
542
    public function flashMsg()
543
    {
544
        $aFlashData = [
545
            self::FLASH_MSG,
546
            self::FLASH_TYPE
547
        ];
548
549
        if ($this->oSession->exists($aFlashData)) {
550
            echo '<div class="center bold alert alert-', $this->oSession->get(self::FLASH_TYPE), '" role="alert">', $this->oSession->get(self::FLASH_MSG), '</div>';
551
552
            $this->oSession->remove($aFlashData);
553
        }
554
    }
555
556
    /**
557
     * Show the user IP address with a link to get the IP information.
558
     *
559
     * @internal If it's an IPv6, show only the beginning, otherwise it would be too long in the template.
560
     *
561
     * @param string $sIp Allows to specify another IP address than the client one.
562
     * @param bool $bPrint Print or Return the HTML code. Default TRUE
563
     *
564
     * @return void|string
565
     */
566
    public function ip($sIp = null, $bPrint = true)
567
    {
568
        $sIp = Ip::get($sIp);
569
        $sHtml = '<a href="' . Ip::api($sIp) . '" title="' . t('See info of this IP, %0%', $sIp) . '" target="_blank" rel="noopener noreferrer">' . $this->oStr->extract($sIp, self::MAX_IP_LENGTH_SHOWN) . '</a>';
0 ignored issues
show
Bug introduced by
It seems like $sIp defined by \PH7\Framework\Ip\Ip::get($sIp) on line 568 can also be of type array; however, PH7\Framework\Ip\Ip::api() does only seem to accept string|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Bug introduced by
It seems like $sIp defined by \PH7\Framework\Ip\Ip::get($sIp) on line 568 can also be of type array; however, PH7\Framework\Str\Str::extract() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
570
571
        if (!$bPrint) {
572
            return $sHtml;
573
        }
574
575
        echo $sHtml;
576
    }
577
578
    /**
579
     * Show the geolocation of the user (with link that points to the Country controller).
580
     *
581
     * @param bool $bPrint Print or Return the HTML code. Default TRUE
582
     *
583
     * @return void|string
584
     */
585
    public function geoIp($bPrint = true)
586
    {
587
        $sCountry = Geo::getCountry();
588
        $sCountryCode = Country::fixCode(Geo::getCountryCode());
589
        $sCountryLang = t($sCountryCode); // Country name translated into the user language
590
        $sCity = Geo::getCity();
591
592
        if (SysMod::isEnabled('map')) {
593
            $sHtml = '<a href="' . Uri::get('map', 'country', 'index', $sCountry . PH7_SH . $sCity) . '" title="' . t('Meet New People in %0%, %1% with %site_name%!', $sCountryLang, $sCity) . '">' . $sCity . '</a>';
594
        } else {
595
            $sHtml = '<abbr title="' . t('Meet New People in %0%, %1% thanks %site_name%!', $sCountryLang, $sCity) . '">' . $sCity . '</abbr>';
596
        }
597
598
        if (!$bPrint) {
599
            return $sHtml;
600
        }
601
602
        echo $sHtml;
603
    }
604
605
    /**
606
     * Pagination.
607
     *
608
     * @param int $iTotalPages
609
     * @param int $iCurrentPage
610
     *
611
     * @return void The HTML pagination code.
612
     */
613
    public function pagination($iTotalPages, $iCurrentPage)
614
    {
615
        echo (new Pagination($iTotalPages, $iCurrentPage))->getHtmlCode();
616
    }
617
618
    /**
619
     * @param string $sUsername
620
     * @param string $sSex
621
     * @param int $iSize
622
     * @param bool $bPrint Print or Return the HTML code.
623
     *
624
     * @return void|string The default 150px avatar URL or the user avatar URL.
625
     */
626
    public function getUserAvatar($sUsername, $sSex = '', $iSize = null, $bPrint = true)
627
    {
628
        $oCache = (new Cache)->start(
629
            self::CACHE_AVATAR_GROUP . $sUsername,
630
            $sSex . $iSize,
631
            self::CACHE_AVATAR_LIFETIME
632
        );
633
634
        if (!$sUrl = $oCache->get()) {
635
            $oUserModel = new UserCoreModel;
636
637
            $iProfileId = $oUserModel->getId(null, $sUsername);
638
            $oGetAvatar = $oUserModel->getAvatar($iProfileId);
639
640
            $sSize = ($iSize == 32 || $iSize == 64 || $iSize == 100 || $iSize == 150 || $iSize == 200 || $iSize == 400) ? '-' . $iSize : '';
641
642
            $sAvatar = @$oGetAvatar->pic;
643
            $sExt = PH7_DOT . (new File)->getFileExt($sAvatar);
644
645
            $sDir = 'user/avatar/img/' . $sUsername . PH7_SH;
646
            $sPath = PH7_PATH_PUBLIC_DATA_SYS_MOD . $sDir . $sAvatar;
647
            $sUrl = PH7_URL_DATA_SYS_MOD . $sDir . str_replace($sExt, $sSize . $sExt, $sAvatar);
648
649
            $bIsModerationMode = AdminCore::isAdminPanel();
650
651
            if (!is_file($sPath) || $oGetAvatar->approvedAvatar == '0') {
652
                /* If sex is empty, it is recovered in the database using information from member */
653
                $sSex = !empty($sSex) ? $sSex : $oUserModel->getSex(null, $sUsername, DbTableName::MEMBER);
654
                $sSex = $this->oStr->lower($sSex);
655
                $sIcon = (GenderTypeUserCore::isGenderValid($sSex) || $sSex === PH7_ADMIN_USERNAME) ? $sSex : 'visitor';
656
                $sUrlTplName = defined('PH7_TPL_NAME') ? PH7_TPL_NAME : PH7_DEFAULT_THEME;
657
658
                /** If the user doesn't have an avatar **/
659
                if (!is_file($sPath)) {
660
                    /* The user has no avatar, we try to get a Gravatar */
661
662
                    // Get the User Email
663
                    $sEmail = $oUserModel->getEmail($iProfileId);
664
665
                    $bSecuredGravatar = Http::isSsl();
666
                    $sUrl = $this->getGravatarUrl($sEmail, '404', $iSize, 'g', $bSecuredGravatar);
667
668
                    if (!(new Validate)->url($sUrl, true)) {
669
                        // If no Gravatar set, it returns 404, and we then set the default pH7CMS's avatar
670
                        $sUrl = PH7_URL_TPL . $sUrlTplName . PH7_SH . PH7_IMG . 'icon/' . $sIcon . '_no_picture' . $sSize . self::AVATAR_IMG_EXT;
671
                    }
672
                } elseif (!$bIsModerationMode) { // We don't display pending approval image when admins are on the panel admin
673
                    $sUrl = PH7_URL_TPL . $sUrlTplName . PH7_SH . PH7_IMG . 'icon/pending' . $sSize . self::AVATAR_IMG_EXT;
674
                }
675
            }
676
            unset($oUserModel);
677
678
            /**
679
             * @internal Clean URL for parameters in Gravatar URL to make the HTML code valid.
680
             * If we set replace '&' by '&amp;' before checking the 404's Gravatar URL, it will always return '200 OK', that's why we need to clean the URL now.
681
             */
682
            $oCache->put(Url::clean($sUrl));
683
        }
684
685
        unset($oCache);
686
687
        if (!$bPrint) {
688
            return $sUrl;
689
        }
690
691
        echo $sUrl;
692
    }
693
694
    /**
695
     * Get the user profile link.
696
     *
697
     * @param string $sUsername
698
     * @param bool $bPrint Print or Return the HTML code.
699
     *
700
     * @return void|string The absolute user profile link.
701
     */
702
    public function getProfileLink($sUsername, $bPrint = true)
703
    {
704
        $sHtml = '<a href="';
705
        $sHtml .= (new UserCore)->getProfileLink($sUsername);
706
        $sHtml .= '" title="' . t("%0%'s profile", $sUsername) . '">' . $sUsername . '</a>';
707
708
        if (!$bPrint) {
709
            return $sHtml;
710
        }
711
712
        echo $sHtml;
713
    }
714
715
    /**
716
     * Get the Gravatar URL.
717
     *
718
     * @param string $sEmail The user email address.
719
     * @param string $sType Default image type to show [ 404 | mp | identicon | monsterid | wavatar ]
720
     * @param int $iSize The size of the image. Default: 80
721
     * @param string $sRating The max image rating allowed. Default: 'g' (for all)
722
     * @param bool $bSecure Display avatar via HTTPS, for example if the site uses HTTPS, you should use this option to not get a warning with most Web browsers. Default: FALSE
723
     *
724
     * @return string The Gravatar Link.
725
     */
726
    public function getGravatarUrl($sEmail, $sType = 'wavatar', $iSize = 80, $sRating = 'g', $bSecure = false)
727
    {
728
        $sProtocol = $bSecure ? 'https' : 'http';
729
        $bSubDomain = $bSecure ? 'secure' : 'www';
730
731
        return $sProtocol . '://' . $bSubDomain . '.gravatar.com/avatar/' . md5(strtolower($sEmail)) . '?d=' . $sType . '&s=' . $iSize . '&r=' . $sRating;
732
    }
733
734
    /**
735
     * Get favicon from a URL.
736
     *
737
     * @param string $sUrl
738
     *
739
     * @return void The HTML favicon image.
740
     */
741
    public function favicon($sUrl)
742
    {
743
        $iFaviconSize = 16;
744
        $sImg = Browser::favicon($sUrl);
745
        $sName = Http::getHostName($sUrl);
746
747
        $this->imgTag(
748
            $sImg,
749
            $sName,
0 ignored issues
show
Security Bug introduced by
It seems like $sName defined by \PH7\Framework\Http\Http::getHostName($sUrl) on line 745 can also be of type false; however, PH7\Framework\Layout\Html\Design::imgTag() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
750
            [
751
                'width' => $iFaviconSize,
752
                'height' => $iFaviconSize
753
            ]
754
        );
755
    }
756
757
    /**
758
     * Like Link.
759
     *
760
     * @param string $sUsername Username of member.
761
     * @param string $sFirstName First name of member.
762
     * @param string $sSex Sex of member.
763
     * @param string $sForceUrlKey Specify a specific URL from the like. Default NULL (current URL).
764
     *
765
     * @return void
766
     */
767
    public function like($sUsername, $sFirstName, $sSex, $sForceUrlKey = null)
768
    {
769
        $aHttpParams = [
770
            'msg' => t('You need to be a member for liking contents.'),
771
            'ref' => $this->oHttpRequest->currentController(),
772
            'a' => 'like',
773
            'u' => $sUsername,
774
            'f_n' => $sFirstName,
775
            's' => $sSex
776
        ];
777
778
        $bIsLogged = UserCore::auth();
779
        $sLikeLink = $bIsLogged ? '#' : Uri::get('user', 'signup', 'step1', '?' . Url::httpBuildQuery($aHttpParams), false);
780
        $sLikeId = $bIsLogged ? ' id="like"' : '';
781
782
        $sUrlKey = empty($sForceUrlKey) ? $this->oHttpRequest->currentUrl() : $sForceUrlKey;
783
        echo '<a rel="nofollow" href="', $sLikeLink, '" data-key="', $sUrlKey, '" title="', t('Like %0%', $sFirstName), '" class="like smooth-pink"', $sLikeId, '>', t('Like %0%', $sFirstName), '</a>';
784
        $this->staticFiles('js', PH7_STATIC . PH7_JS, 'Like.js');
785
    }
786
787
    /**
788
     * Add Normal size Social Media Widgets.
789
     *
790
     * @internal AddToAny JS file will be included through 'ph7_static_files' table.
791
     *
792
     * @return void HTML output.
793
     */
794
    public function likeApi()
795
    {
796
        if ((bool)DbConfig::getSetting('socialMediaWidgets')) {
797
            $sHtml = <<<HTML
798
<div class="a2a_kit a2a_kit_size_32 a2a_default_style">
799
    <a class="a2a_dd" href="https://www.addtoany.com/share" rel="nofollow"></a>
800
    <a class="a2a_button_facebook"></a>
801
    <a class="a2a_button_twitter"></a>
802
    <a class="a2a_button_pinterest"></a>
803
    <a class="a2a_button_facebook_messenger"></a>
804
    <a class="a2a_button_linkedin"></a>
805
</div>
806
HTML;
807
            echo $sHtml;
808
        }
809
    }
810
811
    /**
812
     * Add Small size Social Media Widgets.
813
     *
814
     * @internal AddToAny JS file will be included through 'ph7_static_files' table.
815
     *
816
     * @return void HTML output.
817
     */
818
    public function littleLikeApi()
819
    {
820
        if ((bool)DbConfig::getSetting('socialMediaWidgets')) {
821
            $sHtml = <<<HTML
822
<div class="a2a_kit a2a_kit_size_32 a2a_default_style">
823
    <a class="a2a_dd" href="https://www.addtoany.com/share" rel="nofollow"></a>
824
    <a class="a2a_button_facebook"></a>
825
    <a class="a2a_button_twitter"></a>
826
    <a class="a2a_button_pinterest"></a>
827
</div>
828
HTML;
829
            echo $sHtml;
830
        }
831
    }
832
833
    /**
834
     * Generate a Report Link.
835
     *
836
     * @param int $iId
837
     * @param string $sUsername
838
     * @param string $sFirstName
839
     * @param string $sSex
840
     *
841
     * @internal We do not use Url::httpBuildQuery() method for the first condition otherwise the URL is distorted and it doesn't work.
842
     *
843
     * @return void
844
     */
845
    public function report($iId, $sUsername, $sFirstName, $sSex)
846
    {
847
        $iId = (int)$iId;
848
849
        if ($iId > PH7_GHOST_ID) {
850
            if (UserCore::auth()) {
851
                $aUrlParams = [
852
                    'spammer' => $iId,
853
                    'type' => Registry::getInstance()->module
0 ignored issues
show
Documentation introduced by
The property module does not exist on object<PH7\Framework\Registry\Registry>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
854
                ];
855
                $sReportLink = Uri::get(
856
                    'report',
857
                    'main',
858
                    'abuse',
859
                    '?' . Url::httpBuildQuery($aUrlParams) . '&amp;url=' . $this->oHttpRequest->currentUrl(),
860
                    false
861
                );
862
863
                $sReportLink .= '" data-popup="block-page';
864
            } else {
865
                $aUrlParams = [
866
                    'msg' => t('You need to be a user to report contents.'),
867
                    'ref' => 'profile',
868
                    'a' => 'report',
869
                    'u' => $sUsername,
870
                    'f_n' => $sFirstName,
871
                    's' => $sSex
872
                ];
873
                $sReportLink = Uri::get('user', 'signup', 'step1', '?' . Url::httpBuildQuery($aUrlParams), false);
874
            }
875
876
            echo '<a rel="nofollow" href="', $sReportLink, '" title="', t('Report Abuse'), '"><i class="fa fa-flag smooth-pink"></i></a>';
877
        } else {
878
            echo '<abbr title="' . t('Report feature is not available for this content since the user who posted that content has been removed.') . '""><i class="fa fa-flag smooth-pink"></i></abbr>';
879
        }
880
    }
881
882
    /**
883
     * Generate a Link tag.
884
     *
885
     * @param string $sLink The link.
886
     * @param bool $bNoFollow Set TRUE to set the link "nofollow", FALSE otherwise. Default TRUE
887
     *
888
     * @return void The HTML link tag.
889
     */
890
    public function urlTag($sLink, $bNoFollow = true)
891
    {
892
        $sLinkName = UrlParser::name($sLink);
893
        $aDefAttrs = ['href' => $sLink, 'title' => $sLinkName];
894
895
        if ($bNoFollow) {
896
            /**
897
             * Add "nofollow" attribute if "$bNoFollow" is TURE
898
             * If TRUE, this means we don't trust the link and might be opened on a new tab, so add "noopener noreferrer" to prevent Reverse Tabnabbing attacks.
899
             */
900
            $aDefAttrs += ['rel' => 'nofollow noopener noreferrer'];
901
        }
902
903
        $this->htmlTag('a', $aDefAttrs, true, $sLinkName);
904
    }
905
906
    /**
907
     * Generate a IMG tag.
908
     *
909
     * @param string $sImg The image.
910
     * @param string $sAlt Alternate text.
911
     * @param array $aAttrs Optional. Array containing the "name" and "value" HTML attributes. Default NULL
912
     *
913
     * @return void The HTML image tag.
914
     */
915
    public function imgTag($sImg, $sAlt, array $aAttrs = null)
916
    {
917
        $aDefAttrs = ['src' => $sImg, 'alt' => $sAlt];
918
919
        if ($aAttrs !== null) {
920
            $aDefAttrs += $aAttrs; // Update the attributes if necessary
921
        }
922
923
        $this->htmlTag('img', $aDefAttrs);
924
    }
925
926
    /**
927
     * Generate any HTML tag.
928
     *
929
     * @param string $sTag
930
     * @param array $aAttrs Optional. Default NULL
931
     * @param bool $bPair Optional. Default FALSE
932
     * @param string $sText Optional. Add text, available only for pair tag. Default NULL
933
     *
934
     * @return string The custom HTML tag.
935
     */
936
    public function htmlTag($sTag, array $aAttrs = null, $bPair = false, $sText = null)
937
    {
938
        $sAttrs = '';
939
940
        if ($aAttrs !== null) {
941
            foreach ($aAttrs as $sName => $sValue) {
942
                $sAttrs .= ' ' . $sName . '="' . $sValue . '"';
943
            }
944
        }
945
946
        echo ($bPair ? '<' . $sTag . $sAttrs . '>' . ($sText === null ? '' : $sText) . '</' . $sTag . '>' : '<' . $sTag . $sAttrs . ' />');
947
    }
948
949
    public function htmlHeader()
950
    {
951
        echo '<!DOCTYPE html>';
952
    }
953
954
    /**
955
     * Useful HTML Header.
956
     *
957
     * @param array $aMeta Default NULL
958
     * @param bool $bLogo Default FALSE
959
     *
960
     * @return void
961
     */
962
    final public function usefulHtmlHeader(array $aMeta = null, $bLogo = false)
963
    {
964
        $this->bIsDiv = true;
965
966
        // DO NOT REMOVE THE COPYRIGHT CODE BELOW! Thank you!
967
        echo '<html><head><meta charset="utf-8" />
968
        <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1" />
969
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
970
        <title>', (!empty($aMeta['title']) ? $aMeta['title'] : ''), '</title>';
971
972
        if (!empty($aMeta['description'])) {
973
            echo '<meta name="description" content="', $aMeta['description'], '" />';
974
        }
975
976
        if (!empty($aMeta['keywords'])) {
977
            echo '<meta name="keywords" content="', $aMeta['keywords'], '" />';
978
        }
979
980
        echo '<meta name="author" content="', Kernel::SOFTWARE_COMPANY, '" />
981
        <meta name="copyright" content="', sprintf(Kernel::SOFTWARE_COPYRIGHT, date('Y')), '" />
982
        <meta name="creator" content="', Kernel::SOFTWARE_NAME, '" />
983
        <meta name="designer" content="', Kernel::SOFTWARE_NAME, '" />
984
        <meta name="generator" content="', Kernel::SOFTWARE_NAME, ' ', Kernel::SOFTWARE_VERSION_NAME, ' ', Kernel::SOFTWARE_VERSION, ', Build ', Kernel::SOFTWARE_BUILD, '" />';
985
        $this->softwareComment();
986
        $this->externalCssFile(PH7_URL_STATIC . PH7_CSS . 'js/jquery/smoothness/jquery-ui.css');
987
        $this->staticFiles('css', PH7_LAYOUT . PH7_TPL . PH7_DEFAULT_THEME . PH7_SH . PH7_CSS, 'common.css,style.css,form.css');
988
        $this->externalJsFile(PH7_URL_STATIC . PH7_JS . 'jquery/jquery.js');
989
        $this->externalJsFile(PH7_URL_STATIC . PH7_JS . 'jquery/jquery-ui.js');
990
        echo '<script>var pH7Url={base:\'', PH7_URL_ROOT, '\'}</script></head><body>';
991
992
        if ($bLogo) {
993
            // Website's name
994
            $sSiteName = Registry::getInstance()->site_name;
0 ignored issues
show
Documentation introduced by
The property site_name does not exist on object<PH7\Framework\Registry\Registry>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
995
996
            // Check if the website's name exists, otherwise we displayed the software's name
997
            $sName = !empty($sSiteName) ? $sSiteName : Kernel::SOFTWARE_NAME;
998
999
            echo '<header>
1000
            <div role="banner" id="logo"><h1><a href="', PH7_URL_ROOT, '" title="', $sName, ' — ', Kernel::SOFTWARE_NAME, ', ', Kernel::SOFTWARE_COMPANY, '">', $sName, '</a></h1></div>
1001
            </header>';
1002
        }
1003
1004
        echo $this->flashMsg(), '<div class="msg"></div><div class="m_marg">';
1005
    }
1006
1007
    public function htmlFooter()
1008
    {
1009
        if ($this->bIsDiv) {
1010
            echo '</div>';
1011
        }
1012
1013
        echo '</body></html>';
1014
    }
1015
1016
    /**
1017
     * The XML tag doesn't work in PHP files since it is the same "<?" language tag.
1018
     * So this method can introduce the XML header without causing an error by the PHP interpreter.
1019
     *
1020
     * @return void
1021
     */
1022
    public function xmlHeader()
1023
    {
1024
        echo '<?xml version="1.0" encoding="utf-8"?>';
1025
    }
1026
1027
    /**
1028
     * Get an external CSS file.
1029
     *
1030
     * @param string $sFile CSS file.
1031
     * @param string $sCssMedia Only works for CSS files. The CSS Media type (e.g., screen,handheld,tv,projection). Default NULL
1032
     *
1033
     * @return void HTML link tag.
1034
     */
1035
    public function externalCssFile($sFile, $sCssMedia = null)
1036
    {
1037
        $sCssMedia = $sCssMedia !== null ? ' media="' . $sCssMedia . '"' : '';
1038
1039
        echo '<link rel="stylesheet" href="', $sFile, '"', $sCssMedia, ' />';
1040
    }
1041
1042
    /**
1043
     * Get an external JS file.
1044
     *
1045
     * @param string $sFile JS file.
1046
     *
1047
     * @return void HTML script tag.
1048
     */
1049
    public function externalJsFile($sFile)
1050
    {
1051
        echo '<script src="', $sFile, '"></script>';
1052
    }
1053
}
1054