Core::webAlertAndQuit()   D
last analyzed

Complexity

Conditions 11
Paths 320

Size

Total Lines 65

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
nc 320
nop 2
dl 0
loc 65
rs 4.7503
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php namespace EvolutionCMS;
2
3
use UrlProcessor;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, EvolutionCMS\UrlProcessor.

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...
4
/**
5
 * @see: https://github.com/laravel/framework/blob/5.6/src/Illuminate/Foundation/Bootstrap/LoadConfiguration.php
6
 * @property Mail $mail
7
 *      $this->loadExtension('MODxMailer');
8
 * @property Database $db
9
 *      $this->loadExtension('DBAPI')
10
 * @property Legacy\PhpCompat $phpcompat
11
 *      $this->loadExtension('PHPCOMPAT');
12
 * @property Legacy\Modifiers $filter
13
 *      $this->loadExtension('MODIFIERS');
14
 * @property Legacy\ExportSite $export
15
 *      $this->loadExtension('EXPORT_SITE');
16
 * @property Support\MakeTable $table
17
 *      $this->loadExtension('makeTable');
18
 * @property Legacy\ManagerApi $manager
19
 *      $this->loadExtension('ManagerAPI');
20
 * @property Legacy\PasswordHash $phpass
21
 *      $this->loadExtension('phpass');
22
 */
23
class Core extends AbstractLaravel implements Interfaces\CoreInterface
24
{
25
    use Traits\Settings {
26
        getSettings as loadConfig;
27
    }
28
    use Traits\Path, Traits\Helpers;
29
30
    /**
31
     * event object
32
     * @var Event
33
     */
34
35
    public $event;
36
    /**
37
     * event object
38
     * @var Event
39
     * @deprecated
40
     */
41
    public $Event;
42
43
    /**
44
     * @var array
45
     */
46
    public $pluginEvent = array();
47
48
    /**
49
     * @var array
50
     */
51
    public $configGlobal = null; // contains backup of settings overwritten by user-settings
52
    public $rs;
53
    public $result;
54
    public $sql;
55
    public $debug = false;
56
    public $documentIdentifier;
57
    public $documentMethod;
58
    public $documentGenerated;
59
    public $documentContent;
60
    public $documentOutput;
61
    public $tstart;
62
    public $mstart;
63
    public $minParserPasses;
64
    public $maxParserPasses;
65
    public $documentObject;
66
    public $templateObject;
67
    public $snippetObjects;
68
    public $stopOnNotice = false;
69
    public $executedQueries;
70
    public $queryTime;
71
    public $currentSnippet;
72
    public $documentName;
73
    public $aliases;
74
    public $visitor;
75
    public $entrypage;
76
    /**
77
     * @deprecated use UrlProcessor::getFacadeRoot()->documentListing
78
     */
79
    public $documentListing;
80
    /**
81
     * feed the parser the execution start time
82
     * @var bool
83
     */
84
    public $dumpSnippets = false;
85
    public $snippetsCode;
86
    public $snippetsTime = array();
87
    public $chunkCache;
88
    public $snippetCache;
89
    public $contentTypes;
90
    public $dumpSQL = false;
91
    public $queryCode;
92
    /**
93
     * @deprecated use UrlProcessor::getFacadeRoot()->virtualDir
94
     */
95
    public $virtualDir;
96
    public $placeholders;
97
    public $sjscripts = array();
98
    public $jscripts = array();
99
    public $loadedjscripts = array();
100
    public $documentMap;
101
    public $forwards = 3;
102
    public $error_reporting = 1;
103
    public $dumpPlugins = false;
104
    public $pluginsCode;
105
    public $pluginsTime = array();
106
    public $pluginCache = array();
107
    /**
108
     * @deprecated use UrlProcessor::getFacadeRoot()->aliasListing
109
     */
110
    public $aliasListing;
111
    public $lockedElements = null;
112
    public $tmpCache = array();
113
    private $version = array();
114
    public $extensions = array();
115
    public $cacheKey = null;
116
    public $recentUpdate = 0;
117
    public $useConditional = false;
118
    protected $systemCacheKey = null;
119
    public $snipLapCount = 0;
120
    public $messageQuitCount;
121
    public $time;
122
    public $sid;
123
    private $q;
124
    public $decoded_request_uri;
125
    /**
126
     * @var Legacy\DeprecatedCore
127
     * @deprecated use ->getDeprecatedCore()
128
     */
129
    public $old;
130
131
    /** @var UrlProcessor|null */
132
    public $urlProcessor;
133
134
    /**
135
     * @param array $services
0 ignored issues
show
Bug introduced by
There is no parameter named $services. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
136
     */
137
    public function __construct()
138
    {
139
        $this->tstart = get_by_key($_SERVER, 'REQUEST_TIME_FLOAT', 0);
140
141
        $this->instance('path', $this->path());
142
        $this->instance('path.base', $this->basePath());
143
        $this->instance('path.lang', $this->langPath());
144
        $this->instance('path.config', $this->configPath());
145
        $this->instance('path.public', $this->publicPath());
146
        $this->instance('path.storage', $this->storagePath());
147
        $this->instance('path.database', $this->databasePath());
148
        $this->instance('path.resources', $this->resourcePath());
149
        $this->instance('path.bootstrap', $this->bootstrapPath());
150
151
        parent::__construct();
152
153
        $this->initialize();
154
155
        $this->config['view']['paths'] = $this['config']->get('view.paths');
156
    }
157
158
    public function initialize()
159
    {
160
        if ($this->isLoggedIn()) {
161
            ini_set('display_errors', 1);
162
        }
163
        // events
164
        $this->event = new Event();
165
        $this->Event = &$this->event; //alias for backward compatibility
0 ignored issues
show
Deprecated Code introduced by
The property EvolutionCMS\Core::$Event has been deprecated.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
166
        $this->time = $_SERVER['REQUEST_TIME']; // for having global timestamp
167
168
        $this->getService('ExceptionHandler');
169
        $this->getSettings();
170
        $this->getService('UrlProcessor');
171
        $this->q = UrlProcessor::cleanQueryString(is_cli() ? '' : get_by_key($_GET, 'q', ''));
172
    }
173
174
    final public function __clone()
175
    {
176
    }
177
178
    /**
179
     * @return self
180
     */
181
    public static function getInstance()
182
    {
183
        if (self::$instance === null) {
184
            self::$instance = new static();
185
        }
186
187
        return self::$instance;
188
    }
189
190
    /**
191
     * @param string $name
192
     * @return mixed|null
193
     */
194
    public function __get($name)
195
    {
196
        if ($this->hasEvolutionProperty($name)) {
197
            if ($this->getConfig('error_reporting', 0) > 1) {
198
                trigger_error(
199
                    'Property $' . $name . ' is deprecated and should no longer be used. ',
200
                    E_USER_DEPRECATED
201
                );
202
            }
203
            return $this->getEvolutionProperty($name);
204
        }
205
206
        return parent::__get($name);
207
    }
208
209
    /**
210
     * @param $method_name
211
     * @param $arguments
212
     * @return mixed
213
     */
214
    function __call($method_name, $arguments)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
215
    {
216
        $old = $this->getDeprecatedCore();
217
        //////////@TODO LOAD DeprecatedCore
218
        if (method_exists($old, $method_name)) {
219
            $error_type = 1;
220
        } else {
221
            $error_type = 3;
222
        }
223
224
        if (!isset($this->config['error_reporting']) || 1 < $this->getConfig('error_reporting')) {
225
            if ($error_type == 1) {
226
                $title = 'Call deprecated method';
227
                $msg = $this->getPhpCompat()->htmlspecialchars("\$modx->{$method_name}() is deprecated function");
228
            } else {
229
                $title = 'Call undefined method';
230
                $msg = $this->getPhpCompat()->htmlspecialchars("\$modx->{$method_name}() is undefined function");
231
            }
232
            $info = debug_backtrace();
233
            $m[] = $msg;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$m was never initialized. Although not strictly required by PHP, it is generally a good practice to add $m = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
234
            if (!empty($this->currentSnippet)) {
235
                $m[] = 'Snippet - ' . $this->currentSnippet;
236
            } elseif (!empty($this->event->activePlugin)) {
237
                $m[] = 'Plugin - ' . $this->event->activePlugin;
238
            }
239
            $m[] = $this->decoded_request_uri;
240
            $m[] = str_replace('\\', '/', $info[0]['file']) . '(line:' . $info[0]['line'] . ')';
241
            $msg = implode('<br />', $m);
242
            $this->logEvent(0, $error_type, $msg, $title);
243
        }
244
        if (method_exists($old, $method_name)) {
245
            return call_user_func_array(array($old, $method_name), $arguments);
246
        }
247
    }
248
249
    /**
250
     * @param string $connector
251
     * @return bool
252
     */
253
    public function checkSQLconnect($connector = 'db')
254
    {
255
        $flag = false;
256
        if (is_scalar($connector) && !empty($connector) && isset($this->{$connector}) && $this->{$connector} instanceof Interfaces\DatabaseInterface) {
257
            $flag = (bool)$this->{$connector}->conn;
258
        }
259
260
        return $flag;
261
    }
262
263
    /**
264
     * Loads an extension from the extenders folder.
265
     * You can load any extension creating a boot file:
266
     * MODX_MANAGER_PATH."includes/extenders/ex_{$extname}.inc.php"
267
     * $extname - extension name in lowercase
268
     *
269
     * @deprecated use getService
270
     * @param $extname
271
     * @param bool $reload
272
     * @return bool
273
     */
274
    public function loadExtension($extname, $reload = true)
275
    {
276
        if ($this->isEvolutionProperty($extname)) {
277
            return $this->getEvolutionProperty($extname);
278
        }
279
280
        $out = false;
281
        $found = false;
282
        $flag = ($reload || !in_array($extname, $this->extensions));
283
        if ($this->checkSQLconnect('db') && $flag) {
284
            $evtOut = $this->invokeEvent('OnBeforeLoadExtension', array('name' => $extname, 'reload' => $reload));
285
            if (is_array($evtOut) && count($evtOut) > 0) {
286
                $out = array_pop($evtOut);
287
            }
288
        }
289
        if (!$out && $flag) {
290
            $extname = trim(str_replace(array('..', '/', '\\'), '', strtolower($extname)));
291
            $filename = MODX_MANAGER_PATH . "includes/extenders/ex_{$extname}.inc.php";
292
            $out = is_file($filename) ? include $filename : false;
293
        }
294
        if ($out && !in_array($extname, $this->extensions)) {
295
            $this->extensions[] = $extname;
296
        }
297
298
        return $out;
299
    }
300
301
    /**
302
     * Returns the current micro time
303
     *
304
     * @return float
305
     */
306
    public function getMicroTime()
307
    {
308
        list ($usec, $sec) = explode(' ', microtime());
309
310
        return ((float)$usec + (float)$sec);
311
    }
312
313
    /**
314
     * Redirect
315
     *
316
     * @param string $url
317
     * @param int $count_attempts
318
     * @param string $type $type
319
     * @param string $responseCode
320
     * @return bool|null
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use false|null.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
321
     * @global string $base_url
322
     * @global string $site_url
323
     */
324
    public function sendRedirect($url, $count_attempts = 0, $type = '', $responseCode = '')
325
    {
326
        $header = '';
327
        if (empty ($url)) {
328
            return false;
329
        }
330
        if ($count_attempts == 1) {
331
            // append the redirect count string to the url
332
            $currentNumberOfRedirects = isset ($_REQUEST['err']) ? $_REQUEST['err'] : 0;
333
            if ($currentNumberOfRedirects > 3) {
334
                $this->getService('ExceptionHandler')->messageQuit(
335
                    'Redirection attempt failed - please ensure the document you\'re trying to redirect to exists.' .
336
                    '<p>Redirection URL: <i>' . $url . '</i></p>'
337
                );
338
            } else {
339
                $currentNumberOfRedirects += 1;
340
                if (strpos($url, "?") > 0) {
341
                    $url .= "&err=$currentNumberOfRedirects";
342
                } else {
343
                    $url .= "?err=$currentNumberOfRedirects";
344
                }
345
            }
346
        }
347
        if ($type == 'REDIRECT_REFRESH') {
348
            $header = 'Refresh: 0;URL=' . $url;
349
        } elseif ($type == 'REDIRECT_META') {
350
            $header = '<META HTTP-EQUIV="Refresh" CONTENT="0; URL=' . $url . '" />';
351
            echo $header;
352
            exit;
353
        } elseif ($type == 'REDIRECT_HEADER' || empty ($type)) {
354
            // check if url has /$base_url
355
            if (substr($url, 0, strlen(MODX_BASE_URL)) == MODX_BASE_URL) {
356
                // append $site_url to make it work with Location:
357
                $url = MODX_SITE_URL . substr($url, strlen(MODX_BASE_URL));
358
            }
359
            if (strpos($url, "\n") === false) {
360
                $header = 'Location: ' . $url;
361
            } else {
362
                $this->getService('ExceptionHandler')->messageQuit('No newline allowed in redirect url.');
363
            }
364
        }
365
        if ($responseCode && (strpos($responseCode, '30') !== false)) {
366
            header($responseCode);
367
        }
368
369
        if (!empty($header)) {
370
            header($header);
371
        }
372
373
        exit(0);
374
    }
375
376
    /**
377
     * Forward to another page
378
     *
379
     * @param int|string $id
380
     * @param string $responseCode
381
     */
382
    public function sendForward($id, $responseCode = '')
383
    {
384
        if ($this->forwards > 0) {
385
            $this->forwards = $this->forwards - 1;
386
            $this->documentIdentifier = $id;
387
            $this->documentMethod = 'id';
388
            if ($responseCode) {
389
                header($responseCode);
390
            }
391
            $this->prepareResponse();
392
            exit();
393
        } else {
394
            $this->getService('ExceptionHandler')->messageQuit("Internal Server Error id={$id}");
395
            header('HTTP/1.0 500 Internal Server Error');
396
            die('<h1>ERROR: Too many forward attempts!</h1><p>The request could not be completed due to too many unsuccessful forward attempts.</p>');
397
        }
398
    }
399
400
    /**
401
     * Redirect to the error page, by calling sendForward(). This is called for example when the page was not found.
402
     * @param bool $noEvent
403
     */
404 View Code Duplication
    public function sendErrorPage($noEvent = false)
405
    {
406
        $this->systemCacheKey = 'notfound';
407
        if (!$noEvent) {
408
            // invoke OnPageNotFound event
409
            $this->invokeEvent('OnPageNotFound');
410
        }
411
        $url = UrlProcessor::getNotFoundPageId();
412
413
        $this->sendForward($url, 'HTTP/1.0 404 Not Found');
414
        exit();
415
    }
416
417
    /**
418
     * @param bool $noEvent
419
     */
420 View Code Duplication
    public function sendUnauthorizedPage($noEvent = false)
421
    {
422
        // invoke OnPageUnauthorized event
423
        $_REQUEST['refurl'] = $this->documentIdentifier;
424
        $this->systemCacheKey = 'unauth';
425
        if (!$noEvent) {
426
            $this->invokeEvent('OnPageUnauthorized');
427
        }
428
429
        $this->sendForward(UrlProcessor::getUnAuthorizedPageId(), 'HTTP/1.1 401 Unauthorized');
430
        exit();
431
    }
432
433
    /**
434
     * Returns the document identifier of the current request
435
     *
436
     * @param string $method id and alias are allowed
437
     * @return int
438
     */
439
    public function getDocumentIdentifier($method)
440
    {
441
        // function to test the query and find the retrieval method
442
        if ($method === 'alias') {
443
            return $this->getDatabase()->escape($_REQUEST['q']);
444
        }
445
446
        $id_ = filter_input(INPUT_GET, 'id');
447
        if ($id_) {
448
            if (preg_match('@^[1-9]\d*$@', $id_)) {
449
                return $id_;
450
            } else {
451
                $this->sendErrorPage();
452
            }
453
        } elseif (strpos($_SERVER['REQUEST_URI'], 'index.php/') !== false) {
454
            $this->sendErrorPage();
455
        } else {
456
            return $this->getConfig('site_start');
457
        }
458
    }
459
460
    /**
461
     * Check for manager or webuser login session since v1.2
462
     *
463
     * @param string $context
464
     * @return bool
465
     */
466
    public function isLoggedIn($context = 'mgr')
467
    {
468
        if (substr($context, 0, 1) == 'm') {
469
            $_ = 'mgrValidated';
470
        } else {
471
            $_ = 'webValidated';
472
        }
473
474
        if (is_cli() || (isset($_SESSION[$_]) && !empty($_SESSION[$_]))) {
0 ignored issues
show
Coding Style introduced by
The if-else statement can be simplified to return is_cli() || isset... !empty($_SESSION[$_]);.
Loading history...
475
            return true;
476
        } else {
477
            return false;
478
        }
479
    }
480
481
    /**
482
     * Check for manager login session
483
     *
484
     * @return boolean
485
     */
486
    public function checkSession()
487
    {
488
        return $this->isLoggedin();
489
    }
490
491
    /**
492
     * Checks, if a the result is a preview
493
     *
494
     * @return boolean
495
     */
496
    public function checkPreview()
497
    {
498
        if ($this->isLoggedIn() == true) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
499
            if (isset ($_REQUEST['z']) && $_REQUEST['z'] == 'manprev') {
0 ignored issues
show
Coding Style introduced by
The if-else statement can be simplified to return isset($_REQUEST['...UEST['z'] == 'manprev';.
Loading history...
500
                return true;
501
            } else {
502
                return false;
503
            }
504
        } else {
505
            return false;
506
        }
507
    }
508
509
    /**
510
     * check if site is offline
511
     *
512
     * @return boolean
513
     */
514
    public function checkSiteStatus()
515
    {
516
        if ($this->getConfig('site_status')) {
517
            return true;
518
        }  // site online
519
        elseif ($this->isLoggedin()) {
520
            return true;
521
        }  // site offline but launched via the manager
522
        else {
523
            return false;
524
        } // site is offline
525
    }
526
527
    /**
528
     * @deprecated use UrlProcessor::cleanDocumentIdentifier()
529
     */
530
    public function cleanDocumentIdentifier($qOrig) : string
531
    {
532
        return UrlProcessor::cleanDocumentIdentifier($qOrig, $this->documentMethod);
533
    }
534
535
    /**
536
     * @param $id
537
     * @return array|mixed|null|string
538
     */
539
    public function makePageCacheKey($id)
540
    {
541
        $hash = $id;
542
        $tmp = null;
543
        $params = array();
544
        if (!empty($this->systemCacheKey)) {
545
            $hash = $this->systemCacheKey;
546
        } else {
547
            if (!empty($_GET)) {
548
                // Sort GET parameters so that the order of parameters on the HTTP request don't affect the generated cache ID.
549
                $params = $_GET;
550
                ksort($params);
551
                $hash .= '_' . md5(http_build_query($params));
552
            }
553
        }
554
        $evtOut = $this->invokeEvent("OnMakePageCacheKey", array("hash" => $hash, "id" => $id, 'params' => $params));
555
        if (is_array($evtOut) && count($evtOut) > 0) {
556
            $tmp = array_pop($evtOut);
557
        }
558
559
        return empty($tmp) ? $hash : $tmp;
560
    }
561
562
    /**
563
     * @param $id
564
     * @param bool $loading
565
     * @return string
566
     */
567
    public function checkCache($id, $loading = false)
568
    {
569
        return $this->getDocumentObjectFromCache($id, $loading);
570
    }
571
572
    /**
573
     * Check the cache for a specific document/resource
574
     *
575
     * @param int $id
576
     * @param bool $loading
577
     * @return string
578
     */
579
    public function getDocumentObjectFromCache($id, $loading = false)
580
    {
581
        $key = ($this->getConfig('cache_type') == 2) ? $this->makePageCacheKey($id) : $id;
582
        if ($loading) {
583
            $this->cacheKey = $key;
584
        }
585
586
        $cache_path = $this->getHashFile($key);
587
588
        if (!is_file($cache_path)) {
589
            $this->documentGenerated = 1;
590
591
            return '';
592
        }
593
        $content = file_get_contents($cache_path, false);
594
        if (substr($content, 0, 5) === '<?php') {
595
            $content = substr($content, strpos($content, '?>') + 2);
596
        } // remove php header
597
        $a = explode('<!--__MODxCacheSpliter__-->', $content, 2);
598
        if (count($a) == 1) {
599
            $result = $a[0];
600
        } // return only document content
601
        else {
602
            $docObj = unserialize($a[0]); // rebuild document object
603
            // check page security
604
            if ($docObj['privateweb'] && isset ($docObj['__MODxDocGroups__'])) {
605
                $pass = false;
606
                $usrGrps = $this->getUserDocGroups();
607
                $docGrps = explode(',', $docObj['__MODxDocGroups__']);
608
                // check is user has access to doc groups
609
                if (is_array($usrGrps)) {
610
                    foreach ($usrGrps as $k => $v) {
611
                        if (!in_array($v, $docGrps)) {
612
                            continue;
613
                        }
614
                        $pass = true;
615
                        break;
616
                    }
617
                }
618
                // diplay error pages if user has no access to cached doc
619
                if (!$pass) {
620
                    if ($this->getConfig('unauthorized_page')) {
621
                        // check if file is not public
622
                        $rs = $this->getDatabase()->select(
623
                            'count(id)',
624
                            $this->getDatabase()->getFullTableName('document_groups'),
625
                            "document='{$id}'",
626
                            '',
627
                            '1'
628
                        );
629
                        $total = $this->getDatabase()->getValue($rs);
630
                    } else {
631
                        $total = 0;
632
                    }
633
634
                    if ($total > 0) {
635
                        $this->sendUnauthorizedPage();
636
                    } else {
637
                        $this->sendErrorPage();
638
                    }
639
640
                    exit; // stop here
641
                }
642
            }
643
            // Grab the Scripts
644
            if (isset($docObj['__MODxSJScripts__'])) {
645
                $this->sjscripts = $docObj['__MODxSJScripts__'];
646
            }
647
            if (isset($docObj['__MODxJScripts__'])) {
648
                $this->jscripts = $docObj['__MODxJScripts__'];
649
            }
650
651
            // Remove intermediate variables
652
            unset($docObj['__MODxDocGroups__'], $docObj['__MODxSJScripts__'], $docObj['__MODxJScripts__']);
653
654
            $this->documentObject = $docObj;
655
656
            $result = $a[1]; // return document content
657
        }
658
659
        $this->documentGenerated = 0;
660
        // invoke OnLoadWebPageCache  event
661
        $this->documentContent = $result;
662
        $this->invokeEvent('OnLoadWebPageCache');
663
664
        return $result;
665
    }
666
667
    /**
668
     * Final processing and output of the document/resource.
669
     *
670
     * - runs uncached snippets
671
     * - add javascript to <head>
672
     * - removes unused placeholders
673
     * - converts URL tags [~...~] to URLs
674
     *
675
     * @param boolean $noEvent Default: false
676
     */
677
    public function outputContent($noEvent = false)
678
    {
679
        $this->documentOutput = $this->documentContent;
680
681
        if ($this->documentGenerated == 1 && $this->documentObject['cacheable'] == 1 && $this->documentObject['type'] == 'document' && $this->documentObject['published'] == 1) {
682
            if (!empty($this->sjscripts)) {
683
                $this->documentObject['__MODxSJScripts__'] = $this->sjscripts;
684
            }
685
            if (!empty($this->jscripts)) {
686
                $this->documentObject['__MODxJScripts__'] = $this->jscripts;
687
            }
688
        }
689
690
        // check for non-cached snippet output
691
        if (strpos($this->documentOutput, '[!') > -1) {
692
            $this->recentUpdate = $_SERVER['REQUEST_TIME'] + $this->getConfig('server_offset_time');
693
694
            $this->documentOutput = str_replace('[!', '[[', $this->documentOutput);
695
            $this->documentOutput = str_replace('!]', ']]', $this->documentOutput);
696
697
            // Parse document source
698
            $this->documentOutput = $this->parseDocumentSource($this->documentOutput);
699
        }
700
701
        // Moved from prepareResponse() by sirlancelot
702
        // Insert Startup jscripts & CSS scripts into template - template must have a <head> tag
703
        if ($js = $this->getRegisteredClientStartupScripts()) {
704
            // change to just before closing </head>
705
            // $this->documentContent = preg_replace("/(<head[^>]*>)/i", "\\1\n".$js, $this->documentContent);
706
            $this->documentOutput = preg_replace("/(<\/head>)/i", $js . "\n\\1", $this->documentOutput);
707
        }
708
709
        // Insert jscripts & html block into template - template must have a </body> tag
710
        if ($js = $this->getRegisteredClientScripts()) {
711
            $this->documentOutput = preg_replace("/(<\/body>)/i", $js . "\n\\1", $this->documentOutput);
712
        }
713
        // End fix by sirlancelot
714
715
        $this->documentOutput = $this->cleanUpMODXTags($this->documentOutput);
716
717
        $this->documentOutput = $this->rewriteUrls($this->documentOutput);
0 ignored issues
show
Deprecated Code introduced by
The method EvolutionCMS\Core::rewriteUrls() has been deprecated with message: use UrlProcessor::rewriteUrls()

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
718
719
        // send out content-type and content-disposition headers
720
        if (IN_PARSER_MODE == "true") {
721
            $type = !empty ($this->contentTypes[$this->documentIdentifier]) ? $this->contentTypes[$this->documentIdentifier] : "text/html";
722
            header('Content-Type: ' . $type . '; charset=' . $this->getConfig('modx_charset'));
723
            //            if (($this->documentIdentifier == $this->config['error_page']) || $redirect_error)
724
            //                header('HTTP/1.0 404 Not Found');
725
            if (!$this->checkPreview() && $this->documentObject['content_dispo'] == 1) {
726
                if ($this->documentObject['alias']) {
727
                    $name = $this->documentObject['alias'];
728
                } else {
729
                    // strip title of special characters
730
                    $name = $this->documentObject['pagetitle'];
731
                    $name = strip_tags($name);
732
                    $name = $this->cleanUpMODXTags($name);
733
                    $name = strtolower($name);
734
                    $name = preg_replace('/&.+?;/', '', $name); // kill entities
735
                    $name = preg_replace('/[^\.%a-z0-9 _-]/', '', $name);
736
                    $name = preg_replace('/\s+/', '-', $name);
737
                    $name = preg_replace('|-+|', '-', $name);
738
                    $name = trim($name, '-');
739
                }
740
                $header = 'Content-Disposition: attachment; filename=' . $name;
741
                header($header);
742
            }
743
        }
744
        $this->setConditional();
745
746
        $stats = $this->getTimerStats($this->tstart);
747
748
        $out =& $this->documentOutput;
749
        $out = str_replace("[^q^]", $stats['queries'], $out);
750
        $out = str_replace("[^qt^]", $stats['queryTime'], $out);
751
        $out = str_replace("[^p^]", $stats['phpTime'], $out);
752
        $out = str_replace("[^t^]", $stats['totalTime'], $out);
753
        $out = str_replace("[^s^]", $stats['source'], $out);
754
        $out = str_replace("[^m^]", $stats['phpMemory'], $out);
755
        //$this->documentOutput= $out;
756
757
        // invoke OnWebPagePrerender event
758
        if (!$noEvent) {
759
            $evtOut = $this->invokeEvent('OnWebPagePrerender', array('documentOutput' => $this->documentOutput));
760
            if (is_array($evtOut) && count($evtOut) > 0 && !empty($evtOut['0'])) {
761
                $this->documentOutput = $evtOut['0'];
762
            }
763
        }
764
765
        $this->documentOutput = removeSanitizeSeed($this->documentOutput);
766
767
        if (strpos($this->documentOutput, '\{') !== false) {
768
            $this->documentOutput = $this->RecoveryEscapedTags($this->documentOutput);
769
        } elseif (strpos($this->documentOutput, '\[') !== false) {
770
            $this->documentOutput = $this->RecoveryEscapedTags($this->documentOutput);
771
        }
772
773
        echo $this->documentOutput;
774
775
        if ($this->dumpSQL) {
776
            echo $this->queryCode;
777
        }
778
        if ($this->dumpSnippets) {
779
            $sc = "";
780
            $tt = 0;
781
            foreach ($this->snippetsTime as $s => $v) {
782
                $t = $v['time'];
783
                $sname = $v['sname'];
784
                $sc .= sprintf("%s. %s (%s)<br>", $s, $sname, sprintf("%2.2f ms", $t)); // currentSnippet
785
                $tt += $t;
786
            }
787
            echo "<fieldset><legend><b>Snippets</b> (" . count($this->snippetsTime) . " / " . sprintf("%2.2f ms",
788
                    $tt) . ")</legend>{$sc}</fieldset><br />";
789
            echo $this->snippetsCode;
790
        }
791
        if ($this->dumpPlugins) {
792
            $ps = "";
793
            $tt = 0;
794
            foreach ($this->pluginsTime as $s => $t) {
795
                $ps .= "$s (" . sprintf("%2.2f ms", $t * 1000) . ")<br>";
796
                $tt += $t;
797
            }
798
            echo "<fieldset><legend><b>Plugins</b> (" . count($this->pluginsTime) . " / " . sprintf("%2.2f ms",
799
                    $tt * 1000) . ")</legend>{$ps}</fieldset><br />";
800
            echo $this->pluginsCode;
801
        }
802
803
        ob_end_flush();
804
    }
805
806
    /**
807
     * @param $contents
808
     * @return mixed
809
     */
810
    public function RecoveryEscapedTags($contents)
811
    {
812
        list($sTags, $rTags) = $this->getTagsForEscape();
813
814
        return str_replace($rTags, $sTags, $contents);
815
    }
816
817
    /**
818
     * @param string $tags
819
     * @return array[]
820
     */
821
    public function getTagsForEscape($tags = '{{,}},[[,]],[!,!],[*,*],[(,)],[+,+],[~,~],[^,^]')
822
    {
823
        $srcTags = explode(',', $tags);
824
        $repTags = array();
825
        foreach ($srcTags as $tag) {
826
            $repTags[] = '\\' . $tag[0] . '\\' . $tag[1];
827
        }
828
829
        return array($srcTags, $repTags);
830
    }
831
832
    /**
833
     * @param $tstart
834
     * @return array
835
     */
836
    public function getTimerStats($tstart)
837
    {
838
        $stats = array();
839
840
        $stats['totalTime'] = ($this->getMicroTime() - $tstart);
841
        $stats['queryTime'] = $this->queryTime;
842
        $stats['phpTime'] = $stats['totalTime'] - $stats['queryTime'];
843
844
        $stats['queryTime'] = sprintf("%2.4f s", $stats['queryTime']);
845
        $stats['totalTime'] = sprintf("%2.4f s", $stats['totalTime']);
846
        $stats['phpTime'] = sprintf("%2.4f s", $stats['phpTime']);
847
        $stats['source'] = $this->documentGenerated == 1 ? "database" : "cache";
848
        $stats['queries'] = isset ($this->executedQueries) ? $this->executedQueries : 0;
849
        $stats['phpMemory'] = (memory_get_peak_usage(true) / 1024 / 1024) . " mb";
850
851
        return $stats;
852
    }
853
854
    public function setConditional()
855
    {
856
        if (!empty($_POST) || (defined('MODX_API_MODE') && MODX_API_MODE) || $this->getLoginUserID('mgr') || !$this->useConditional || empty($this->recentUpdate)) {
857
            return;
858
        }
859
        $last_modified = gmdate('D, d M Y H:i:s T', $this->recentUpdate);
860
        $etag = md5($last_modified);
861
        $HTTP_IF_MODIFIED_SINCE = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? $_SERVER['HTTP_IF_MODIFIED_SINCE'] : false;
862
        $HTTP_IF_NONE_MATCH = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? $_SERVER['HTTP_IF_NONE_MATCH'] : false;
863
        header('Pragma: no-cache');
864
865
        if ($HTTP_IF_MODIFIED_SINCE == $last_modified || strpos($HTTP_IF_NONE_MATCH, $etag) !== false) {
866
            header('HTTP/1.1 304 Not Modified');
867
            header('Content-Length: 0');
868
            exit;
869
        } else {
870
            header("Last-Modified: {$last_modified}");
871
            header("ETag: '{$etag}'");
872
        }
873
    }
874
875
    /**
876
     * Checks the publish state of page
877
     */
878
    public function updatePubStatus()
879
    {
880
        $cacheRefreshTime = 0;
881
        $recent_update = 0;
882
        if(file_exists($this->getSitePublishingFilePath())) {
883
            @include($this->getSitePublishingFilePath());
884
        }
885
        $this->recentUpdate = $recent_update;
886
887
        $timeNow = $_SERVER['REQUEST_TIME'] + $this->getConfig('server_offset_time');
888
        if ($timeNow < $cacheRefreshTime || $cacheRefreshTime == 0) {
889
            return;
890
        }
891
892
        // now, check for documents that need publishing
893
        $field = array('published' => 1, 'publishedon' => $timeNow);
894
        $where = "pub_date <= {$timeNow} AND pub_date!=0 AND published=0";
895
        $result_pub = $this->getDatabase()->select(
896
            'id',
897
            $this->getDatabase()->getFullTableName('site_content'),
898
            $where
899
        );
900
        $this->getDatabase()->update($field, $this->getDatabase()->getFullTableName('site_content'), $where);
901 View Code Duplication
        if ($this->getDatabase()->getRecordCount($result_pub) >= 1) { //Event unPublished doc
902
            while ($row_pub = $this->getDatabase()->getRow($result_pub)) {
903
                $this->invokeEvent("OnDocUnPublished", array(
904
                    "docid" => $row_pub['id']
905
                ));
906
            }
907
        }
908
909
        // now, check for documents that need un-publishing
910
        $field = array('published' => 0, 'publishedon' => 0);
911
        $where = "unpub_date <= {$timeNow} AND unpub_date!=0 AND published=1";
912
        $result_unpub = $this->getDatabase()->select(
913
            'id',
914
            $this->getDatabase()->getFullTableName('site_content'),
915
            $where
916
        );
917
        $this->getDatabase()->update($field, $this->getDatabase()->getFullTableName('site_content'), $where);
918 View Code Duplication
        if ($this->getDatabase()->getRecordCount($result_unpub) >= 1) { //Event unPublished doc
919
            while ($row_unpub = $this->getDatabase()->getRow($result_unpub)) {
920
                $this->invokeEvent("OnDocUnPublished", array(
921
                    "docid" => $row_unpub['id']
922
                ));
923
            }
924
        }
925
926
        $this->recentUpdate = $timeNow;
927
928
        // clear the cache
929
        $this->clearCache('full');
930
    }
931
932
    public function checkPublishStatus()
933
    {
934
        $this->updatePubStatus();
935
    }
936
937
    /**
938
     * Final jobs.
939
     *
940
     * - cache page
941
     */
942
    public function postProcess()
943
    {
944
        // if the current document was generated, cache it!
945
        $cacheable = ($this->getConfig('enable_cache') && $this->documentObject['cacheable']) ? 1 : 0;
946
        if ($cacheable && $this->documentGenerated && $this->documentObject['type'] == 'document' && $this->documentObject['published']) {
947
            // invoke OnBeforeSaveWebPageCache event
948
            $this->invokeEvent("OnBeforeSaveWebPageCache");
949
950
            if (!empty($this->cacheKey) && is_scalar($this->cacheKey)) {
951
                // get and store document groups inside document object. Document groups will be used to check security on cache pages
952
                $where = "document='{$this->documentIdentifier}'";
953
                $rs = $this->getDatabase()->select(
954
                    'document_group',
955
                    $this->getDatabase()->getFullTableName('document_groups'),
956
                    $where
957
                );
958
                $docGroups = $this->getDatabase()->getColumn('document_group', $rs);
959
960
                // Attach Document Groups and Scripts
961
                if (is_array($docGroups)) {
962
                    $this->documentObject['__MODxDocGroups__'] = implode(",", $docGroups);
963
                }
964
965
                $docObjSerial = serialize($this->documentObject);
966
                $cacheContent = $docObjSerial . "<!--__MODxCacheSpliter__-->" . $this->documentContent;
967
                $page_cache_path = $this->getHashFile($this->cacheKey);
968
                file_put_contents($page_cache_path, "<?php die('Unauthorized access.'); ?>$cacheContent");
969
            }
970
        }
971
972
        // Useful for example to external page counters/stats packages
973
        $this->invokeEvent('OnWebPageComplete');
974
975
        // end post processing
976
    }
977
978
    /**
979
     * @param $content
980
     * @param string $left
981
     * @param string $right
982
     * @return array
983
     */
984
    public function getTagsFromContent($content, $left = '[+', $right = '+]')
985
    {
986
        $_ = $this->_getTagsFromContent($content, $left, $right);
987
        if (empty($_)) {
988
            return array();
989
        }
990
        foreach ($_ as $v) {
991
            $tags[0][] = "{$left}{$v}{$right}";
0 ignored issues
show
Coding Style Comprehensibility introduced by
$tags was never initialized. Although not strictly required by PHP, it is generally a good practice to add $tags = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
992
            $tags[1][] = $v;
993
        }
994
995
        return $tags;
0 ignored issues
show
Bug introduced by
The variable $tags does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
996
    }
997
998
    /**
999
     * @param $content
1000
     * @param string $left
1001
     * @param string $right
1002
     * @return array
1003
     */
1004
    public function _getTagsFromContent($content, $left = '[+', $right = '+]')
1005
    {
1006
        if (strpos($content, $left) === false) {
1007
            return array();
1008
        }
1009
        $spacer = md5('<<<EVO>>>');
1010
        if ($left === '{{' && strpos($content, ';}}') !== false) {
1011
            $content = str_replace(';}}', sprintf(';}%s}', $spacer), $content);
1012
        }
1013
        if ($left === '{{' && strpos($content, '{{}}') !== false) {
1014
            $content = str_replace('{{}}', sprintf('{%$1s{}%$1s}', $spacer), $content);
1015
        }
1016
        if ($left === '[[' && strpos($content, ']]]]') !== false) {
1017
            $content = str_replace(']]]]', sprintf(']]%s]]', $spacer), $content);
1018
        }
1019
        if ($left === '[[' && strpos($content, ']]]') !== false) {
1020
            $content = str_replace(']]]', sprintf(']%s]]', $spacer), $content);
1021
        }
1022
1023
        $pos['<![CDATA['] = strpos($content, '<![CDATA[');
0 ignored issues
show
Coding Style Comprehensibility introduced by
$pos was never initialized. Although not strictly required by PHP, it is generally a good practice to add $pos = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
1024
        $pos[']]>'] = strpos($content, ']]>');
1025
1026
        if ($pos['<![CDATA['] !== false && $pos[']]>'] !== false) {
1027
            $content = substr($content, 0, $pos['<![CDATA[']) . substr($content, $pos[']]>'] + 3);
1028
        }
1029
1030
        $lp = explode($left, $content);
1031
        $piece = array();
1032
        foreach ($lp as $lc => $lv) {
1033
            if ($lc !== 0) {
1034
                $piece[] = $left;
1035
            }
1036
            if (strpos($lv, $right) === false) {
1037
                $piece[] = $lv;
1038
            } else {
1039
                $rp = explode($right, $lv);
1040
                foreach ($rp as $rc => $rv) {
1041
                    if ($rc !== 0) {
1042
                        $piece[] = $right;
1043
                    }
1044
                    $piece[] = $rv;
1045
                }
1046
            }
1047
        }
1048
        $lc = 0;
1049
        $rc = 0;
1050
        $fetch = '';
1051
        $tags = array();
1052
        foreach ($piece as $v) {
1053
            if ($v === $left) {
1054
                if (0 < $lc) {
1055
                    $fetch .= $left;
1056
                }
1057
                $lc++;
1058
            } elseif ($v === $right) {
1059
                if ($lc === 0) {
1060
                    continue;
1061
                }
1062
                $rc++;
1063
                if ($lc === $rc) {
1064
                    // #1200 Enable modifiers in Wayfinder - add nested placeholders to $tags like for $fetch = "phx:input=`[+wf.linktext+]`:test"
1065
                    if (strpos($fetch, $left) !== false) {
1066
                        $nested = $this->_getTagsFromContent($fetch, $left, $right);
1067
                        foreach ($nested as $tag) {
1068
                            if (!in_array($tag, $tags)) {
1069
                                $tags[] = $tag;
1070
                            }
1071
                        }
1072
                    }
1073
1074
                    if (!in_array($fetch, $tags)) {  // Avoid double Matches
1075
                        $tags[] = $fetch; // Fetch
1076
                    };
1077
                    $fetch = ''; // and reset
1078
                    $lc = 0;
1079
                    $rc = 0;
1080
                } else {
1081
                    $fetch .= $right;
1082
                }
1083
            } else {
1084
                if (0 < $lc) {
1085
                    $fetch .= $v;
1086
                } else {
1087
                    continue;
1088
                }
1089
            }
1090
        }
1091
        foreach ($tags as $i => $tag) {
1092
            if (strpos($tag, $spacer) !== false) {
1093
                $tags[$i] = str_replace($spacer, '', $tag);
1094
            }
1095
        }
1096
1097
        return $tags;
1098
    }
1099
1100
    /**
1101
     * Merge content fields and TVs
1102
     *
1103
     * @param $content
1104
     * @param bool $ph
1105
     * @return string
1106
     * @internal param string $template
1107
     */
1108
    public function mergeDocumentContent($content, $ph = false)
1109
    {
1110 View Code Duplication
        if ($this->getConfig('enable_at_syntax')) {
1111
            if (stripos($content, '<@LITERAL>') !== false) {
1112
                $content = $this->escapeLiteralTagsContent($content);
1113
            }
1114
        }
1115
        if (strpos($content, '[*') === false) {
1116
            return $content;
1117
        }
1118
        if (!isset($this->documentIdentifier)) {
1119
            return $content;
1120
        }
1121
        if (!isset($this->documentObject) || empty($this->documentObject)) {
1122
            return $content;
1123
        }
1124
1125
        if (!$ph) {
1126
            $ph = $this->documentObject;
1127
        }
1128
1129
        $matches = $this->getTagsFromContent($content, '[*', '*]');
1130
        if (!$matches) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $matches of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1131
            return $content;
1132
        }
1133
1134
        foreach ($matches[1] as $i => $key) {
1135
            if (strpos($key, '[+') !== false) {
1136
                continue;
1137
            } // Allow chunk {{chunk?&param=`xxx`}} with [*tv_name_[+param+]*] as content
1138
            if (substr($key, 0, 1) == '#') {
1139
                $key = substr($key, 1);
1140
            } // remove # for QuickEdit format
1141
1142
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1143
            if (strpos($key, '@') !== false) {
1144
                list($key, $context) = explode('@', $key, 2);
1145
            } else {
1146
                $context = false;
1147
            }
1148
1149
            // if(!isset($ph[$key]) && !$context) continue; // #1218 TVs/PHs will not be rendered if custom_meta_title is not assigned to template like [*custom_meta_title:ne:then=`[*custom_meta_title*]`:else=`[*pagetitle*]`*]
1150
            if ($context) {
1151
                $value = $this->_contextValue("{$key}@{$context}", $this->documentObject['parent']);
0 ignored issues
show
Documentation introduced by
$this->documentObject['parent'] is of type array|string, but the function expects a boolean|integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1152
            } else {
1153
                $value = isset($ph[$key]) ? $ph[$key] : '';
1154
            }
1155
1156 View Code Duplication
            if (is_array($value)) {
1157
                $value = getTVDisplayFormat($value[0], $value[1], $value[2], $value[3], $value[4]);
1158
            }
1159
1160
            $s = &$matches[0][$i];
1161
            if ($modifiers !== false) {
1162
                $value = $this->applyFilter($value, $modifiers, $key);
1163
            }
1164
1165 View Code Duplication
            if (strpos($content, $s) !== false) {
1166
                $content = str_replace($s, $value, $content);
1167
            } elseif ($this->debug) {
1168
                $this->addLog('mergeDocumentContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1169
            }
1170
        }
1171
1172
        return $content;
1173
    }
1174
1175
    /**
1176
     * @param $key
1177
     * @param bool|int $parent
1178
     * @return bool|mixed|string
1179
     */
1180
    public function _contextValue($key, $parent = false)
1181
    {
1182 View Code Duplication
        if (preg_match('/@\d+\/u/', $key)) {
1183
            $key = str_replace(array('@', '/u'), array('@u(', ')'), $key);
1184
        }
1185
        list($key, $str) = explode('@', $key, 2);
1186
1187 View Code Duplication
        if (strpos($str, '(')) {
1188
            list($context, $option) = explode('(', $str, 2);
1189
        } else {
1190
            list($context, $option) = array($str, false);
1191
        }
1192
1193
        if ($option) {
1194
            $option = trim($option, ')(\'"`');
1195
        }
1196
1197
        switch (strtolower($context)) {
1198
            case 'site_start':
1199
                $docid = $this->getConfig('site_start');
1200
                break;
1201
            case 'parent':
1202
            case 'p':
1203
                $docid = $parent;
1204
                if ($docid == 0) {
1205
                    $docid = $this->getConfig('site_start');
1206
                }
1207
                break;
1208
            case 'ultimateparent':
1209
            case 'uparent':
1210
            case 'up':
1211
            case 'u':
1212 View Code Duplication
                if (strpos($str, '(') !== false) {
1213
                    $top = substr($str, strpos($str, '('));
1214
                    $top = trim($top, '()"\'');
1215
                } else {
1216
                    $top = 0;
1217
                }
1218
                $docid = $this->getUltimateParentId($this->documentIdentifier, $top);
1219
                break;
1220
            case 'alias':
1221
                $str = substr($str, strpos($str, '('));
1222
                $str = trim($str, '()"\'');
1223
                $docid = UrlProcessor::getIdFromAlias($str);
1224
                break;
1225 View Code Duplication
            case 'prev':
1226
                if (!$option) {
1227
                    $option = 'menuindex,ASC';
1228
                } elseif (strpos($option, ',') === false) {
1229
                    $option .= ',ASC';
1230
                }
1231
                list($by, $dir) = explode(',', $option, 2);
1232
                $children = $this->getActiveChildren($parent, $by, $dir);
1233
                $find = false;
1234
                $prev = false;
1235
                foreach ($children as $row) {
1236
                    if ($row['id'] == $this->documentIdentifier) {
1237
                        $find = true;
1238
                        break;
1239
                    }
1240
                    $prev = $row;
1241
                }
1242
                if ($find) {
1243
                    if (isset($prev[$key])) {
1244
                        return $prev[$key];
1245
                    } else {
1246
                        $docid = $prev['id'];
1247
                    }
1248
                } else {
1249
                    $docid = '';
1250
                }
1251
                break;
1252 View Code Duplication
            case 'next':
1253
                if (!$option) {
1254
                    $option = 'menuindex,ASC';
1255
                } elseif (strpos($option, ',') === false) {
1256
                    $option .= ',ASC';
1257
                }
1258
                list($by, $dir) = explode(',', $option, 2);
1259
                $children = $this->getActiveChildren($parent, $by, $dir);
1260
                $find = false;
1261
                $next = false;
1262
                foreach ($children as $row) {
1263
                    if ($find) {
1264
                        $next = $row;
1265
                        break;
1266
                    }
1267
                    if ($row['id'] == $this->documentIdentifier) {
1268
                        $find = true;
1269
                    }
1270
                }
1271
                if ($find) {
1272
                    if (isset($next[$key])) {
1273
                        return $next[$key];
1274
                    } else {
1275
                        $docid = $next['id'];
1276
                    }
1277
                } else {
1278
                    $docid = '';
1279
                }
1280
                break;
1281
            default:
1282
                $docid = $str;
1283
        }
1284
        if (preg_match('@^[1-9]\d*$@', $docid)) {
1285
            $value = $this->getField($key, $docid);
1286
        } else {
1287
            $value = '';
1288
        }
1289
1290
        return $value;
1291
    }
1292
1293
    /**
1294
     * Merge system settings
1295
     *
1296
     * @param $content
1297
     * @param bool|array $ph
1298
     * @return string
1299
     * @internal param string $template
1300
     */
1301
    public function mergeSettingsContent($content, $ph = false)
1302
    {
1303 View Code Duplication
        if ($this->getConfig('enable_at_syntax')) {
1304
            if (stripos($content, '<@LITERAL>') !== false) {
1305
                $content = $this->escapeLiteralTagsContent($content);
1306
            }
1307
        }
1308
        if (strpos($content, '[(') === false) {
1309
            return $content;
1310
        }
1311
1312
        if (empty($ph)) {
1313
1314
            $ph = array_merge(
1315
                $this->allConfig(),
1316
                [
1317
                    'base_url' => MODX_BASE_URL,
1318
                    'base_path' => MODX_BASE_PATH,
1319
                    'site_url' => MODX_SITE_URL,
1320
                    'valid_hostnames' => MODX_SITE_HOSTNAMES,
1321
                    'site_manager_url' => MODX_MANAGER_URL,
1322
                    'site_manager_path' => MODX_MANAGER_PATH
1323
                ]
1324
            );
1325
        }
1326
1327
        $matches = $this->getTagsFromContent($content, '[(', ')]');
1328
        if (empty($matches)) {
1329
            return $content;
1330
        }
1331
1332
        foreach ($matches[1] as $i => $key) {
1333
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1334
1335
            if (isset($ph[$key])) {
1336
                $value = $ph[$key];
1337
            } else {
1338
                continue;
1339
            }
1340
1341
            if ($modifiers !== false) {
1342
                $value = $this->applyFilter($value, $modifiers, $key);
1343
            }
1344
            $s = &$matches[0][$i];
1345 View Code Duplication
            if (strpos($content, $s) !== false) {
1346
                $content = str_replace($s, $value, $content);
1347
            } elseif ($this->debug) {
1348
                $this->addLog('mergeSettingsContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1349
            }
1350
        }
1351
1352
        return $content;
1353
    }
1354
1355
    /**
1356
     * Merge chunks
1357
     *
1358
     * @param string $content
1359
     * @param bool|array $ph
1360
     * @return string
1361
     */
1362
    public function mergeChunkContent($content, $ph = false)
1363
    {
1364
        if ($this->getConfig('enable_at_syntax')) {
1365
            if (strpos($content, '{{ ') !== false) {
1366
                $content = str_replace(array('{{ ', ' }}'), array('\{\{ ', ' \}\}'), $content);
1367
            }
1368
            if (stripos($content, '<@LITERAL>') !== false) {
1369
                $content = $this->escapeLiteralTagsContent($content);
1370
            }
1371
        }
1372
        if (strpos($content, '{{') === false) {
1373
            return $content;
1374
        }
1375
1376
        if (empty($ph)) {
1377
            $ph = $this->chunkCache;
1378
        }
1379
1380
        $matches = $this->getTagsFromContent($content, '{{', '}}');
1381
        if (empty($matches)) {
1382
            return $content;
1383
        }
1384
1385
        foreach ($matches[1] as $i => $key) {
1386
            $snip_call = $this->_split_snip_call($key);
1387
            $key = $snip_call['name'];
1388
            $params = $this->getParamsFromString($snip_call['params']);
1389
1390
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1391
1392
            if (!isset($ph[$key])) {
1393
                $ph[$key] = $this->getChunk($key);
1394
            }
1395
            $value = $ph[$key];
1396
1397
            if (is_null($value)) {
1398
                continue;
1399
            }
1400
1401
            $value = $this->parseText($value, $params); // parse local scope placeholers for ConditionalTags
0 ignored issues
show
Bug introduced by
It seems like $params defined by $this->getParamsFromString($snip_call['params']) on line 1388 can also be of type null; however, EvolutionCMS\Core::parseText() does only seem to accept array, 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...
1402
            $value = $this->mergePlaceholderContent($value, $params);  // parse page global placeholers
0 ignored issues
show
Bug introduced by
It seems like $params defined by $this->getParamsFromString($snip_call['params']) on line 1388 can also be of type null; however, EvolutionCMS\Core::mergePlaceholderContent() does only seem to accept boolean|array, 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...
1403
            if ($this->getConfig('enable_at_syntax')) {
1404
                $value = $this->mergeConditionalTagsContent($value);
1405
            }
1406
            $value = $this->mergeDocumentContent($value);
1407
            $value = $this->mergeSettingsContent($value);
1408
            $value = $this->mergeChunkContent($value);
1409
1410
            if ($modifiers !== false) {
1411
                $value = $this->applyFilter($value, $modifiers, $key);
1412
            }
1413
1414
            $s = &$matches[0][$i];
1415 View Code Duplication
            if (strpos($content, $s) !== false) {
1416
                $content = str_replace($s, $value, $content);
1417
            } elseif ($this->debug) {
1418
                $this->addLog('mergeChunkContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1419
            }
1420
        }
1421
1422
        return $content;
1423
    }
1424
1425
    /**
1426
     * Merge placeholder values
1427
     *
1428
     * @param string $content
1429
     * @param bool|array $ph
1430
     * @return string
1431
     */
1432
    public function mergePlaceholderContent($content, $ph = false)
1433
    {
1434
1435 View Code Duplication
        if ($this->getConfig('enable_at_syntax')) {
1436
            if (stripos($content, '<@LITERAL>') !== false) {
1437
                $content = $this->escapeLiteralTagsContent($content);
1438
            }
1439
        }
1440
        if (strpos($content, '[+') === false) {
1441
            return $content;
1442
        }
1443
1444
        if (empty($ph)) {
1445
            $ph = $this->placeholders;
1446
        }
1447
1448
        if ($this->getConfig('enable_at_syntax')) {
1449
            $content = $this->mergeConditionalTagsContent($content);
1450
        }
1451
1452
        $content = $this->mergeDocumentContent($content);
1453
        $content = $this->mergeSettingsContent($content);
1454
        $matches = $this->getTagsFromContent($content, '[+', '+]');
1455
        if (empty($matches)) {
1456
            return $content;
1457
        }
1458
        foreach ($matches[1] as $i => $key) {
1459
1460
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1461
1462
            if (isset($ph[$key])) {
1463
                $value = $ph[$key];
1464
            } elseif ($key === 'phx') {
1465
                $value = '';
1466
            } else {
1467
                continue;
1468
            }
1469
1470
            if ($modifiers !== false) {
1471
                $modifiers = $this->mergePlaceholderContent($modifiers);
1472
                $value = $this->applyFilter($value, $modifiers, $key);
0 ignored issues
show
Documentation introduced by
$modifiers is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1473
            }
1474
            $s = &$matches[0][$i];
1475 View Code Duplication
            if (strpos($content, $s) !== false) {
1476
                $content = str_replace($s, $value, $content);
1477
            } elseif ($this->debug) {
1478
                $this->addLog('mergePlaceholderContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1479
            }
1480
        }
1481
1482
        return $content;
1483
    }
1484
1485
    /**
1486
     * @param $content
1487
     * @param string $iftag
1488
     * @param string $elseiftag
1489
     * @param string $elsetag
1490
     * @param string $endiftag
1491
     * @return mixed|string
1492
     */
1493
    public function mergeConditionalTagsContent(
1494
        $content,
1495
        $iftag = '<@IF:',
1496
        $elseiftag = '<@ELSEIF:',
1497
        $elsetag = '<@ELSE>',
1498
        $endiftag = '<@ENDIF>'
1499
    ) {
1500
        if (strpos($content, '@IF') !== false) {
1501
            $content = $this->_prepareCTag($content, $iftag, $elseiftag, $elsetag, $endiftag);
1502
        }
1503
1504
        if (strpos($content, $iftag) === false) {
1505
            return $content;
1506
        }
1507
1508
        $sp = '#' . md5('ConditionalTags' . $_SERVER['REQUEST_TIME']) . '#';
1509
        $content = str_replace(array('<?php', '<?=', '<?', '?>'), array("{$sp}b", "{$sp}p", "{$sp}s", "{$sp}e"),
1510
            $content);
1511
1512
        $pieces = explode('<@IF:', $content);
1513 View Code Duplication
        foreach ($pieces as $i => $split) {
1514
            if ($i === 0) {
1515
                $content = $split;
1516
                continue;
1517
            }
1518
            list($cmd, $text) = explode('>', $split, 2);
1519
            $cmd = str_replace("'", "\'", $cmd);
1520
            $content .= "<?php if(\$this->_parseCTagCMD('" . $cmd . "')): ?>";
1521
            $content .= $text;
1522
        }
1523
        $pieces = explode('<@ELSEIF:', $content);
1524 View Code Duplication
        foreach ($pieces as $i => $split) {
1525
            if ($i === 0) {
1526
                $content = $split;
1527
                continue;
1528
            }
1529
            list($cmd, $text) = explode('>', $split, 2);
1530
            $cmd = str_replace("'", "\'", $cmd);
1531
            $content .= "<?php elseif(\$this->_parseCTagCMD('" . $cmd . "')): ?>";
1532
            $content .= $text;
1533
        }
1534
1535
        $content = str_replace(array('<@ELSE>', '<@ENDIF>'), array('<?php else:?>', '<?php endif;?>'), $content);
1536
        ob_start();
1537
        $content = eval('?>' . $content);
0 ignored issues
show
Unused Code introduced by
$content is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1538
        $content = ob_get_clean();
1539
        $content = str_replace(array("{$sp}b", "{$sp}p", "{$sp}s", "{$sp}e"), array('<?php', '<?=', '<?', '?>'),
1540
            $content);
1541
1542
        return $content;
1543
    }
1544
1545
    /**
1546
     * @param $content
1547
     * @param string $iftag
1548
     * @param string $elseiftag
1549
     * @param string $elsetag
1550
     * @param string $endiftag
1551
     * @return mixed
1552
     */
1553
    private function _prepareCTag(
1554
        $content,
1555
        $iftag = '<@IF:',
1556
        $elseiftag = '<@ELSEIF:',
1557
        $elsetag = '<@ELSE>',
1558
        $endiftag = '<@ENDIF>'
1559
    ) {
1560
        if (strpos($content, '<!--@IF ') !== false) {
1561
            $content = str_replace('<!--@IF ', $iftag, $content);
1562
        } // for jp
1563
        if (strpos($content, '<!--@IF:') !== false) {
1564
            $content = str_replace('<!--@IF:', $iftag, $content);
1565
        }
1566
        if (strpos($content, $iftag) === false) {
1567
            return $content;
1568
        }
1569
        if (strpos($content, '<!--@ELSEIF:') !== false) {
1570
            $content = str_replace('<!--@ELSEIF:', $elseiftag, $content);
1571
        } // for jp
1572
        if (strpos($content, '<!--@ELSE-->') !== false) {
1573
            $content = str_replace('<!--@ELSE-->', $elsetag, $content);
1574
        }  // for jp
1575
        if (strpos($content, '<!--@ENDIF-->') !== false) {
1576
            $content = str_replace('<!--@ENDIF-->', $endiftag, $content);
1577
        }    // for jp
1578
        if (strpos($content, '<@ENDIF-->') !== false) {
1579
            $content = str_replace('<@ENDIF-->', $endiftag, $content);
1580
        }
1581
        $tags = array($iftag, $elseiftag, $elsetag, $endiftag);
1582
        $content = str_ireplace($tags, $tags, $content); // Change to capital letters
1583
1584
        return $content;
1585
    }
1586
1587
    /**
1588
     * @param $cmd
1589
     * @return mixed|string
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use integer|string|boolean.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
1590
     */
1591
    private function _parseCTagCMD($cmd)
1592
    {
1593
        $cmd = trim($cmd);
1594
        $reverse = substr($cmd, 0, 1) === '!' ? true : false;
1595
        if ($reverse) {
1596
            $cmd = ltrim($cmd, '!');
1597
        }
1598
        if (strpos($cmd, '[!') !== false) {
1599
            $cmd = str_replace(array('[!', '!]'), array('[[', ']]'), $cmd);
1600
        }
1601
        $safe = 0;
1602
        while ($safe < 20) {
1603
            $bt = md5($cmd);
1604
            if (strpos($cmd, '[*') !== false) {
1605
                $cmd = $this->mergeDocumentContent($cmd);
1606
            }
1607
            if (strpos($cmd, '[(') !== false) {
1608
                $cmd = $this->mergeSettingsContent($cmd);
1609
            }
1610
            if (strpos($cmd, '{{') !== false) {
1611
                $cmd = $this->mergeChunkContent($cmd);
1612
            }
1613
            if (strpos($cmd, '[[') !== false) {
1614
                $cmd = $this->evalSnippets($cmd);
1615
            }
1616
            if (strpos($cmd, '[+') !== false && strpos($cmd, '[[') === false) {
1617
                $cmd = $this->mergePlaceholderContent($cmd);
1618
            }
1619
            if ($bt === md5($cmd)) {
1620
                break;
1621
            }
1622
            $safe++;
1623
        }
1624
        $cmd = ltrim($cmd);
1625
        $cmd = rtrim($cmd, '-');
1626
        $cmd = str_ireplace(array(' and ', ' or '), array('&&', '||'), $cmd);
1627
1628
        if (!preg_match('@^\d*$@', $cmd) && preg_match('@^[0-9<= \-\+\*/\(\)%!&|]*$@', $cmd)) {
1629
            $cmd = eval("return {$cmd};");
1630
        } else {
1631
            $_ = explode(',', '[*,[(,{{,[[,[!,[+');
1632
            foreach ($_ as $left) {
1633
                if (strpos($cmd, $left) !== false) {
1634
                    $cmd = 0;
1635
                    break;
1636
                }
1637
            }
1638
        }
1639
        $cmd = trim($cmd);
1640
        if (!preg_match('@^\d+$@', $cmd)) {
1641
            $cmd = empty($cmd) ? 0 : 1;
1642
        } elseif ($cmd <= 0) {
1643
            $cmd = 0;
1644
        }
1645
1646
        if ($reverse) {
1647
            $cmd = !$cmd;
1648
        }
1649
1650
        return $cmd;
1651
    }
1652
1653
    /**
1654
     * Remove Comment-Tags from output like <!--@- Comment -@-->
1655
     * @param $content
1656
     * @param string $left
1657
     * @param string $right
1658
     * @return mixed
1659
     */
1660
    function ignoreCommentedTagsContent($content, $left = '<!--@-', $right = '-@-->')
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1661
    {
1662
        if (strpos($content, $left) === false) {
1663
            return $content;
1664
        }
1665
1666
        $matches = $this->getTagsFromContent($content, $left, $right);
1667
        if (!empty($matches)) {
1668
            foreach ($matches[0] as $i => $v) {
1669
                $addBreakMatches[$i] = $v . "\n";
0 ignored issues
show
Coding Style Comprehensibility introduced by
$addBreakMatches was never initialized. Although not strictly required by PHP, it is generally a good practice to add $addBreakMatches = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
1670
            }
1671
            $content = str_replace($addBreakMatches, '', $content);
0 ignored issues
show
Bug introduced by
The variable $addBreakMatches does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1672
            if (strpos($content, $left) !== false) {
1673
                $content = str_replace($matches[0], '', $content);
1674
            }
1675
        }
1676
1677
        return $content;
1678
    }
1679
1680
    /**
1681
     * @param $content
1682
     * @param string $left
1683
     * @param string $right
1684
     * @return mixed
1685
     */
1686
    public function escapeLiteralTagsContent($content, $left = '<@LITERAL>', $right = '<@ENDLITERAL>')
1687
    {
1688
        if (stripos($content, $left) === false) {
1689
            return $content;
1690
        }
1691
1692
        $matches = $this->getTagsFromContent($content, $left, $right);
1693
        if (empty($matches)) {
1694
            return $content;
1695
        }
1696
1697
        list($sTags, $rTags) = $this->getTagsForEscape();
1698
        foreach ($matches[1] as $i => $v) {
1699
            $v = str_ireplace($sTags, $rTags, $v);
1700
            $s = &$matches[0][$i];
1701 View Code Duplication
            if (strpos($content, $s) !== false) {
1702
                $content = str_replace($s, $v, $content);
1703
            } elseif ($this->debug) {
1704
                $this->addLog('ignoreCommentedTagsContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1705
            }
1706
        }
1707
1708
        return $content;
1709
    }
1710
1711
    /**
1712
     * Detect PHP error according to Evolution CMS error level
1713
     *
1714
     * @param integer $error PHP error level
1715
     * @return boolean Error detected
1716
     */
1717
1718
    public function detectError($error)
1719
    {
1720
        $detected = false;
1721
        if ($this->getConfig('error_reporting') == 99 && $error) {
1722
            $detected = true;
1723
        } elseif ($this->getConfig('error_reporting') == 2 && ($error & ~E_NOTICE)) {
1724
            $detected = true;
1725
        } elseif ($this->getConfig('error_reporting') == 1 && ($error & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT)) {
1726
            $detected = true;
1727
        }
1728
1729
        return $detected;
1730
    }
1731
1732
    /**
1733
     * Run a plugin
1734
     *
1735
     * @param string $pluginCode Code to run
1736
     * @param array $params
1737
     */
1738
    public function evalPlugin($pluginCode, $params)
1739
    {
1740
        $modx = &$this;
1741
        $modx->event->params = &$params; // store params inside event object
1742
        if (is_array($params)) {
1743
            extract($params, EXTR_SKIP);
1744
        }
1745
        /* if uncomment incorrect work plugin, cant understend where use this code and for what?
1746
        // This code will avoid further execution of plugins in case they cause a fatal-error. clearCache() will delete those locks to allow execution of locked plugins again.
1747
        // Related to https://github.com/modxcms/evolution/issues/1130
1748
        $lock_file_path = MODX_BASE_PATH . 'assets/cache/lock_' . str_replace(' ','-',strtolower($this->event->activePlugin)) . '.pageCache.php';
1749
        if($this->isBackend()) {
1750
            if(is_file($lock_file_path)) {
1751
                $msg = sprintf("Plugin parse error, Temporarily disabled '%s'.", $this->event->activePlugin);
1752
                $this->logEvent(0, 3, $msg, $msg);
1753
                return;
1754
            }
1755
            elseif(stripos($this->event->activePlugin,'ElementsInTree')===false) touch($lock_file_path);
1756
        }*/
1757
        ob_start();
1758
        eval($pluginCode);
1759
        $msg = ob_get_contents();
1760
        ob_end_clean();
1761
        // When reached here, no fatal error occured so the lock should be removed.
1762
        /*if(is_file($lock_file_path)) unlink($lock_file_path);*/
1763
        $error_info = error_get_last();
1764
1765 View Code Duplication
        if ((0 < $this->getConfig('error_reporting')) && $msg && $error_info !== null && $this->detectError($error_info['type'])) {
1766
            $msg = ($msg === false) ? 'ob_get_contents() error' : $msg;
1767
            $this->getService('ExceptionHandler')->messageQuit(
1768
                'PHP Parse Error',
1769
                '',
1770
                true,
1771
                $error_info['type'],
1772
                $error_info['file'],
1773
                'Plugin',
1774
                $error_info['message'],
1775
                $error_info['line'],
1776
                $msg
1777
            );
1778
            if ($this->isBackend()) {
1779
                $this->event->alert(
1780
                    'An error occurred while loading. Please see the event log for more information.' .
1781
                    '<p>' . $msg . '</p>'
1782
                );
1783
            }
1784
        } else {
1785
            echo $msg;
1786
        }
1787
        unset($modx->event->params);
1788
    }
1789
1790
    /**
1791
     * Run a snippet
1792
     *
1793
     * @param $phpcode
1794
     * @param array $params
1795
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be array|object|string? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
1796
     * @internal param string $snippet Code to run
1797
     */
1798
    public function evalSnippet($phpcode, $params)
1799
    {
1800
        $modx = &$this;
1801
        /*
1802
        if(isset($params) && is_array($params)) {
1803
            foreach($params as $k=>$v) {
1804
                $v = strtolower($v);
1805
                if($v==='false')    $params[$k] = false;
1806
                elseif($v==='true') $params[$k] = true;
1807
            }
1808
        }*/
1809
        $modx->event->params = &$params; // store params inside event object
1810
        if (is_array($params)) {
1811
            extract($params, EXTR_SKIP);
1812
        }
1813
        ob_start();
1814
        if (strpos($phpcode, ';') !== false) {
1815
            if (substr($phpcode, 0, 5) === '<?php') {
1816
                $phpcode = substr($phpcode, 5);
1817
            }
1818
            $return = eval($phpcode);
1819
        } else {
1820
            $return = call_user_func_array($phpcode, array($params));
1821
        }
1822
        $echo = ob_get_contents();
1823
        ob_end_clean();
1824
        $error_info = error_get_last();
1825 View Code Duplication
        if ((0 < $this->getConfig('error_reporting')) && $error_info !== null && $this->detectError($error_info['type'])) {
1826
            $echo = ($echo === false) ? 'ob_get_contents() error' : $echo;
1827
            $this->getService('ExceptionHandler')->messageQuit(
1828
                'PHP Parse Error',
1829
                '',
1830
                true,
1831
                $error_info['type'],
1832
                $error_info['file'],
1833
                'Snippet',
1834
                $error_info['message'],
1835
                $error_info['line'],
1836
                $echo
1837
            );
1838
            if ($this->isBackend()) {
1839
                $this->event->alert(
1840
                    'An error occurred while loading. Please see the event log for more information' .
1841
                    '<p>' . $echo . $return . '</p>'
1842
                );
1843
            }
1844
        }
1845
        unset($modx->event->params);
1846
        if (is_array($return) || is_object($return)) {
1847
            return $return;
1848
        } else {
1849
            return $echo . $return;
1850
        }
1851
    }
1852
1853
    /**
1854
     * Run snippets as per the tags in $documentSource and replace the tags with the returned values.
1855
     *
1856
     * @param $content
1857
     * @return string
1858
     * @internal param string $documentSource
1859
     */
1860
    public function evalSnippets($content)
1861
    {
1862
        if (strpos($content, '[[') === false) {
1863
            return $content;
1864
        }
1865
1866
        $matches = $this->getTagsFromContent($content, '[[', ']]');
1867
1868
        if (empty($matches)) {
1869
            return $content;
1870
        }
1871
1872
        $this->snipLapCount++;
1873
        if ($this->dumpSnippets) {
1874
            $this->snippetsCode .= sprintf('<fieldset><legend><b style="color: #821517;">PARSE PASS %s</b></legend><p>The following snippets (if any) were parsed during this pass.</p>',
1875
                $this->snipLapCount);
1876
        }
1877
1878
        foreach ($matches[1] as $i => $call) {
1879
            $s = &$matches[0][$i];
1880
            if (substr($call, 0, 2) === '$_') {
1881
                if (strpos($content, '_PHX_INTERNAL_') === false) {
1882
                    $value = $this->_getSGVar($call);
1883
                } else {
1884
                    $value = $s;
1885
                }
1886 View Code Duplication
                if (strpos($content, $s) !== false) {
1887
                    $content = str_replace($s, $value, $content);
1888
                } elseif ($this->debug) {
1889
                    $this->addLog('evalSnippetsSGVar parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1890
                }
1891
                continue;
1892
            }
1893
            $value = $this->_get_snip_result($call);
1894
            if (is_null($value)) {
1895
                continue;
1896
            }
1897
1898 View Code Duplication
            if (strpos($content, $s) !== false) {
1899
                $content = str_replace($s, $value, $content);
1900
            } elseif ($this->debug) {
1901
                $this->addLog('evalSnippets parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1902
            }
1903
        }
1904
1905
        if ($this->dumpSnippets) {
1906
            $this->snippetsCode .= '</fieldset><br />';
1907
        }
1908
1909
        return $content;
1910
    }
1911
1912
    /**
1913
     * @param $value
1914
     * @return mixed|string
1915
     */
1916
    public function _getSGVar($value)
1917
    { // Get super globals
1918
        $key = $value;
1919
        $_ = $this->getConfig('enable_filter');
1920
        $this->setConfig('enable_filter', 1);
1921
        list($key, $modifiers) = $this->splitKeyAndFilter($key);
1922
        $this->setConfig('enable_filter', $_);
1923
        $key = str_replace(array('(', ')'), array("['", "']"), $key);
1924
        $key = rtrim($key, ';');
1925
        if (strpos($key, '$_SESSION') !== false) {
1926
            $_ = $_SESSION;
1927
            $key = str_replace('$_SESSION', '$_', $key);
1928
            if (isset($_['mgrFormValues'])) {
1929
                unset($_['mgrFormValues']);
1930
            }
1931
            if (isset($_['token'])) {
1932
                unset($_['token']);
1933
            }
1934
        }
1935
        if (strpos($key, '[') !== false) {
1936
            $value = $key ? eval("return {$key};") : '';
1937
        } elseif (0 < eval("return count({$key});")) {
1938
            $value = eval("return print_r({$key},true);");
1939
        } else {
1940
            $value = '';
1941
        }
1942
        if ($modifiers !== false) {
1943
            $value = $this->applyFilter($value, $modifiers, $key);
1944
        }
1945
1946
        return $value;
1947
    }
1948
1949
    /**
1950
     * @param $piece
1951
     * @return null|string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null|array|object? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
1952
     */
1953
    private function _get_snip_result($piece)
1954
    {
1955
        if (ltrim($piece) !== $piece) {
1956
            return '';
1957
        }
1958
1959
        $eventtime = $this->dumpSnippets ? $this->getMicroTime() : 0;
1960
1961
        $snip_call = $this->_split_snip_call($piece);
1962
        $key = $snip_call['name'];
1963
1964
        list($key, $modifiers) = $this->splitKeyAndFilter($key);
1965
        $snip_call['name'] = $key;
1966
        $snippetObject = $this->_getSnippetObject($key);
1967
        if (is_null($snippetObject['content'])) {
1968
            return null;
1969
        }
1970
1971
        $this->currentSnippet = $snippetObject['name'];
1972
1973
        // current params
1974
        $params = $this->getParamsFromString($snip_call['params']);
1975
1976
        if (!isset($snippetObject['properties'])) {
1977
            $snippetObject['properties'] = array();
1978
        }
1979
        $default_params = $this->parseProperties($snippetObject['properties'], $this->currentSnippet, 'snippet');
1980
        $params = array_merge($default_params, $params);
1981
1982
        $value = $this->evalSnippet($snippetObject['content'], $params);
1983
        $this->currentSnippet = '';
1984
        if ($modifiers !== false) {
1985
            $value = $this->applyFilter($value, $modifiers, $key);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type array or object; however, EvolutionCMS\Core::applyFilter() 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...
1986
        }
1987
1988
        if ($this->dumpSnippets) {
1989
            $eventtime = $this->getMicroTime() - $eventtime;
1990
            $eventtime = sprintf('%2.2f ms', $eventtime * 1000);
1991
            $code = str_replace("\t", '  ', $this->getPhpCompat()->htmlspecialchars($value));
0 ignored issues
show
Bug introduced by
It seems like $value defined by $this->evalSnippet($snip...ct['content'], $params) on line 1982 can also be of type object; however, EvolutionCMS\Legacy\PhpCompat::htmlspecialchars() does only seem to accept string|array, 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...
1992
            $piece = str_replace("\t", '  ', $this->getPhpCompat()->htmlspecialchars($piece));
1993
            $print_r_params = str_replace("\t", '  ',
1994
                $this->getPhpCompat()->htmlspecialchars('$modx->event->params = ' . print_r($params, true)));
1995
            $this->snippetsCode .= sprintf('<fieldset style="margin:1em;"><legend><b>%s</b>(%s)</legend><pre style="white-space: pre-wrap;background-color:#fff;width:90%%;">[[%s]]</pre><pre style="white-space: pre-wrap;background-color:#fff;width:90%%;">%s</pre><pre style="white-space: pre-wrap;background-color:#fff;width:90%%;">%s</pre></fieldset>',
1996
                $snippetObject['name'], $eventtime, $piece, $print_r_params, $code);
1997
            $this->snippetsTime[] = array('sname' => $key, 'time' => $eventtime);
1998
        }
1999
2000
        return $value;
2001
    }
2002
2003
    /**
2004
     * @param string $string
2005
     * @return array
0 ignored issues
show
Documentation introduced by
Should the return type not be array|null? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
2006
     */
2007
    public function getParamsFromString($string = '')
2008
    {
2009
        if (empty($string)) {
2010
            return array();
2011
        }
2012
2013
        if (strpos($string, '&_PHX_INTERNAL_') !== false) {
2014
            $string = str_replace(array('&_PHX_INTERNAL_091_&', '&_PHX_INTERNAL_093_&'), array('[', ']'), $string);
2015
        }
2016
2017
        $_ = $this->documentOutput;
2018
        $this->documentOutput = $string;
2019
        $this->invokeEvent('OnBeforeParseParams');
2020
        $string = $this->documentOutput;
2021
        $this->documentOutput = $_;
2022
2023
        $_tmp = $string;
2024
        $_tmp = ltrim($_tmp, '?&');
2025
        $temp_params = array();
2026
        $key = '';
2027
        $value = null;
2028
        while ($_tmp !== '') {
2029
            $bt = $_tmp;
2030
            $char = substr($_tmp, 0, 1);
2031
            $_tmp = substr($_tmp, 1);
2032
2033
            if ($char === '=') {
2034
                $_tmp = trim($_tmp);
2035
                $delim = substr($_tmp, 0, 1);
2036
                if (in_array($delim, array('"', "'", '`'))) {
2037
                    $null = null;
2038
                    //list(, $value, $_tmp)
2039
                    list($null, $value, $_tmp) = explode($delim, $_tmp, 3);
2040
                    unset($null);
2041
2042
                    if (substr(trim($_tmp), 0, 2) === '//') {
2043
                        $_tmp = strstr(trim($_tmp), "\n");
2044
                    }
2045
                    $i = 0;
2046
                    while ($delim === '`' && substr(trim($_tmp), 0, 1) !== '&' && 1 < substr_count($_tmp, '`')) {
2047
                        list($inner, $outer, $_tmp) = explode('`', $_tmp, 3);
2048
                        $value .= "`{$inner}`{$outer}";
2049
                        $i++;
2050
                        if (100 < $i) {
2051
                            exit('The nest of values are hard to read. Please use three different quotes.');
2052
                        }
2053
                    }
2054
                    if ($i && $delim === '`') {
2055
                        $value = rtrim($value, '`');
2056
                    }
2057
                } elseif (strpos($_tmp, '&') !== false) {
2058
                    list($value, $_tmp) = explode('&', $_tmp, 2);
2059
                    $value = trim($value);
2060
                } else {
2061
                    $value = $_tmp;
2062
                    $_tmp = '';
2063
                }
2064
            } elseif ($char === '&') {
2065
                if (trim($key) !== '') {
2066
                    $value = '1';
2067
                } else {
2068
                    continue;
2069
                }
2070
            } elseif ($_tmp === '') {
2071
                $key .= $char;
2072
                $value = '1';
2073
            } elseif ($key !== '' || trim($char) !== '') {
2074
                $key .= $char;
2075
            }
2076
2077
            if (isset($value) && !is_null($value)) {
2078
                if (strpos($key, 'amp;') !== false) {
2079
                    $key = str_replace('amp;', '', $key);
2080
                }
2081
                $key = trim($key);
2082 View Code Duplication
                if (strpos($value, '[!') !== false) {
2083
                    $value = str_replace(array('[!', '!]'), array('[[', ']]'), $value);
2084
                }
2085
                $value = $this->mergeDocumentContent($value);
2086
                $value = $this->mergeSettingsContent($value);
2087
                $value = $this->mergeChunkContent($value);
2088
                $value = $this->evalSnippets($value);
2089
                if (substr($value, 0, 6) !== '@CODE:') {
2090
                    $value = $this->mergePlaceholderContent($value);
2091
                }
2092
2093
                $temp_params[][$key] = $value;
2094
2095
                $key = '';
2096
                $value = null;
2097
2098
                $_tmp = ltrim($_tmp, " ,\t");
2099
                if (substr($_tmp, 0, 2) === '//') {
2100
                    $_tmp = strstr($_tmp, "\n");
2101
                }
2102
            }
2103
2104
            if ($_tmp === $bt) {
2105
                $key = trim($key);
2106
                if ($key !== '') {
2107
                    $temp_params[][$key] = '';
2108
                }
2109
                break;
2110
            }
2111
        }
2112
2113
        foreach ($temp_params as $p) {
2114
            $k = key($p);
2115
            if (substr($k, -2) === '[]') {
2116
                $k = substr($k, 0, -2);
2117
                $params[$k][] = current($p);
0 ignored issues
show
Coding Style Comprehensibility introduced by
$params was never initialized. Although not strictly required by PHP, it is generally a good practice to add $params = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
2118
            } elseif (strpos($k, '[') !== false && substr($k, -1) === ']') {
2119
                list($k, $subk) = explode('[', $k, 2);
2120
                $subk = substr($subk, 0, -1);
2121
                $params[$k][$subk] = current($p);
0 ignored issues
show
Bug introduced by
The variable $params does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
2122
            } else {
2123
                $params[$k] = current($p);
2124
            }
2125
        }
2126
2127
        return $params;
2128
    }
2129
2130
    /**
2131
     * @param $str
2132
     * @return bool|int
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use false|integer.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
2133
     */
2134
    public function _getSplitPosition($str)
2135
    {
2136
        $closeOpt = false;
2137
        $maybePos = false;
2138
        $inFilter = false;
2139
        $pos = false;
2140
        $total = strlen($str);
2141
        for ($i = 0; $i < $total; $i++) {
2142
            $c = substr($str, $i, 1);
2143
            $cc = substr($str, $i, 2);
2144
            if (!$inFilter) {
2145
                if ($c === ':') {
2146
                    $inFilter = true;
2147
                } elseif ($c === '?') {
2148
                    $pos = $i;
2149
                } elseif ($c === ' ') {
2150
                    $maybePos = $i;
2151
                } elseif ($c === '&' && $maybePos) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $maybePos of type integer|false is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
2152
                    $pos = $maybePos;
2153
                } elseif ($c === "\n") {
2154
                    $pos = $i;
2155
                } else {
2156
                    $pos = false;
2157
                }
2158
            } else {
2159
                if ($cc == $closeOpt) {
2160
                    $closeOpt = false;
2161
                } elseif ($c == $closeOpt) {
2162
                    $closeOpt = false;
2163
                } elseif ($closeOpt) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $closeOpt of type false|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
2164
                    continue;
2165
                } elseif ($cc === "('") {
2166
                    $closeOpt = "')";
2167
                } elseif ($cc === '("') {
2168
                    $closeOpt = '")';
2169
                } elseif ($cc === '(`') {
2170
                    $closeOpt = '`)';
2171
                } elseif ($c === '(') {
2172
                    $closeOpt = ')';
2173
                } elseif ($c === '?') {
2174
                    $pos = $i;
2175
                } elseif ($c === ' ' && strpos($str, '?') === false) {
2176
                    $pos = $i;
2177
                } else {
2178
                    $pos = false;
2179
                }
2180
            }
2181
            if ($pos) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $pos of type false|integer is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
2182
                break;
2183
            }
2184
        }
2185
2186
        return $pos;
2187
    }
2188
2189
    /**
2190
     * @param $call
2191
     * @return mixed
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,string>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
2192
     */
2193
    private function _split_snip_call($call)
2194
    {
2195
        $spacer = md5('dummy');
2196 View Code Duplication
        if (strpos($call, ']]>') !== false) {
2197
            $call = str_replace(']]>', "]{$spacer}]>", $call);
2198
        }
2199
2200
        $splitPosition = $this->_getSplitPosition($call);
2201
2202
        if ($splitPosition !== false) {
2203
            $name = substr($call, 0, $splitPosition);
2204
            $params = substr($call, $splitPosition + 1);
2205
        } else {
2206
            $name = $call;
2207
            $params = '';
2208
        }
2209
2210
        $snip['name'] = trim($name);
0 ignored issues
show
Coding Style Comprehensibility introduced by
$snip was never initialized. Although not strictly required by PHP, it is generally a good practice to add $snip = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
2211 View Code Duplication
        if (strpos($params, $spacer) !== false) {
2212
            $params = str_replace("]{$spacer}]>", ']]>', $params);
2213
        }
2214
        $snip['params'] = ltrim($params, "?& \t\n");
2215
2216
        return $snip;
2217
    }
2218
2219
    /**
2220
     * @param $snip_name
2221
     * @return mixed
2222
     */
2223
    private function _getSnippetObject($snip_name)
2224
    {
2225
        if (array_key_exists($snip_name, $this->snippetCache)) {
2226
            $snippetObject['name'] = $snip_name;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$snippetObject was never initialized. Although not strictly required by PHP, it is generally a good practice to add $snippetObject = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
2227
            $snippetObject['content'] = $this->snippetCache[$snip_name];
2228
            if (isset($this->snippetCache["{$snip_name}Props"])) {
2229
                if (!isset($this->snippetCache["{$snip_name}Props"])) {
2230
                    $this->snippetCache["{$snip_name}Props"] = '';
2231
                }
2232
                $snippetObject['properties'] = $this->snippetCache["{$snip_name}Props"];
2233
            }
2234
        } elseif (substr($snip_name, 0, 1) === '@' && isset($this->pluginEvent[substr($snip_name, 1)])) {
2235
            $snippetObject['name'] = substr($snip_name, 1);
0 ignored issues
show
Coding Style Comprehensibility introduced by
$snippetObject was never initialized. Although not strictly required by PHP, it is generally a good practice to add $snippetObject = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
2236
            $snippetObject['content'] = sprintf(
2237
                '$rs=$this->invokeEvent("%s",$params);echo trim(implode("",$rs));',
2238
                $snippetObject['name']
2239
            );
2240
            $snippetObject['properties'] = '';
2241
        } else {
2242
            $snippetObject = $this->_getSnippetFromDatabase($snip_name);
2243
2244
            $this->snippetCache[$snip_name] = $snippetObject['content'];
2245
            $this->snippetCache["{$snip_name}Props"] = $snippetObject['properties'];
2246
        }
2247
2248
        return $snippetObject;
2249
    }
2250
2251
    private function _getSnippetFromDatabase($snip_name) : array
2252
    {
2253
        $snippetObject = [];
2254
2255
        /** @var \Illuminate\Database\Eloquent\Collection $snippetModelCollection */
2256
        $snippetModelCollection = Models\SiteSnippet::where('name', '=', $snip_name)
2257
            ->where('disabled', '=', 0)
2258
            ->get();
2259
        if ($snippetModelCollection->count() > 1) {
2260
            exit('Error $modx->_getSnippetObject()' . $snip_name);
2261
        } elseif ($snippetModelCollection->count() === 1) {
2262
            /** @var Models\SiteSnippet $snippetModel */
2263
            $snippetModel = $snippetModelCollection->first();
2264
            $snip_content = $snippetModel->snippet;
2265
            $snip_prop = $snippetModel->properties;
2266
            $snip_prop = array_merge(
2267
                $this->parseProperties($snip_prop),
2268
                $this->parseProperties($snippetModel->activeModule->properties)
2269
            );
2270
            $snip_prop = empty($snip_prop) ? '{}' : json_encode($snip_prop);
2271
2272
        } else {
2273
            $snip_content = null;
2274
            $snip_prop = '';
2275
        }
2276
        $snippetObject['name'] = $snip_name;
2277
        $snippetObject['content'] = $snip_content;
2278
        $snippetObject['properties'] = $snip_prop;
2279
2280
        return $snippetObject;
2281
    }
2282
2283
    /**
2284
     * @deprecated use UrlProcessor::toAlias()
2285
     */
2286
    public function toAlias($text)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
2287
    {
2288
        return UrlProcessor::toAlias($text);
2289
    }
2290
2291
    /**
2292
     * @deprecated use UrlProcessor::makeFriendlyURL()
2293
     */
2294
    public function makeFriendlyURL($pre, $suff, $alias, $isfolder = 0, $id = 0)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
2295
    {
2296
        return UrlProcessor::makeFriendlyURL($pre, $suff, $alias, (bool)$isfolder, (int)$id);
2297
    }
2298
2299
    /**
2300
     * @deprecated use UrlProcessor::rewriteUrls()
2301
     */
2302
    public function rewriteUrls($documentSource)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
2303
    {
2304
        return UrlProcessor::rewriteUrls($documentSource);
2305
    }
2306
2307
    public function sendStrictURI()
2308
    {
2309
        $url = UrlProcessor::strictURI((string)$this->q, (int)$this->documentIdentifier);
2310
        if ($url !== null) {
2311
            $this->sendRedirect($url, 0, 'REDIRECT_HEADER', 'HTTP/1.0 301 Moved Permanently');
2312
        }
2313
    }
2314
2315
    /**
2316
     * Get all db fields and TVs for a document/resource
2317
     *
2318
     * @param string $method
2319
     * @param mixed $identifier
2320
     * @param bool $isPrepareResponse
2321
     * @return array
2322
     */
2323
    public function getDocumentObject($method, $identifier, $isPrepareResponse = false)
2324
    {
2325
2326
        $cacheKey = md5(print_r(func_get_args(), true));
2327
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
2328
            return $this->tmpCache[__FUNCTION__][$cacheKey];
2329
        }
2330
2331
        $tblsc = $this->getDatabase()->getFullTableName("site_content");
2332
        $tbldg = $this->getDatabase()->getFullTableName("document_groups");
2333
2334
        // allow alias to be full path
2335
        if ($method == 'alias') {
2336
            $identifier = $this->cleanDocumentIdentifier($identifier);
0 ignored issues
show
Deprecated Code introduced by
The method EvolutionCMS\Core::cleanDocumentIdentifier() has been deprecated with message: use UrlProcessor::cleanDocumentIdentifier()

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
2337
            $method = $this->documentMethod;
2338
        }
2339
        if ($method == 'alias' && $this->getConfig('use_alias_path') && array_key_exists($identifier,
2340
                UrlProcessor::getFacadeRoot()->documentListing)) {
2341
            $method = 'id';
2342
            $identifier = UrlProcessor::getFacadeRoot()->documentListing[$identifier];
2343
        }
2344
2345
        $out = $this->invokeEvent('OnBeforeLoadDocumentObject', compact('method', 'identifier'));
2346
        if (is_array($out) && is_array($out[0])) {
2347
            $documentObject = $out[0];
2348
        } else {
2349
            // get document groups for current user
2350
            if ($docgrp = $this->getUserDocGroups()) {
2351
                $docgrp = implode(",", $docgrp);
2352
            }
2353
            // get document
2354
            $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
2355
            $rs = $this->getDatabase()->select('sc.*', "{$tblsc} sc
2356
                LEFT JOIN {$tbldg} dg ON dg.document = sc.id", "sc.{$method} = '{$identifier}' AND ({$access})", "", 1);
2357
            if ($this->getDatabase()->getRecordCount($rs) < 1) {
2358
                $seclimit = 0;
2359
                if ($this->getConfig('unauthorized_page')) {
2360
                    // method may still be alias, while identifier is not full path alias, e.g. id not found above
2361
                    if ($method === 'alias') {
2362
                        $secrs = $this->getDatabase()->select('count(dg.id)', "{$tbldg} as dg, {$tblsc} as sc",
2363
                            "dg.document = sc.id AND sc.alias = '{$identifier}'", '', 1);
2364
                    } else {
2365
                        $secrs = $this->getDatabase()->select('count(id)', $tbldg, "document = '{$identifier}'", '', 1);
2366
                    }
2367
                    // check if file is not public
2368
                    $seclimit = $this->getDatabase()->getValue($secrs);
2369
                }
2370
                if ($seclimit > 0) {
2371
                    // match found but not publicly accessible, send the visitor to the unauthorized_page
2372
                    $this->sendUnauthorizedPage();
2373
                    exit; // stop here
2374
                } else {
2375
                    $this->sendErrorPage();
2376
                    exit;
2377
                }
2378
            }
2379
            # this is now the document :) #
2380
            $documentObject = $this->getDatabase()->getRow($rs);
2381
2382
            if ($isPrepareResponse === 'prepareResponse') {
2383
                $this->documentObject = &$documentObject;
2384
            }
2385
            $out = $this->invokeEvent('OnLoadDocumentObject', compact('method', 'identifier', 'documentObject'));
2386
            if (is_array($out) && is_array($out[0])) {
2387
                $documentObject = $out[0];
2388
            }
2389
            if ($documentObject['template']) {
2390
                // load TVs and merge with document - Orig by Apodigm - Docvars
2391
                $rs = $this->getDatabase()->select("tv.*, IF(tvc.value!='',tvc.value,tv.default_text) as value",
2392
                    $this->getDatabase()->getFullTableName("site_tmplvars") . " tv
2393
                INNER JOIN " . $this->getDatabase()->getFullTableName("site_tmplvar_templates") . " tvtpl ON tvtpl.tmplvarid = tv.id
2394
                LEFT JOIN " . $this->getDatabase()->getFullTableName("site_tmplvar_contentvalues") . " tvc ON tvc.tmplvarid=tv.id AND tvc.contentid = '{$documentObject['id']}'",
2395
                    "tvtpl.templateid = '{$documentObject['template']}'");
2396
                $tmplvars = array();
2397
                while ($row = $this->getDatabase()->getRow($rs)) {
2398
                    $tmplvars[$row['name']] = array(
2399
                        $row['name'],
2400
                        $row['value'],
2401
                        $row['display'],
2402
                        $row['display_params'],
2403
                        $row['type']
2404
                    );
2405
                }
2406
                $documentObject = array_merge($documentObject, $tmplvars);
2407
            }
2408
            $out = $this->invokeEvent('OnAfterLoadDocumentObject', compact('method', 'identifier', 'documentObject'));
2409
            if (is_array($out) && array_key_exists(0, $out) !== false && is_array($out[0])) {
2410
                $documentObject = $out[0];
2411
            }
2412
        }
2413
2414
        $this->tmpCache[__FUNCTION__][$cacheKey] = $documentObject;
2415
2416
        return $documentObject;
2417
    }
2418
2419
    /**
2420
     * Parse a source string.
2421
     *
2422
     * Handles most Evolution CMS tags. Exceptions include:
2423
     *   - uncached snippet tags [!...!]
2424
     *   - URL tags [~...~]
2425
     *
2426
     * @param string $source
2427
     * @return string
2428
     */
2429
    public function parseDocumentSource($source)
2430
    {
2431
        // set the number of times we are to parse the document source
2432
        $this->minParserPasses = empty ($this->minParserPasses) ? 2 : $this->minParserPasses;
2433
        $this->maxParserPasses = empty ($this->maxParserPasses) ? 10 : $this->maxParserPasses;
2434
        $passes = $this->minParserPasses;
2435
        for ($i = 0; $i < $passes; $i++) {
2436
            // get source length if this is the final pass
2437
            if ($i == ($passes - 1)) {
2438
                $st = md5($source);
2439
            }
2440
            if ($this->dumpSnippets == 1) {
2441
                $this->snippetsCode .= "<fieldset><legend><b style='color: #821517;'>PARSE PASS " . ($i + 1) . "</b></legend><p>The following snippets (if any) were parsed during this pass.</p>";
2442
            }
2443
2444
            // invoke OnParseDocument event
2445
            $this->documentOutput = $source; // store source code so plugins can
2446
            $this->invokeEvent("OnParseDocument"); // work on it via $modx->documentOutput
2447
            $source = $this->documentOutput;
2448
2449
            if ($this->getConfig('enable_at_syntax')) {
2450
                $source = $this->ignoreCommentedTagsContent($source);
2451
                $source = $this->mergeConditionalTagsContent($source);
2452
            }
2453
2454
            $source = $this->mergeSettingsContent($source);
2455
            $source = $this->mergeDocumentContent($source);
2456
            $source = $this->mergeChunkContent($source);
2457
            $source = $this->evalSnippets($source);
2458
            $source = $this->mergePlaceholderContent($source);
2459
2460
            if ($this->dumpSnippets == 1) {
2461
                $this->snippetsCode .= "</fieldset><br />";
2462
            }
2463
            if ($i == ($passes - 1) && $i < ($this->maxParserPasses - 1)) {
2464
                // check if source content was changed
2465
                if ($st != md5($source)) {
0 ignored issues
show
Bug introduced by
The variable $st does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
2466
                    $passes++;
2467
                } // if content change then increase passes because
2468
            } // we have not yet reached maxParserPasses
2469
        }
2470
2471
        return $source;
2472
    }
2473
2474
    /**
2475
     * Starts the parsing operations.
2476
     *
2477
     * - connects to the db
2478
     * - gets the settings (including system_settings)
2479
     * - gets the document/resource identifier as in the query string
2480
     * - finally calls prepareResponse()
2481
     */
2482
    public function executeParser()
2483
    {
2484
        if (is_cli()) {
2485
            throw new \RuntimeException('Call DocumentParser::executeParser on CLI mode');
2486
        }
2487
        //error_reporting(0);
2488
2489
        $this->_IIS_furl_fix(); // IIS friendly url fix
2490
2491
        // check site settings
2492
        if ($this->checkSiteStatus()) {
2493
            // make sure the cache doesn't need updating
2494
            $this->updatePubStatus();
2495
2496
            // find out which document we need to display
2497
            $this->documentMethod = filter_input(INPUT_GET, 'q') ? 'alias' : 'id';
2498
            $this->documentIdentifier = $this->getDocumentIdentifier($this->documentMethod);
2499
        } else {
2500
            header('HTTP/1.0 503 Service Unavailable');
2501
            $this->systemCacheKey = 'unavailable';
2502
            if (!$this->config['site_unavailable_page']) {
2503
                // display offline message
2504
                $this->documentContent = $this->getConfig('site_unavailable_message');
2505
                $this->outputContent();
2506
                exit; // stop processing here, as the site's offline
2507
            } else {
2508
                // setup offline page document settings
2509
                $this->documentMethod = 'id';
2510
                $this->documentIdentifier = $this->getConfig('site_unavailable_page');
2511
            }
2512
        }
2513
2514
        if ($this->documentMethod == "alias") {
2515
            $this->documentIdentifier = $this->cleanDocumentIdentifier($this->documentIdentifier);
0 ignored issues
show
Deprecated Code introduced by
The method EvolutionCMS\Core::cleanDocumentIdentifier() has been deprecated with message: use UrlProcessor::cleanDocumentIdentifier()

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
2516
2517
            // Check use_alias_path and check if $this->virtualDir is set to anything, then parse the path
2518
            if ($this->getConfig('use_alias_path') == 1) {
2519
                $virtualDir = UrlProcessor::getFacadeRoot()->virtualDir;
2520
                $alias = (strlen($virtualDir) > 0 ? $virtualDir . '/' : '') . $this->documentIdentifier;
2521
                if (isset(UrlProcessor::getFacadeRoot()->documentListing[$alias])) {
2522
                    $this->documentIdentifier = UrlProcessor::getFacadeRoot()->documentListing[$alias];
2523
                } else {
2524
                    //@TODO: check new $alias;
2525
                    if ($this->getConfig('aliaslistingfolder') == 1) {
2526
                        $tbl_site_content = $this->getDatabase()->getFullTableName('site_content');
2527
2528
                        $parentId = empty($virtualDir) ? 0 : UrlProcessor::getIdFromAlias($virtualDir);
2529
                        $parentId = ($parentId > 0) ? $parentId : '0';
2530
2531
                        $docAlias = $this->getDatabase()->escape($this->documentIdentifier);
2532
2533
                        $rs = $this->getDatabase()->select('id', $tbl_site_content,
2534
                            "deleted=0 and parent='{$parentId}' and alias='{$docAlias}'");
2535
                        if ($this->getDatabase()->getRecordCount($rs) == 0) {
2536
                            $this->sendErrorPage();
2537
                        }
2538
                        $docId = $this->getDatabase()->getValue($rs);
2539
2540
                        if (!$docId) {
2541
                            $alias = $this->q;
2542
                            if ((int)$this->getConfig('friendly_url_suffix') !== 0) {
2543
                                $pos = strrpos($alias, $this->getConfig('friendly_url_suffix'));
2544
2545
                                if ($pos !== false) {
2546
                                    $alias = substr($alias, 0, $pos);
2547
                                }
2548
                            }
2549
                            $docId = UrlProcessor::getIdFromAlias($alias);
2550
                        }
2551
2552
                        if ($docId > 0) {
2553
                            $this->documentIdentifier = $docId;
2554
                        } else {
2555
                            /*
2556
                            $rs  = $this->getDatabase()->select('id', $tbl_site_content, "deleted=0 and alias='{$docAlias}'");
2557
                            if($this->getDatabase()->getRecordCount($rs)==0)
2558
                            {
2559
                                $rs  = $this->getDatabase()->select('id', $tbl_site_content, "deleted=0 and id='{$docAlias}'");
2560
                            }
2561
                            $docId = $this->getDatabase()->getValue($rs);
2562
2563
                            if ($docId > 0)
2564
                            {
2565
                                $this->documentIdentifier = $docId;
2566
2567
                            }else{
2568
                            */
2569
                            $this->sendErrorPage();
2570
                            //}
2571
                        }
2572
                    } else {
2573
                        $this->sendErrorPage();
2574
                    }
2575
                }
2576
            } else {
2577
                if (isset(UrlProcessor::getFacadeRoot()->documentListing[$this->documentIdentifier])) {
2578
                    $this->documentIdentifier = UrlProcessor::getFacadeRoot()->documentListing[$this->documentIdentifier];
2579
                } else {
2580
                    $docAlias = $this->getDatabase()->escape($this->documentIdentifier);
2581
                    $rs = $this->getDatabase()->select('id', $this->getDatabase()->getFullTableName('site_content'),
2582
                        "deleted=0 and alias='{$docAlias}'");
2583
                    $this->documentIdentifier = (int)$this->getDatabase()->getValue($rs);
2584
                }
2585
            }
2586
            $this->documentMethod = 'id';
2587
        }
2588
2589
        //$this->_fixURI();
2590
        // invoke OnWebPageInit event
2591
        $this->invokeEvent("OnWebPageInit");
2592
        // invoke OnLogPageView event
2593
        if ($this->getConfig('track_visitors') == 1) {
2594
            $this->invokeEvent("OnLogPageHit");
2595
        }
2596
        if ($this->getConfig('seostrict') == '1') {
2597
            $this->sendStrictURI();
2598
        }
2599
        $this->prepareResponse();
2600
    }
2601
2602
    /**
2603
     * @param $path
2604
     * @param null $suffix
2605
     * @return mixed
2606
     */
2607
    public function mb_basename($path, $suffix = null)
2608
    {
2609
        $exp = explode('/', $path);
2610
2611
        return str_replace($suffix, '', end($exp));
2612
    }
2613
2614
    public function _IIS_furl_fix()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
2615
    {
2616
        if ($this->getConfig('friendly_urls') != 1) {
2617
            return;
2618
        }
2619
2620
        if (strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') === false) {
2621
            return;
2622
        }
2623
2624
        $url = $_SERVER['QUERY_STRING'];
2625
        $err = substr($url, 0, 3);
2626
        if ($err !== '404' && $err !== '405') {
2627
            return;
2628
        }
2629
2630
        $k = array_keys($_GET);
2631
        unset ($_GET[$k[0]]);
2632
        unset ($_REQUEST[$k[0]]); // remove 404,405 entry
2633
        $qp = parse_url(str_replace(MODX_SITE_URL, '', substr($url, 4)));
2634
        $_SERVER['QUERY_STRING'] = $qp['query'];
2635
        if (!empty ($qp['query'])) {
2636
            parse_str($qp['query'], $qv);
2637
            foreach ($qv as $n => $v) {
0 ignored issues
show
Bug introduced by
The expression $qv of type null|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
2638
                $_REQUEST[$n] = $_GET[$n] = $v;
2639
            }
2640
        }
2641
        $_SERVER['PHP_SELF'] = MODX_BASE_URL . $qp['path'];
2642
        $this->q = $qp['path'];
2643
2644
        return $qp['path'];
2645
    }
2646
2647
    /**
2648
     * The next step called at the end of executeParser()
2649
     *
2650
     * - checks cache
2651
     * - checks if document/resource is deleted/unpublished
2652
     * - checks if resource is a weblink and redirects if so
2653
     * - gets template and parses it
2654
     * - ensures that postProcess is called when PHP is finished
2655
     */
2656
    public function prepareResponse()
2657
    {
2658
        // we now know the method and identifier, let's check the cache
2659
2660
        if ($this->getConfig('enable_cache') == 2 && $this->isLoggedIn()) {
2661
            $this->setConfig('enable_cache', 0);
2662
        }
2663
2664
        if ($this->getConfig('enable_cache')) {
2665
            $this->documentContent = $this->getDocumentObjectFromCache($this->documentIdentifier, true);
2666
        } else {
2667
            $this->documentContent = '';
2668
        }
2669
2670
        if ($this->documentContent == '') {
2671
            // get document object from DB
2672
            $this->documentObject = $this->getDocumentObject($this->documentMethod, $this->documentIdentifier,
2673
                'prepareResponse');
2674
2675
            // write the documentName to the object
2676
            $this->documentName = &$this->documentObject['pagetitle'];
2677
2678
            // check if we should not hit this document
2679
            if ($this->documentObject['donthit'] == 1) {
2680
                $this->setConfig('track_visitors', 0);
2681
            }
2682
2683
            if ($this->documentObject['deleted'] == 1) {
2684
                $this->sendErrorPage();
2685
            } // validation routines
2686
            elseif ($this->documentObject['published'] == 0) {
2687
                $this->_sendErrorForUnpubPage();
2688
            } elseif ($this->documentObject['type'] == 'reference') {
2689
                $this->_sendRedirectForRefPage($this->documentObject['content']);
2690
            }
2691
2692
            // get the template and start parsing!
2693
            if (!$this->documentObject['template']) {
2694
                $templateCode = '[*content*]';
2695
            } // use blank template
2696
            else {
2697
                $templateCode = $this->_getTemplateCodeFromDB($this->documentObject['template']);
2698
            }
2699
2700
            if (substr($templateCode, 0, 8) === '@INCLUDE') {
2701
                $templateCode = $this->atBindInclude($templateCode);
2702
            }
2703
2704
2705
            $this->documentContent = &$templateCode;
2706
2707
            // invoke OnLoadWebDocument event
2708
            $this->invokeEvent('OnLoadWebDocument');
2709
2710
            // Parse document source
2711
            $this->documentContent = $this->parseDocumentSource($templateCode);
2712
2713
            $this->documentGenerated = 1;
2714
        } else {
2715
            $this->documentGenerated = 0;
2716
        }
2717
2718
        if ($this->getConfig('error_page') == $this->documentIdentifier && $this->getConfig('error_page') != $this->getConfig('site_start')) {
2719
            header('HTTP/1.0 404 Not Found');
2720
        }
2721
2722
        register_shutdown_function(array(
2723
            &$this,
2724
            'postProcess'
2725
        )); // tell PHP to call postProcess when it shuts down
2726
        $this->outputContent();
2727
        //$this->postProcess();
2728
    }
2729
2730
    public function _sendErrorForUnpubPage()
2731
    {
2732
        // Can't view unpublished pages !$this->checkPreview()
2733
        if (!$this->hasPermission('view_unpublished')) {
2734
            $this->sendErrorPage();
2735
        } else {
2736
            $udperms = new Legacy\Permissions();
2737
            $udperms->user = $this->getLoginUserID();
0 ignored issues
show
Documentation Bug introduced by
The property $user was declared of type integer, but $this->getLoginUserID() is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
2738
            $udperms->document = $this->documentIdentifier;
2739
            $udperms->role = $_SESSION['mgrRole'];
2740
            // Doesn't have access to this document
2741
            if (!$udperms->checkPermissions()) {
2742
                $this->sendErrorPage();
2743
            }
2744
        }
2745
    }
2746
2747
    /**
2748
     * @param $url
2749
     */
2750
    public function _sendRedirectForRefPage($url)
2751
    {
2752
        // check whether it's a reference
2753
        if (preg_match('@^[1-9]\d*$@', $url)) {
2754
            $url = $this->makeUrl($url); // if it's a bare document id
0 ignored issues
show
Deprecated Code introduced by
The method EvolutionCMS\Core::makeUrl() has been deprecated with message: use UrlProcessor::makeUrl()

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
2755
        } elseif (strpos($url, '[~') !== false) {
2756
            $url = $this->rewriteUrls($url); // if it's an internal docid tag, process it
0 ignored issues
show
Deprecated Code introduced by
The method EvolutionCMS\Core::rewriteUrls() has been deprecated with message: use UrlProcessor::rewriteUrls()

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
2757
        }
2758
        $this->sendRedirect($url, 0, '', 'HTTP/1.0 301 Moved Permanently');
2759
        exit;
2760
    }
2761
2762
    /**
2763
     * @param $templateID
2764
     * @return mixed
2765
     */
2766
    public function _getTemplateCodeFromDB($templateID)
2767
    {
2768
        $rs = $this->getDatabase()->select(
2769
            'content',
2770
            $this->getDatabase()->getFullTableName('site_templates'),
2771
            "id = '{$templateID}'"
2772
        );
2773
        if ($this->getDatabase()->getRecordCount($rs) == 1) {
2774
            return $this->getDatabase()->getValue($rs);
2775
        } else {
2776
            $this->getService('ExceptionHandler')->messageQuit('Incorrect number of templates returned from database');
2777
        }
2778
    }
2779
2780
    /**
2781
     * Returns an array of all parent record IDs for the id passed.
2782
     *
2783
     * @param int $id Docid to get parents for.
2784
     * @param int $height The maximum number of levels to go up, default 10.
2785
     * @return array
2786
     */
2787
    public function getParentIds($id, $height = 10)
2788
    {
2789
        $parents = array();
2790
        while ($id && $height--) {
2791
            $thisid = $id;
2792
            $aliasListing = get_by_key(UrlProcessor::getFacadeRoot()->aliasListing, $id, [], 'is_array');
2793
            $tmp = get_by_key($aliasListing, 'parent');
2794
            if ($this->getConfig('aliaslistingfolder')) {
2795
                $id = $tmp ?? (int)Models\SiteContent::findOrNew($id)->parent;
2796
            } else {
2797
                $id = $tmp;
2798
            }
2799
2800
            if ((int)$id === 0) {
2801
                break;
2802
            }
2803
            $parents[$thisid] = (int)$id;
2804
        }
2805
2806
        return $parents;
2807
    }
2808
2809
    /**
2810
     * @param $id
2811
     * @param int $top
2812
     * @return mixed
2813
     */
2814
    public function getUltimateParentId($id, $top = 0)
2815
    {
2816
        $i = 0;
2817
        while ($id && $i < 20) {
2818
            if ($top == UrlProcessor::getFacadeRoot()->aliasListing[$id]['parent']) {
2819
                break;
2820
            }
2821
            $id = UrlProcessor::getFacadeRoot()->aliasListing[$id]['parent'];
2822
            $i++;
2823
        }
2824
2825
        return $id;
2826
    }
2827
2828
    /**
2829
     * Returns an array of child IDs belonging to the specified parent.
2830
     *
2831
     * @param int $id The parent resource/document to start from
2832
     * @param int $depth How many levels deep to search for children, default: 10
2833
     * @param array $children Optional array of docids to merge with the result.
2834
     * @return array Contains the document Listing (tree) like the sitemap
2835
     */
2836
    public function getChildIds($id, $depth = 10, $children = array())
2837
    {
2838
2839
        $cacheKey = md5(print_r(func_get_args(), true));
2840
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
2841
            return $this->tmpCache[__FUNCTION__][$cacheKey];
2842
        }
2843
2844
        if ($this->getConfig('aliaslistingfolder') == 1) {
2845
2846
            $res = $this->getDatabase()->select("id,alias,isfolder,parent",
2847
                $this->getDatabase()->getFullTableName('site_content'), "parent IN (" . $id . ") AND deleted = '0'");
2848
            $idx = array();
2849
            while ($row = $this->getDatabase()->getRow($res)) {
2850
                $pAlias = '';
2851
                if (isset(UrlProcessor::getFacadeRoot()->aliasListing[$row['parent']])) {
2852
                    $pAlias .= !empty(UrlProcessor::getFacadeRoot()->aliasListing[$row['parent']]['path']) ? UrlProcessor::getFacadeRoot()->aliasListing[$row['parent']]['path'] . '/' : '';
2853
                    $pAlias .= !empty(UrlProcessor::getFacadeRoot()->aliasListing[$row['parent']]['alias']) ? UrlProcessor::getFacadeRoot()->aliasListing[$row['parent']]['alias'] . '/' : '';
2854
                };
2855
                $children[$pAlias . $row['alias']] = $row['id'];
2856
                if ($row['isfolder'] == 1) {
2857
                    $idx[] = $row['id'];
2858
                }
2859
            }
2860
            $depth--;
2861
            $idx = implode(',', $idx);
2862
            if (!empty($idx)) {
2863
                if ($depth) {
2864
                    $children = $this->getChildIds($idx, $depth, $children);
2865
                }
2866
            }
2867
            $this->tmpCache[__FUNCTION__][$cacheKey] = $children;
2868
2869
            return $children;
2870
2871
        } else {
2872
2873
            // Initialise a static array to index parents->children
2874
            static $documentMap_cache = array();
2875
            if (!count($documentMap_cache)) {
2876
                foreach ($this->documentMap as $document) {
2877
                    foreach ($document as $p => $c) {
2878
                        $documentMap_cache[$p][] = $c;
2879
                    }
2880
                }
2881
            }
2882
2883
            // Get all the children for this parent node
2884
            if (isset($documentMap_cache[$id])) {
2885
                $depth--;
2886
2887
                foreach ($documentMap_cache[$id] as $childId) {
2888
                    $pkey = (strlen(UrlProcessor::getFacadeRoot()->aliasListing[$childId]['path']) ? "{UrlProcessor::getFacadeRoot()->aliasListing[$childId]['path']}/" : '') . UrlProcessor::getFacadeRoot()->aliasListing[$childId]['alias'];
2889
                    if (!strlen($pkey)) {
2890
                        $pkey = "{$childId}";
2891
                    }
2892
                    $children[$pkey] = $childId;
2893
2894
                    if ($depth && isset($documentMap_cache[$childId])) {
2895
                        $children += $this->getChildIds($childId, $depth);
2896
                    }
2897
                }
2898
            }
2899
            $this->tmpCache[__FUNCTION__][$cacheKey] = $children;
2900
2901
            return $children;
2902
2903
        }
2904
    }
2905
2906
    /**
2907
     * Displays a javascript alert message in the web browser and quit
2908
     *
2909
     * @param string $msg Message to show
2910
     * @param string $url URL to redirect to
2911
     */
2912
    public function webAlertAndQuit($msg, $url = '')
2913
    {
2914
        global $modx_manager_charset, $modx_lang_attribute, $modx_textdir, $lastInstallTime;
2915
2916
        if(empty($modx_manager_charset)) {
2917
            $modx_manager_charset = $this->getConfig('modx_charset');
2918
        }
2919
2920
        if(empty($modx_lang_attribute)) {
2921
            $modx_lang_attribute = $this->getConfig('lang_code');
2922
        }
2923
2924
        if(empty($modx_textdir)) {
2925
            $modx_textdir = $this->getConfig('manager_direction');
2926
        }
2927
        $textdir = $modx_textdir === 'rtl' ? 'rtl' : 'ltr';
2928
2929
        switch (true) {
2930
            case (0 === stripos($url, 'javascript:')):
2931
                $fnc = substr($url, 11);
2932
                break;
2933
            case $url === '#':
2934
                $fnc = '';
2935
                break;
2936
            case empty($url):
2937
                $fnc = 'history.back(-1);';
2938
                break;
2939
            default:
2940
                $fnc = "window.location.href='" . addslashes($url) . "';";
2941
        }
2942
2943
        $style = '';
2944
        if (IN_MANAGER_MODE) {
2945
            if (empty($lastInstallTime)) {
2946
                $lastInstallTime = time();
2947
            }
2948
2949
            $path =  'media/style/' . $this->getConfig('manager_theme') . '/';
2950
            $css = file_exists(MODX_MANAGER_PATH .  $path . '/css/styles.min.css') ? '/css/styles.min.css' : 'style.css';
2951
            $style = '<link rel="stylesheet" type="text/css" href="' . MODX_MANAGER_URL . $path . $css . '?v=' . $lastInstallTime . '"/>';
2952
        }
2953
2954
        ob_get_clean();
2955
        echo "<!DOCTYPE html>
2956
            <html lang=\"{$modx_lang_attribute}\" dir=\"{$textdir}\">
2957
                <head>
2958
                <title>MODX :: Alert</title>
2959
                <meta http-equiv=\"Content-Type\" content=\"text/html; charset={$modx_manager_charset};\">
2960
                {$style}
2961
                <script>
2962
                    function __alertQuit() {
2963
                        var el = document.querySelector('p');
2964
                        alert(el.innerHTML);
2965
                        el.remove();
2966
                        {$fnc}
2967
                    }
2968
                    window.setTimeout('__alertQuit();',100);
2969
                </script>
2970
            </head>
2971
            <body>
2972
                <p>{$msg}</p>
2973
            </body>
2974
        </html>";
2975
        exit;
2976
    }
2977
2978
    /**
2979
     * Returns 1 if user has the currect permission
2980
     *
2981
     * @param string $pm Permission name
2982
     * @return int Why not bool?
2983
     */
2984
    public function hasPermission($pm)
2985
    {
2986
        $state = 0;
2987
        $pms = $_SESSION['mgrPermissions'];
2988
        if ($pms) {
2989
            $state = (isset($pms[$pm]) && (bool)$pms[$pm] === true);
2990
        }
2991
2992
        return (int)$state;
2993
    }
2994
2995
    /**
2996
     * @param array $permissions
2997
     * @return bool
2998
     */
2999
    public function hasAnyPermissions(array $permissions) {
3000
        foreach ($permissions as $p) {
3001
            if ($this->hasPermission($p)) {
3002
                return true;
3003
            }
3004
        }
3005
3006
        return false;
3007
    }
3008
3009
    /**
3010
     * Returns true if element is locked
3011
     *
3012
     * @param int $type Types: 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3013
     * @param int $id Element- / Resource-id
3014
     * @param bool $includeThisUser true = Return also info about actual user
3015
     * @return array lock-details or null
3016
     */
3017
    public function elementIsLocked($type, $id, $includeThisUser = false)
3018
    {
3019
        $id = (int)$id;
3020
        $type = (int)$type;
3021
        if (!$type || !$id) {
3022
            return null;
3023
        }
3024
3025
        // Build lockedElements-Cache at first call
3026
        $this->buildLockedElementsCache();
3027
3028
        if (!$includeThisUser && $this->lockedElements[$type][$id]['sid'] == $this->sid) {
3029
            return null;
3030
        }
3031
3032
        if (isset($this->lockedElements[$type][$id])) {
3033
            return $this->lockedElements[$type][$id];
3034
        } else {
3035
            return null;
3036
        }
3037
    }
3038
3039
    /**
3040
     * Returns Locked Elements as Array
3041
     *
3042
     * @param int $type Types: 0=all, 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3043
     * @param bool $minimumDetails true =
3044
     * @return array|mixed|null
3045
     */
3046
    public function getLockedElements($type = 0, $minimumDetails = false)
3047
    {
3048
        $this->buildLockedElementsCache();
3049
3050
        if (!$minimumDetails) {
3051
            $lockedElements = $this->lockedElements;
3052
        } else {
3053
            // Minimum details for HTML / Ajax-requests
3054
            $lockedElements = array();
3055
            foreach ($this->lockedElements as $elType => $elements) {
3056
                foreach ($elements as $elId => $el) {
3057
                    $lockedElements[$elType][$elId] = array(
3058
                        'username'   => $el['username'],
3059
                        'lasthit_df' => $el['lasthit_df'],
3060
                        'state'      => $this->determineLockState($el['internalKey'])
3061
                    );
3062
                }
3063
            }
3064
        }
3065
3066
        if ($type == 0) {
3067
            return $lockedElements;
3068
        }
3069
3070
        $type = (int)$type;
3071
        if (isset($lockedElements[$type])) {
3072
            return $lockedElements[$type];
3073
        } else {
3074
            return array();
3075
        }
3076
    }
3077
3078
    /**
3079
     * Builds the Locked Elements Cache once
3080
     */
3081
    public function buildLockedElementsCache()
3082
    {
3083
        if (is_null($this->lockedElements)) {
3084
            $this->lockedElements = array();
3085
            $this->cleanupExpiredLocks();
3086
3087
            $rs = $this->getDatabase()->select('sid,internalKey,elementType,elementId,lasthit,username',
3088
                $this->getDatabase()->getFullTableName('active_user_locks') . " ul
3089
                LEFT JOIN {$this->getDatabase()->getFullTableName('manager_users')} mu on ul.internalKey = mu.id");
3090
            while ($row = $this->getDatabase()->getRow($rs)) {
3091
                $this->lockedElements[$row['elementType']][$row['elementId']] = array(
3092
                    'sid'         => $row['sid'],
3093
                    'internalKey' => $row['internalKey'],
3094
                    'username'    => $row['username'],
3095
                    'elementType' => $row['elementType'],
3096
                    'elementId'   => $row['elementId'],
3097
                    'lasthit'     => $row['lasthit'],
3098
                    'lasthit_df'  => $this->toDateFormat($row['lasthit']),
3099
                    'state'       => $this->determineLockState($row['sid'])
3100
                );
3101
            }
3102
        }
3103
    }
3104
3105
    /**
3106
     * Cleans up the active user locks table
3107
     */
3108
    public function cleanupExpiredLocks()
3109
    {
3110
        // Clean-up active_user_sessions first
3111
        $timeout = (int)$this->getConfig('session_timeout') < 2 ? 120 : $this->getConfig('session_timeout') * 60; // session.js pings every 10min, updateMail() in mainMenu pings every minute, so 2min is minimum
3112
        $validSessionTimeLimit = $this->time - $timeout;
3113
        $this->getDatabase()->delete($this->getDatabase()->getFullTableName('active_user_sessions'),
3114
            "lasthit < {$validSessionTimeLimit}");
3115
3116
        // Clean-up active_user_locks
3117
        $rs = $this->getDatabase()->select('sid,internalKey',
3118
            $this->getDatabase()->getFullTableName('active_user_sessions'));
3119
        $count = $this->getDatabase()->getRecordCount($rs);
3120
        if ($count) {
3121
            $rs = $this->getDatabase()->makeArray($rs);
3122
            $userSids = array();
3123
            foreach ($rs as $row) {
3124
                $userSids[] = $row['sid'];
3125
            }
3126
            $userSids = "'" . implode("','", $userSids) . "'";
3127
            $this->getDatabase()->delete($this->getDatabase()->getFullTableName('active_user_locks'),
3128
                "sid NOT IN({$userSids})");
3129
        } else {
3130
            $this->getDatabase()->delete($this->getDatabase()->getFullTableName('active_user_locks'));
3131
        }
3132
3133
    }
3134
3135
    /**
3136
     * Cleans up the active users table
3137
     */
3138
    public function cleanupMultipleActiveUsers()
3139
    {
3140
        $timeout = 20 * 60; // Delete multiple user-sessions after 20min
3141
        $validSessionTimeLimit = $this->time - $timeout;
3142
3143
        $activeUserSids = array();
3144
        $rs = $this->getDatabase()->select('sid', $this->getDatabase()->getFullTableName('active_user_sessions'));
3145
        $count = $this->getDatabase()->getRecordCount($rs);
3146
        if ($count) {
3147
            $rs = $this->getDatabase()->makeArray($rs);
3148
            foreach ($rs as $row) {
3149
                $activeUserSids[] = $row['sid'];
3150
            }
3151
        }
3152
3153
        $rs = $this->getDatabase()->select("sid,internalKey,lasthit",
3154
            "{$this->getDatabase()->getFullTableName('active_users')}", "", "lasthit DESC");
3155
        if ($this->getDatabase()->getRecordCount($rs)) {
3156
            $rs = $this->getDatabase()->makeArray($rs);
3157
            $internalKeyCount = array();
3158
            $deleteSids = '';
3159
            foreach ($rs as $row) {
3160
                if (!isset($internalKeyCount[$row['internalKey']])) {
3161
                    $internalKeyCount[$row['internalKey']] = 0;
3162
                }
3163
                $internalKeyCount[$row['internalKey']]++;
3164
3165
                if ($internalKeyCount[$row['internalKey']] > 1 && !in_array($row['sid'],
3166
                        $activeUserSids) && $row['lasthit'] < $validSessionTimeLimit) {
3167
                    $deleteSids .= $deleteSids == '' ? '' : ' OR ';
3168
                    $deleteSids .= "sid='{$row['sid']}'";
3169
                };
3170
3171
            }
3172
            if ($deleteSids) {
3173
                $this->getDatabase()->delete($this->getDatabase()->getFullTableName('active_users'), $deleteSids);
3174
            }
3175
        }
3176
3177
    }
3178
3179
    /**
3180
     * Determines state of a locked element acc. to user-permissions
3181
     *
3182
     * @param $sid
3183
     * @return int $state States: 0=No display, 1=viewing this element, 2=locked, 3=show unlock-button
3184
     * @internal param int $internalKey : ID of User who locked actual element
3185
     */
3186
    public function determineLockState($sid)
3187
    {
3188
        $state = 0;
3189
        if ($this->hasPermission('display_locks')) {
3190
            if ($sid == $this->sid) {
3191
                $state = 1;
3192
            } else {
3193
                if ($this->hasPermission('remove_locks')) {
3194
                    $state = 3;
3195
                } else {
3196
                    $state = 2;
3197
                }
3198
            }
3199
        }
3200
3201
        return $state;
3202
    }
3203
3204
    /**
3205
     * Locks an element
3206
     *
3207
     * @param int $type Types: 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3208
     * @param int $id Element- / Resource-id
3209
     * @return bool
0 ignored issues
show
Documentation introduced by
Should the return type not be false|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
3210
     */
3211
    public function lockElement($type, $id)
3212
    {
3213
        $userId = $this->isBackend() && $_SESSION['mgrInternalKey'] ? $_SESSION['mgrInternalKey'] : 0;
3214
        $type = (int)$type;
3215
        $id = (int)$id;
3216
        if (!$type || !$id || !$userId) {
3217
            return false;
3218
        }
3219
3220
        $sql = sprintf('REPLACE INTO %s (internalKey, elementType, elementId, lasthit, sid)
3221
                VALUES (%d, %d, %d, %d, \'%s\')', $this->getDatabase()->getFullTableName('active_user_locks'), $userId,
3222
            $type, $id, $this->time, $this->sid);
3223
        $this->getDatabase()->query($sql);
3224
    }
3225
3226
    /**
3227
     * Unlocks an element
3228
     *
3229
     * @param int $type Types: 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3230
     * @param int $id Element- / Resource-id
3231
     * @param bool $includeAllUsers true = Deletes not only own user-locks
3232
     * @return bool
0 ignored issues
show
Documentation introduced by
Should the return type not be false|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
3233
     */
3234
    public function unlockElement($type, $id, $includeAllUsers = false)
3235
    {
3236
        $userId = $this->isBackend() && $_SESSION['mgrInternalKey'] ? $_SESSION['mgrInternalKey'] : 0;
3237
        $type = (int)$type;
3238
        $id = (int)$id;
3239
        if (!$type || !$id) {
3240
            return false;
3241
        }
3242
3243
        if (!$includeAllUsers) {
3244
            $sql = sprintf('DELETE FROM %s WHERE internalKey = %d AND elementType = %d AND elementId = %d;',
3245
                $this->getDatabase()->getFullTableName('active_user_locks'), $userId, $type, $id);
3246
        } else {
3247
            $sql = sprintf('DELETE FROM %s WHERE elementType = %d AND elementId = %d;',
3248
                $this->getDatabase()->getFullTableName('active_user_locks'), $type, $id);
3249
        }
3250
        $this->getDatabase()->query($sql);
3251
    }
3252
3253
    /**
3254
     * Updates table "active_user_sessions" with userid, lasthit, IP
3255
     */
3256
    public function updateValidatedUserSession()
3257
    {
3258
        if (!$this->sid) {
3259
            return;
3260
        }
3261
3262
        // web users are stored with negative keys
3263
        $userId = $this->getLoginUserType() == 'manager' ? $this->getLoginUserID() : -$this->getLoginUserID();
3264
3265
        // Get user IP
3266 View Code Duplication
        if ($cip = getenv("HTTP_CLIENT_IP")) {
3267
            $ip = $cip;
3268
        } elseif ($cip = getenv("HTTP_X_FORWARDED_FOR")) {
3269
            $ip = $cip;
3270
        } elseif ($cip = getenv("REMOTE_ADDR")) {
3271
            $ip = $cip;
3272
        } else {
3273
            $ip = "UNKNOWN";
3274
        }
3275
        $_SESSION['ip'] = $ip;
3276
3277
        Models\ActiveUserSession::updateOrCreate([
3278
            'internalKey' => $userId,
3279
            'sid' => $this->sid,
3280
        ], [
3281
            'lasthit' => $this->time,
3282
            'ip' => $ip,
3283
        ]);
3284
    }
3285
3286
    /**
3287
     * Add an a alert message to the system event log
3288
     *
3289
     * @param int $evtid Event ID
3290
     * @param int $type Types: 1 = information, 2 = warning, 3 = error
3291
     * @param string $msg Message to be logged
3292
     * @param string $source source of the event (module, snippet name, etc.)
3293
     *                       Default: Parser
3294
     */
3295
    public function logEvent($evtid, $type, $msg, $source = 'Parser')
3296
    {
3297
        if (!$this->getDatabase()->getDriver()->isConnected()) {
3298
            return;
3299
        }
3300
        $msg = $this->getDatabase()->escape($msg);
3301
        if (strpos($this['config']->get('database.connections.default.charset'), 'utf8') === 0 && extension_loaded('mbstring')) {
3302
            $esc_source = mb_substr($source, 0, 50, "UTF-8");
3303
        } else {
3304
            $esc_source = substr($source, 0, 50);
3305
        }
3306
        $esc_source = $this->getDatabase()->escape($esc_source);
3307
3308
        $LoginUserID = $this->getLoginUserID();
3309
        if ($LoginUserID == '') {
3310
            $LoginUserID = 0;
3311
        }
3312
3313
        $usertype = $this->isFrontend() ? 1 : 0;
3314
        $evtid = (int)$evtid;
3315
        $type = (int)$type;
3316
3317
        // Types: 1 = information, 2 = warning, 3 = error
3318
        if ($type < 1) {
3319
            $type = 1;
3320
        } elseif ($type > 3) {
3321
            $type = 3;
3322
        }
3323
3324
        $this->getDatabase()->insert(array(
3325
            'eventid'     => $evtid,
3326
            'type'        => $type,
3327
            'createdon'   => $_SERVER['REQUEST_TIME'] + $this->getConfig('server_offset_time'),
3328
            'source'      => $esc_source,
3329
            'description' => $msg,
3330
            'user'        => $LoginUserID,
3331
            'usertype'    => $usertype
3332
        ), $this->getDatabase()->getFullTableName("event_log"));
3333
3334
        if ($this->getConfig('send_errormail', '0') != '0') {
3335
            if ($this->getConfig('send_errormail') <= $type) {
3336
                $this->sendmail(array(
3337
                    'subject' => 'Evolution CMS System Error on ' . $this->getConfig('site_name'),
3338
                    'body'    => 'Source: ' . $source . ' - The details of the error could be seen in the Evolution CMS system events log.',
3339
                    'type'    => 'text'
3340
                ));
3341
            }
3342
        }
3343
    }
3344
3345
    /**
3346
     * @param string|array $params
3347
     * @param string $msg
3348
     * @param array $files
3349
     * @return bool
3350
     * @throws \PHPMailer\PHPMailer\Exception
3351
     */
3352
    public function sendmail($params = array(), $msg = '', $files = array())
3353
    {
3354
        if (\is_scalar($params)) {
3355
            if (strpos($params, '=') === false) {
3356
                if (strpos($params, '@') !== false) {
3357
                    $p['to'] = $params;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$p was never initialized. Although not strictly required by PHP, it is generally a good practice to add $p = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
3358
                } else {
3359
                    $p['subject'] = $params;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$p was never initialized. Although not strictly required by PHP, it is generally a good practice to add $p = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
3360
                }
3361
            } else {
3362
                $params_array = explode(',', $params);
3363
                foreach ($params_array as $k => $v) {
3364
                    $k = trim($k);
3365
                    $v = trim($v);
3366
                    $p[$k] = $v;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$p was never initialized. Although not strictly required by PHP, it is generally a good practice to add $p = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
3367
                }
3368
            }
3369
        } else {
3370
            $p = $params;
3371
            unset($params);
3372
        }
3373
        if (isset($p['sendto'])) {
3374
            $p['to'] = $p['sendto'];
3375
        }
3376
3377
        if (isset($p['to']) && preg_match('@^\d+$@', $p['to'])) {
3378
            $userinfo = $this->getUserInfo($p['to']);
3379
            $p['to'] = $userinfo['email'];
3380
        }
3381
        if (isset($p['from']) && preg_match('@^\d+$@', $p['from'])) {
3382
            $userinfo = $this->getUserInfo($p['from']);
3383
            $p['from'] = $userinfo['email'];
3384
            $p['fromname'] = $userinfo['username'];
3385
        }
3386
        if ($msg === '' && !isset($p['body'])) {
3387
            $p['body'] = $_SERVER['REQUEST_URI'] . "\n" . $_SERVER['HTTP_USER_AGENT'] . "\n" . $_SERVER['HTTP_REFERER'];
0 ignored issues
show
Bug introduced by
The variable $p does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
3388
        } elseif (is_string($msg) && 0 < strlen($msg)) {
3389
            $p['body'] = $msg;
3390
        }
3391
3392
        $sendto = (!isset($p['to'])) ? $this->getConfig('emailsender') : $p['to'];
3393
        $sendto = explode(',', $sendto);
3394
        foreach ($sendto as $address) {
3395
            list($name, $address) = $this->getMail()->address_split($address);
3396
            $this->getMail()->AddAddress($address, $name);
3397
        }
3398 View Code Duplication
        if (isset($p['cc'])) {
3399
            $p['cc'] = explode(',', $p['cc']);
3400
            foreach ($p['cc'] as $address) {
3401
                list($name, $address) = $this->getMail()->address_split($address);
3402
                $this->getMail()->AddCC($address, $name);
3403
            }
3404
        }
3405 View Code Duplication
        if (isset($p['bcc'])) {
3406
            $p['bcc'] = explode(',', $p['bcc']);
3407
            foreach ($p['bcc'] as $address) {
3408
                list($name, $address) = $this->getMail()->address_split($address);
3409
                $this->getMail()->AddBCC($address, $name);
3410
            }
3411
        }
3412
        if (isset($p['from']) && strpos($p['from'], '<') !== false && substr($p['from'], -1) === '>') {
3413
            list($p['fromname'], $p['from']) = $this->getMail()->address_split($p['from']);
3414
        }
3415
        $this->getMail()->setFrom(
3416
            isset($p['from']) ? $p['from'] : $this->getConfig('emailsender'),
3417
            isset($p['fromname']) ? $p['fromname'] : $this->getConfig('site_name')
3418
        );
3419
        $this->getMail()->Subject = (!isset($p['subject'])) ? $this->getConfig('emailsubject') : $p['subject'];
3420
        $this->getMail()->Body = $p['body'];
3421
        if (isset($p['type']) && $p['type'] === 'text') {
3422
            $this->getMail()->IsHTML(false);
3423
        }
3424
        if (!is_array($files)) {
3425
            $files = array();
3426
        }
3427
        foreach ($files as $f) {
3428
            if (file_exists(MODX_BASE_PATH . $f) && is_file(MODX_BASE_PATH . $f) && is_readable(MODX_BASE_PATH . $f)) {
3429
                $this->getMail()->AddAttachment(MODX_BASE_PATH . $f);
3430
            }
3431
        }
3432
3433
        return $this->getMail()->send();
3434
    }
3435
3436
    /**
3437
     * @param string $target
3438
     * @param int $limit
3439
     * @param int $trim
3440
     */
3441
    public function rotate_log($target = 'event_log', $limit = 3000, $trim = 100)
3442
    {
3443
        if ($limit < $trim) {
3444
            $trim = $limit;
3445
        }
3446
3447
        $table_name = $this->getDatabase()->getFullTableName($target);
3448
        $count = $this->getDatabase()->getValue($this->getDatabase()->select('COUNT(id)', $table_name));
3449
        $over = $count - $limit;
3450
        if (0 < $over) {
3451
            $trim = ($over + $trim);
3452
            $this->getDatabase()->delete($table_name, '', '', $trim);
3453
        }
3454
        $this->getDatabase()->optimize($table_name);
3455
    }
3456
3457
    /**
3458
     * Returns true if we are currently in the manager backend
3459
     *
3460
     * @return boolean
3461
     */
3462
    public function isBackend()
3463
    {
3464
        return (defined('IN_MANAGER_MODE') && IN_MANAGER_MODE === true);
3465
    }
3466
3467
    /**
3468
     * Returns true if we are currently in the frontend
3469
     *
3470
     * @return boolean
3471
     */
3472
    public function isFrontend()
3473
    {
3474
        return !$this->isBackend();
3475
    }
3476
3477
    /**
3478
     * Gets all child documents of the specified document, including those which are unpublished or deleted.
3479
     *
3480
     * @param int $id The Document identifier to start with
3481
     * @param string $sort Sort field
3482
     *                     Default: menuindex
3483
     * @param string $dir Sort direction, ASC and DESC is possible
3484
     *                    Default: ASC
3485
     * @param string $fields Default: id, pagetitle, description, parent, alias, menutitle
3486
     * @return array
3487
     */
3488 View Code Duplication
    public function getAllChildren(
3489
        $id = 0,
3490
        $sort = 'menuindex',
3491
        $dir = 'ASC',
3492
        $fields = 'id, pagetitle, description, parent, alias, menutitle'
3493
    ) {
3494
3495
        $cacheKey = md5(print_r(func_get_args(), true));
3496
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3497
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3498
        }
3499
3500
        $tblsc = $this->getDatabase()->getFullTableName("site_content");
3501
        $tbldg = $this->getDatabase()->getFullTableName("document_groups");
3502
        // modify field names to use sc. table reference
3503
        $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3504
        $sort = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
3505
        // get document groups for current user
3506
        if ($docgrp = $this->getUserDocGroups()) {
3507
            $docgrp = implode(",", $docgrp);
3508
        }
3509
        // build query
3510
        $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
3511
        $result = $this->getDatabase()->select("DISTINCT {$fields}", "{$tblsc} sc
3512
                LEFT JOIN {$tbldg} dg on dg.document = sc.id", "sc.parent = '{$id}' AND ({$access}) GROUP BY sc.id",
3513
            "{$sort} {$dir}");
3514
        $resourceArray = $this->getDatabase()->makeArray($result);
3515
        $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
3516
3517
        return $resourceArray;
3518
    }
3519
3520
    /**
3521
     * Gets all active child documents of the specified document, i.e. those which published and not deleted.
3522
     *
3523
     * @param int $id The Document identifier to start with
3524
     * @param string $sort Sort field
3525
     *                     Default: menuindex
3526
     * @param string $dir Sort direction, ASC and DESC is possible
3527
     *                    Default: ASC
3528
     * @param string $fields Default: id, pagetitle, description, parent, alias, menutitle
3529
     * @return array
3530
     */
3531 View Code Duplication
    public function getActiveChildren(
3532
        $id = 0,
3533
        $sort = 'menuindex',
3534
        $dir = 'ASC',
3535
        $fields = 'id, pagetitle, description, parent, alias, menutitle'
3536
    ) {
3537
        $cacheKey = md5(print_r(func_get_args(), true));
3538
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3539
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3540
        }
3541
3542
        $tblsc = $this->getDatabase()->getFullTableName("site_content");
3543
        $tbldg = $this->getDatabase()->getFullTableName("document_groups");
3544
3545
        // modify field names to use sc. table reference
3546
        $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3547
        $sort = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
3548
        // get document groups for current user
3549
        if ($docgrp = $this->getUserDocGroups()) {
3550
            $docgrp = implode(",", $docgrp);
3551
        }
3552
        // build query
3553
        $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
3554
        $result = $this->getDatabase()->select("DISTINCT {$fields}", "{$tblsc} sc
3555
                LEFT JOIN {$tbldg} dg on dg.document = sc.id",
3556
            "sc.parent = '{$id}' AND sc.published=1 AND sc.deleted=0 AND ({$access}) GROUP BY sc.id", "{$sort} {$dir}");
3557
        $resourceArray = $this->getDatabase()->makeArray($result);
3558
3559
        $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
3560
3561
        return $resourceArray;
3562
    }
3563
3564
    /**
3565
     * getDocumentChildren
3566
     * @version 1.1.1 (2014-02-19)
3567
     *
3568
     * @desc Returns the children of the selected document/folder as an associative array.
3569
     *
3570
     * @param $parentid {integer} - The parent document identifier. Default: 0 (site root).
3571
     * @param $published {0; 1; 'all'} - Document publication status. Once the parameter equals 'all', the result will be returned regardless of whether the ducuments are published or they are not. Default: 1.
3572
     * @param $deleted {0; 1; 'all'} - Document removal status. Once the parameter equals 'all', the result will be returned regardless of whether the ducuments are deleted or they are not. Default: 0.
3573
     * @param $fields {comma separated string; '*'} - Comma separated list of document fields to get. Default: '*' (all fields).
3574
     * @param $where {string} - Where condition in SQL style. Should include a leading 'AND '. Default: ''.
3575
     * @param $sort {comma separated string} - Should be a comma-separated list of field names on which to sort. Default: 'menuindex'.
3576
     * @param $dir {'ASC'; 'DESC'} - Sort direction, ASC and DESC is possible. Default: 'ASC'.
3577
     * @param $limit {string} - Should be a valid SQL LIMIT clause without the 'LIMIT ' i.e. just include the numbers as a string. Default: Empty string (no limit).
3578
     *
3579
     * @return {array; false} - Result array, or false.
0 ignored issues
show
Documentation introduced by
The doc-type {array; could not be parsed: Unknown type name "{array" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
3580
     */
3581
    public function getDocumentChildren(
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
3582
        $parentid = 0,
3583
        $published = 1,
3584
        $deleted = 0,
3585
        $fields = '*',
3586
        $where = '',
3587
        $sort = 'menuindex',
3588
        $dir = 'ASC',
3589
        $limit = ''
3590
    ) {
3591
3592
        $cacheKey = md5(print_r(func_get_args(), true));
3593
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3594
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3595
        }
3596
3597
        $published = ($published !== 'all') ? 'AND sc.published = ' . $published : '';
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of $published (integer) and 'all' (string) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
3598
        $deleted = ($deleted !== 'all') ? 'AND sc.deleted = ' . $deleted : '';
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of $deleted (integer) and 'all' (string) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
3599
3600
        if ($where != '') {
3601
            $where = 'AND ' . $where;
3602
        }
3603
3604
        // modify field names to use sc. table reference
3605
        $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3606
        $sort = ($sort == '') ? '' : 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
3607
3608
        // get document groups for current user
3609
        if ($docgrp = $this->getUserDocGroups()) {
3610
            $docgrp = implode(',', $docgrp);
3611
        }
3612
3613
        // build query
3614
        $access = ($this->isFrontend() ? 'sc.privateweb=0' : '1="' . $_SESSION['mgrRole'] . '" OR sc.privatemgr=0') . (!$docgrp ? '' : ' OR dg.document_group IN (' . $docgrp . ')');
3615
3616
        $tblsc = $this->getDatabase()->getFullTableName('site_content');
3617
        $tbldg = $this->getDatabase()->getFullTableName('document_groups');
3618
3619
        $result = $this->getDatabase()->select("DISTINCT {$fields}", "{$tblsc} sc
3620
                LEFT JOIN {$tbldg} dg on dg.document = sc.id",
3621
            "sc.parent = '{$parentid}' {$published} {$deleted} {$where} AND ({$access}) GROUP BY sc.id",
3622
            ($sort ? "{$sort} {$dir}" : ""), $limit);
3623
3624
        $resourceArray = $this->getDatabase()->makeArray($result);
3625
3626
        $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
3627
3628
        return $resourceArray;
3629
    }
3630
3631
    /**
3632
     * getDocuments
3633
     * @version 1.1.1 (2013-02-19)
3634
     *
3635
     * @desc Returns required documents (their fields).
3636
     *
3637
     * @param $ids {array; comma separated string} - Documents Ids to get. @required
3638
     * @param $published {0; 1; 'all'} - Documents publication status. Once the parameter equals 'all', the result will be returned regardless of whether the documents are published or they are not. Default: 1.
3639
     * @param $deleted {0; 1; 'all'} - Documents removal status. Once the parameter equals 'all', the result will be returned regardless of whether the documents are deleted or they are not. Default: 0.
3640
     * @param $fields {comma separated string; '*'} - Documents fields to get. Default: '*'.
3641
     * @param $where {string} - SQL WHERE clause. Default: ''.
3642
     * @param $sort {comma separated string} - A comma-separated list of field names to sort by. Default: 'menuindex'.
3643
     * @param $dir {'ASC'; 'DESC'} - Sorting direction. Default: 'ASC'.
3644
     * @param $limit {string} - SQL LIMIT (without 'LIMIT '). An empty string means no limit. Default: ''.
3645
     *
3646
     * @return {array; false} - Result array with documents, or false.
0 ignored issues
show
Documentation introduced by
The doc-type {array; could not be parsed: Unknown type name "{array" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
3647
     */
3648
    public function getDocuments(
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
3649
        $ids = array(),
3650
        $published = 1,
3651
        $deleted = 0,
3652
        $fields = '*',
3653
        $where = '',
3654
        $sort = 'menuindex',
3655
        $dir = 'ASC',
3656
        $limit = ''
3657
    ) {
3658
3659
        $cacheKey = md5(print_r(func_get_args(), true));
3660
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3661
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3662
        }
3663
3664
        if (is_string($ids)) {
3665
            if (strpos($ids, ',') !== false) {
3666
                $ids = array_filter(array_map('intval', explode(',', $ids)));
3667
            } else {
3668
                $ids = array($ids);
3669
            }
3670
        }
3671
        if (count($ids) == 0) {
3672
            $this->tmpCache[__FUNCTION__][$cacheKey] = false;
3673
3674
            return false;
3675
        } else {
3676
            // modify field names to use sc. table reference
3677
            $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3678
            $sort = ($sort == '') ? '' : 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
3679
            if ($where != '') {
3680
                $where = 'AND ' . $where;
3681
            }
3682
3683
            $published = ($published !== 'all') ? "AND sc.published = '{$published}'" : '';
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of $published (integer) and 'all' (string) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
3684
            $deleted = ($deleted !== 'all') ? "AND sc.deleted = '{$deleted}'" : '';
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of $deleted (integer) and 'all' (string) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
3685
3686
            // get document groups for current user
3687
            if ($docgrp = $this->getUserDocGroups()) {
3688
                $docgrp = implode(',', $docgrp);
3689
            }
3690
3691
            $access = ($this->isFrontend() ? 'sc.privateweb=0' : '1="' . $_SESSION['mgrRole'] . '" OR sc.privatemgr=0') . (!$docgrp ? '' : ' OR dg.document_group IN (' . $docgrp . ')');
3692
3693
            $tblsc = $this->getDatabase()->getFullTableName('site_content');
3694
            $tbldg = $this->getDatabase()->getFullTableName('document_groups');
3695
3696
            $result = $this->getDatabase()->select("DISTINCT {$fields}", "{$tblsc} sc
3697
                    LEFT JOIN {$tbldg} dg on dg.document = sc.id", "(sc.id IN (" . implode(',',
3698
                    $ids) . ") {$published} {$deleted} {$where}) AND ({$access}) GROUP BY sc.id",
3699
                ($sort ? "{$sort} {$dir}" : ""), $limit);
3700
3701
            $resourceArray = $this->getDatabase()->makeArray($result);
3702
3703
            $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
3704
3705
            return $resourceArray;
3706
        }
3707
    }
3708
3709
    /**
3710
     * getDocument
3711
     * @version 1.0.1 (2014-02-19)
3712
     *
3713
     * @desc Returns required fields of a document.
3714
     *
3715
     * @param int $id {integer}
3716
     * - Id of a document which data has to be gained. @required
3717
     * @param string $fields {comma separated string; '*'}
3718
     * - Comma separated list of document fields to get. Default: '*'.
3719
     * @param int $published {0; 1; 'all'}
3720
     * - Document publication status. Once the parameter equals 'all', the result will be returned regardless of whether the documents are published or they are not. Default: false.
3721
     * @param int $deleted {0; 1; 'all'}
3722
     * - Document removal status. Once the parameter equals 'all', the result will be returned regardless of whether the documents are deleted or they are not. Default: 0.
3723
     * @return bool {array; false} - Result array with fields or false.
3724
     * - Result array with fields or false.
3725
     */
3726 View Code Duplication
    public function getDocument($id = 0, $fields = '*', $published = 1, $deleted = 0)
3727
    {
3728
        if ($id == 0) {
3729
            return false;
3730
        } else {
3731
            $docs = $this->getDocuments(array($id), $published, $deleted, $fields, '', '', '', 1);
3732
3733
            if ($docs != false) {
3734
                return $docs[0];
3735
            } else {
3736
                return false;
3737
            }
3738
        }
3739
    }
3740
3741
    /**
3742
     * @param string $field
3743
     * @param string $docid
3744
     * @return bool|mixed
3745
     */
3746
    public function getField($field = 'content', $docid = '')
3747
    {
3748
        if (empty($docid) && isset($this->documentIdentifier)) {
3749
            $docid = $this->documentIdentifier;
3750
        } elseif (!preg_match('@^\d+$@', $docid)) {
3751
            $docid = UrlProcessor::getIdFromAlias($docid);
3752
        }
3753
3754
        if (empty($docid)) {
3755
            return false;
3756
        }
3757
3758
        $cacheKey = md5(print_r(func_get_args(), true));
3759
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3760
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3761
        }
3762
3763
        $doc = $this->getDocumentObject('id', $docid);
3764
        if (is_array($doc[$field])) {
3765
            $tvs = $this->getTemplateVarOutput($field, $docid, 1);
0 ignored issues
show
Documentation introduced by
$field is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
3766
            $content = $tvs[$field];
3767
        } else {
3768
            $content = $doc[$field];
3769
        }
3770
3771
        $this->tmpCache[__FUNCTION__][$cacheKey] = $content;
3772
3773
        return $content;
3774
    }
3775
3776
    /**
3777
     * Returns the page information as database row, the type of result is
3778
     * defined with the parameter $rowMode
3779
     *
3780
     * @param int $pageid The parent document identifier
3781
     *                    Default: -1 (no result)
3782
     * @param int $active Should we fetch only published and undeleted documents/resources?
3783
     *                     1 = yes, 0 = no
3784
     *                     Default: 1
3785
     * @param string $fields List of fields
3786
     *                       Default: id, pagetitle, description, alias
3787
     * @return boolean|array
3788
     */
3789
    public function getPageInfo($pageid = -1, $active = 1, $fields = 'id, pagetitle, description, alias')
3790
    {
3791
3792
        $cacheKey = md5(print_r(func_get_args(), true));
3793
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3794
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3795
        }
3796
3797
        if ($pageid == 0) {
3798
            return false;
3799
        } else {
3800
            $tblsc = $this->getDatabase()->getFullTableName("site_content");
3801
            $tbldg = $this->getDatabase()->getFullTableName("document_groups");
3802
            $activeSql = $active == 1 ? "AND sc.published=1 AND sc.deleted=0" : "";
3803
            // modify field names to use sc. table reference
3804
            $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3805
            // get document groups for current user
3806
            if ($docgrp = $this->getUserDocGroups()) {
3807
                $docgrp = implode(",", $docgrp);
3808
            }
3809
            $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
3810
            $result = $this->getDatabase()->select($fields, "{$tblsc} sc LEFT JOIN {$tbldg} dg on dg.document = sc.id",
3811
                "(sc.id='{$pageid}' {$activeSql}) AND ({$access})", "", 1);
3812
            $pageInfo = $this->getDatabase()->getRow($result);
3813
3814
            $this->tmpCache[__FUNCTION__][$cacheKey] = $pageInfo;
3815
3816
            return $pageInfo;
3817
        }
3818
    }
3819
3820
    /**
3821
     * Returns the parent document/resource of the given docid
3822
     *
3823
     * @param int $pid The parent docid. If -1, then fetch the current document/resource's parent
3824
     *                 Default: -1
3825
     * @param int $active Should we fetch only published and undeleted documents/resources?
3826
     *                     1 = yes, 0 = no
3827
     *                     Default: 1
3828
     * @param string $fields List of fields
3829
     *                       Default: id, pagetitle, description, alias
3830
     * @return boolean|array
3831
     */
3832
    public function getParent($pid = -1, $active = 1, $fields = 'id, pagetitle, description, alias, parent')
3833
    {
3834
        if ($pid == -1) {
3835
            $pid = $this->documentObject['parent'];
3836
3837
            return ($pid == 0) ? false : $this->getPageInfo($pid, $active, $fields);
3838
        } else {
3839
            if ($pid == 0) {
3840
                return false;
3841
            } else {
3842
                // first get the child document
3843
                $child = $this->getPageInfo($pid, $active, "parent");
3844
                // now return the child's parent
3845
                $pid = ($child['parent']) ? $child['parent'] : 0;
3846
3847
                return ($pid == 0) ? false : $this->getPageInfo($pid, $active, $fields);
3848
            }
3849
        }
3850
    }
3851
3852
    /**
3853
     * Returns the id of the current snippet.
3854
     *
3855
     * @return int
3856
     */
3857
    public function getSnippetId()
3858
    {
3859
        if ($this->currentSnippet) {
3860
            $tbl = $this->getDatabase()->getFullTableName("site_snippets");
3861
            $rs = $this->getDatabase()->select('id', $tbl,
3862
                "name='" . $this->getDatabase()->escape($this->currentSnippet) . "'", '', 1);
3863
            if ($snippetId = $this->getDatabase()->getValue($rs)) {
3864
                return $snippetId;
3865
            }
3866
        }
3867
3868
        return 0;
3869
    }
3870
3871
    /**
3872
     * Returns the name of the current snippet.
3873
     *
3874
     * @return string
3875
     */
3876
    public function getSnippetName()
3877
    {
3878
        return $this->currentSnippet;
3879
    }
3880
3881
    /**
3882
     * Clear the cache of MODX.
3883
     *
3884
     * @param string $type
3885
     * @param bool $report
3886
     * @return bool
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
3887
     */
3888
    public function clearCache($type = '', $report = false)
3889
    {
3890
        $cache_dir = $this->bootstrapPath();
3891
        if (is_array($type)) {
3892
            foreach ($type as $_) {
3893
                $this->clearCache($_, $report);
3894
            }
3895
        } elseif ($type == 'full') {
3896
            $sync = new Legacy\Cache();
3897
            $sync->setCachepath($cache_dir);
3898
            $sync->setReport($report);
3899
            $sync->emptyCache();
3900
        } elseif (preg_match('@^[1-9]\d*$@', $type)) {
3901
            $key = ($this->getConfig('cache_type') == 2) ? $this->makePageCacheKey($type) : $type;
3902
            $file_name = "docid_" . $key . "_*.pageCache.php";
3903
            $cache_path = $cache_dir . $file_name;
3904
            $files = glob($cache_path);
3905
            $files[] = $cache_dir . "docid_" . $key . ".pageCache.php";
3906
            foreach ($files as $file) {
3907
                if (!is_file($file)) {
3908
                    continue;
3909
                }
3910
                unlink($file);
3911
            }
3912
        } else {
3913
            $files = glob($cache_dir . '*');
3914
            foreach ($files as $file) {
3915
                $name = basename($file);
3916
                if (strpos($name, '.pageCache.php') === false) {
3917
                    continue;
3918
                }
3919
                if (!is_file($file)) {
3920
                    continue;
3921
                }
3922
                unlink($file);
3923
            }
3924
        }
3925
    }
3926
3927
    /**
3928
     * @deprecated use UrlProcessor::makeUrl()
3929
     */
3930
    public function makeUrl($id, $alias = '', $args = '', $scheme = '')
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
3931
    {
3932
        return UrlProcessor::makeUrl((int)$id, $alias, $args, $scheme);
3933
    }
3934
3935
    /**
3936
     * @deprecated use UrlProcessor::getAliasListing()
3937
     */
3938
    public function getAliasListing($id)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
3939
    {
3940
        return UrlProcessor::getAliasListing($id);
3941
    }
3942
3943
    /**
3944
     * Returns the Evolution CMS version information as version, branch, release date and full application name.
3945
     *
3946
     * @param null $data
3947
     * @return string|array
3948
     */
3949
3950
    public function getVersionData($data = null)
3951
    {
3952
        if (empty($this->version) || !is_array($this->version)) {
3953
            //include for compatibility modx version < 1.0.10
3954
            $version = include EVO_CORE_PATH . 'factory/version.php';
3955
            $this->version = $version;
3956
            $this->version['new_version'] = $this->getConfig('newversiontext', '');
3957
        }
3958
        return ($data !== null && \is_array($this->version) && isset($this->version[$data])) ?
3959
            $this->version[$data] : $this->version;
3960
    }
3961
3962
    /**
3963
     * Executes a snippet.
3964
     *
3965
     * @param string $snippetName
3966
     * @param array $params Default: Empty array
3967
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be array|object|string? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
3968
     */
3969
    public function runSnippet($snippetName, $params = array())
3970
    {
3971
        if (isset ($this->snippetCache[$snippetName])) {
3972
            $snippet = $this->snippetCache[$snippetName];
3973
            $properties = !empty($this->snippetCache[$snippetName . "Props"]) ? $this->snippetCache[$snippetName . "Props"] : '';
3974
        } else { // not in cache so let's check the db
3975
            $snippetObject = $this->_getSnippetFromDatabase($snippetName);
3976
3977
            $snippet = $this->snippetCache[$snippetName] = $snippetObject['content'] === null ?? "return false;";
3978
            $properties = $this->snippetCache[$snippetName . "Props"] = $snippetObject['properties'];
3979
        }
3980
        // load default params/properties
3981
        $parameters = $this->parseProperties($properties, $snippetName, 'snippet');
3982
        $parameters = array_merge($parameters, $params);
3983
3984
        // run snippet
3985
        return $this->evalSnippet($snippet, $parameters);
3986
    }
3987
3988
    /**
3989
     * Returns the chunk content for the given chunk name
3990
     *
3991
     * @param string $chunkName
3992
     * @return boolean|string
3993
     */
3994
    public function getChunk($chunkName)
3995
    {
3996
        $out = null;
3997
        if (empty($chunkName)) {
3998
            // nop
3999
        } elseif ($this->isChunkProcessor('DLTemplate')) {
4000
            $out = \DLTemplate::getInstance($this)->getChunk($chunkName);
4001
        } elseif (isset ($this->chunkCache[$chunkName])) {
4002
            $out = $this->chunkCache[$chunkName];
4003
        } elseif (stripos($chunkName, '@FILE') === 0) {
4004
            $out = $this->chunkCache[$chunkName] = $this->atBindFileContent($chunkName);
4005
        } else {
4006
            $where = sprintf("`name`='%s' AND disabled=0", $this->getDatabase()->escape($chunkName));
4007
            $rs = $this->getDatabase()->select(
4008
                'snippet',
4009
                $this->getDatabase()->getFullTableName('site_htmlsnippets'),
4010
                $where
4011
            );
4012
            if ($this->getDatabase()->getRecordCount($rs) === 1) {
4013
                $row = $this->getDatabase()->getRow($rs);
4014
                $out = $this->chunkCache[$chunkName] = $row['snippet'];
4015
            } else {
4016
                $out = $this->chunkCache[$chunkName] = null;
4017
            }
4018
        }
4019
        return $out;
4020
    }
4021
4022
    /**
4023
     * @param string|object $processor
4024
     * @return bool
4025
     */
4026
    public function isChunkProcessor($processor)
4027
    {
4028
        $value = (string)$this->getConfig('chunk_processor');
4029
        if(is_object($processor)) {
4030
            $processor = get_class($processor);
4031
        }
4032
        return is_scalar($processor) && mb_strtolower($value) === mb_strtolower($processor) && class_exists($processor, false);
4033
    }
4034
4035
    /**
4036
     * parseText
4037
     * @version 1.0 (2013-10-17)
4038
     *
4039
     * @desc Replaces placeholders in text with required values.
4040
     *
4041
     * @param string $tpl
4042
     * @param array $ph
4043
     * @param string $left
4044
     * @param string $right
4045
     * @param bool $execModifier
4046
     * @return string {string} - Parsed text.
4047
     * - Parsed text.
4048
     * @internal param $chunk {string} - String to parse. - String to parse. @required
4049
     * @internal param $chunkArr {array} - Array of values. Key — placeholder name, value — value. - Array of values. Key — placeholder name, value — value. @required
4050
     * @internal param $prefix {string} - Placeholders prefix. Default: '[+'. - Placeholders prefix. Default: '[+'.
4051
     * @internal param $suffix {string} - Placeholders suffix. Default: '+]'. - Placeholders suffix. Default: '+]'.
4052
     *
4053
     */
4054
    public function parseText($tpl = '', $ph = array(), $left = '[+', $right = '+]', $execModifier = true)
4055
    {
4056
        if (empty($ph) || empty($tpl)) {
4057
            return $tpl;
4058
        }
4059
4060 View Code Duplication
        if ($this->getConfig('enable_at_syntax')) {
4061
            if (stripos($tpl, '<@LITERAL>') !== false) {
4062
                $tpl = $this->escapeLiteralTagsContent($tpl);
4063
            }
4064
        }
4065
4066
        $matches = $this->getTagsFromContent($tpl, $left, $right);
4067
        if (empty($matches)) {
4068
            return $tpl;
4069
        }
4070
4071
        foreach ($matches[1] as $i => $key) {
4072
4073
            if (strpos($key, ':') !== false && $execModifier) {
4074
                list($key, $modifiers) = $this->splitKeyAndFilter($key);
4075
            } else {
4076
                $modifiers = false;
4077
            }
4078
4079
            //          if(!isset($ph[$key])) continue;
4080
            if (!array_key_exists($key, $ph)) {
4081
                continue;
4082
            } //NULL values must be saved in placeholders, if we got them from database string
4083
4084
            $value = $ph[$key];
4085
4086
            $s = &$matches[0][$i];
4087
            if ($modifiers !== false) {
4088
                if (strpos($modifiers, $left) !== false) {
4089
                    $modifiers = $this->parseText($modifiers, $ph, $left, $right);
4090
                }
4091
                $value = $this->applyFilter($value, $modifiers, $key);
4092
            }
4093 View Code Duplication
            if (strpos($tpl, $s) !== false) {
4094
                $tpl = str_replace($s, $value, $tpl);
4095
            } elseif($this->debug) {
4096
                $this->addLog('parseText parse error', $_SERVER['REQUEST_URI'] . $s, 2);
4097
            }
4098
        }
4099
4100
        return $tpl;
4101
    }
4102
4103
    /**
4104
     * parseChunk
4105
     * @version 1.1 (2013-10-17)
4106
     *
4107
     * @desc Replaces placeholders in a chunk with required values.
4108
     *
4109
     * @param $chunkName {string} - Name of chunk to parse. @required
4110
     * @param $chunkArr {array} - Array of values. Key — placeholder name, value — value. @required
4111
     * @param string $prefix {string}
4112
     * - Placeholders prefix. Default: '{'.
4113
     * @param string $suffix {string}
4114
     * - Placeholders suffix. Default: '}'.
4115
     * @return bool|mixed|string {string; false} - Parsed chunk or false if $chunkArr is not array.
4116
     * - Parsed chunk or false if $chunkArr is not array.
4117
     */
4118
    public function parseChunk($chunkName, $chunkArr, $prefix = '{', $suffix = '}')
4119
    {
4120
        //TODO: Wouldn't it be more practical to return the contents of a chunk instead of false?
4121
        if (!is_array($chunkArr)) {
4122
            return false;
4123
        }
4124
4125
        return $prefix === '[+' && $suffix === '+]' && $this->isChunkProcessor('DLTemplate') ?
4126
            \DLTemplate::getInstance($this)->parseChunk($chunkName, $chunkArr) :
4127
            $this->parseText($this->getChunk($chunkName), $chunkArr, $prefix, $suffix);
0 ignored issues
show
Bug introduced by
It seems like $this->getChunk($chunkName) targeting EvolutionCMS\Core::getChunk() can also be of type boolean; however, EvolutionCMS\Core::parseText() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
4128
    }
4129
4130
    /**
4131
     * getTpl
4132
     * get template for snippets
4133
     * @param $tpl {string}
4134
     * @return bool|string {string}
4135
     */
4136
    public function getTpl($tpl)
4137
    {
4138
        $template = $tpl;
4139
        $command = '';
4140
        if (preg_match("/^@([^:\s]+)[:\s]+(.+)$/s", trim($tpl), $match)) {
4141
            $command = strtoupper($match[1]);
4142
            $template = $match[2];
4143
        }
4144
        switch ($command) {
4145
            case 'CODE':
4146
                break;
4147
            case 'FILE':
4148
                $template = file_get_contents(MODX_BASE_PATH . $template);
4149
                break;
4150
            case 'CHUNK':
4151
                $template = $this->getChunk($template);
4152
                break;
4153
            case 'DOCUMENT':
4154
                $doc = $this->getDocument($template, 'content', 'all');
4155
                $template = $doc['content'];
4156
                break;
4157
            case 'SELECT':
4158
                $this->getDatabase()->getValue($this->getDatabase()->query("SELECT {$template}"));
4159
                break;
4160
            default:
4161
                if (!($template = $this->getChunk($tpl))) {
4162
                    $template = $tpl;
4163
                }
4164
        }
4165
        return $template;
4166
    }
4167
4168
    /**
4169
     * Returns the timestamp in the date format defined in $this->config['datetime_format']
4170
     *
4171
     * @param int $timestamp Default: 0
4172
     * @param string $mode Default: Empty string (adds the time as below). Can also be 'dateOnly' for no time or 'formatOnly' to get the datetime_format string.
4173
     * @return string
4174
     */
4175
    public function toDateFormat($timestamp = 0, $mode = '')
4176
    {
4177
        $timestamp = trim($timestamp);
4178
        if ($mode !== 'formatOnly' && empty($timestamp)) {
4179
            return '-';
4180
        }
4181
        $timestamp = (int)$timestamp;
4182
4183
        switch ($this->getConfig('datetime_format')) {
4184
            case 'YYYY/mm/dd':
4185
                $dateFormat = '%Y/%m/%d';
4186
                break;
4187
            case 'dd-mm-YYYY':
4188
                $dateFormat = '%d-%m-%Y';
4189
                break;
4190
            case 'mm/dd/YYYY':
4191
                $dateFormat = '%m/%d/%Y';
4192
                break;
4193
            /*
4194
            case 'dd-mmm-YYYY':
4195
                $dateFormat = '%e-%b-%Y';
4196
                break;
4197
            */
4198
        }
4199
4200
        if (empty($mode)) {
4201
            $strTime = strftime($dateFormat . " %H:%M:%S", $timestamp);
0 ignored issues
show
Bug introduced by
The variable $dateFormat does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
4202
        } elseif ($mode == 'dateOnly') {
4203
            $strTime = strftime($dateFormat, $timestamp);
4204
        } elseif ($mode == 'formatOnly') {
4205
            $strTime = $dateFormat;
4206
        }
4207
        return $strTime;
0 ignored issues
show
Bug introduced by
The variable $strTime does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
4208
    }
4209
4210
    /**
4211
     * Make a timestamp from a string corresponding to the format in $this->config['datetime_format']
4212
     *
4213
     * @param string $str
4214
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|integer?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
4215
     */
4216
    public function toTimeStamp($str)
4217
    {
4218
        $str = trim($str);
4219
        if (empty($str)) {
4220
            return '';
4221
        }
4222
4223
        switch ($this->getConfig('datetime_format')) {
4224 View Code Duplication
            case 'YYYY/mm/dd':
4225
                if (!preg_match('/^\d{4}\/\d{2}\/\d{2}[\d :]*$/', $str)) {
4226
                    return '';
4227
                }
4228
                list ($Y, $m, $d, $H, $M, $S) = sscanf($str, '%4d/%2d/%2d %2d:%2d:%2d');
4229
                break;
4230 View Code Duplication
            case 'dd-mm-YYYY':
4231
                if (!preg_match('/^\d{2}-\d{2}-\d{4}[\d :]*$/', $str)) {
4232
                    return '';
4233
                }
4234
                list ($d, $m, $Y, $H, $M, $S) = sscanf($str, '%2d-%2d-%4d %2d:%2d:%2d');
4235
                break;
4236 View Code Duplication
            case 'mm/dd/YYYY':
4237
                if (!preg_match('/^\d{2}\/\d{2}\/\d{4}[\d :]*$/', $str)) {
4238
                    return '';
4239
                }
4240
                list ($m, $d, $Y, $H, $M, $S) = sscanf($str, '%2d/%2d/%4d %2d:%2d:%2d');
4241
                break;
4242
            /*
4243
            case 'dd-mmm-YYYY':
4244
                if (!preg_match('/^[0-9]{2}-[0-9a-z]+-[0-9]{4}[0-9 :]*$/i', $str)) {return '';}
4245
                list ($m, $d, $Y, $H, $M, $S) = sscanf($str, '%2d-%3s-%4d %2d:%2d:%2d');
4246
                break;
4247
            */
4248
        }
4249
        if (!$H && !$M && !$S) {
0 ignored issues
show
Bug introduced by
The variable $H does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $M does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $S does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
4250
            $H = 0;
4251
            $M = 0;
4252
            $S = 0;
4253
        }
4254
        $timeStamp = mktime($H, $M, $S, $m, $d, $Y);
0 ignored issues
show
Bug introduced by
The variable $m does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $d does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $Y does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
4255
        $timeStamp = (int)$timeStamp;
4256
        return $timeStamp;
4257
    }
4258
4259
    /**
4260
     * Get the TVs of a document's children. Returns an array where each element represents one child doc.
4261
     *
4262
     * Ignores deleted children. Gets all children - there is no where clause available.
4263
     *
4264
     * @param int $parentid The parent docid
4265
     *                 Default: 0 (site root)
4266
     * @param array $tvidnames . Which TVs to fetch - Can relate to the TV ids in the db (array elements should be numeric only)
4267
     *                                               or the TV names (array elements should be names only)
4268
     *                      Default: Empty array
4269
     * @param int $published Whether published or unpublished documents are in the result
4270
     *                      Default: 1
4271
     * @param string $docsort How to sort the result array (field)
4272
     *                      Default: menuindex
4273
     * @param ASC|string $docsortdir How to sort the result array (direction)
4274
     *                      Default: ASC
4275
     * @param string $tvfields Fields to fetch from site_tmplvars, default '*'
4276
     *                      Default: *
4277
     * @param string $tvsort How to sort each element of the result array i.e. how to sort the TVs (field)
4278
     *                      Default: rank
4279
     * @param string $tvsortdir How to sort each element of the result array i.e. how to sort the TVs (direction)
4280
     *                      Default: ASC
4281
     * @return array|bool
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use false|array.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
4282
     */
4283
    public function getDocumentChildrenTVars($parentid = 0, $tvidnames = array(), $published = 1, $docsort = "menuindex", $docsortdir = "ASC", $tvfields = "*", $tvsort = "rank", $tvsortdir = "ASC")
4284
    {
4285
        $docs = $this->getDocumentChildren($parentid, $published, 0, '*', '', $docsort, $docsortdir);
4286
        if (!$docs) {
4287
            return false;
4288
        } else {
4289
            $result = array();
4290
            // get user defined template variables
4291
            if ($tvfields) {
4292
                $_ = array_filter(array_map('trim', explode(',', $tvfields)));
4293
                foreach ($_ as $i => $v) {
4294
                    if ($v === 'value') {
4295
                        unset($_[$i]);
4296
                    } else {
4297
                        $_[$i] = 'tv.' . $v;
4298
                    }
4299
                }
4300
                $fields = implode(',', $_);
4301
            } else {
4302
                $fields = "tv.*";
4303
            }
4304
4305
            if ($tvsort != '') {
4306
                $tvsort = 'tv.' . implode(',tv.', array_filter(array_map('trim', explode(',', $tvsort))));
4307
            }
4308 View Code Duplication
            if ($tvidnames == "*") {
4309
                $query = "tv.id<>0";
4310
            } else {
4311
                $query = (is_numeric($tvidnames[0]) ? "tv.id" : "tv.name") . " IN ('" . implode("','", $tvidnames) . "')";
4312
            }
4313
4314
            $this->getUserDocGroups();
4315
4316
            foreach ($docs as $doc) {
4317
4318
                $docid = $doc['id'];
4319
4320
                $rs = $this->getDatabase()->select(
4321
                    "{$fields}, IF(tvc.value!='',tvc.value,tv.default_text) as value ",
4322
4323
                    $this->getDatabase()->getFullTableName("site_tmplvars") .
4324
                    " tv INNER JOIN " . $this->getDatabase()->getFullTableName("site_tmplvar_templates") .
4325
                    " tvtpl ON tvtpl.tmplvarid = tv.id LEFT JOIN " .
4326
                    $this->getDatabase()->getFullTableName("site_tmplvar_contentvalues") .
4327
                    " tvc ON tvc.tmplvarid=tv.id AND tvc.contentid='{$docid}'",
4328
4329
                    "{$query} AND tvtpl.templateid = '{$doc['template']}'",
4330
                    ($tvsort ? "{$tvsort} {$tvsortdir}" : "")
4331
                );
4332
                $tvs = $this->getDatabase()->makeArray($rs);
4333
4334
                // get default/built-in template variables
4335
                ksort($doc);
4336
                foreach ($doc as $key => $value) {
4337
                    if ($tvidnames == '*' || in_array($key, $tvidnames)) {
4338
                        $tvs[] = array('name' => $key, 'value' => $value);
4339
                    }
4340
                }
4341
                if (is_array($tvs) && count($tvs)) {
4342
                    $result[] = $tvs;
4343
                }
4344
            }
4345
            return $result;
4346
        }
4347
    }
4348
4349
    /**
4350
     * getDocumentChildrenTVarOutput
4351
     * @version 1.1 (2014-02-19)
4352
     *
4353
     * @desc Returns an array where each element represents one child doc and contains the result from getTemplateVarOutput().
4354
     *
4355
     * @param int $parentid {integer}
4356
     * - Id of parent document. Default: 0 (site root).
4357
     * @param array $tvidnames {array; '*'}
4358
     * - Which TVs to fetch. In the form expected by getTemplateVarOutput(). Default: array().
4359
     * @param int $published {0; 1; 'all'}
4360
     * - Document publication status. Once the parameter equals 'all', the result will be returned regardless of whether the ducuments are published or they are not. Default: 1.
4361
     * @param string $sortBy {string}
4362
     * - How to sort the result array (field). Default: 'menuindex'.
4363
     * @param string $sortDir {'ASC'; 'DESC'}
4364
     * - How to sort the result array (direction). Default: 'ASC'.
4365
     * @param string $where {string}
4366
     * - SQL WHERE condition (use only document fields, not TV). Default: ''.
4367
     * @param string $resultKey {string; false}
4368
     * - Field, which values are keys into result array. Use the “false”, that result array keys just will be numbered. Default: 'id'.
4369
     * @return array {array; false} - Result array, or false.
0 ignored issues
show
Documentation introduced by
Should the return type not be false|array? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
4370
     * - Result array, or false.
4371
     */
4372
    public function getDocumentChildrenTVarOutput($parentid = 0, $tvidnames = array(), $published = 1, $sortBy = 'menuindex', $sortDir = 'ASC', $where = '', $resultKey = 'id')
4373
    {
4374
        $docs = $this->getDocumentChildren($parentid, $published, 0, 'id', $where, $sortBy, $sortDir);
4375
4376
        if (!$docs) {
4377
            return false;
4378
        } else {
4379
            $result = array();
4380
4381
            $unsetResultKey = false;
4382
4383
            if ($resultKey !== false) {
4384
                if (is_array($tvidnames)) {
4385
                    if (count($tvidnames) != 0 && !in_array($resultKey, $tvidnames)) {
4386
                        $tvidnames[] = $resultKey;
4387
                        $unsetResultKey = true;
4388
                    }
4389
                } else if ($tvidnames != '*' && $tvidnames != $resultKey) {
4390
                    $tvidnames = array($tvidnames, $resultKey);
4391
                    $unsetResultKey = true;
4392
                }
4393
            }
4394
4395
            for ($i = 0; $i < count($docs); $i++) {
4396
                $tvs = $this->getTemplateVarOutput($tvidnames, $docs[$i]['id'], $published);
4397
4398
                if ($tvs) {
4399
                    if ($resultKey !== false && array_key_exists($resultKey, $tvs)) {
4400
                        $result[$tvs[$resultKey]] = $tvs;
4401
4402
                        if ($unsetResultKey) {
4403
                            unset($result[$tvs[$resultKey]][$resultKey]);
4404
                        }
4405
                    } else {
4406
                        $result[] = $tvs;
4407
                    }
4408
                }
4409
            }
4410
4411
            return $result;
4412
        }
4413
    }
4414
4415
    /**
4416
     * Modified by Raymond for TV - Orig Modified by Apodigm - DocVars
4417
     * Returns a single site_content field or TV record from the db.
4418
     *
4419
     * If a site content field the result is an associative array of 'name' and 'value'.
4420
     *
4421
     * If a TV the result is an array representing a db row including the fields specified in $fields.
4422
     *
4423
     * @param string $idname Can be a TV id or name
4424
     * @param string $fields Fields to fetch from site_tmplvars. Default: *
4425
     * @param string|type $docid Docid. Defaults to empty string which indicates the current document.
4426
     * @param int $published Whether published or unpublished documents are in the result
4427
     *                        Default: 1
4428
     * @return bool
4429
     */
4430 View Code Duplication
    public function getTemplateVar($idname = "", $fields = "*", $docid = "", $published = 1)
4431
    {
4432
        if ($idname == "") {
4433
            return false;
4434
        } else {
4435
            $result = $this->getTemplateVars(array($idname), $fields, $docid, $published, "", ""); //remove sorting for speed
4436
            return ($result != false) ? $result[0] : false;
4437
        }
4438
    }
4439
4440
    /**
4441
     * getTemplateVars
4442
     * @version 1.0.1 (2014-02-19)
4443
     *
4444
     * @desc Returns an array of site_content field fields and/or TV records from the db.
4445
     * Elements representing a site content field consist of an associative array of 'name' and 'value'.
4446
     * Elements representing a TV consist of an array representing a db row including the fields specified in $fields.
4447
     *
4448
     * @param string|array $idnames {array; '*'} - Which TVs to fetch. Can relate to the TV ids in the db (array elements should be numeric only) or the TV names (array elements should be names only). @required
4449
     * @param string|array $fields {comma separated string; '*'} - Fields names in the TV table of MODx database. Default: '*'
4450
     * @param int|string $docid {integer; ''} - Id of a document to get. Default: an empty string which indicates the current document.
4451
     * @param int|string $published {0; 1; 'all'} - Document publication status. Once the parameter equals 'all', the result will be returned regardless of whether the ducuments are published or they are not. Default: 1.
0 ignored issues
show
Documentation introduced by
Consider making the type for parameter $published a bit more specific; maybe use integer.
Loading history...
4452
     * @param string $sort {comma separated string} - Fields of the TV table to sort by. Default: 'rank'.
4453
     * @param string $dir {'ASC'; 'DESC'} - How to sort the result array (direction). Default: 'ASC'.
4454
     *
4455
     * @return array|bool Result array, or false.
4456
     */
4457
    public function getTemplateVars($idnames = array(), $fields = '*', $docid = '', $published = 1, $sort = 'rank', $dir = 'ASC')
4458
    {
4459
        $cacheKey = md5(print_r(func_get_args(), true));
4460
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
4461
            return $this->tmpCache[__FUNCTION__][$cacheKey];
4462
        }
4463
4464
        if (($idnames !== '*' && !is_array($idnames)) || empty($idnames) ) {
4465
            return false;
4466
        } else {
4467
4468
            // get document record
4469
            if (empty($docid)) {
4470
                $docid = $this->documentIdentifier;
4471
                $docRow = $this->documentObject;
4472
            } else {
4473
                $docRow = $this->getDocument($docid, '*', $published);
4474
4475
                if (!$docRow) {
4476
                    $this->tmpCache[__FUNCTION__][$cacheKey] = false;
4477
                    return false;
4478
                }
4479
            }
4480
4481
            // get user defined template variables
4482
            if (!empty($fields) && (is_scalar($fields) || \is_array($fields))) {
4483
                if(\is_scalar($fields)) {
4484
                    $fields = explode(',', $fields);
4485
                }
4486
                $fields = array_filter(array_map('trim', $fields), function($value) {
4487
                    return $value !== 'value';
4488
                });
4489
                $fields = 'tv.' . implode(',tv.', $fields);
4490
            } else {
4491
                $fields = 'tv.*';
4492
            }
4493
            $sort = ($sort == '') ? '' : 'tv.' . implode(',tv.', array_filter(array_map('trim', explode(',', $sort))));
4494
4495 View Code Duplication
            if ($idnames === '*') {
4496
                $query = 'tv.id<>0';
4497
            } else {
4498
                $query = (is_numeric($idnames[0]) ? 'tv.id' : 'tv.name') . " IN ('" . implode("','", $idnames) . "')";
4499
            }
4500
4501
            $rs = $this->getDatabase()->select(
4502
                "{$fields}, IF(tvc.value != '', tvc.value, tv.default_text) as value",
4503
                $this->getDatabase()->getFullTableName('site_tmplvars') . ' tv ' .
4504
                'INNER JOIN ' . $this->getDatabase()->getFullTableName('site_tmplvar_templates') . ' tvtpl ON tvtpl.tmplvarid = tv.id ' .
4505
                'LEFT JOIN ' . $this->getDatabase()->getFullTableName('site_tmplvar_contentvalues') . " tvc ON tvc.tmplvarid = tv.id AND tvc.contentid = '" . $docid . "'",
4506
                $query . " AND tvtpl.templateid = '" . $docRow['template'] . "'",
4507
                ($sort ? ($sort . ' ' . $dir) : '')
4508
            );
4509
            $result = $this->getDatabase()->makeArray($rs);
4510
4511
            // get default/built-in template variables
4512
            if(is_array($docRow)){
4513
                ksort($docRow);
4514
4515
                foreach ($docRow as $name => $value) {
4516
                    if ($idnames === '*' || \in_array($name, $idnames)) {
4517
                        $result[] = compact('name', 'value');
4518
                    }
4519
                }
4520
            }
4521
4522
            $this->tmpCache[__FUNCTION__][$cacheKey] = $result;
4523
4524
            return $result;
4525
        }
4526
    }
4527
4528
    /**
4529
     * getTemplateVarOutput
4530
     * @version 1.0.1 (2014-02-19)
4531
     *
4532
     * @desc Returns an associative array containing TV rendered output values.
4533
     *
4534
     * @param array $idnames {array; '*'}
4535
     * - Which TVs to fetch - Can relate to the TV ids in the db (array elements should be numeric only) or the TV names (array elements should be names only). @required
4536
     * @param string $docid {integer; ''}
4537
     * - Id of a document to get. Default: an empty string which indicates the current document.
4538
     * @param int $published {0; 1; 'all'}
4539
     * - Document publication status. Once the parameter equals 'all', the result will be returned regardless of whether the ducuments are published or they are not. Default: 1.
4540
     * @param string $sep {string}
4541
     * - Separator that is used while concatenating in getTVDisplayFormat(). Default: ''.
4542
     * @return array {array; false} - Result array, or false.
0 ignored issues
show
Documentation introduced by
Should the return type not be false|array? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
4543
     * - Result array, or false.
4544
     */
4545
    public function getTemplateVarOutput($idnames = array(), $docid = '', $published = 1, $sep = '')
4546
    {
4547
        if (is_array($idnames) && empty($idnames) ) {
4548
            return false;
4549
        } else {
4550
            $output = array();
4551
            $vars = ($idnames == '*' || is_array($idnames)) ? $idnames : array($idnames);
4552
4553
            $docid = (int)$docid > 0 ? (int)$docid : $this->documentIdentifier;
4554
            // remove sort for speed
4555
            $result = $this->getTemplateVars($vars, '*', $docid, $published, '', '');
4556
4557
            if ($result == false) {
4558
                return false;
4559
            } else {
4560
                for ($i = 0; $i < count($result); $i++) {
4561
                    $row = $result[$i];
4562
4563
                    if (!isset($row['id']) or !$row['id']) {
4564
                        $output[$row['name']] = $row['value'];
4565
                    } else {
4566
                        $output[$row['name']] = getTVDisplayFormat($row['name'], $row['value'], $row['display'], $row['display_params'], $row['type'], $docid, $sep);
4567
                    }
4568
                }
4569
4570
                return $output;
4571
            }
4572
        }
4573
    }
4574
4575
    /**
4576
     * Returns the full table name based on db settings
4577
     *
4578
     * @param string $tbl Table name
4579
     * @return string Table name with prefix
4580
     * @deprecated use ->getDatabase()->getFullTableName()
4581
     */
4582
    public function getFullTableName($tbl)
4583
    {
4584
        return $this->getDatabase()->getFullTableName($tbl);
4585
    }
4586
4587
    /**
4588
     * Returns the placeholder value
4589
     *
4590
     * @param string $name Placeholder name
4591
     * @return string Placeholder value
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
4592
     */
4593
    public function getPlaceholder($name)
4594
    {
4595
        return isset($this->placeholders[$name]) ? $this->placeholders[$name] : null;
4596
    }
4597
4598
    /**
4599
     * Sets a value for a placeholder
4600
     *
4601
     * @param string $name The name of the placeholder
4602
     * @param string $value The value of the placeholder
4603
     */
4604
    public function setPlaceholder($name, $value)
4605
    {
4606
        $this->placeholders[$name] = $value;
4607
    }
4608
4609
    /**
4610
     * Set placeholders en masse via an array or object.
4611
     *
4612
     * @param object|array $subject
4613
     * @param string $prefix
4614
     */
4615
    public function toPlaceholders($subject, $prefix = '')
4616
    {
4617
        if (is_object($subject)) {
4618
            $subject = get_object_vars($subject);
4619
        }
4620
        if (is_array($subject)) {
4621
            foreach ($subject as $key => $value) {
4622
                $this->toPlaceholder($key, $value, $prefix);
4623
            }
4624
        }
4625
    }
4626
4627
    /**
4628
     * For use by toPlaceholders(); For setting an array or object element as placeholder.
4629
     *
4630
     * @param string $key
4631
     * @param object|array $value
4632
     * @param string $prefix
4633
     */
4634
    public function toPlaceholder($key, $value, $prefix = '')
4635
    {
4636
        if (is_array($value) || is_object($value)) {
4637
            $this->toPlaceholders($value, "{$prefix}{$key}.");
4638
        } else {
4639
            $this->setPlaceholder("{$prefix}{$key}", $value);
4640
        }
4641
    }
4642
4643
    /**
4644
     * Sends a message to a user's message box.
4645
     *
4646
     * @param string $type Type of the message
4647
     * @param string $to The recipient of the message
4648
     * @param string $from The sender of the message
4649
     * @param string $subject The subject of the message
4650
     * @param string $msg The message body
4651
     * @param int $private Whether it is a private message, or not
4652
     *                     Default : 0
4653
     */
4654
    public function sendAlert($type, $to, $from, $subject, $msg, $private = 0)
4655
    {
4656
        $private = ($private) ? 1 : 0;
4657 View Code Duplication
        if (!is_numeric($to)) {
4658
            // Query for the To ID
4659
            $rs = $this->getDatabase()->select('id', $this->getDatabase()->getFullTableName("manager_users"), "username='{$to}'");
4660
            $to = $this->getDatabase()->getValue($rs);
4661
        }
4662 View Code Duplication
        if (!is_numeric($from)) {
4663
            // Query for the From ID
4664
            $rs = $this->getDatabase()->select('id', $this->getDatabase()->getFullTableName("manager_users"), "username='{$from}'");
4665
            $from = $this->getDatabase()->getValue($rs);
4666
        }
4667
        // insert a new message into user_messages
4668
        $this->getDatabase()->insert(array(
4669
            'type' => $type,
4670
            'subject' => $subject,
4671
            'message' => $msg,
4672
            'sender' => $from,
4673
            'recipient' => $to,
4674
            'private' => $private,
4675
            'postdate' => $_SERVER['REQUEST_TIME'] + $this->getConfig('server_offset_time'),
4676
            'messageread' => 0,
4677
        ), $this->getDatabase()->getFullTableName('user_messages'));
4678
    }
4679
4680
    /**
4681
     * Returns current user id.
4682
     *
4683
     * @param string $context . Default is an empty string which indicates the method should automatically pick 'web (frontend) or 'mgr' (backend)
4684
     * @return string
4685
     */
4686 View Code Duplication
    public function getLoginUserID($context = '')
4687
    {
4688
        $out = false;
4689
4690
        if (!empty($context)) {
4691
            if (is_scalar($context) && isset($_SESSION[$context . 'Validated'])) {
4692
                $out = $_SESSION[$context . 'InternalKey'];
4693
            }
4694
        } else {
4695
            switch (true) {
4696
                case ($this->isFrontend() && isset ($_SESSION['webValidated'])): {
4697
                    $out = $_SESSION['webInternalKey'];
4698
                    break;
4699
                }
4700
                case ($this->isBackend() && isset ($_SESSION['mgrValidated'])): {
4701
                    $out = $_SESSION['mgrInternalKey'];
4702
                    break;
4703
                }
4704
            }
4705
        }
4706
        return $out;
4707
    }
4708
4709
    /**
4710
     * Returns current user name
4711
     *
4712
     * @param string $context . Default is an empty string which indicates the method should automatically pick 'web (frontend) or 'mgr' (backend)
4713
     * @return string
4714
     */
4715 View Code Duplication
    public function getLoginUserName($context = '')
4716
    {
4717
        $out = false;
4718
4719
        if (!empty($context)) {
4720
            if (is_scalar($context) && isset($_SESSION[$context . 'Validated'])) {
4721
                $out = $_SESSION[$context . 'Shortname'];
4722
            }
4723
        } else {
4724
            switch (true) {
4725
                case ($this->isFrontend() && isset ($_SESSION['webValidated'])): {
4726
                    $out = $_SESSION['webShortname'];
4727
                    break;
4728
                }
4729
                case ($this->isBackend() && isset ($_SESSION['mgrValidated'])): {
4730
                    $out = $_SESSION['mgrShortname'];
4731
                    break;
4732
                }
4733
            }
4734
        }
4735
        return $out;
4736
    }
4737
4738
    /**
4739
     * Returns current login user type - web or manager
4740
     *
4741
     * @return string
4742
     */
4743
    public function getLoginUserType()
4744
    {
4745
        if ($this->isFrontend() && isset ($_SESSION['webValidated'])) {
4746
            return 'web';
4747
        } elseif ($this->isBackend() && isset ($_SESSION['mgrValidated'])) {
4748
            return 'manager';
4749
        } else {
4750
            return '';
4751
        }
4752
    }
4753
4754
    public function getContext() : string
4755
    {
4756
        return $this->isFrontend() ? 'web' : 'mgr';
4757
    }
4758
4759
    /**
4760
     * Returns a user info record for the given manager user
4761
     *
4762
     * @param int $uid
4763
     * @return boolean|string
4764
     */
4765
    public function getUserInfo($uid)
4766
    {
4767
        if (isset($this->tmpCache[__FUNCTION__][$uid])) {
4768
            return $this->tmpCache[__FUNCTION__][$uid];
4769
        }
4770
4771
        $from = $this->getDatabase()->getFullTableName('manager_users') . ' mu INNER JOIN ' .
4772
            $this->getDatabase()->getFullTableName('user_attributes'). ' mua ON mua.internalkey=mu.id';
4773
        $where = sprintf("mu.id='%s'", $this->getDatabase()->escape($uid));
4774
        $rs = $this->getDatabase()->select('mu.username, mu.password, mua.*', $from, $where, '', 1);
4775
4776
        if (!$this->getDatabase()->getRecordCount($rs)) {
4777
            return $this->tmpCache[__FUNCTION__][$uid] = false;
4778
        }
4779
4780
        $row = $this->getDatabase()->getRow($rs);
4781 View Code Duplication
        if (!isset($row['usertype']) || !$row['usertype']) {
4782
            $row['usertype'] = 'manager';
4783
        }
4784
4785
        $this->tmpCache[__FUNCTION__][$uid] = $row;
4786
4787
        return $row;
4788
    }
4789
4790
    /**
4791
     * Returns a record for the web user
4792
     *
4793
     * @param int $uid
4794
     * @return boolean|string
4795
     */
4796
    public function getWebUserInfo($uid)
4797
    {
4798
        $rs = $this->getDatabase()->select(
4799
            'wu.username, wu.password, wua.*',
4800
            $this->getDatabase()->getFullTableName("web_users") . ' wu ' .
4801
                'INNER JOIN ' . $this->getDatabase()->getFullTableName("web_user_attributes") . ' wua ' .
4802
                'ON wua.internalkey=wu.id',
4803
            "wu.id='{$uid}'"
4804
        );
4805
        if ($row = $this->getDatabase()->getRow($rs)) {
4806 View Code Duplication
            if (!isset($row['usertype']) or !$row['usertype']) {
4807
                $row['usertype'] = 'web';
4808
            }
4809
            return $row;
4810
        }
4811
    }
4812
4813
    /**
4814
     * Returns an array of document groups that current user is assigned to.
4815
     * This function will first return the web user doc groups when running from
4816
     * frontend otherwise it will return manager user's docgroup.
4817
     *
4818
     * @param boolean $resolveIds Set to true to return the document group names
4819
     *                            Default: false
4820
     * @return string|array
4821
     */
4822
    public function getUserDocGroups($resolveIds = false)
4823
    {
4824
        if ($this->isFrontend() && isset($_SESSION['webDocgroups']) && isset($_SESSION['webValidated'])) {
4825
            $dg = $_SESSION['webDocgroups'];
4826
            $dgn = isset($_SESSION['webDocgrpNames']) ? $_SESSION['webDocgrpNames'] : false;
4827
        } else if ($this->isBackend() && isset($_SESSION['mgrDocgroups']) && isset($_SESSION['mgrValidated'])) {
4828
            $dg = $_SESSION['mgrDocgroups'];
4829
            $dgn = isset($_SESSION['mgrDocgrpNames']) ? $_SESSION['mgrDocgrpNames'] : false;
4830
        } else {
4831
            $dg = '';
4832
        }
4833
        if (!$resolveIds) {
4834
            return $dg;
4835
        } else if (is_array($dgn)) {
4836
            return $dgn;
0 ignored issues
show
Bug introduced by
The variable $dgn does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
4837
        } else if (is_array($dg)) {
4838
            // resolve ids to names
4839
            $dgn = array();
4840
            $ds = $this->getDatabase()->select(
4841
                'name',
4842
                $this->getDatabase()->getFullTableName("documentgroup_names"),
4843
                "id IN (" . implode(",", $dg) . ")"
4844
            );
4845
            while ($row = $this->getDatabase()->getRow($ds)) {
4846
                $dgn[] = $row['name'];
4847
            }
4848
            // cache docgroup names to session
4849
            $_SESSION[$this->getContext() . 'DocgrpNames'] = $dgn;
4850
            return $dgn;
4851
        }
4852
    }
4853
4854
    /**
4855
     * Change current web user's password
4856
     *
4857
     * @todo Make password length configurable, allow rules for passwords and translation of messages
4858
     * @param string $oldPwd
4859
     * @param string $newPwd
4860
     * @return string|boolean Returns true if successful, oterhwise return error
4861
     *                        message
4862
     */
4863
    public function changeWebUserPassword($oldPwd, $newPwd)
4864
    {
4865
        $rt = false;
4866
        if ($_SESSION["webValidated"] == 1) {
4867
            $tbl = $this->getDatabase()->getFullTableName("web_users");
4868
            $ds = $this->getDatabase()->select('id, username, password', $tbl, "id='" . $this->getLoginUserID() . "'");
4869
            if ($row = $this->getDatabase()->getRow($ds)) {
4870
                if ($row["password"] == md5($oldPwd)) {
4871
                    if (strlen($newPwd) < 6) {
4872
                        return "Password is too short!";
4873
                    } elseif ($newPwd == "") {
4874
                        return "You didn't specify a password for this user!";
4875
                    } else {
4876
                        $this->getDatabase()->update(array(
4877
                            'password' => $this->getDatabase()->escape($newPwd),
4878
                        ), $tbl, "id='" . $this->getLoginUserID() . "'");
4879
                        // invoke OnWebChangePassword event
4880
                        $this->invokeEvent("OnWebChangePassword", array(
4881
                            "userid" => $row["id"],
4882
                            "username" => $row["username"],
4883
                            "userpassword" => $newPwd
4884
                        ));
4885
                        return true;
4886
                    }
4887
                } else {
4888
                    return "Incorrect password.";
4889
                }
4890
            }
4891
        }
4892
        return $rt;
4893
    }
4894
4895
    /**
4896
     * Returns true if the current web user is a member the specified groups
4897
     *
4898
     * @param array $groupNames
4899
     * @return boolean
4900
     */
4901
    public function isMemberOfWebGroup($groupNames = array())
4902
    {
4903
        if (!is_array($groupNames)) {
4904
            return false;
4905
        }
4906
        // check cache
4907
        $grpNames = isset ($_SESSION['webUserGroupNames']) ? $_SESSION['webUserGroupNames'] : false;
4908
        if (!is_array($grpNames)) {
4909
            $rs = $this->getDatabase()->select(
4910
                'wgn.name',
4911
                $this->getDatabase()->getFullTableName("webgroup_names") . ' wgn ' .
4912
                    'INNER JOIN ' . $this->getDatabase()->getFullTableName("web_groups") . " wg ON wg.webgroup=wgn.id AND wg.webuser='" . $this->getLoginUserID() . "'"
4913
            );
4914
            $grpNames = $this->getDatabase()->getColumn("name", $rs);
4915
            // save to cache
4916
            $_SESSION['webUserGroupNames'] = $grpNames;
4917
        }
4918
        foreach ($groupNames as $k => $v) {
4919
            if (in_array(trim($v), $grpNames)) {
4920
                return true;
4921
            }
4922
        }
4923
        return false;
4924
    }
4925
4926
    /**
4927
     * Registers Client-side CSS scripts - these scripts are loaded at inside
4928
     * the <head> tag
4929
     *
4930
     * @param string $src
4931
     * @param string $media Default: Empty string
4932
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
4933
     */
4934
    public function regClientCSS($src, $media = '')
4935
    {
4936
        if (empty($src) || isset ($this->loadedjscripts[$src])) {
4937
            return '';
4938
        }
4939
        $nextpos = max(array_merge(array(0), array_keys($this->sjscripts))) + 1;
4940
        $this->loadedjscripts[$src]['startup'] = true;
4941
        $this->loadedjscripts[$src]['version'] = '0';
4942
        $this->loadedjscripts[$src]['pos'] = $nextpos;
4943
        if (strpos(strtolower($src), "<style") !== false || strpos(strtolower($src), "<link") !== false) {
4944
            $this->sjscripts[$nextpos] = $src;
4945
        } else {
4946
            $this->sjscripts[$nextpos] = "\t" . '<link rel="stylesheet" type="text/css" href="' . $src . '" ' . ($media ? 'media="' . $media . '" ' : '') . '/>';
4947
        }
4948
    }
4949
4950
    /**
4951
     * Registers Startup Client-side JavaScript - these scripts are loaded at inside the <head> tag
4952
     *
4953
     * @param string $src
4954
     * @param array $options Default: 'name'=>'', 'version'=>'0', 'plaintext'=>false
4955
     */
4956
    public function regClientStartupScript($src, $options = array('name' => '', 'version' => '0', 'plaintext' => false))
4957
    {
4958
        $this->regClientScript($src, $options, true);
4959
    }
4960
4961
    /**
4962
     * Registers Client-side JavaScript these scripts are loaded at the end of the page unless $startup is true
4963
     *
4964
     * @param string $src
4965
     * @param array $options Default: 'name'=>'', 'version'=>'0', 'plaintext'=>false
4966
     * @param boolean $startup Default: false
4967
     * @return string
4968
     */
4969
    public function regClientScript($src, $options = array('name' => '', 'version' => '0', 'plaintext' => false), $startup = false)
4970
    {
4971
        if (empty($src)) {
4972
            return '';
4973
        } // nothing to register
4974
        if (!is_array($options)) {
4975
            if (\is_bool($options)) {
4976
                // backward compatibility with old plaintext parameter
4977
                $options = array('plaintext' => $options);
4978
            } elseif (\is_string($options)) {
4979
                // Also allow script name as 2nd param
4980
                $options = array('name' => $options);
4981
            } else {
4982
                $options = array();
4983
            }
4984
        }
4985
        $name = isset($options['name']) ? strtolower($options['name']) : '';
4986
        $version = isset($options['version']) ? $options['version'] : '0';
4987
        $plaintext = isset($options['plaintext']) ? $options['plaintext'] : false;
4988
        $key = !empty($name) ? $name : $src;
4989
        unset($overwritepos); // probably unnecessary--just making sure
4990
4991
        $useThisVer = true;
4992
        if (isset($this->loadedjscripts[$key])) { // a matching script was found
4993
            // if existing script is a startup script, make sure the candidate is also a startup script
4994
            if ($this->loadedjscripts[$key]['startup']) {
4995
                $startup = true;
4996
            }
4997
4998
            if (empty($name)) {
4999
                $useThisVer = false; // if the match was based on identical source code, no need to replace the old one
5000
            } else {
5001
                $useThisVer = version_compare($this->loadedjscripts[$key]['version'], $version, '<');
5002
            }
5003
5004
            if ($useThisVer) {
5005
                if ($startup == true && $this->loadedjscripts[$key]['startup'] == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
5006
                    // remove old script from the bottom of the page (new one will be at the top)
5007
                    unset($this->jscripts[$this->loadedjscripts[$key]['pos']]);
5008
                } else {
5009
                    // overwrite the old script (the position may be important for dependent scripts)
5010
                    $overwritepos = $this->loadedjscripts[$key]['pos'];
5011
                }
5012
            } else { // Use the original version
5013
                if ($startup == true && $this->loadedjscripts[$key]['startup'] == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
5014
                    // need to move the exisiting script to the head
5015
                    $version = $this->loadedjscripts[$key][$version];
5016
                    $src = $this->jscripts[$this->loadedjscripts[$key]['pos']];
5017
                    unset($this->jscripts[$this->loadedjscripts[$key]['pos']]);
5018
                } else {
5019
                    return ''; // the script is already in the right place
5020
                }
5021
            }
5022
        }
5023
5024
        if ($useThisVer && $plaintext != true && (strpos(strtolower($src), "<script") === false)) {
5025
            $src = "\t" . '<script type="text/javascript" src="' . $src . '"></script>';
5026
        }
5027
        if ($startup) {
5028
            $pos = isset($overwritepos) ? $overwritepos : max(array_merge(array(0), array_keys($this->sjscripts))) + 1;
5029
            $this->sjscripts[$pos] = $src;
5030
        } else {
5031
            $pos = isset($overwritepos) ? $overwritepos : max(array_merge(array(0), array_keys($this->jscripts))) + 1;
5032
            $this->jscripts[$pos] = $src;
5033
        }
5034
        $this->loadedjscripts[$key]['version'] = $version;
5035
        $this->loadedjscripts[$key]['startup'] = $startup;
5036
        $this->loadedjscripts[$key]['pos'] = $pos;
5037
        return '';
5038
    }
5039
5040
    /**
5041
     * Returns all registered JavaScripts
5042
     *
5043
     * @return string
5044
     */
5045
    public function regClientStartupHTMLBlock($html)
5046
    {
5047
        return $this->regClientScript($html, true, true);
0 ignored issues
show
Documentation introduced by
true is of type boolean, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
5048
    }
5049
5050
    /**
5051
     * Returns all registered startup scripts
5052
     *
5053
     * @return string
5054
     */
5055
    public function regClientHTMLBlock($html)
5056
    {
5057
        return $this->regClientScript($html, true);
0 ignored issues
show
Documentation introduced by
true is of type boolean, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
5058
    }
5059
5060
    /**
5061
     * Remove unwanted html tags and snippet, settings and tags
5062
     *
5063
     * @param string $html
5064
     * @param string $allowed Default: Empty string
5065
     * @return string
5066
     */
5067
    public function stripTags($html, $allowed = "")
5068
    {
5069
        $t = strip_tags($html, $allowed);
5070
        $t = preg_replace('~\[\*(.*?)\*\]~', "", $t); //tv
5071
        $t = preg_replace('~\[\[(.*?)\]\]~', "", $t); //snippet
5072
        $t = preg_replace('~\[\!(.*?)\!\]~', "", $t); //snippet
5073
        $t = preg_replace('~\[\((.*?)\)\]~', "", $t); //settings
5074
        $t = preg_replace('~\[\+(.*?)\+\]~', "", $t); //placeholders
5075
        $t = preg_replace('~{{(.*?)}}~', "", $t); //chunks
5076
        return $t;
5077
    }
5078
5079
    /**
5080
     * Add an event listener to a plugin - only for use within the current execution cycle
5081
     *
5082
     * @param string $evtName
5083
     * @param string $pluginName
5084
     * @return boolean|int
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use false|integer.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
5085
     */
5086
    public function addEventListener($evtName, $pluginName)
5087
    {
5088
        if (!$evtName || !$pluginName) {
5089
            return false;
5090
        }
5091
        if (!array_key_exists($evtName, $this->pluginEvent)) {
5092
            $this->pluginEvent[$evtName] = array();
5093
        }
5094
        return array_push($this->pluginEvent[$evtName], $pluginName); // return array count
5095
    }
5096
5097
    /**
5098
     * Remove event listener - only for use within the current execution cycle
5099
     *
5100
     * @param string $evtName
5101
     * @return boolean
0 ignored issues
show
Documentation introduced by
Should the return type not be false|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
5102
     */
5103
    public function removeEventListener($evtName)
5104
    {
5105
        if (!$evtName) {
5106
            return false;
5107
        }
5108
        unset ($this->pluginEvent[$evtName]);
5109
    }
5110
5111
    /**
5112
     * Remove all event listeners - only for use within the current execution cycle
5113
     */
5114
    public function removeAllEventListener()
5115
    {
5116
        unset ($this->pluginEvent);
5117
        $this->pluginEvent = array();
5118
    }
5119
5120
    protected function restoreEvent()
5121
    {
5122
        $event = $this->event->getPreviousEvent();
5123
        if ($event) {
5124
            unset($this->event);
5125
            $this->event = $event;
5126
            $this->Event = &$this->event;
0 ignored issues
show
Deprecated Code introduced by
The property EvolutionCMS\Core::$Event has been deprecated.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
5127
        } else {
5128
            $this->event->activePlugin = '';
5129
        }
5130
5131
        return $event;
5132
    }
5133
5134
    protected function storeEvent()
5135
    {
5136
        if ($this->event->activePlugin !== '') {
5137
            $event = new Event;
5138
            $event->setPreviousEvent($this->event);
5139
            $this->event = $event;
5140
            $this->Event = &$this->event;
0 ignored issues
show
Deprecated Code introduced by
The property EvolutionCMS\Core::$Event has been deprecated.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
5141
        } else {
5142
            $event = $this->event;
5143
        }
5144
5145
        return $event;
5146
    }
5147
5148
    /**
5149
     * Invoke an event.
5150
     *
5151
     * @param string $evtName
5152
     * @param array $extParams Parameters available to plugins. Each array key will be the PHP variable name, and the array value will be the variable value.
5153
     * @return boolean|array
0 ignored issues
show
Documentation introduced by
Should the return type not be false|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
5154
     */
5155
    public function invokeEvent($evtName, $extParams = array())
5156
    {
5157
        $results = null;
5158
5159
        if (!$evtName) {
5160
            return false;
5161
        }
5162
5163
        $this->storeEvent();
5164
5165
        $out = $this['events']->dispatch('evolution.' . $evtName, [$extParams]);
5166
        if ($out === false) {
5167
            $this->restoreEvent();
5168
            return false;
5169
        }
5170
5171
        if (\is_array($out)) {
5172
            foreach ($out as $result) {
5173
                if ($result !== null) {
5174
                    $results[] = $result;
5175
                }
5176
            }
5177
        }
5178
5179
        if (!isset ($this->pluginEvent[$evtName])) {
5180
            $this->restoreEvent();
5181
            return $results ?? false;
5182
        }
5183
5184
        foreach ($this->pluginEvent[$evtName] as $pluginName) { // start for loop
5185
            if ($this->dumpPlugins) {
5186
                $eventtime = $this->getMicroTime();
5187
            }
5188
            // reset event object
5189
            $e = &$this->event;
5190
            $e->_resetEventObject();
5191
            $e->name = $evtName;
5192
            $e->activePlugin = $pluginName;
5193
5194
            // get plugin code
5195
            $_ = $this->getPluginCode($pluginName);
5196
            $pluginCode = $_['code'];
5197
            $pluginProperties = $_['props'];
5198
5199
            // load default params/properties
5200
            $parameter = $this->parseProperties($pluginProperties);
5201
            if (!is_array($parameter)) {
5202
                $parameter = array();
5203
            }
5204
            if (!empty($extParams)) {
5205
                $parameter = array_merge($parameter, $extParams);
5206
            }
5207
5208
            // eval plugin
5209
            $this->evalPlugin($pluginCode, $parameter);
5210
5211
            if (class_exists('PHxParser')) {
5212
                $this->setConfig('enable_filter', 0);
5213
            }
5214
5215
            if ($this->dumpPlugins) {
5216
                $eventtime = $this->getMicroTime() - $eventtime;
0 ignored issues
show
Bug introduced by
The variable $eventtime does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
5217
                $this->pluginsCode .= sprintf('<fieldset><legend><b>%s / %s</b> (%2.2f ms)</legend>', $evtName, $pluginName, $eventtime * 1000);
5218
                foreach ($parameter as $k => $v) {
5219
                    $this->pluginsCode .= "{$k} => " . print_r($v, true) . '<br>';
5220
                }
5221
                $this->pluginsCode .= '</fieldset><br />';
5222
                $this->pluginsTime["{$evtName} / {$pluginName}"] += $eventtime;
5223
            }
5224
            if ($this->event->getOutput() != '') {
5225
                $results[] = $this->event->getOutput();
5226
            }
5227
            if ($this->event->_propagate != true) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison !== instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
5228
                break;
5229
            }
5230
        }
5231
5232
        $this->restoreEvent();
5233
        return $results;
5234
    }
5235
5236
    /**
5237
     * Returns plugin-code and properties
5238
     *
5239
     * @param string $pluginName
5240
     * @return array Associative array consisting of 'code' and 'props'
5241
     */
5242
    public function getPluginCode($pluginName)
5243
    {
5244
        $plugin = array();
5245
        if (isset ($this->pluginCache[$pluginName])) {
5246
            $pluginCode = $this->pluginCache[$pluginName];
5247
            $pluginProperties = isset($this->pluginCache[$pluginName . "Props"]) ? $this->pluginCache[$pluginName . "Props"] : '';
5248
        } else {
5249
            $pluginName = $this->getDatabase()->escape($pluginName);
5250
            $result = $this->getDatabase()->select('name, plugincode, properties', $this->getDatabase()->getFullTableName("site_plugins"), "name='{$pluginName}' AND disabled=0");
5251
            if ($row = $this->getDatabase()->getRow($result)) {
5252
                $pluginCode = $this->pluginCache[$row['name']] = $row['plugincode'];
5253
                $pluginProperties = $this->pluginCache[$row['name'] . "Props"] = $row['properties'];
5254
            } else {
5255
                $pluginCode = $this->pluginCache[$pluginName] = "return false;";
5256
                $pluginProperties = '';
5257
            }
5258
        }
5259
        $plugin['code'] = $pluginCode;
5260
        $plugin['props'] = $pluginProperties;
5261
5262
        return $plugin;
5263
    }
5264
5265
    /**
5266
     * Parses a resource property string and returns the result as an array
5267
     *
5268
     * @param string|array $propertyString
5269
     * @param string|null $elementName
5270
     * @param string|null $elementType
5271
     * @return array Associative array in the form property name => property value
5272
     */
5273
    public function parseProperties($propertyString, $elementName = null, $elementType = null)
5274
    {
5275
        $property = array();
5276
5277
        if(\is_scalar($propertyString)) {
5278
            $propertyString = trim($propertyString);
5279
            $propertyString = str_replace('{}', '', $propertyString);
5280
            $propertyString = str_replace('} {', ',', $propertyString);
5281
            if (!empty($propertyString) && $propertyString != '{}') {
5282
                $jsonFormat = data_is_json($propertyString, true);
5283
                // old format
5284
                if ($jsonFormat === false) {
5285
                    $props = explode('&', $propertyString);
5286
                    foreach ($props as $prop) {
5287
5288
                        if (empty($prop)) {
5289
                            continue;
5290
                        } elseif (strpos($prop, '=') === false) {
5291
                            $property[trim($prop)] = '';
5292
                            continue;
5293
                        }
5294
5295
                        $_ = explode('=', $prop, 2);
5296
                        $key = trim($_[0]);
5297
                        $p = explode(';', trim($_[1]));
5298
                        switch ($p[1]) {
5299
                            case 'list':
5300
                            case 'list-multi':
5301
                            case 'checkbox':
5302
                            case 'radio':
5303
                                $value = !isset($p[3]) ? '' : $p[3];
5304
                                break;
5305
                            default:
5306
                                $value = !isset($p[2]) ? '' : $p[2];
5307
                        }
5308
                        if (!empty($key)) {
5309
                            $property[$key] = $value;
5310
                        }
5311
                    }
5312
                    // new json-format
5313
                } else if (!empty($jsonFormat)) {
5314
                    foreach ($jsonFormat as $key => $row) {
5315
                        if (!empty($key)) {
5316 View Code Duplication
                            if (is_array($row)) {
5317
                                if (isset($row[0]['value'])) {
5318
                                    $value = $row[0]['value'];
5319
                                }
5320
                            } else {
5321
                                $value = $row;
5322
                            }
5323
                            if (isset($value) && $value !== '') {
5324
                                $property[$key] = $value;
5325
                            }
5326
                        }
5327
                    }
5328
                }
5329
            }
5330
        }
5331
        elseif(\is_array($propertyString)) {
5332
            $property = $propertyString;
5333
        }
5334
        if (!empty($elementName) && !empty($elementType)) {
5335
            $out = $this->invokeEvent('OnParseProperties', array(
5336
                'element' => $elementName,
5337
                'type' => $elementType,
5338
                'args' => $property
5339
            ));
5340
            if (is_array($out)) {
5341
                $out = array_pop($out);
5342
            }
5343
            if (is_array($out)) {
5344
                $property = $out;
5345
            }
5346
        }
5347
5348
        return $property;
5349
    }
5350
5351
    /**
5352
     * @deprecated
5353
     */
5354
    public function parseDocBlockFromFile($element_dir, $filename, $escapeValues = false) {
5355
        $data = $this->get('DocBlock')->parseFromFile($element_dir, $filename);
5356
        if ($escapeValues) {
5357
            $data = $this->getDatabase()->escape($data);
5358
        }
5359
        return $data;
5360
    }
5361
5362
    /**
5363
     * @deprecated
5364
     */
5365
    public function parseDocBlockFromString($string, $escapeValues = false) {
5366
        $data = $this->get('DocBlock')->parseFromString($string);
5367
        if ($escapeValues) {
5368
            $data = $this->getDatabase()->escape($data);
5369
        }
5370
        return $data;
5371
    }
5372
5373
    /**
5374
     * @deprecated
5375
     */
5376
    public function parseDocBlockLine($line, $docblock_start_found, $name_found, $description_found, $docblock_end_found) {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
5377
        return $this->get('DocBlock')->parseLine($line, $docblock_start_found, $name_found, $description_found, $docblock_end_found);
5378
    }
5379
5380
    /**
5381
     * @deprecated
5382
     */
5383
    public function convertDocBlockIntoList($parsed) {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
5384
        return $this->get('DocBlock')->convertIntoList($parsed);
5385
    }
5386
5387
    /**
5388
     * @param string $string
5389
     * @return string
5390
     * @deprecated
5391
     */
5392
    public function removeSanitizeSeed($string = '')
5393
    {
5394
        return removeSanitizeSeed($string);
5395
    }
5396
5397
    /**
5398
     * @param string $content
5399
     * @return string
5400
     */
5401
    public function cleanUpMODXTags($content = '')
5402
    {
5403
        if ($this->minParserPasses < 1) {
5404
            return $content;
5405
        }
5406
5407
        $enable_filter = $this->getConfig('enable_filter');
5408
        $this->setConfig('enable_filter', 1);
5409
        $_ = array('[* *]', '[( )]', '{{ }}', '[[ ]]', '[+ +]');
5410
        foreach ($_ as $brackets) {
5411
            list($left, $right) = explode(' ', $brackets);
5412
            if (strpos($content, $left) !== false) {
5413
                if ($left === '[*') {
5414
                    $content = $this->mergeDocumentContent($content);
5415
                } elseif ($left === '[(') {
5416
                    $content = $this->mergeSettingsContent($content);
5417
                } elseif ($left === '{{') {
5418
                    $content = $this->mergeChunkContent($content);
5419
                } elseif ($left === '[[') {
5420
                    $content = $this->evalSnippets($content);
5421
                }
5422
            }
5423
        }
5424
        foreach ($_ as $brackets) {
5425
            list($left, $right) = explode(' ', $brackets);
5426
            if (strpos($content, $left) !== false) {
5427
                $matches = $this->getTagsFromContent($content, $left, $right);
5428
                $content = isset($matches[0]) ? str_replace($matches[0], '', $content) : $content;
5429
            }
5430
        }
5431
        $this->setConfig('enable_filter', $enable_filter);
5432
        return $content;
5433
    }
5434
5435
    /**
5436
     * @param string $str
5437
     * @param string $allowable_tags
5438
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be array|string? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
5439
     */
5440
    public function strip_tags($str = '', $allowable_tags = '')
5441
    {
5442
        $str = strip_tags($str, $allowable_tags);
5443
        modx_sanitize_gpc($str);
5444
        return $str;
5445
    }
5446
5447
    /**
5448
     * {@inheritdoc}
5449
     */
5450
    public function addSnippet($name, $phpCode, $namespace = '#', array $defaultParams = array())
5451
    {
5452
        $this->snippetCache[$namespace . $name] = $phpCode;
5453
        $this->snippetCache[$namespace . $name . 'Props'] = $defaultParams;
5454
    }
5455
5456
    /**
5457
     * {@inheritdoc}
5458
     */
5459
    public function addChunk($name, $text, $namespace = '#')
5460
    {
5461
        $this->chunkCache[$namespace . $name] = $text;
5462
    }
5463
5464
    /**
5465
     * {@inheritdoc}
5466
     */
5467
    public function findElements($type, $scanPath, array $ext)
5468
    {
5469
        $out = array();
5470
5471
        if (! is_dir($scanPath) || empty($ext)) {
5472
            return $out;
5473
        }
5474
        $iterator = new \RecursiveIteratorIterator(
5475
            new \RecursiveDirectoryIterator($scanPath, \RecursiveDirectoryIterator::SKIP_DOTS),
5476
            \RecursiveIteratorIterator::SELF_FIRST
5477
        );
5478
        foreach ($iterator as $item) {
5479
            /**
5480
             * @var \SplFileInfo $item
5481
             */
5482
            if ($item->isFile() && $item->isReadable() && \in_array($item->getExtension(), $ext)) {
5483
                $name = $item->getBasename('.' . $item->getExtension());
5484
                $path = ltrim(str_replace(
5485
                    array(rtrim($scanPath, '//'), '/'),
5486
                    array('', '\\'),
5487
                    $item->getPath() . '/'
5488
                ), '\\');
5489
5490
                if (!empty($path)) {
5491
                    $name = $path . $name;
5492
                }
5493
                switch ($type) {
5494
                    case 'chunk':
5495
                        $out[$name] = file_get_contents($item->getRealPath());
5496
                        break;
5497
                    case 'snippet':
5498
                        $out[$name] = "return require '" . $item->getRealPath() . "';";
5499
                        break;
5500
                    default:
5501
                        throw new \Exception;
5502
                }
5503
            }
5504
        }
5505
5506
        return $out;
5507
    }
5508
5509
    /**
5510
     * @param string $phpcode
5511
     * @param string $evalmode
5512
     * @param string $safe_functions
5513
     * @return string|void
0 ignored issues
show
Documentation introduced by
Should the return type not be array|string|null? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
5514
     */
5515
    public function safeEval($phpcode = '', $evalmode = '', $safe_functions = '')
5516
    {
5517
        if ($evalmode == '') {
5518
            $evalmode = $this->getConfig('allow_eval');
5519
        }
5520
        if ($safe_functions == '') {
5521
            $safe_functions = $this->getConfig('safe_functions_at_eval');
5522
        }
5523
5524
        modx_sanitize_gpc($phpcode);
5525
5526
        switch ($evalmode) {
5527
            case 'with_scan'         :
5528
                $isSafe = $this->isSafeCode($phpcode, $safe_functions);
0 ignored issues
show
Bug introduced by
It seems like $phpcode can also be of type array; however, EvolutionCMS\Core::isSafeCode() 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...
5529
                break;
5530
            case 'with_scan_at_post' :
5531
                $isSafe = $_POST ? $this->isSafeCode($phpcode, $safe_functions) : true;
0 ignored issues
show
Bug introduced by
It seems like $phpcode can also be of type array; however, EvolutionCMS\Core::isSafeCode() 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...
5532
                break;
5533
            case 'everytime_eval'    :
5534
                $isSafe = true;
5535
                break; // Should debug only
5536
            case 'dont_eval'         :
5537
            default                  :
5538
                return $phpcode;
5539
        }
5540
5541
        if (!$isSafe) {
5542
            $msg = $phpcode . "\n" . $this->currentSnippet . "\n" . print_r($_SERVER, true);
5543
            $title = sprintf('Unknown eval was executed (%s)', $this->getPhpCompat()->htmlspecialchars(substr(trim($phpcode), 0, 50)));
5544
            $this->getService('ExceptionHandler')
5545
                ->messageQuit($title, '', true, '', '', 'Parser', $msg);
5546
            return;
5547
        }
5548
5549
        ob_start();
5550
        $return = eval($phpcode);
5551
        $echo = ob_get_clean();
5552
5553
        if (is_array($return)) {
5554
            return 'array()';
5555
        }
5556
5557
        $output = $echo . $return;
5558
        modx_sanitize_gpc($output);
5559
        return $this->getPhpCompat()->htmlspecialchars($output); // Maybe, all html tags are dangerous
5560
    }
5561
5562
    /**
5563
     * @param string $phpcode
5564
     * @param string $safe_functions
5565
     * @return bool
5566
     */
5567
    public function isSafeCode($phpcode = '', $safe_functions = '')
5568
    { // return true or false
5569
        if ($safe_functions == '') {
5570
            return false;
5571
        }
5572
5573
        $safe = explode(',', $safe_functions);
5574
5575
        $phpcode = rtrim($phpcode, ';') . ';';
5576
        $tokens = token_get_all('<?php ' . $phpcode);
5577
        foreach ($tokens as $i => $token) {
5578
            if (!is_array($token)) {
5579
                continue;
5580
            }
5581
            $tokens[$i]['token_name'] = token_name($token[0]);
5582
        }
5583
        foreach ($tokens as $token) {
5584
            if (!is_array($token)) {
5585
                continue;
5586
            }
5587
            switch ($token['token_name']) {
5588
                case 'T_STRING':
5589
                    if (!in_array($token[1], $safe)) {
5590
                        return false;
5591
                    }
5592
                    break;
5593
                case 'T_VARIABLE':
5594
                    if ($token[1] == '$GLOBALS') {
5595
                        return false;
5596
                    }
5597
                    break;
5598
                case 'T_EVAL':
5599
                    return false;
5600
            }
5601
        }
5602
        return true;
5603
    }
5604
5605
    /**
5606
     * @param string $str
5607
     * @return bool|mixed|string
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
5608
     */
5609
    public function atBindFileContent($str = '')
5610
    {
5611
5612
        $search_path = array('assets/tvs/', 'assets/chunks/', 'assets/templates/', $this->getConfig('rb_base_url') . 'files/', '');
5613
5614
        if (stripos($str, '@FILE') !== 0) {
5615
            return $str;
5616
        }
5617
        if (strpos($str, "\n") !== false) {
5618
            $str = substr($str, 0, strpos("\n", $str));
5619
        }
5620
5621
        if ($this->getExtFromFilename($str) === '.php') {
5622
            return 'Could not retrieve PHP file.';
5623
        }
5624
5625
        $str = substr($str, 6);
5626
        $str = trim($str);
5627
        if (strpos($str, '\\') !== false) {
5628
            $str = str_replace('\\', '/', $str);
5629
        }
5630
        $str = ltrim($str, '/');
5631
5632
        $errorMsg = sprintf("Could not retrieve string '%s'.", $str);
5633
5634
        foreach ($search_path as $path) {
5635
            $file_path = MODX_BASE_PATH . $path . $str;
5636
            if (strpos($file_path, MODX_MANAGER_PATH) === 0) {
5637
                return $errorMsg;
5638
            } elseif (is_file($file_path)) {
5639
                break;
5640
            } else {
5641
                $file_path = false;
5642
            }
5643
        }
5644
5645
        if (!$file_path) {
0 ignored issues
show
Bug introduced by
The variable $file_path does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug Best Practice introduced by
The expression $file_path of type string|false is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
5646
            return $errorMsg;
5647
        }
5648
5649
        $content = (string)file_get_contents($file_path);
5650
        if ($content === false) {
5651
            return $errorMsg;
5652
        }
5653
5654
        return $content;
5655
    }
5656
5657
    /**
5658
     * @param $str
5659
     * @return bool|string
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use false|string.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
5660
     */
5661
    public function getExtFromFilename($str)
5662
    {
5663
        $str = strtolower(trim($str));
5664
        $pos = strrpos($str, '.');
5665
        if ($pos === false) {
5666
            return false;
5667
        } else {
5668
            return substr($str, $pos);
5669
        }
5670
    }
5671
5672
    /**
5673
     * Get Evolution CMS settings including, but not limited to, the system_settings table
5674
     */
5675
    public function getSettings()
5676
    {
5677
        if (empty($this->config)) {
5678
            $this->recoverySiteCache();
5679
        }
5680
5681
        $this->loadConfig();
5682
5683
        // now merge user settings into evo-configuration
5684
        $this->getUserSettings();
5685
    }
5686
    /**
5687
     * Get user settings and merge into Evolution CMS configuration
5688
     * @return array
5689
     */
5690
    public function getUserSettings()
5691
    {
5692
        $tbl_web_user_settings = $this->getDatabase()->getFullTableName('web_user_settings');
0 ignored issues
show
Unused Code introduced by
$tbl_web_user_settings is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
5693
        $tbl_user_settings = $this->getDatabase()->getFullTableName('user_settings');
0 ignored issues
show
Unused Code introduced by
$tbl_user_settings is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
5694
5695
        // load user setting if user is logged in
5696
        $usrSettings = array();
5697
        if ($id = $this->getLoginUserID()) {
5698
            $usrType = $this->getLoginUserType();
5699
            if (isset ($usrType) && $usrType == 'manager') {
5700
                $usrType = 'mgr';
5701
            }
5702
5703
            if ($usrType == 'mgr' && $this->isBackend()) {
5704
                // invoke the OnBeforeManagerPageInit event, only if in backend
5705
                $this->invokeEvent("OnBeforeManagerPageInit");
5706
            }
5707
5708
            if ($usrType == 'web') {
5709
                $usrSettings = Models\WebUserSetting::where('user', '=', $id)->get()
5710
                    ->pluck('setting_value', 'setting_name')
5711
                    ->toArray();
5712
            } else {
5713
                $usrSettings = Models\UserSetting::where('user', '=', $id)->get()
5714
                    ->pluck('setting_value', 'setting_name')
5715
                    ->toArray();
5716
            }
5717
5718
            $which_browser_default = $this->configGlobal['which_browser'] ?
5719
                $this->configGlobal['which_browser'] : $this->getConfig('which_browser');
5720
5721
            if (get_by_key($usrSettings, 'which_browser') === 'default') {
5722
                $row['setting_value'] = $which_browser_default;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$row was never initialized. Although not strictly required by PHP, it is generally a good practice to add $row = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
5723
            }
5724
5725
            if (isset ($usrType)) {
5726
                $_SESSION[$usrType . 'UsrConfigSet'] = $usrSettings;
5727
            } // store user settings in session
5728
        }
5729
        if ($this->isFrontend() && $mgrid = $this->getLoginUserID('mgr')) {
5730
            $musrSettings = Models\UserSetting::where('user', '=', $mgrid)->get()
5731
                ->pluck('setting_value', 'setting_name')
5732
                ->toArray();
5733
5734
            $_SESSION['mgrUsrConfigSet'] = $musrSettings; // store user settings in session
5735
            if (!empty ($musrSettings)) {
5736
                $usrSettings = array_merge($musrSettings, $usrSettings);
5737
            }
5738
        }
5739
        // save global values before overwriting/merging array
5740
        foreach ($usrSettings as $param => $value) {
5741
            if ($this->getConfig($param) !== null) {
5742
                $this->configGlobal[$param] = $this->getConfig($param);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $this->configGlobal[$param] is correct as $this->getConfig($param) (which targets EvolutionCMS\Traits\Settings::getConfig()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
5743
            }
5744
        }
5745
5746
        $this->config = array_merge($this->config, $usrSettings);
5747
        $this->setConfig(
5748
            'filemanager_path',
5749
            str_replace('[(base_path)]', MODX_BASE_PATH, $this->getConfig('filemanager_path'))
5750
        );
5751
        $this->setConfig(
5752
            'rb_base_dir',
5753
            str_replace('[(base_path)]', MODX_BASE_PATH, $this->getConfig('rb_base_dir'))
5754
        );
5755
5756
        return $usrSettings;
5757
    }
5758
5759
    private function recoverySiteCache()
5760
    {
5761
        $siteCacheDir = $this->bootstrapPath();
5762
        $siteCachePath = $this->getSiteCacheFilePath();
5763
5764
        if (is_file($siteCachePath)) {
5765
            include $siteCachePath;
5766
        }
5767
5768
        if (! empty($this->config)) {
5769
            return;
5770
        }
5771
5772
        $cache = new Legacy\Cache();
5773
        $cache->setCachepath($siteCacheDir);
5774
        $cache->setReport(false);
5775
        $cache->buildCache($this);
0 ignored issues
show
Documentation introduced by
$this is of type this<EvolutionCMS\Core>, but the function expects a object<EvolutionCMS\Lega...terfaces\CoreInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
5776
5777
        clearstatcache();
5778
        if (is_file($siteCachePath)) {
5779
            include $siteCachePath;
5780
        }
5781
        if (! empty($this->config)) {
5782
            return;
5783
        }
5784
5785
        $this->config = Models\SystemSetting::all()
5786
            ->pluck('setting_value', 'setting_name')
5787
            ->toArray();
5788
5789
        if ($this->getConfig('enable_filter') === null) {
5790
            return;
5791
        }
5792
5793
        if (Models\SitePlugin::activePhx()->count() === 0) {
5794
            $this->setConfig('enable_filter', '0');
5795
        }
5796
    }
5797
5798
5799
    /***************************************************************************************/
5800
    /* End of API functions                                       */
5801
    /***************************************************************************************/
5802
5803
    /**
5804
     * PHP error handler set by http://www.php.net/manual/en/function.set-error-handler.php
5805
     *
5806
     * Checks the PHP error and calls messageQuit() unless:
5807
     *  - error_reporting() returns 0, or
5808
     *  - the PHP error level is 0, or
5809
     *  - the PHP error level is 8 (E_NOTICE) and stopOnNotice is false
5810
     *
5811
     * @param int $nr The PHP error level as per http://www.php.net/manual/en/errorfunc.constants.php
5812
     * @param string $text Error message
5813
     * @param string $file File where the error was detected
5814
     * @param string $line Line number within $file
5815
     * @return boolean
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
5816
     * @deprecated
5817
     */
5818
    public function phpError($nr, $text, $file, $line)
5819
    {
5820
        $this->getService('ExceptionHandler')->phpError($nr, $text, $file, $line);
5821
    }
5822
5823
    /**
5824
     * @param string $msg
5825
     * @param string $query
5826
     * @param bool $is_error
5827
     * @param string $nr
5828
     * @param string $file
5829
     * @param string $source
5830
     * @param string $text
5831
     * @param string $line
5832
     * @param string $output
5833
     * @return bool
5834
     * @deprecated
5835
     */
5836
    public function messageQuit($msg = 'unspecified error', $query = '', $is_error = true, $nr = '', $file = '', $source = '', $text = '', $line = '', $output = '')
5837
    {
5838
        return $this->getService('ExceptionHandler')->messageQuit($msg, $query, $is_error, $nr, $file, $source, $text, $line, $output);
5839
    }
5840
5841
    /**
5842
     * @deprecated
5843
     * @param $backtrace
5844
     * @return string
5845
     */
5846
    public function get_backtrace($backtrace)
5847
    {
5848
        return $this->getService('ExceptionHandler')->getBacktrace($backtrace);
5849
    }
5850
5851
    /**
5852
     * @return string
5853
     */
5854
    public function getRegisteredClientScripts()
5855
    {
5856
        return implode("\n", $this->jscripts);
5857
    }
5858
5859
    /**
5860
     * @return string
5861
     */
5862
    public function getRegisteredClientStartupScripts()
5863
    {
5864
        return implode("\n", $this->sjscripts);
5865
    }
5866
5867
    /**
5868
     * Format alias to be URL-safe. Strip invalid characters.
5869
     *
5870
     * @param string $alias Alias to be formatted
5871
     * @return string Safe alias
5872
     */
5873
    public function stripAlias($alias)
5874
    {
5875
        // let add-ons overwrite the default behavior
5876
        $results = $this->invokeEvent('OnStripAlias', array('alias' => $alias));
5877
        if (!empty($results)) {
5878
            // if multiple plugins are registered, only the last one is used
5879
            return end($results);
5880
        } else {
5881
            // default behavior: strip invalid characters and replace spaces with dashes.
5882
            $alias = strip_tags($alias); // strip HTML
5883
            $alias = preg_replace('/[^\.A-Za-z0-9 _-]/', '', $alias); // strip non-alphanumeric characters
5884
            $alias = preg_replace('/\s+/', '-', $alias); // convert white-space to dash
5885
            $alias = preg_replace('/-+/', '-', $alias);  // convert multiple dashes to one
5886
            $alias = trim($alias, '-'); // trim excess
5887
            return $alias;
5888
        }
5889
    }
5890
5891
    /**
5892
     * @param $size
5893
     * @return string
5894
     * @deprecated
5895
     */
5896
    public function nicesize($size)
5897
    {
5898
        return nicesize($size);
5899
    }
5900
5901
    /**
5902
     * @deprecated use UrlProcessor::getHiddenIdFromAlias()
5903
     */
5904
    public function getHiddenIdFromAlias($parentid, $alias)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
5905
    {
5906
        return UrlProcessor::getHiddenIdFromAlias($parentid, $alias);
5907
    }
5908
5909
    /**
5910
     * @deprecated use UrlProcessor::getIdFromAlias()
5911
     */
5912
    public function getIdFromAlias($alias)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
5913
    {
5914
        return UrlProcessor::getIdFromAlias($alias);
5915
    }
5916
5917
    /**
5918
     * @param string $str
5919
     * @return bool|mixed|string
5920
     */
5921
    public function atBindInclude($str = '')
5922
    {
5923
        if (strpos($str, '@INCLUDE') !== 0) {
5924
            return $str;
5925
        }
5926
        if (strpos($str, "\n") !== false) {
5927
            $str = substr($str, 0, strpos("\n", $str));
5928
        }
5929
5930
        $str = substr($str, 9);
5931
        $str = trim($str);
5932
        $str = str_replace('\\', '/', $str);
5933
        $str = ltrim($str, '/');
5934
5935
        $tpl_dir = 'assets/templates/';
5936
5937
        if (strpos($str, MODX_MANAGER_PATH) === 0) {
5938
            return false;
5939
        } elseif (is_file(MODX_BASE_PATH . $str)) {
5940
            $file_path = MODX_BASE_PATH . $str;
5941
        } elseif (is_file(MODX_BASE_PATH . "{$tpl_dir}{$str}")) {
5942
            $file_path = MODX_BASE_PATH . $tpl_dir . $str;
5943
        } else {
5944
            return false;
5945
        }
5946
5947
        if (!$file_path || !is_file($file_path)) {
5948
            return false;
5949
        }
5950
5951
        ob_start();
5952
        $modx = &$this;
5953
        $result = include($file_path);
5954
        if ($result === 1) {
5955
            $result = '';
5956
        }
5957
        $content = ob_get_clean();
5958
        if (!$content && $result) {
5959
            $content = $result;
5960
        }
5961
        return $content;
5962
    }
5963
5964
    // php compat
5965
5966
    /**
5967
     * @param $str
5968
     * @param int $flags
5969
     * @param string $encode
5970
     * @return mixed
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string|array.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
5971
     */
5972
    public function htmlspecialchars($str, $flags = ENT_COMPAT, $encode = '')
5973
    {
5974
        return $this->getPhpCompat()->htmlspecialchars($str, $flags, $encode);
5975
    }
5976
5977
    /**
5978
     * @param $string
5979
     * @param bool $returnData
5980
     * @return bool|mixed
5981
     * @deprecated
5982
     */
5983
    public function isJson($string, $returnData = false)
5984
    {
5985
        return data_is_json($string, $returnData);
5986
    }
5987
5988
    /**
5989
     * @param $key
5990
     * @return array
5991
     */
5992
    public function splitKeyAndFilter($key)
5993
    {
5994
        if ($this->getConfig('enable_filter') == 1 && strpos($key, ':') !== false && stripos($key, '@FILE') !== 0) {
5995
            list($key, $modifiers) = explode(':', $key, 2);
5996
        } else {
5997
            $modifiers = false;
5998
        }
5999
6000
        $key = trim($key);
6001
        if ($modifiers !== false) {
6002
            $modifiers = trim($modifiers);
6003
        }
6004
6005
        return array($key, $modifiers);
6006
    }
6007
6008
    /**
6009
     * @param string $value
6010
     * @param bool $modifiers
6011
     * @param string $key
6012
     * @return string
6013
     */
6014
    public function applyFilter($value = '', $modifiers = false, $key = '')
6015
    {
6016
        if ($modifiers === false || $modifiers == 'raw') {
6017
            return $value;
6018
        }
6019
        if ($modifiers !== false) {
6020
            $modifiers = trim($modifiers);
6021
        }
6022
6023
        return $this->getModifiers()->phxFilter($key, $value, $modifiers);
6024
    }
6025
6026
    // End of class.
6027
6028
    /**
6029
     * @param string $title
6030
     * @param string $msg
6031
     * @param int $type
6032
     */
6033
    public function addLog($title = 'no title', $msg = '', $type = 1)
6034
    {
6035
        if ($title === '') {
6036
            $title = 'no title';
6037
        }
6038
        if (is_array($msg)) {
6039
            $msg = '<pre>' . print_r($msg, true) . '</pre>';
6040
        } elseif ($msg === '') {
6041
            $msg = $_SERVER['REQUEST_URI'];
6042
        }
6043
        $this->logEvent(0, $type, $msg, $title);
6044
    }
6045
6046
}
6047