Completed
Push — develop ( cb7ecf...5e631f )
by Dmytro
17s
created

Core::regClientScript()   F

Complexity

Conditions 23
Paths 6913

Size

Total Lines 70
Code Lines 47

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 23
eloc 47
nc 6913
nop 3
dl 0
loc 70
rs 2.6272
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
class Core implements Interfaces\CoreInterface
0 ignored issues
show
Coding Style introduced by
The property $table_prefix is not named in camelCase.

This check marks property names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
Coding Style introduced by
The property $error_reporting is not named in camelCase.

This check marks property names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
Coding Style introduced by
The property $decoded_request_uri is not named in camelCase.

This check marks property names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
4
{
5
    /**
6
     * This is New evolution
7
     * @var string
8
     */
9
    public $apiVersion = '1.0.0';
10
11
    /**
12
     * @var Mail
13
     * @deprecated use ->getMail()
14
     * @see /manager/includes/extenders/ex_modxmailer.inc.php
15
     * @example $this->loadExtension('MODxMailer');
16
     */
17
    public $mail;
18
19
    /**
20
     * db object
21
     * @deprecated use ->getDatabase()
22
     * @var Database
23
     * @see /manager/includes/extenders/ex_dbapi.inc.php
24
     * @example $this->loadExtension('DBAPI')
25
     */
26
    public $db;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $db. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
27
28
    /**
29
     * @var Legacy\PhpCompat
30
     * @deprecated use ->getPhpCompat()
31
     * @see /manager/includes/extenders/ex_phpcompat.inc.php
32
     * @example $this->loadExtension('PHPCOMPAT');
33
     */
34
    public $phpcompat;
35
36
    /**
37
     * @var \MODIFIERS
38
     * @deprecated use ->getModifiers()
39
     * @see /manager/includes/extenders/ex_modifiers.inc.php
40
     * @example $this->loadExtension('MODIFIERS');
41
     */
42
    public $filter;
43
44
    /**
45
     * @var Legacy\ExportSite
46
     * @deprecated use ->getExportSite()
47
     * @see /manager/includes/extenders/ex_export_site.inc.php
48
     * @example $this->loadExtension('EXPORT_SITE');
49
     */
50
    public $export;
51
52
    /**
53
     * @var Support\MakeTable
54
     * @deprecated use ->getMakeTable
55
     * @see /manager/includes/extenders/ex_maketable.inc.php
56
     * @example $this->loadExtension('makeTable');
57
     */
58
    public $table;
59
60
    /**
61
     * @var Legacy\ManagerApi
62
     * @deprecated use ->getManagerApi()
63
     * @see /manager/includes/extenders/ex_managerapi.inc.php
64
     * @example $this->loadExtension('ManagerAPI');
65
     */
66
    public $manager;
67
68
    /**
69
     * @var Legacy\PasswordHash
70
     * @deprecated use ->getPasswordHash()
71
     * @see manager/includes/extenders/ex_phpass.inc.php
72
     * @example $this->loadExtension('phpass');
73
     */
74
    public $phpass;
75
76
    /**
77
     * event object
78
     * @var Event
79
     */
80
81
    public $event;
82
    /**
83
     * event object
84
     * @var Event
85
     * @deprecated
86
     */
87
    public $Event;
88
89
    /**
90
     * @var array
91
     */
92
    public $pluginEvent = array();
93
94
    /**
95
     * @var array
96
     */
97
    public $config = array();
98
    /**
99
     * @var array
100
     */
101
    public $dbConfig = array();
102
    public $configGlobal = null; // contains backup of settings overwritten by user-settings
103
    public $rs;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rs. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
104
    public $result;
105
    public $sql;
106
    public $table_prefix;
107
    public $debug = false;
108
    public $documentIdentifier;
109
    public $documentMethod;
110
    public $documentGenerated;
111
    public $documentContent;
112
    public $documentOutput;
113
    public $tstart;
114
    public $mstart;
115
    public $minParserPasses;
116
    public $maxParserPasses;
117
    public $documentObject;
118
    public $templateObject;
119
    public $snippetObjects;
120
    public $stopOnNotice = false;
121
    public $executedQueries;
122
    public $queryTime;
123
    public $currentSnippet;
124
    public $documentName;
125
    public $aliases;
126
    public $visitor;
127
    public $entrypage;
128
    public $documentListing;
129
    /**
130
     * feed the parser the execution start time
131
     * @var bool
132
     */
133
    public $dumpSnippets = false;
134
    public $snippetsCode;
135
    public $snippetsTime = array();
136
    public $chunkCache;
137
    public $snippetCache;
138
    public $contentTypes;
139
    public $dumpSQL = false;
140
    public $queryCode;
141
    public $virtualDir;
142
    public $placeholders;
143
    public $sjscripts = array();
144
    public $jscripts = array();
145
    public $loadedjscripts = array();
146
    public $documentMap;
147
    public $forwards = 3;
148
    public $error_reporting = 1;
149
    public $dumpPlugins = false;
150
    public $pluginsCode;
151
    public $pluginsTime = array();
152
    public $pluginCache = array();
153
    public $aliasListing;
154
    public $lockedElements = null;
155
    public $tmpCache = array();
156
    private $version = array();
157
    public $extensions = array();
158
    public $cacheKey = null;
159
    public $recentUpdate = 0;
160
    public $useConditional = false;
161
    protected $systemCacheKey = null;
162
    public $snipLapCount = 0;
163
    public $messageQuitCount;
164
    public $time;
165
    public $sid;
166
    private $q;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $q. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
167
    public $decoded_request_uri;
168
    /**
169
     * @var Legacy\DeprecatedCore
170
     * @deprecated use ->getDeprecatedCore()
171
     */
172
    public $old;
173
174
    /**
175
     * Hold the class instance.
176
     * @var self
177
     */
178
    private static $instance = null;
179
180
    private $services;
181
    private $serviceStore = [];
182
183
    /**
184
     * @var array
185
     * $this->{$key}
186
     */
187
    public $providerAliases = [
188
        'db' => Interfaces\DatabaseInterface::class,
189
        'mail' => Interfaces\MailInterface::class,
190
        'phpcompat' => Interfaces\PhpCompatInterface::class,
191
        'phpass' => Interfaces\PasswordHashInterface::class,
192
        'table' => Interfaces\MakeTableInterface::class,
193
        'export' => Interfaces\ExportSiteInerface::class,
194
        'manager' => Interfaces\ManagerApiInterface::class,
195
        'filter' => Interfaces\ModifiersInterface::class
196
    ];
197
198
    /**
199
     * @var array
200
     * $this->loadExtension($key)
201
     */
202
    public $extensionAlias = [
203
        'DBAPI' => Interfaces\DatabaseInterface::class,
204
        'MODxMailer' => Interfaces\MailInterface::class,
205
        'PHPCOMPAT' => Interfaces\PhpCompatInterface::class,
206
        'phpass' => Interfaces\PasswordHashInterface::class,
207
        'makeTable' => Interfaces\MakeTableInterface::class,
208
        'EXPORT_SITE' => Interfaces\ExportSiteInerface::class,
209
        'ManagerAPI' => Interfaces\ManagerApiInterface::class,
210
        'MODIFIERS' => Interfaces\ModifiersInterface::class
211
    ];
212
213
    /**
214
     * @param array $services
215
     * @param array $parameters
0 ignored issues
show
Bug introduced by
There is no parameter named $parameters. 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...
216
     */
217
    public function __construct(array $services = array())
218
    {
219
        self::$instance = $this;
220
        if (empty($services)) {
221
            $services   = include EVO_SERVICES_FILE;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned correctly; expected 1 space but found 3 spaces

This check looks for improperly formatted assignments.

Every assignment must have exactly one space before and one space after the equals operator.

To illustrate:

$a = "a";
$ab = "ab";
$abc = "abc";

will have no issues, while

$a   = "a";
$ab  = "ab";
$abc = "abc";

will report issues in lines 1 and 2.

Loading history...
222
        }
223
        $this->services     =  $services;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned correctly; expected 1 space but found 5 spaces

This check looks for improperly formatted assignments.

Every assignment must have exactly one space before and one space after the equals operator.

To illustrate:

$a = "a";
$ab = "ab";
$abc = "abc";

will have no issues, while

$a   = "a";
$ab  = "ab";
$abc = "abc";

will report issues in lines 1 and 2.

Loading history...
224
225
        $this->initialize();
226
    }
227
228
    /**
229
     * {@inheritDoc}
230
     */
231
    public function getService($name)
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...
232
    {
233
        if (!$this->hasService($name)) {
234
            throw new Exceptions\ServiceNotFoundException('Service not found: '.$name);
235
        }
236
        // If we haven't created it, create it and save to store
237
        if (!isset($this->serviceStore[$name])) {
238
            $this->serviceStore[$name] = $this->createService($name);
239
        }
240
        // Return service from store
241
        return $this->serviceStore[$name];
242
    }
243
    /**
244
     * {@inheritDoc}
245
     */
246
    public function hasService($name)
247
    {
248
        return isset($this->services[$name]);
249
    }
250
251
    /**
252
     * Attempt to create a service.
253
     *
254
     * @param string $name The service name.
255
     *
256
     * @return mixed The created service.
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use object.

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...
257
     *
258
     * @throws Exceptions\ContainerException On failure.
259
     */
260
    private function createService($name)
261
    {
262
        $entry = &$this->services[$name];
263
        if (!is_array($entry) || !isset($entry['class'])) {
264
            throw new Exceptions\ContainerException($name.' service entry must be an array containing a \'class\' key');
265
        } elseif (!class_exists($entry['class'])) {
266
            throw new Exceptions\ContainerException($name.' service class does not exist: '.$entry['class']);
267
        } elseif (isset($entry['lock'])) {
268
            throw new Exceptions\ContainerException($name.' contains circular reference');
269
        }
270
        $entry['lock'] = true;
271
        $arguments = isset($entry['arguments']) ? $this->resolveServiceArguments($entry['arguments']) : [];
0 ignored issues
show
Documentation introduced by
$entry['arguments'] 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...
272
        $reflector = new \ReflectionClass($entry['class']);
273
        $service = $reflector->newInstanceArgs($arguments);
274
        if (isset($entry['calls'])) {
275
            $this->initializeService($service, $name, $entry['calls']);
0 ignored issues
show
Documentation introduced by
$entry['calls'] 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...
276
        }
277
278
        if ($alias = $this->checkServiceAlias($name)) {
279
            $this->{$alias} = $service;
280
        }
281
282
        return $service;
283
    }
284
285
    private function checkServiceAlias($name){
286
        foreach ($this->providerAliases as $alias => $interface) {
287
            if($name === $interface) {
288
                return $alias;
289
            }
290
        }
291
292
        return false;
293
    }
294
295
    /**
296
     * Resolve argument definitions into an array of arguments.
297
     *
298
     * @param array  $argumentDefinitions The service arguments definition.
299
     *
300
     * @return array The service constructor arguments.
301
     *
302
     * @throws Exceptions\ContainerException On failure.
303
     */
304
    private function resolveServiceArguments(array $argumentDefinitions)
305
    {
306
        $arguments = [];
307
        foreach ($argumentDefinitions as $argumentDefinition) {
308
            if ($argumentDefinition instanceof Interfaces\ServiceProviderInterface) {
309
                $argumentServiceName = $argumentDefinition->getName();
310
                $arguments[] = $this->getService($argumentServiceName);
311
            } else {
312
                $arguments[] = $argumentDefinition;
313
            }
314
        }
315
        return $arguments;
316
    }
317
    /**
318
     * Initialize a service using the call definitions.
319
     *
320
     * @param object $service         The service.
321
     * @param string $name            The service name.
322
     * @param array  $callDefinitions The service calls definition.
323
     *
324
     * @throws Exceptions\ContainerException On failure.
325
     */
326
    private function initializeService($service, $name, array $callDefinitions)
327
    {
328
        foreach ($callDefinitions as $callDefinition) {
329
            if (!is_array($callDefinition) || !isset($callDefinition['method'])) {
330
                throw new Exceptions\ContainerException($name.' service calls must be arrays containing a \'method\' key');
331
            } elseif (!is_callable([$service, $callDefinition['method']])) {
332
                throw new Exceptions\ContainerException($name.' service asks for call to uncallable method: '.$callDefinition['method']);
333
            }
334
            $arguments = isset($callDefinition['arguments']) ? $this->resolveServiceArguments($callDefinition['arguments']) : [];
335
336
            call_user_func_array([$service, $callDefinition['method']], $arguments);
337
        }
338
    }
339
340
    /**
341
     * @return Database
342
     * @throws Exceptions\ServiceNotFoundException
343
     */
344
    public function getDatabase()
345
    {
346
        return $this->getService(Interfaces\DatabaseInterface::class);
347
    }
348
349
    /**
350
     * @return Mail
351
     * @throws Exceptions\ServiceNotFoundException
352
     */
353
    public function getMail()
354
    {
355
        return $this->getService(Interfaces\MailInterface::class);
356
    }
357
358
    /**
359
     * @return Legacy\PhpCompat
360
     * @throws Exceptions\ServiceNotFoundException
361
     */
362
    public function getPhpCompat()
363
    {
364
        return $this->getService(Interfaces\PhpCompatInterface::class);
365
    }
366
367
    /**
368
     * @return Legacy\PasswordHash
369
     * @throws Exceptions\ServiceNotFoundException
370
     */
371
    public function getPasswordHash()
372
    {
373
        return $this->getService(Interfaces\PasswordHashInterface::class);
374
    }
375
376
    /**
377
     * @return Support\MakeTable
378
     * @throws Exceptions\ServiceNotFoundException
379
     */
380
    public function getMakeTable()
381
    {
382
        return $this->getService(Interfaces\MakeTableInterface::class);
383
    }
384
385
    /**
386
     * @return Legacy\ExportSite
387
     * @throws Exceptions\ServiceNotFoundException
388
     */
389
    public function getExportSite()
390
    {
391
        return $this->getService(Interfaces\ExportSiteInerface::class);
392
    }
393
394
    /**
395
     * @return mixed
396
     * @throws Exceptions\ServiceNotFoundException
397
     */
398
    public function getDeprecatedCore()
399
    {
400
        return $this->getService(Interfaces\DeprecatedCoreInterface::class);
401
    }
402
403
    /**
404
     * @return mixed
405
     * @throws Exceptions\ServiceNotFoundException
406
     */
407
    public function getManagerApi()
408
    {
409
        return $this->getService(Interfaces\ManagerApiInterface::class);
410
    }
411
412
    /**
413
     * @return mixed
414
     * @throws Exceptions\ServiceNotFoundException
415
     */
416
    public function getModifiers()
417
    {
418
        return $this->getService(Interfaces\ModifiersInterface::class);
419
    }
420
421
    public function initialize()
0 ignored issues
show
Coding Style introduced by
initialize uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
422
    {
423
        if ($this->isLoggedIn()) {
424
            ini_set('display_errors', 1);
425
        }
426
        global $database_server;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
427
        if (substr(PHP_OS, 0, 3) === 'WIN' && $database_server === 'localhost') {
428
            $database_server = '127.0.0.1';
429
        }
430
        $this->dbConfig = &$this->getDatabase()->config; // alias for backward compatibility
431
        // events
432
        $this->event = new Event();
433
        $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...
434
        // set track_errors ini variable
435
        @ ini_set("track_errors", "1"); // enable error tracking in $php_errormsg
436
        $this->time = $_SERVER['REQUEST_TIME']; // for having global timestamp
437
438
        $this->q = self::_getCleanQueryString();
439
    }
440
441
    final public function __clone()
442
    {
443
    }
444
445
    /**
446
     * @param array $services
447
     * @return self
448
     */
449
    public static function getInstance(array $services = array())
450
    {
451
        if (self::$instance === null) {
452
            self::$instance = new self($services);
453
        }
454
        return self::$instance;
455
    }
456
457
    /**
458
     * @param $method_name
459
     * @param $arguments
460
     * @return mixed
461
     */
462
    function __call($method_name, $arguments)
0 ignored issues
show
Coding Style Naming introduced by
The parameter $method_name is not named in camelCase.

This check marks parameter names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
463
    {
464
        $old = $this->getDeprecatedCore();
465
        //////////@TODO LOAD DeprecatedCore
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
466
        if (method_exists($old, $method_name)) {
467
            $error_type = 1;
468
        } else {
469
            $error_type = 3;
470
        }
471
472
        if (!isset($this->config['error_reporting']) || 1 < $this->config['error_reporting']) {
473
            if ($error_type == 1) {
474
                $title = 'Call deprecated method';
475
                $msg = $this->getPhpCompat()->htmlspecialchars("\$modx->{$method_name}() is deprecated function");
476
            } else {
477
                $title = 'Call undefined method';
478
                $msg = $this->getPhpCompat()->htmlspecialchars("\$modx->{$method_name}() is undefined function");
479
            }
480
            $info = debug_backtrace();
481
            $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...
Comprehensibility introduced by
Avoid variables with short names like $m. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
482
            if (!empty($this->currentSnippet)) {
483
                $m[] = 'Snippet - ' . $this->currentSnippet;
484
            } elseif (!empty($this->event->activePlugin)) {
485
                $m[] = 'Plugin - ' . $this->event->activePlugin;
486
            }
487
            $m[] = $this->decoded_request_uri;
488
            $m[] = str_replace('\\', '/', $info[0]['file']) . '(line:' . $info[0]['line'] . ')';
489
            $msg = implode('<br />', $m);
490
            $this->logEvent(0, $error_type, $msg, $title);
491
        }
492
        if (method_exists($old, $method_name)) {
493
            return call_user_func_array(array($old, $method_name), $arguments);
494
        }
495
    }
496
497
    /**
498
     * @param string $connector
499
     * @return bool
500
     */
501
    public function checkSQLconnect($connector = 'db')
502
    {
503
        $flag = false;
504
        if (is_scalar($connector) && !empty($connector) && isset($this->{$connector}) && $this->{$connector} instanceof Interfaces\DatabaseInterface) {
505
            $flag = (bool)$this->{$connector}->conn;
506
        }
507
        return $flag;
508
    }
509
510
    /**
511
     * Loads an extension from the extenders folder.
512
     * You can load any extension creating a boot file:
513
     * MODX_MANAGER_PATH."includes/extenders/ex_{$extname}.inc.php"
514
     * $extname - extension name in lowercase
515
     *
516
     * @deprecated use getService
517
     * @param $extname
518
     * @param bool $reload
519
     * @return bool
520
     */
521
    public function loadExtension($extname, $reload = true)
522
    {
523
        $out = false;
524
        if (isset($this->extensionAlias[$extname])) {
525
            $out = $this->getService($this->extensionAlias[$extname]);
526
        } else {
527
            $flag = ($reload || !in_array($extname, $this->extensions));
528
            if ($this->checkSQLconnect('db') && $flag) {
529
                $evtOut = $this->invokeEvent('OnBeforeLoadExtension', array('name' => $extname, 'reload' => $reload));
530
                if (is_array($evtOut) && count($evtOut) > 0) {
531
                    $out = array_pop($evtOut);
532
                }
533
            }
534
            if (!$out && $flag) {
535
                $extname = trim(str_replace(array('..', '/', '\\'), '', strtolower($extname)));
536
                $filename = MODX_MANAGER_PATH . "includes/extenders/ex_{$extname}.inc.php";
537
                $out = is_file($filename) ? include $filename : false;
538
            }
539
            if ($out && !in_array($extname, $this->extensions)) {
540
                $this->extensions[] = $extname;
541
            }
542
        }
543
        return $out;
544
    }
545
546
    /**
547
     * Returns the current micro time
548
     *
549
     * @return float
550
     */
551
    public function getMicroTime()
552
    {
553
        list ($usec, $sec) = explode(' ', microtime());
554
        return ((float)$usec + (float)$sec);
555
    }
556
557
    /**
558
     * Redirect
559
     *
560
     * @param string $url
561
     * @param int $count_attempts
562
     * @param string $type $type
563
     * @param string $responseCode
564
     * @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...
565
     * @global string $base_url
566
     * @global string $site_url
567
     */
568
    public function sendRedirect($url, $count_attempts = 0, $type = '', $responseCode = '')
0 ignored issues
show
Coding Style introduced by
sendRedirect uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style Naming introduced by
The parameter $count_attempts is not named in camelCase.

This check marks parameter names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
569
    {
570
        $header = '';
571
        if (empty ($url)) {
572
            return false;
573
        }
574
        if ($count_attempts == 1) {
575
            // append the redirect count string to the url
576
            $currentNumberOfRedirects = isset ($_REQUEST['err']) ? $_REQUEST['err'] : 0;
577
            if ($currentNumberOfRedirects > 3) {
578
                $this->messageQuit('Redirection attempt failed - please ensure the document you\'re trying to redirect to exists. <p>Redirection URL: <i>' . $url . '</i></p>');
579
            } else {
580
                $currentNumberOfRedirects += 1;
0 ignored issues
show
Coding Style introduced by
Increment operators should be used where possible; found "$currentNumberOfRedirects += 1;" but expected "$currentNumberOfRedirects++"
Loading history...
581
                if (strpos($url, "?") > 0) {
582
                    $url .= "&err=$currentNumberOfRedirects";
583
                } else {
584
                    $url .= "?err=$currentNumberOfRedirects";
585
                }
586
            }
587
        }
588
        if ($type == 'REDIRECT_REFRESH') {
589
            $header = 'Refresh: 0;URL=' . $url;
590
        } elseif ($type == 'REDIRECT_META') {
591
            $header = '<META HTTP-EQUIV="Refresh" CONTENT="0; URL=' . $url . '" />';
592
            echo $header;
593
            exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method sendRedirect() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
594
        } elseif ($type == 'REDIRECT_HEADER' || empty ($type)) {
595
            // check if url has /$base_url
596
            global $base_url, $site_url;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
597
            if (substr($url, 0, strlen($base_url)) == $base_url) {
598
                // append $site_url to make it work with Location:
599
                $url = $site_url . substr($url, strlen($base_url));
600
            }
601
            if (strpos($url, "\n") === false) {
602
                $header = 'Location: ' . $url;
603
            } else {
604
                $this->messageQuit('No newline allowed in redirect url.');
605
            }
606
        }
607
        if ($responseCode && (strpos($responseCode, '30') !== false)) {
608
            header($responseCode);
609
        }
610
611
        if(!empty($header)) {
612
            header($header);
613
        }
614
615
        exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method sendRedirect() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
616
    }
617
618
    /**
619
     * Forward to another page
620
     *
621
     * @param int|string $id
622
     * @param string $responseCode
623
     */
624
    public function sendForward($id, $responseCode = '')
625
    {
626
        if ($this->forwards > 0) {
627
            $this->forwards = $this->forwards - 1;
628
            $this->documentIdentifier = $id;
629
            $this->documentMethod = 'id';
630
            if ($responseCode) {
631
                header($responseCode);
632
            }
633
            $this->prepareResponse();
634
            exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method sendForward() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
635
        } else {
636
            $this->messageQuit("Internal Server Error id={$id}");
637
            header('HTTP/1.0 500 Internal Server Error');
638
            die('<h1>ERROR: Too many forward attempts!</h1><p>The request could not be completed due to too many unsuccessful forward attempts.</p>');
0 ignored issues
show
Coding Style Compatibility introduced by
The method sendForward() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
639
        }
640
    }
641
642
    /**
643
     * Redirect to the error page, by calling sendForward(). This is called for example when the page was not found.
644
     * @param bool $noEvent
645
     */
646
    public function sendErrorPage($noEvent = false)
647
    {
648
        $this->systemCacheKey = 'notfound';
649
        if (!$noEvent) {
650
            // invoke OnPageNotFound event
651
            $this->invokeEvent('OnPageNotFound');
652
        }
653
        $url = $this->config['error_page'] ? $this->config['error_page'] : $this->config['site_start'];
654
655
        $this->sendForward($url, 'HTTP/1.0 404 Not Found');
656
        exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method sendErrorPage() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
657
    }
658
659
    /**
660
     * @param bool $noEvent
661
     */
662
    public function sendUnauthorizedPage($noEvent = false)
0 ignored issues
show
Coding Style introduced by
sendUnauthorizedPage uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
663
    {
664
        // invoke OnPageUnauthorized event
665
        $_REQUEST['refurl'] = $this->documentIdentifier;
666
        $this->systemCacheKey = 'unauth';
667
        if (!$noEvent) {
668
            $this->invokeEvent('OnPageUnauthorized');
669
        }
670
        if ($this->config['unauthorized_page']) {
671
            $unauthorizedPage = $this->config['unauthorized_page'];
672
        } elseif ($this->config['error_page']) {
673
            $unauthorizedPage = $this->config['error_page'];
674
        } else {
675
            $unauthorizedPage = $this->config['site_start'];
676
        }
677
        $this->sendForward($unauthorizedPage, 'HTTP/1.1 401 Unauthorized');
678
        exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method sendUnauthorizedPage() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
679
    }
680
681
    /**
682
     * Get MODX settings including, but not limited to, the system_settings table
683
     */
684
    public function getSettings()
685
    {
686
        if (!isset($this->config['site_name'])) {
687
            $this->recoverySiteCache();
688
        }
689
690
        // setup default site id - new installation should generate a unique id for the site.
691
        if (!isset($this->config['site_id'])) {
692
            $this->config['site_id'] = "MzGeQ2faT4Dw06+U49x3";
693
        }
694
695
        // store base_url and base_path inside config array
696
        $this->config['base_url'] = MODX_BASE_URL;
697
        $this->config['base_path'] = MODX_BASE_PATH;
698
        $this->config['site_url'] = MODX_SITE_URL;
699
        $this->config['valid_hostnames'] = MODX_SITE_HOSTNAMES;
700
        $this->config['site_manager_url'] = MODX_MANAGER_URL;
701
        $this->config['site_manager_path'] = MODX_MANAGER_PATH;
702
        $this->error_reporting = $this->config['error_reporting'];
703
        $this->config['filemanager_path'] = str_replace('[(base_path)]', MODX_BASE_PATH, $this->config['filemanager_path']);
704
        $this->config['rb_base_dir'] = str_replace('[(base_path)]', MODX_BASE_PATH, $this->config['rb_base_dir']);
705
706
        if (!isset($this->config['enable_at_syntax'])) {
707
            $this->config['enable_at_syntax'] = 1;
708
        } // @TODO: This line is temporary, should be remove in next version
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
709
710
        // now merge user settings into evo-configuration
711
        $this->getUserSettings();
712
    }
713
714
    private function recoverySiteCache()
715
    {
716
        $site_cache_dir = MODX_BASE_PATH . $this->getCacheFolder();
717
        $site_cache_path = $site_cache_dir . 'siteCache.idx.php';
718
719
        if (is_file($site_cache_path)) {
720
            include($site_cache_path);
721
        }
722
        if (isset($this->config['site_name'])) {
723
            return;
724
        }
725
726
        $cache = new Cache();
727
        $cache->setCachepath($site_cache_dir);
728
        $cache->setReport(false);
729
        $cache->buildCache($this);
0 ignored issues
show
Documentation introduced by
$this is of type this<EvolutionCMS\Core>, but the function expects a object<EvolutionCMS\DocumentParser>.

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...
730
731
        clearstatcache();
732
        if (is_file($site_cache_path)) {
733
            include($site_cache_path);
734
        }
735
        if (isset($this->config['site_name'])) {
736
            return;
737
        }
738
739
        $rs = $this->getDatabase()->select('setting_name, setting_value', '[+prefix+]system_settings');
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rs. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
740
        while ($row = $this->getDatabase()->getRow($rs)) {
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se...efix+]system_settings') on line 739 can also be of type boolean; however, EvolutionCMS\Database::getRow() does only seem to accept object<mysqli_result>, 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...
741
            $this->config[$row['setting_name']] = $row['setting_value'];
742
        }
743
744
        if (!$this->config['enable_filter']) {
745
            return;
746
        }
747
748
        $where = "plugincode LIKE '%phx.parser.class.inc.php%OnParseDocument();%' AND disabled != 1";
749
        $rs = $this->getDatabase()->select('id', '[+prefix+]site_plugins', $where);
750
        if ($this->getDatabase()->getRecordCount($rs)) {
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se...]site_plugins', $where) on line 749 can also be of type boolean; however, EvolutionCMS\Database::getRecordCount() does only seem to accept object<mysqli_result>, 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...
751
            $this->config['enable_filter'] = '0';
752
        }
753
    }
754
755
    /**
756
     * Get user settings and merge into MODX configuration
757
     * @return array
758
     */
759
    public function getUserSettings()
0 ignored issues
show
Coding Style introduced by
getUserSettings uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
760
    {
761
        $tbl_web_user_settings = $this->getFullTableName('web_user_settings');
762
        $tbl_user_settings = $this->getFullTableName('user_settings');
763
764
        // load user setting if user is logged in
765
        $usrSettings = array();
766
        if ($id = $this->getLoginUserID()) {
767
            $usrType = $this->getLoginUserType();
768
            if (isset ($usrType) && $usrType == 'manager') {
769
                $usrType = 'mgr';
770
            }
771
772
            if ($usrType == 'mgr' && $this->isBackend()) {
773
                // invoke the OnBeforeManagerPageInit event, only if in backend
774
                $this->invokeEvent("OnBeforeManagerPageInit");
775
            }
776
777
            if (isset ($_SESSION[$usrType . 'UsrConfigSet'])) {
778
                $usrSettings = &$_SESSION[$usrType . 'UsrConfigSet'];
779
            } else {
780
                if ($usrType == 'web') {
781
                    $from = $tbl_web_user_settings;
782
                    $where = "webuser='{$id}'";
783
                } else {
784
                    $from = $tbl_user_settings;
785
                    $where = "user='{$id}'";
786
                }
787
788
                $which_browser_default = $this->configGlobal['which_browser'] ? $this->configGlobal['which_browser'] : $this->config['which_browser'];
789
790
                $result = $this->getDatabase()->select('setting_name, setting_value', $from, $where);
791
                while ($row = $this->getDatabase()->getRow($result)) {
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->getDatabase()->se..._value', $from, $where) on line 790 can also be of type boolean; however, EvolutionCMS\Database::getRow() does only seem to accept object<mysqli_result>, 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...
792 View Code Duplication
                    if ($row['setting_name'] == 'which_browser' && $row['setting_value'] == 'default') {
793
                        $row['setting_value'] = $which_browser_default;
794
                    }
795
                    $usrSettings[$row['setting_name']] = $row['setting_value'];
796
                }
797
                if (isset ($usrType)) {
798
                    $_SESSION[$usrType . 'UsrConfigSet'] = $usrSettings;
799
                } // store user settings in session
800
            }
801
        }
802
        if ($this->isFrontend() && $mgrid = $this->getLoginUserID('mgr')) {
803
            $musrSettings = array();
804
            if (isset ($_SESSION['mgrUsrConfigSet'])) {
805
                $musrSettings = &$_SESSION['mgrUsrConfigSet'];
806
            } else {
807
                if ($result = $this->getDatabase()->select('setting_name, setting_value', $tbl_user_settings, "user='{$mgrid}'")) {
808
                    while ($row = $this->getDatabase()->getRow($result)) {
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->getDatabase()->se...ngs, "user='{$mgrid}'") on line 807 can also be of type boolean; however, EvolutionCMS\Database::getRow() does only seem to accept object<mysqli_result>, 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...
809
                        $musrSettings[$row['setting_name']] = $row['setting_value'];
810
                    }
811
                    $_SESSION['mgrUsrConfigSet'] = $musrSettings; // store user settings in session
812
                }
813
            }
814
            if (!empty ($musrSettings)) {
815
                $usrSettings = array_merge($musrSettings, $usrSettings);
816
            }
817
        }
818
        // save global values before overwriting/merging array
819
        foreach ($usrSettings as $param => $value) {
820
            if (isset($this->config[$param])) {
821
                $this->configGlobal[$param] = $this->config[$param];
822
            }
823
        }
824
825
        $this->config = array_merge($this->config, $usrSettings);
826
        $this->config['filemanager_path'] = str_replace('[(base_path)]', MODX_BASE_PATH, $this->config['filemanager_path']);
827
        $this->config['rb_base_dir'] = str_replace('[(base_path)]', MODX_BASE_PATH, $this->config['rb_base_dir']);
828
829
        return $usrSettings;
830
    }
831
832
    /**
833
     * Returns the document identifier of the current request
834
     *
835
     * @param string $method id and alias are allowed
836
     * @return int
837
     */
838
    public function getDocumentIdentifier($method)
0 ignored issues
show
Coding Style introduced by
getDocumentIdentifier uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
getDocumentIdentifier uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
839
    {
840
        // function to test the query and find the retrieval method
841
        if ($method === 'alias') {
842
            return $this->getDatabase()->escape($_REQUEST['q']);
843
        }
844
845
        $id_ = filter_input(INPUT_GET, 'id');
846
        if ($id_) {
847
            if (preg_match('@^[1-9][0-9]*$@', $id_)) {
848
                return $id_;
849
            } else {
850
                $this->sendErrorPage();
851
            }
852
        } elseif (strpos($_SERVER['REQUEST_URI'], 'index.php/') !== false) {
853
            $this->sendErrorPage();
854
        } else {
855
            return $this->config['site_start'];
856
        }
857
    }
858
859
    /**
860
     * Check for manager or webuser login session since v1.2
861
     *
862
     * @param string $context
863
     * @return bool
864
     */
865
    public function isLoggedIn($context = 'mgr')
0 ignored issues
show
Coding Style introduced by
isLoggedIn uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
866
    {
867
        if (substr($context, 0, 1) == 'm') {
868
            $_ = 'mgrValidated';
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $_. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
869
        } else {
870
            $_ = 'webValidated';
871
        }
872
873
        if (MODX_CLI || (isset($_SESSION[$_]) && !empty($_SESSION[$_]))) {
0 ignored issues
show
Coding Style introduced by
The if-else statement can be simplified to return MODX_CLI || isset... !empty($_SESSION[$_]);.
Loading history...
874
            return true;
875
        } else {
876
            return false;
877
        }
878
    }
879
880
    /**
881
     * Check for manager login session
882
     *
883
     * @return boolean
884
     */
885
    public function checkSession()
886
    {
887
        return $this->isLoggedin();
888
    }
889
890
    /**
891
     * Checks, if a the result is a preview
892
     *
893
     * @return boolean
894
     */
895
    public function checkPreview()
0 ignored issues
show
Coding Style introduced by
checkPreview uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
896
    {
897
        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...
898
            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...
899
                return true;
900
            } else {
901
                return false;
902
            }
903
        } else {
904
            return false;
905
        }
906
    }
907
908
    /**
909
     * check if site is offline
910
     *
911
     * @return boolean
912
     */
913
    public function checkSiteStatus()
914
    {
915
        if ($this->config['site_status']) {
916
            return true;
917
        }  // site online
918
        elseif ($this->isLoggedin()) {
919
            return true;
920
        }  // site offline but launched via the manager
921
        else {
922
            return false;
923
        } // site is offline
924
    }
925
926
    /**
927
     * Create a 'clean' document identifier with path information, friendly URL suffix and prefix.
928
     *
929
     * @param string $qOrig
930
     * @return string
931
     */
932
    public function cleanDocumentIdentifier($qOrig)
933
    {
934
        if (!$qOrig) {
935
            $qOrig = $this->config['site_start'];
936
        }
937
        $q = $qOrig;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $q. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
938
939
        $pre = $this->config['friendly_url_prefix'];
940
        $suf = $this->config['friendly_url_suffix'];
941
        $pre = preg_quote($pre, '/');
942
        $suf = preg_quote($suf, '/');
943 View Code Duplication
        if ($pre && preg_match('@^' . $pre . '(.*)$@', $q, $_)) {
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $_. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
944
            $q = $_[1];
945
        }
946 View Code Duplication
        if ($suf && preg_match('@(.*)' . $suf . '$@', $q, $_)) {
947
            $q = $_[1];
948
        }
949
950
        /* First remove any / before or after */
951
        $q = trim($q, '/');
952
953
        /* Save path if any */
954
        /* FS#476 and FS#308: only return virtualDir if friendly paths are enabled */
955
        if ($this->config['use_alias_path'] == 1) {
956
            $_ = strrpos($q, '/');
957
            $this->virtualDir = $_ !== false ? substr($q, 0, $_) : '';
958
            if ($_ !== false) {
959
                $q = preg_replace('@.*/@', '', $q);
960
            }
961
        } else {
962
            $this->virtualDir = '';
963
        }
964
965
        if (preg_match('@^[1-9][0-9]*$@', $q) && !isset($this->documentListing[$q])) { /* we got an ID returned, check to make sure it's not an alias */
966
            /* FS#476 and FS#308: check that id is valid in terms of virtualDir structure */
967
            if ($this->config['use_alias_path'] == 1) {
968
                if (($this->virtualDir != '' && !isset($this->documentListing[$this->virtualDir . '/' . $q]) || ($this->virtualDir == '' && !isset($this->documentListing[$q]))) && (($this->virtualDir != '' && isset($this->documentListing[$this->virtualDir]) && in_array($q, $this->getChildIds($this->documentListing[$this->virtualDir], 1))) || ($this->virtualDir == '' && in_array($q, $this->getChildIds(0, 1))))) {
969
                    $this->documentMethod = 'id';
970
                    return $q;
971
                } else { /* not a valid id in terms of virtualDir, treat as alias */
972
                    $this->documentMethod = 'alias';
973
                    return $q;
974
                }
975
            } else {
976
                $this->documentMethod = 'id';
977
                return $q;
978
            }
979
        } else { /* we didn't get an ID back, so instead we assume it's an alias */
980
            if ($this->config['friendly_alias_urls'] != 1) {
981
                $q = $qOrig;
982
            }
983
            $this->documentMethod = 'alias';
984
            return $q;
985
        }
986
    }
987
988
    /**
989
     * @return string
990
     */
991
    public function getCacheFolder()
992
    {
993
        return "assets/cache/";
994
    }
995
996
    /**
997
     * @param $key
998
     * @return string
999
     */
1000
    public function getHashFile($key)
1001
    {
1002
        return $this->getCacheFolder() . "docid_" . $key . ".pageCache.php";
1003
    }
1004
1005
    /**
1006
     * @param $id
1007
     * @return array|mixed|null|string
1008
     */
1009
    public function makePageCacheKey($id){
0 ignored issues
show
Coding Style introduced by
makePageCacheKey uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1010
        $hash = $id;
1011
        $tmp = null;
1012
        $params = array();
1013
        if(!empty($this->systemCacheKey)){
1014
            $hash = $this->systemCacheKey;
1015
        }else {
1016
            if (!empty($_GET)) {
1017
                // Sort GET parameters so that the order of parameters on the HTTP request don't affect the generated cache ID.
1018
                $params = $_GET;
1019
                ksort($params);
1020
                $hash .= '_'.md5(http_build_query($params));
1021
            }
1022
        }
1023
        $evtOut = $this->invokeEvent("OnMakePageCacheKey", array ("hash" => $hash, "id" => $id, 'params' => $params));
1024
        if (is_array($evtOut) && count($evtOut) > 0){
1025
            $tmp = array_pop($evtOut);
1026
        }
1027
        return empty($tmp) ? $hash : $tmp;
1028
    }
1029
1030
    /**
1031
     * @param $id
1032
     * @param bool $loading
1033
     * @return string
1034
     */
1035
    public function checkCache($id, $loading = false)
1036
    {
1037
        return $this->getDocumentObjectFromCache($id, $loading);
1038
    }
1039
1040
    /**
1041
     * Check the cache for a specific document/resource
1042
     *
1043
     * @param int $id
1044
     * @param bool $loading
1045
     * @return string
1046
     */
1047
    public function getDocumentObjectFromCache($id, $loading = false)
1048
    {
1049
        $key = ($this->config['cache_type'] == 2) ? $this->makePageCacheKey($id) : $id;
1050
        if ($loading) {
1051
            $this->cacheKey = $key;
1052
        }
1053
1054
        $cache_path = $this->getHashFile($key);
1055
1056
        if (!is_file($cache_path)) {
1057
            $this->documentGenerated = 1;
1058
            return '';
1059
        }
1060
        $content = file_get_contents($cache_path, false);
1061
        if (substr($content, 0, 5) === '<?php') {
1062
            $content = substr($content, strpos($content, '?>') + 2);
1063
        } // remove php header
1064
        $a = explode('<!--__MODxCacheSpliter__-->', $content, 2);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $a. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
1065
        if (count($a) == 1) {
1066
            $result = $a[0];
1067
        } // return only document content
1068
        else {
1069
            $docObj = unserialize($a[0]); // rebuild document object
1070
            // check page security
1071
            if ($docObj['privateweb'] && isset ($docObj['__MODxDocGroups__'])) {
1072
                $pass = false;
1073
                $usrGrps = $this->getUserDocGroups();
1074
                $docGrps = explode(',', $docObj['__MODxDocGroups__']);
1075
                // check is user has access to doc groups
1076
                if (is_array($usrGrps)) {
1077
                    foreach ($usrGrps as $k => $v) {
1078
                        if (!in_array($v, $docGrps)) {
1079
                            continue;
1080
                        }
1081
                        $pass = true;
1082
                        break;
1083
                    }
1084
                }
1085
                // diplay error pages if user has no access to cached doc
1086
                if (!$pass) {
1087
                    if ($this->config['unauthorized_page']) {
1088
                        // check if file is not public
1089
                        $rs = $this->getDatabase()->select('count(id)', '[+prefix+]document_groups', "document='{$id}'", '', '1');
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rs. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
1090
                        $total = $this->getDatabase()->getValue($rs);
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se...ment='{$id}'", '', '1') on line 1089 can also be of type boolean; however, EvolutionCMS\Database::getValue() does only seem to accept object<mysqli_result>|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...
1091
                    } else {
1092
                        $total = 0;
1093
                    }
1094
1095
                    if ($total > 0) {
1096
                        $this->sendUnauthorizedPage();
1097
                    } else {
1098
                        $this->sendErrorPage();
1099
                    }
1100
1101
                    exit; // stop here
0 ignored issues
show
Coding Style Compatibility introduced by
The method getDocumentObjectFromCache() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
1102
                }
1103
            }
1104
            // Grab the Scripts
1105
            if (isset($docObj['__MODxSJScripts__'])) {
1106
                $this->sjscripts = $docObj['__MODxSJScripts__'];
1107
            }
1108
            if (isset($docObj['__MODxJScripts__'])) {
1109
                $this->jscripts = $docObj['__MODxJScripts__'];
1110
            }
1111
1112
            // Remove intermediate variables
1113
            unset($docObj['__MODxDocGroups__'], $docObj['__MODxSJScripts__'], $docObj['__MODxJScripts__']);
1114
1115
            $this->documentObject = $docObj;
1116
1117
            $result = $a[1]; // return document content
1118
        }
1119
1120
        $this->documentGenerated = 0;
1121
        // invoke OnLoadWebPageCache  event
1122
        $this->documentContent = $result;
1123
        $this->invokeEvent('OnLoadWebPageCache');
1124
        return $result;
1125
    }
1126
1127
    /**
1128
     * Final processing and output of the document/resource.
1129
     *
1130
     * - runs uncached snippets
1131
     * - add javascript to <head>
1132
     * - removes unused placeholders
1133
     * - converts URL tags [~...~] to URLs
1134
     *
1135
     * @param boolean $noEvent Default: false
1136
     */
1137
    public function outputContent($noEvent = false)
0 ignored issues
show
Coding Style introduced by
outputContent uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1138
    {
1139
        $this->documentOutput = $this->documentContent;
1140
1141
        if ($this->documentGenerated == 1 && $this->documentObject['cacheable'] == 1 && $this->documentObject['type'] == 'document' && $this->documentObject['published'] == 1) {
1142
            if (!empty($this->sjscripts)) {
1143
                $this->documentObject['__MODxSJScripts__'] = $this->sjscripts;
1144
            }
1145
            if (!empty($this->jscripts)) {
1146
                $this->documentObject['__MODxJScripts__'] = $this->jscripts;
1147
            }
1148
        }
1149
1150
        // check for non-cached snippet output
1151
        if (strpos($this->documentOutput, '[!') > -1) {
1152
            $this->recentUpdate = $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'];
1153
1154
            $this->documentOutput = str_replace('[!', '[[', $this->documentOutput);
1155
            $this->documentOutput = str_replace('!]', ']]', $this->documentOutput);
1156
1157
            // Parse document source
1158
            $this->documentOutput = $this->parseDocumentSource($this->documentOutput);
1159
        }
1160
1161
        // Moved from prepareResponse() by sirlancelot
1162
        // Insert Startup jscripts & CSS scripts into template - template must have a <head> tag
1163
        if ($js = $this->getRegisteredClientStartupScripts()) {
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $js. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
1164
            // change to just before closing </head>
1165
            // $this->documentContent = preg_replace("/(<head[^>]*>)/i", "\\1\n".$js, $this->documentContent);
0 ignored issues
show
Unused Code Comprehensibility introduced by
55% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
1166
            $this->documentOutput = preg_replace("/(<\/head>)/i", $js . "\n\\1", $this->documentOutput);
1167
        }
1168
1169
        // Insert jscripts & html block into template - template must have a </body> tag
1170
        if ($js = $this->getRegisteredClientScripts()) {
1171
            $this->documentOutput = preg_replace("/(<\/body>)/i", $js . "\n\\1", $this->documentOutput);
1172
        }
1173
        // End fix by sirlancelot
1174
1175
        $this->documentOutput = $this->cleanUpMODXTags($this->documentOutput);
1176
1177
        $this->documentOutput = $this->rewriteUrls($this->documentOutput);
1178
1179
        // send out content-type and content-disposition headers
1180
        if (IN_PARSER_MODE == "true") {
1181
            $type = !empty ($this->contentTypes[$this->documentIdentifier]) ? $this->contentTypes[$this->documentIdentifier] : "text/html";
1182
            header('Content-Type: ' . $type . '; charset=' . $this->config['modx_charset']);
1183
            //            if (($this->documentIdentifier == $this->config['error_page']) || $redirect_error)
0 ignored issues
show
Unused Code Comprehensibility introduced by
61% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
1184
            //                header('HTTP/1.0 404 Not Found');
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
1185
            if (!$this->checkPreview() && $this->documentObject['content_dispo'] == 1) {
1186
                if ($this->documentObject['alias']) {
1187
                    $name = $this->documentObject['alias'];
1188
                } else {
1189
                    // strip title of special characters
1190
                    $name = $this->documentObject['pagetitle'];
1191
                    $name = strip_tags($name);
1192
                    $name = $this->cleanUpMODXTags($name);
1193
                    $name = strtolower($name);
1194
                    $name = preg_replace('/&.+?;/', '', $name); // kill entities
1195
                    $name = preg_replace('/[^\.%a-z0-9 _-]/', '', $name);
1196
                    $name = preg_replace('/\s+/', '-', $name);
1197
                    $name = preg_replace('|-+|', '-', $name);
1198
                    $name = trim($name, '-');
1199
                }
1200
                $header = 'Content-Disposition: attachment; filename=' . $name;
1201
                header($header);
1202
            }
1203
        }
1204
        $this->setConditional();
1205
1206
        $stats = $this->getTimerStats($this->tstart);
1207
1208
        $out =& $this->documentOutput;
1209
        $out = str_replace("[^q^]", $stats['queries'], $out);
1210
        $out = str_replace("[^qt^]", $stats['queryTime'], $out);
1211
        $out = str_replace("[^p^]", $stats['phpTime'], $out);
1212
        $out = str_replace("[^t^]", $stats['totalTime'], $out);
1213
        $out = str_replace("[^s^]", $stats['source'], $out);
1214
        $out = str_replace("[^m^]", $stats['phpMemory'], $out);
1215
        //$this->documentOutput= $out;
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
1216
1217
        // invoke OnWebPagePrerender event
1218
        if (!$noEvent) {
1219
            $evtOut = $this->invokeEvent('OnWebPagePrerender', array('documentOutput' => $this->documentOutput));
1220
            if (is_array($evtOut) && count($evtOut) > 0) {
1221
                $this->documentOutput = $evtOut['0'];
1222
            }
1223
        }
1224
1225
        $this->documentOutput = $this->removeSanitizeSeed($this->documentOutput);
1226
1227
        if (strpos($this->documentOutput, '\{') !== false) {
1228
            $this->documentOutput = $this->RecoveryEscapedTags($this->documentOutput);
1229
        } elseif (strpos($this->documentOutput, '\[') !== false) {
1230
            $this->documentOutput = $this->RecoveryEscapedTags($this->documentOutput);
1231
        }
1232
1233
        echo $this->documentOutput;
1234
1235
        if ($this->dumpSQL) {
1236
            echo $this->queryCode;
1237
        }
1238
        if ($this->dumpSnippets) {
1239
            $sc = "";
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $sc. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
1240
            $tt = 0;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $tt. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
1241
            foreach ($this->snippetsTime as $s => $v) {
1242
                $t = $v['time'];
1243
                $sname = $v['sname'];
1244
                $sc .= sprintf("%s. %s (%s)<br>", $s, $sname, sprintf("%2.2f ms", $t)); // currentSnippet
1245
                $tt += $t;
1246
            }
1247
            echo "<fieldset><legend><b>Snippets</b> (" . count($this->snippetsTime) . " / " . sprintf("%2.2f ms", $tt) . ")</legend>{$sc}</fieldset><br />";
1248
            echo $this->snippetsCode;
1249
        }
1250
        if ($this->dumpPlugins) {
1251
            $ps = "";
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ps. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
1252
            $tt = 0;
1253
            foreach ($this->pluginsTime as $s => $t) {
1254
                $ps .= "$s (" . sprintf("%2.2f ms", $t * 1000) . ")<br>";
1255
                $tt += $t;
1256
            }
1257
            echo "<fieldset><legend><b>Plugins</b> (" . count($this->pluginsTime) . " / " . sprintf("%2.2f ms", $tt * 1000) . ")</legend>{$ps}</fieldset><br />";
1258
            echo $this->pluginsCode;
1259
        }
1260
1261
        ob_end_flush();
1262
    }
1263
1264
    /**
1265
     * @param $contents
1266
     * @return mixed
1267
     */
1268
    public function RecoveryEscapedTags($contents)
0 ignored issues
show
Coding Style introduced by
This method is not in camel caps format.

This check looks for method names that are not written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection seeker becomes databaseConnectionSeeker.

Loading history...
1269
    {
1270
        list($sTags, $rTags) = $this->getTagsForEscape();
1271
        return str_replace($rTags, $sTags, $contents);
1272
    }
1273
1274
    /**
1275
     * @param string $tags
1276
     * @return array[]
1277
     */
1278
    public function getTagsForEscape($tags = '{{,}},[[,]],[!,!],[*,*],[(,)],[+,+],[~,~],[^,^]')
1279
    {
1280
        $srcTags = explode(',', $tags);
1281
        $repTags = array();
1282
        foreach ($srcTags as $tag) {
1283
            $repTags[] = '\\' . $tag[0] . '\\' . $tag[1];
1284
        }
1285
        return array($srcTags, $repTags);
1286
    }
1287
1288
    /**
1289
     * @param $tstart
1290
     * @return array
1291
     */
1292
    public function getTimerStats($tstart)
1293
    {
1294
        $stats = array();
1295
1296
        $stats['totalTime'] = ($this->getMicroTime() - $tstart);
1297
        $stats['queryTime'] = $this->queryTime;
1298
        $stats['phpTime'] = $stats['totalTime'] - $stats['queryTime'];
1299
1300
        $stats['queryTime'] = sprintf("%2.4f s", $stats['queryTime']);
1301
        $stats['totalTime'] = sprintf("%2.4f s", $stats['totalTime']);
1302
        $stats['phpTime'] = sprintf("%2.4f s", $stats['phpTime']);
1303
        $stats['source'] = $this->documentGenerated == 1 ? "database" : "cache";
1304
        $stats['queries'] = isset ($this->executedQueries) ? $this->executedQueries : 0;
1305
        $stats['phpMemory'] = (memory_get_peak_usage(true) / 1024 / 1024) . " mb";
1306
1307
        return $stats;
1308
    }
1309
1310
    public function setConditional()
0 ignored issues
show
Coding Style introduced by
setConditional uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
setConditional uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1311
    {
1312
        if (!empty($_POST) || (defined('MODX_API_MODE') && MODX_API_MODE) || $this->getLoginUserID('mgr') || !$this->useConditional || empty($this->recentUpdate)) {
1313
            return;
1314
        }
1315
        $last_modified = gmdate('D, d M Y H:i:s T', $this->recentUpdate);
1316
        $etag = md5($last_modified);
1317
        $HTTP_IF_MODIFIED_SINCE = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? $_SERVER['HTTP_IF_MODIFIED_SINCE'] : false;
1318
        $HTTP_IF_NONE_MATCH = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? $_SERVER['HTTP_IF_NONE_MATCH'] : false;
1319
        header('Pragma: no-cache');
1320
1321
        if ($HTTP_IF_MODIFIED_SINCE == $last_modified || strpos($HTTP_IF_NONE_MATCH, $etag) !== false) {
1322
            header('HTTP/1.1 304 Not Modified');
1323
            header('Content-Length: 0');
1324
            exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method setConditional() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
1325
        } else {
1326
            header("Last-Modified: {$last_modified}");
1327
            header("ETag: '{$etag}'");
1328
        }
1329
    }
1330
1331
    /**
1332
     * Checks the publish state of page
1333
     */
1334
    public function updatePubStatus()
0 ignored issues
show
Coding Style introduced by
updatePubStatus uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1335
    {
1336
        $cacheRefreshTime = 0;
1337
        $recent_update = 0;
1338
        @include(MODX_BASE_PATH . $this->getCacheFolder() . 'sitePublishing.idx.php');
1339
        $this->recentUpdate = $recent_update;
1340
1341
        $timeNow = $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'];
1342
        if ($timeNow < $cacheRefreshTime || $cacheRefreshTime == 0) {
1343
            return;
1344
        }
1345
1346
        // now, check for documents that need publishing
1347
        $field = array('published' => 1, 'publishedon' => $timeNow);
1348
        $where = "pub_date <= {$timeNow} AND pub_date!=0 AND published=0";
1349
        $result_pub = $this->getDatabase()->select( 'id', '[+prefix+]site_content',  $where);
1350
        $this->getDatabase()->update($field, '[+prefix+]site_content', $where);
1351 View Code Duplication
        if ($this->getDatabase()->getRecordCount($result_pub) >= 1) { //Event unPublished doc
0 ignored issues
show
Bug introduced by
It seems like $result_pub defined by $this->getDatabase()->se...]site_content', $where) on line 1349 can also be of type boolean; however, EvolutionCMS\Database::getRecordCount() does only seem to accept object<mysqli_result>, 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...
1352
            while ($row_pub = $this->getDatabase()->getRow($result_pub)) {
0 ignored issues
show
Bug introduced by
It seems like $result_pub defined by $this->getDatabase()->se...]site_content', $where) on line 1349 can also be of type boolean; however, EvolutionCMS\Database::getRow() does only seem to accept object<mysqli_result>, 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...
1353
                $this->invokeEvent("OnDocUnPublished", array(
1354
                    "docid" => $row_pub['id']
1355
                ));
1356
            }
1357
        }
1358
1359
        // now, check for documents that need un-publishing
1360
        $field = array('published' => 0, 'publishedon' => 0);
1361
        $where = "unpub_date <= {$timeNow} AND unpub_date!=0 AND published=1";
1362
        $result_unpub = $this->getDatabase()->select( 'id', '[+prefix+]site_content',  $where);
1363
        $this->getDatabase()->update($field, '[+prefix+]site_content', $where);
1364 View Code Duplication
        if ($this->getDatabase()->getRecordCount($result_unpub) >= 1) { //Event unPublished doc
0 ignored issues
show
Bug introduced by
It seems like $result_unpub defined by $this->getDatabase()->se...]site_content', $where) on line 1362 can also be of type boolean; however, EvolutionCMS\Database::getRecordCount() does only seem to accept object<mysqli_result>, 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...
1365
            while ($row_unpub = $this->getDatabase()->getRow($result_unpub)) {
0 ignored issues
show
Bug introduced by
It seems like $result_unpub defined by $this->getDatabase()->se...]site_content', $where) on line 1362 can also be of type boolean; however, EvolutionCMS\Database::getRow() does only seem to accept object<mysqli_result>, 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...
1366
                $this->invokeEvent("OnDocUnPublished", array(
1367
                    "docid" => $row_unpub['id']
1368
                ));
1369
            }
1370
        }
1371
1372
        $this->recentUpdate = $timeNow;
1373
1374
        // clear the cache
1375
        $this->clearCache('full');
1376
    }
1377
1378
    public function checkPublishStatus()
1379
    {
1380
        $this->updatePubStatus();
1381
    }
1382
1383
    /**
1384
     * Final jobs.
1385
     *
1386
     * - cache page
1387
     */
1388
    public function postProcess()
1389
    {
1390
        // if the current document was generated, cache it!
1391
        $cacheable = ($this->config['enable_cache'] && $this->documentObject['cacheable']) ? 1 : 0;
1392
        if ($cacheable && $this->documentGenerated && $this->documentObject['type'] == 'document' && $this->documentObject['published']) {
1393
            // invoke OnBeforeSaveWebPageCache event
1394
            $this->invokeEvent("OnBeforeSaveWebPageCache");
1395
1396
            if (!empty($this->cacheKey) && is_scalar($this->cacheKey)) {
1397
                // get and store document groups inside document object. Document groups will be used to check security on cache pages
1398
                $where = "document='{$this->documentIdentifier}'";
1399
                $rs = $this->getDatabase()->select('document_group', '[+prefix+]document_groups', $where);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rs. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
1400
                $docGroups = $this->getDatabase()->getColumn('document_group', $rs);
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se...cument_groups', $where) on line 1399 can also be of type boolean; however, EvolutionCMS\Database::getColumn() does only seem to accept object<mysqli_result>|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...
1401
1402
                // Attach Document Groups and Scripts
1403
                if (is_array($docGroups)) {
1404
                    $this->documentObject['__MODxDocGroups__'] = implode(",", $docGroups);
1405
                }
1406
1407
                $docObjSerial = serialize($this->documentObject);
1408
                $cacheContent = $docObjSerial . "<!--__MODxCacheSpliter__-->" . $this->documentContent;
1409
                $page_cache_path = MODX_BASE_PATH . $this->getHashFile($this->cacheKey);
1410
                file_put_contents($page_cache_path, "<?php die('Unauthorized access.'); ?>$cacheContent");
1411
            }
1412
        }
1413
1414
        // Useful for example to external page counters/stats packages
1415
        $this->invokeEvent('OnWebPageComplete');
1416
1417
        // end post processing
1418
    }
1419
1420
    /**
1421
     * @param $content
1422
     * @param string $left
1423
     * @param string $right
1424
     * @return array
1425
     */
1426
    public function getTagsFromContent($content, $left = '[+', $right = '+]')
1427
    {
1428
        $_ = $this->_getTagsFromContent($content, $left, $right);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $_. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
1429
        if (empty($_)) {
1430
            return array();
1431
        }
1432
        foreach ($_ as $v) {
1433
            $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...
1434
            $tags[1][] = $v;
1435
        }
1436
        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...
1437
    }
1438
1439
    /**
1440
     * @param $content
1441
     * @param string $left
1442
     * @param string $right
1443
     * @return array
1444
     */
1445
    public function _getTagsFromContent($content, $left = '[+', $right = '+]')
1446
    {
1447
        if (strpos($content, $left) === false) {
1448
            return array();
1449
        }
1450
        $spacer = md5('<<<EVO>>>');
1451
        if($left==='{{' && strpos($content,';}}')!==false)  $content = str_replace(';}}', sprintf(';}%s}',   $spacer),$content);
1452
        if($left==='{{' && strpos($content,'{{}}')!==false) $content = str_replace('{{}}',sprintf('{%$1s{}%$1s}',$spacer),$content);
1453
        if($left==='[[' && strpos($content,']]]]')!==false) $content = str_replace(']]]]',sprintf(']]%s]]',  $spacer),$content);
1454
        if($left==='[[' && strpos($content,']]]')!==false)  $content = str_replace(']]]', sprintf(']%s]]',   $spacer),$content);
1455
1456
        $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...
1457
        $pos[']]>'] = strpos($content, ']]>');
1458
1459
        if ($pos['<![CDATA['] !== false && $pos[']]>'] !== false) {
1460
            $content = substr($content, 0, $pos['<![CDATA[']) . substr($content, $pos[']]>'] + 3);
1461
        }
1462
1463
        $lp = explode($left, $content);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $lp. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
1464
        $piece = array();
1465
        foreach ($lp as $lc => $lv) {
1466
            if ($lc !== 0) {
1467
                $piece[] = $left;
1468
            }
1469
            if (strpos($lv, $right) === false) {
1470
                $piece[] = $lv;
1471
            } else {
1472
                $rp = explode($right, $lv);
1473
                foreach ($rp as $rc => $rv) {
1474
                    if ($rc !== 0) {
1475
                        $piece[] = $right;
1476
                    }
1477
                    $piece[] = $rv;
1478
                }
1479
            }
1480
        }
1481
        $lc = 0;
1482
        $rc = 0;
1483
        $fetch = '';
1484
        $tags = array();
1485
        foreach ($piece as $v) {
1486
            if ($v === $left) {
1487
                if (0 < $lc) {
1488
                    $fetch .= $left;
1489
                }
1490
                $lc++;
1491
            } elseif ($v === $right) {
1492
                if ($lc === 0) {
1493
                    continue;
1494
                }
1495
                $rc++;
1496
                if ($lc === $rc) {
1497
                    // #1200 Enable modifiers in Wayfinder - add nested placeholders to $tags like for $fetch = "phx:input=`[+wf.linktext+]`:test"
1498
                    if (strpos($fetch, $left) !== false) {
1499
                        $nested = $this->_getTagsFromContent($fetch, $left, $right);
1500
                        foreach ($nested as $tag) {
1501
                            if (!in_array($tag, $tags)) {
1502
                                $tags[] = $tag;
1503
                            }
1504
                        }
1505
                    }
1506
1507
                    if (!in_array($fetch, $tags)) {  // Avoid double Matches
1508
                        $tags[] = $fetch; // Fetch
1509
                    };
1510
                    $fetch = ''; // and reset
1511
                    $lc = 0;
1512
                    $rc = 0;
1513
                } else {
1514
                    $fetch .= $right;
1515
                }
1516
            } else {
1517
                if (0 < $lc) {
1518
                    $fetch .= $v;
1519
                } else {
1520
                    continue;
1521
                }
1522
            }
1523
        }
1524
        foreach($tags as $i=>$tag) {
1525
            if(strpos($tag,$spacer)!==false) $tags[$i] = str_replace($spacer, '', $tag);
1526
        }
1527
        return $tags;
1528
    }
1529
1530
    /**
1531
     * Merge content fields and TVs
1532
     *
1533
     * @param $content
1534
     * @param bool $ph
1535
     * @return string
1536
     * @internal param string $template
1537
     */
1538
    public function mergeDocumentContent($content, $ph = false)
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ph. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
Coding Style introduced by
mergeDocumentContent uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1539
    {
1540 View Code Duplication
        if ($this->config['enable_at_syntax']) {
1541
            if (stripos($content, '<@LITERAL>') !== false) {
1542
                $content = $this->escapeLiteralTagsContent($content);
1543
            }
1544
        }
1545
        if (strpos($content, '[*') === false) {
1546
            return $content;
1547
        }
1548
        if (!isset($this->documentIdentifier)) {
1549
            return $content;
1550
        }
1551
        if (!isset($this->documentObject) || empty($this->documentObject)) {
1552
            return $content;
1553
        }
1554
1555
        if (!$ph) {
1556
            $ph = $this->documentObject;
1557
        }
1558
1559
        $matches = $this->getTagsFromContent($content, '[*', '*]');
1560
        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...
1561
            return $content;
1562
        }
1563
1564
        foreach ($matches[1] as $i => $key) {
1565
            if(strpos($key,'[+')!==false) continue; // Allow chunk {{chunk?&param=`xxx`}} with [*tv_name_[+param+]*] as content
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
1566
            if (substr($key, 0, 1) == '#') {
1567
                $key = substr($key, 1);
1568
            } // remove # for QuickEdit format
1569
1570
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1571
            if (strpos($key, '@') !== false) {
1572
                list($key, $context) = explode('@', $key, 2);
1573
            } else {
1574
                $context = false;
1575
            }
1576
1577
            // 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*]`*]
0 ignored issues
show
Unused Code Comprehensibility introduced by
77% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
1578
            if ($context) {
1579
                $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...
1580
            } else {
1581
                $value = isset($ph[$key]) ? $ph[$key] : '';
1582
            }
1583
1584 View Code Duplication
            if (is_array($value)) {
1585
                $value = getTVDisplayFormat($value[0], $value[1], $value[2], $value[3], $value[4]);
1586
            }
1587
1588
            $s = &$matches[0][$i];
1589
            if ($modifiers !== false) {
1590
                $value = $this->applyFilter($value, $modifiers, $key);
1591
            }
1592
1593 View Code Duplication
            if (strpos($content, $s) !== false) {
1594
                $content = str_replace($s, $value, $content);
1595
            } elseif($this->debug) {
1596
                $this->addLog('mergeDocumentContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1597
            }
1598
        }
1599
1600
        return $content;
1601
    }
1602
1603
    /**
1604
     * @param $key
1605
     * @param bool|int $parent
1606
     * @return bool|mixed|string
1607
     */
1608
    public function _contextValue($key, $parent = false)
1609
    {
1610
        if (preg_match('/@\d+\/u/', $key)) {
1611
            $key = str_replace(array('@', '/u'), array('@u(', ')'), $key);
1612
        }
1613
        list($key, $str) = explode('@', $key, 2);
1614
1615
        if (strpos($str, '(')) {
1616
            list($context, $option) = explode('(', $str, 2);
1617
        } else {
1618
            list($context, $option) = array($str, false);
1619
        }
1620
1621
        if ($option) {
1622
            $option = trim($option, ')(\'"`');
1623
        }
1624
1625
        switch (strtolower($context)) {
1626
            case 'site_start':
1627
                $docid = $this->config['site_start'];
1628
                break;
1629
            case 'parent':
1630
            case 'p':
1631
                $docid = $parent;
1632
                if ($docid == 0) {
1633
                    $docid = $this->config['site_start'];
1634
                }
1635
                break;
1636
            case 'ultimateparent':
1637
            case 'uparent':
1638
            case 'up':
1639
            case 'u':
1640 View Code Duplication
                if (strpos($str, '(') !== false) {
1641
                    $top = substr($str, strpos($str, '('));
1642
                    $top = trim($top, '()"\'');
1643
                } else {
1644
                    $top = 0;
1645
                }
1646
                $docid = $this->getUltimateParentId($this->documentIdentifier, $top);
1647
                break;
1648
            case 'alias':
1649
                $str = substr($str, strpos($str, '('));
1650
                $str = trim($str, '()"\'');
1651
                $docid = $this->getIdFromAlias($str);
1652
                break;
1653 View Code Duplication
            case 'prev':
1654
                if (!$option) {
1655
                    $option = 'menuindex,ASC';
1656
                } elseif (strpos($option, ',') === false) {
1657
                    $option .= ',ASC';
1658
                }
1659
                list($by, $dir) = explode(',', $option, 2);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $by. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
1660
                $children = $this->getActiveChildren($parent, $by, $dir);
1661
                $find = false;
1662
                $prev = false;
1663
                foreach ($children as $row) {
1664
                    if ($row['id'] == $this->documentIdentifier) {
1665
                        $find = true;
1666
                        break;
1667
                    }
1668
                    $prev = $row;
1669
                }
1670
                if ($find) {
1671
                    if (isset($prev[$key])) {
1672
                        return $prev[$key];
1673
                    } else {
1674
                        $docid = $prev['id'];
1675
                    }
1676
                } else {
1677
                    $docid = '';
1678
                }
1679
                break;
1680 View Code Duplication
            case 'next':
1681
                if (!$option) {
1682
                    $option = 'menuindex,ASC';
1683
                } elseif (strpos($option, ',') === false) {
1684
                    $option .= ',ASC';
1685
                }
1686
                list($by, $dir) = explode(',', $option, 2);
1687
                $children = $this->getActiveChildren($parent, $by, $dir);
1688
                $find = false;
1689
                $next = false;
1690
                foreach ($children as $row) {
1691
                    if ($find) {
1692
                        $next = $row;
1693
                        break;
1694
                    }
1695
                    if ($row['id'] == $this->documentIdentifier) {
1696
                        $find = true;
1697
                    }
1698
                }
1699
                if ($find) {
1700
                    if (isset($next[$key])) {
1701
                        return $next[$key];
1702
                    } else {
1703
                        $docid = $next['id'];
1704
                    }
1705
                } else {
1706
                    $docid = '';
1707
                }
1708
                break;
1709
            default:
1710
                $docid = $str;
1711
        }
1712
        if (preg_match('@^[1-9][0-9]*$@', $docid)) {
1713
            $value = $this->getField($key, $docid);
1714
        } else {
1715
            $value = '';
1716
        }
1717
        return $value;
1718
    }
1719
1720
    /**
1721
     * Merge system settings
1722
     *
1723
     * @param $content
1724
     * @param bool|array $ph
1725
     * @return string
1726
     * @internal param string $template
1727
     */
1728
    public function mergeSettingsContent($content, $ph = false)
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ph. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
Coding Style introduced by
mergeSettingsContent uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1729
    {
1730 View Code Duplication
        if ($this->config['enable_at_syntax']) {
1731
            if (stripos($content, '<@LITERAL>') !== false) {
1732
                $content = $this->escapeLiteralTagsContent($content);
1733
            }
1734
        }
1735
        if (strpos($content, '[(') === false) {
1736
            return $content;
1737
        }
1738
1739
        if (empty($ph)) {
1740
            $ph = $this->config;
1741
        }
1742
1743
        $matches = $this->getTagsFromContent($content, '[(', ')]');
1744
        if (empty($matches)) {
1745
            return $content;
1746
        }
1747
1748
        foreach ($matches[1] as $i => $key) {
1749
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1750
1751
            if (isset($ph[$key])) {
1752
                $value = $ph[$key];
1753
            } else {
1754
                continue;
1755
            }
1756
1757
            if ($modifiers !== false) {
1758
                $value = $this->applyFilter($value, $modifiers, $key);
1759
            }
1760
            $s = &$matches[0][$i];
1761 View Code Duplication
            if (strpos($content, $s) !== false) {
1762
                $content = str_replace($s, $value, $content);
1763
            } elseif($this->debug) {
1764
                $this->addLog('mergeSettingsContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1765
            }
1766
        }
1767
        return $content;
1768
    }
1769
1770
    /**
1771
     * Merge chunks
1772
     *
1773
     * @param string $content
1774
     * @param bool|array $ph
1775
     * @return string
1776
     */
1777
    public function mergeChunkContent($content, $ph = false)
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ph. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
Coding Style introduced by
mergeChunkContent uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1778
    {
1779
        if ($this->config['enable_at_syntax']) {
1780
            if (strpos($content, '{{ ') !== false) {
1781
                $content = str_replace(array('{{ ', ' }}'), array('\{\{ ', ' \}\}'), $content);
1782
            }
1783
            if (stripos($content, '<@LITERAL>') !== false) {
1784
                $content = $this->escapeLiteralTagsContent($content);
1785
            }
1786
        }
1787
        if (strpos($content, '{{') === false) {
1788
            return $content;
1789
        }
1790
1791
        if (empty($ph)) {
1792
            $ph = $this->chunkCache;
1793
        }
1794
1795
        $matches = $this->getTagsFromContent($content, '{{', '}}');
1796
        if (empty($matches)) {
1797
            return $content;
1798
        }
1799
1800
        foreach ($matches[1] as $i => $key) {
1801
            $snip_call = $this->_split_snip_call($key);
1802
            $key = $snip_call['name'];
1803
            $params = $this->getParamsFromString($snip_call['params']);
1804
1805
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1806
1807
            if (!isset($ph[$key])) {
1808
                $ph[$key] = $this->getChunk($key);
1809
            }
1810
            $value = $ph[$key];
1811
1812
            if (is_null($value)) {
1813
                continue;
1814
            }
1815
1816
            $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 1803 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...
1817
            $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 1803 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...
1818
            if ($this->config['enable_at_syntax']) {
1819
                $value = $this->mergeConditionalTagsContent($value);
1820
            }
1821
            $value = $this->mergeDocumentContent($value);
1822
            $value = $this->mergeSettingsContent($value);
1823
            $value = $this->mergeChunkContent($value);
1824
1825
            if ($modifiers !== false) {
1826
                $value = $this->applyFilter($value, $modifiers, $key);
1827
            }
1828
1829
            $s = &$matches[0][$i];
1830 View Code Duplication
            if (strpos($content, $s) !== false) {
1831
                $content = str_replace($s, $value, $content);
1832
            } elseif($this->debug) {
1833
                $this->addLog('mergeChunkContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1834
            }
1835
        }
1836
        return $content;
1837
    }
1838
1839
    /**
1840
     * Merge placeholder values
1841
     *
1842
     * @param string $content
1843
     * @param bool|array $ph
1844
     * @return string
1845
     */
1846
    public function mergePlaceholderContent($content, $ph = false)
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ph. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
Coding Style introduced by
mergePlaceholderContent uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1847
    {
1848
1849 View Code Duplication
        if ($this->config['enable_at_syntax']) {
1850
            if (stripos($content, '<@LITERAL>') !== false) {
1851
                $content = $this->escapeLiteralTagsContent($content);
1852
            }
1853
        }
1854
        if (strpos($content, '[+') === false) {
1855
            return $content;
1856
        }
1857
1858
        if (empty($ph)) {
1859
            $ph = $this->placeholders;
1860
        }
1861
1862
        if ($this->config['enable_at_syntax']) {
1863
            $content = $this->mergeConditionalTagsContent($content);
1864
        }
1865
1866
        $content = $this->mergeDocumentContent($content);
1867
        $content = $this->mergeSettingsContent($content);
1868
        $matches = $this->getTagsFromContent($content, '[+', '+]');
1869
        if (empty($matches)) {
1870
            return $content;
1871
        }
1872
        foreach ($matches[1] as $i => $key) {
1873
1874
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1875
1876
            if (isset($ph[$key])) {
1877
                $value = $ph[$key];
1878
            } elseif ($key === 'phx') {
1879
                $value = '';
1880
            } else {
1881
                continue;
1882
            }
1883
1884
            if ($modifiers !== false) {
1885
                $modifiers = $this->mergePlaceholderContent($modifiers);
1886
                $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...
1887
            }
1888
            $s = &$matches[0][$i];
1889 View Code Duplication
            if (strpos($content, $s) !== false) {
1890
                $content = str_replace($s, $value, $content);
1891
            } elseif($this->debug) {
1892
                $this->addLog('mergePlaceholderContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1893
            }
1894
        }
1895
        return $content;
1896
    }
1897
1898
    /**
1899
     * @param $content
1900
     * @param string $iftag
1901
     * @param string $elseiftag
1902
     * @param string $elsetag
1903
     * @param string $endiftag
1904
     * @return mixed|string
1905
     */
1906
    public function mergeConditionalTagsContent($content, $iftag = '<@IF:', $elseiftag = '<@ELSEIF:', $elsetag = '<@ELSE>', $endiftag = '<@ENDIF>')
0 ignored issues
show
Coding Style introduced by
mergeConditionalTagsContent uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1907
    {
1908
        if (strpos($content, '@IF') !== false) {
1909
            $content = $this->_prepareCTag($content, $iftag, $elseiftag, $elsetag, $endiftag);
1910
        }
1911
1912
        if (strpos($content, $iftag) === false) {
1913
            return $content;
1914
        }
1915
1916
        $sp = '#' . md5('ConditionalTags' . $_SERVER['REQUEST_TIME']) . '#';
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $sp. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
1917
        $content = str_replace(array('<?php', '<?=', '<?', '?>'), array("{$sp}b", "{$sp}p", "{$sp}s", "{$sp}e"), $content);
1918
1919
        $pieces = explode('<@IF:', $content);
1920 View Code Duplication
        foreach ($pieces as $i => $split) {
1921
            if ($i === 0) {
1922
                $content = $split;
1923
                continue;
1924
            }
1925
            list($cmd, $text) = explode('>', $split, 2);
1926
            $cmd = str_replace("'", "\'", $cmd);
1927
            $content .= "<?php if(\$this->_parseCTagCMD('" . $cmd . "')): ?>";
1928
            $content .= $text;
1929
        }
1930
        $pieces = explode('<@ELSEIF:', $content);
1931 View Code Duplication
        foreach ($pieces as $i => $split) {
1932
            if ($i === 0) {
1933
                $content = $split;
1934
                continue;
1935
            }
1936
            list($cmd, $text) = explode('>', $split, 2);
1937
            $cmd = str_replace("'", "\'", $cmd);
1938
            $content .= "<?php elseif(\$this->_parseCTagCMD('" . $cmd . "')): ?>";
1939
            $content .= $text;
1940
        }
1941
1942
        $content = str_replace(array('<@ELSE>', '<@ENDIF>'), array('<?php else:?>', '<?php endif;?>'), $content);
1943
        ob_start();
1944
        $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...
Coding Style introduced by
It is generally not recommended to use eval unless absolutely required.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
1945
        $content = ob_get_clean();
1946
        $content = str_replace(array("{$sp}b", "{$sp}p", "{$sp}s", "{$sp}e"), array('<?php', '<?=', '<?', '?>'), $content);
1947
1948
        return $content;
1949
    }
1950
1951
    /**
1952
     * @param $content
1953
     * @param string $iftag
1954
     * @param string $elseiftag
1955
     * @param string $elsetag
1956
     * @param string $endiftag
1957
     * @return mixed
1958
     */
1959
    private function _prepareCTag($content, $iftag = '<@IF:', $elseiftag = '<@ELSEIF:', $elsetag = '<@ELSE>', $endiftag = '<@ENDIF>')
1960
    {
1961
        if (strpos($content, '<!--@IF ') !== false) {
1962
            $content = str_replace('<!--@IF ', $iftag, $content);
1963
        } // for jp
1964
        if (strpos($content, '<!--@IF:') !== false) {
1965
            $content = str_replace('<!--@IF:', $iftag, $content);
1966
        }
1967
        if (strpos($content, $iftag) === false) {
1968
            return $content;
1969
        }
1970
        if (strpos($content, '<!--@ELSEIF:') !== false) {
1971
            $content = str_replace('<!--@ELSEIF:', $elseiftag, $content);
1972
        } // for jp
1973
        if (strpos($content, '<!--@ELSE-->') !== false) {
1974
            $content = str_replace('<!--@ELSE-->', $elsetag, $content);
1975
        }  // for jp
1976
        if (strpos($content, '<!--@ENDIF-->') !== false) {
1977
            $content = str_replace('<!--@ENDIF-->', $endiftag, $content);
1978
        }    // for jp
1979
        if (strpos($content, '<@ENDIF-->') !== false) {
1980
            $content = str_replace('<@ENDIF-->', $endiftag, $content);
1981
        }
1982
        $tags = array($iftag, $elseiftag, $elsetag, $endiftag);
1983
        $content = str_ireplace($tags, $tags, $content); // Change to capital letters
1984
        return $content;
1985
    }
1986
1987
    /**
1988
     * @param $cmd
1989
     * @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...
1990
     */
1991
    private function _parseCTagCMD($cmd)
1992
    {
1993
        $cmd = trim($cmd);
1994
        $reverse = substr($cmd, 0, 1) === '!' ? true : false;
1995
        if ($reverse) {
1996
            $cmd = ltrim($cmd, '!');
1997
        }
1998
        if (strpos($cmd, '[!') !== false) {
1999
            $cmd = str_replace(array('[!', '!]'), array('[[', ']]'), $cmd);
2000
        }
2001
        $safe = 0;
2002
        while ($safe < 20) {
2003
            $bt = md5($cmd);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $bt. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
2004
            if (strpos($cmd, '[*') !== false) {
2005
                $cmd = $this->mergeDocumentContent($cmd);
2006
            }
2007
            if (strpos($cmd, '[(') !== false) {
2008
                $cmd = $this->mergeSettingsContent($cmd);
2009
            }
2010
            if (strpos($cmd, '{{') !== false) {
2011
                $cmd = $this->mergeChunkContent($cmd);
2012
            }
2013
            if (strpos($cmd, '[[') !== false) {
2014
                $cmd = $this->evalSnippets($cmd);
2015
            }
2016
            if (strpos($cmd, '[+') !== false && strpos($cmd, '[[') === false) {
2017
                $cmd = $this->mergePlaceholderContent($cmd);
2018
            }
2019
            if ($bt === md5($cmd)) {
2020
                break;
2021
            }
2022
            $safe++;
2023
        }
2024
        $cmd = ltrim($cmd);
2025
        $cmd = rtrim($cmd, '-');
2026
        $cmd = str_ireplace(array(' and ', ' or '), array('&&', '||'), $cmd);
2027
2028
        if (!preg_match('@^[0-9]*$@', $cmd) && preg_match('@^[0-9<= \-\+\*/\(\)%!&|]*$@', $cmd)) {
2029
            $cmd = eval("return {$cmd};");
0 ignored issues
show
Coding Style introduced by
It is generally not recommended to use eval unless absolutely required.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
2030
        } else {
2031
            $_ = explode(',', '[*,[(,{{,[[,[!,[+');
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $_. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
2032
            foreach ($_ as $left) {
2033
                if (strpos($cmd, $left) !== false) {
2034
                    $cmd = 0;
2035
                    break;
2036
                }
2037
            }
2038
        }
2039
        $cmd = trim($cmd);
2040
        if (!preg_match('@^[0-9]+$@', $cmd)) {
2041
            $cmd = empty($cmd) ? 0 : 1;
2042
        } elseif ($cmd <= 0) {
2043
            $cmd = 0;
2044
        }
2045
2046
        if ($reverse) {
2047
            $cmd = !$cmd;
2048
        }
2049
2050
        return $cmd;
2051
    }
2052
2053
    /**
2054
     * Remove Comment-Tags from output like <!--@- Comment -@-->
2055
     * @param $content
2056
     * @param string $left
2057
     * @param string $right
2058
     * @return mixed
2059
     */
2060
    function ignoreCommentedTagsContent($content, $left = '<!--@-', $right = '-@-->')
2061
    {
2062
        if (strpos($content, $left) === false) {
2063
            return $content;
2064
        }
2065
2066
        $matches = $this->getTagsFromContent($content, $left, $right);
2067
        if (!empty($matches)) {
2068
            foreach ($matches[0] as $i => $v) {
2069
                $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...
2070
            }
2071
            $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...
2072
            if (strpos($content, $left) !== false) {
2073
                $content = str_replace($matches[0], '', $content);
2074
            }
2075
        }
2076
        return $content;
2077
    }
2078
2079
    /**
2080
     * @param $content
2081
     * @param string $left
2082
     * @param string $right
2083
     * @return mixed
2084
     */
2085
    public function escapeLiteralTagsContent($content, $left = '<@LITERAL>', $right = '<@ENDLITERAL>')
0 ignored issues
show
Coding Style introduced by
escapeLiteralTagsContent uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
2086
    {
2087
        if (stripos($content, $left) === false) {
2088
            return $content;
2089
        }
2090
2091
        $matches = $this->getTagsFromContent($content, $left, $right);
2092
        if (empty($matches)) {
2093
            return $content;
2094
        }
2095
2096
        list($sTags, $rTags) = $this->getTagsForEscape();
2097
        foreach ($matches[1] as $i => $v) {
2098
            $v = str_ireplace($sTags, $rTags, $v);
2099
            $s = &$matches[0][$i];
2100 View Code Duplication
            if (strpos($content, $s) !== false) {
2101
                $content = str_replace($s, $v, $content);
2102
            } elseif($this->debug) {
2103
                $this->addLog('ignoreCommentedTagsContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
2104
            }
2105
        }
2106
        return $content;
2107
    }
2108
2109
    /**
2110
     * Detect PHP error according to MODX error level
2111
     *
2112
     * @param integer $error PHP error level
2113
     * @return boolean Error detected
2114
     */
2115
2116
    public function detectError($error)
2117
    {
2118
        $detected = false;
2119
        if ($this->config['error_reporting'] == 99 && $error) {
2120
            $detected = true;
2121
        } elseif ($this->config['error_reporting'] == 2 && ($error & ~E_NOTICE)) {
2122
            $detected = true;
2123
        } elseif ($this->config['error_reporting'] == 1 && ($error & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT)) {
2124
            $detected = true;
2125
        }
2126
        return $detected;
2127
    }
2128
2129
    /**
2130
     * Run a plugin
2131
     *
2132
     * @param string $pluginCode Code to run
2133
     * @param array $params
2134
     */
2135
    public function evalPlugin($pluginCode, $params)
2136
    {
2137
        $modx = &$this;
2138
        $modx->event->params = &$params; // store params inside event object
2139
        if (is_array($params)) {
2140
            extract($params, EXTR_SKIP);
2141
        }
2142
        /* if uncomment incorrect work plugin, cant understend where use this code and for what?
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
2143
        // 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.
2144
        // Related to https://github.com/modxcms/evolution/issues/1130
2145
        $lock_file_path = MODX_BASE_PATH . 'assets/cache/lock_' . str_replace(' ','-',strtolower($this->event->activePlugin)) . '.pageCache.php';
2146
        if($this->isBackend()) {
2147
            if(is_file($lock_file_path)) {
2148
                $msg = sprintf("Plugin parse error, Temporarily disabled '%s'.", $this->event->activePlugin);
2149
                $this->logEvent(0, 3, $msg, $msg);
2150
                return;
2151
            }
2152
            elseif(stripos($this->event->activePlugin,'ElementsInTree')===false) touch($lock_file_path);
2153
        }*/
2154
        ob_start();
2155
        eval($pluginCode);
0 ignored issues
show
Coding Style introduced by
It is generally not recommended to use eval unless absolutely required.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
2156
        $msg = ob_get_contents();
2157
        ob_end_clean();
2158
        // When reached here, no fatal error occured so the lock should be removed.
2159
        /*if(is_file($lock_file_path)) unlink($lock_file_path);*/
0 ignored issues
show
Unused Code Comprehensibility introduced by
77% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
2160
2161 View Code Duplication
        if ((0 < $this->config['error_reporting']) && $msg && isset($php_errormsg)) {
2162
            $error_info = error_get_last();
2163
            if ($this->detectError($error_info['type'])) {
2164
                $msg = ($msg === false) ? 'ob_get_contents() error' : $msg;
2165
                $this->messageQuit('PHP Parse Error', '', true, $error_info['type'], $error_info['file'], 'Plugin', $error_info['message'], $error_info['line'], $msg);
2166
                if ($this->isBackend()) {
2167
                    $this->event->alert('An error occurred while loading. Please see the event log for more information.<p>' . $msg . '</p>');
2168
                }
2169
            }
2170
        } else {
2171
            echo $msg;
2172
        }
2173
        unset($modx->event->params);
2174
    }
2175
2176
    /**
2177
     * Run a snippet
2178
     *
2179
     * @param $phpcode
2180
     * @param array $params
2181
     * @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...
2182
     * @internal param string $snippet Code to run
2183
     */
2184
    public function evalSnippet($phpcode, $params)
2185
    {
2186
        $modx = &$this;
2187
        /*
0 ignored issues
show
Unused Code Comprehensibility introduced by
65% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
2188
        if(isset($params) && is_array($params)) {
2189
            foreach($params as $k=>$v) {
2190
                $v = strtolower($v);
2191
                if($v==='false')    $params[$k] = false;
2192
                elseif($v==='true') $params[$k] = true;
2193
            }
2194
        }*/
2195
        $modx->event->params = &$params; // store params inside event object
2196
        if (is_array($params)) {
2197
            extract($params, EXTR_SKIP);
2198
        }
2199
        ob_start();
2200
        if (strpos($phpcode, ';') !== false) {
2201
            if (substr($phpcode, 0, 5) === '<?php') {
2202
                $phpcode = substr($phpcode, 5);
2203
            }
2204
            $return = eval($phpcode);
0 ignored issues
show
Coding Style introduced by
It is generally not recommended to use eval unless absolutely required.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
2205
        } else {
2206
            $return = call_user_func_array($phpcode, array($params));
2207
        }
2208
        $echo = ob_get_contents();
2209
        ob_end_clean();
2210 View Code Duplication
        if ((0 < $this->config['error_reporting']) && isset($php_errormsg)) {
2211
            $error_info = error_get_last();
2212
            if ($this->detectError($error_info['type'])) {
2213
                $echo = ($echo === false) ? 'ob_get_contents() error' : $echo;
2214
                $this->messageQuit('PHP Parse Error', '', true, $error_info['type'], $error_info['file'], 'Snippet', $error_info['message'], $error_info['line'], $echo);
2215
                if ($this->isBackend()) {
2216
                    $this->event->alert('An error occurred while loading. Please see the event log for more information<p>' . $echo . $return . '</p>');
2217
                }
2218
            }
2219
        }
2220
        unset($modx->event->params);
2221
        if (is_array($return) || is_object($return)) {
2222
            return $return;
2223
        } else {
2224
            return $echo . $return;
2225
        }
2226
    }
2227
2228
    /**
2229
     * Run snippets as per the tags in $documentSource and replace the tags with the returned values.
2230
     *
2231
     * @param $content
2232
     * @return string
2233
     * @internal param string $documentSource
2234
     */
2235
    public function evalSnippets($content)
0 ignored issues
show
Coding Style introduced by
evalSnippets uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
2236
    {
2237
        if (strpos($content, '[[') === false) {
2238
            return $content;
2239
        }
2240
2241
        $matches = $this->getTagsFromContent($content, '[[', ']]');
2242
2243
        if (empty($matches)) {
2244
            return $content;
2245
        }
2246
2247
        $this->snipLapCount++;
2248
        if ($this->dumpSnippets) {
2249
            $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>', $this->snipLapCount);
2250
        }
2251
2252
        foreach ($matches[1] as $i => $call) {
2253
            $s = &$matches[0][$i];
2254
            if (substr($call, 0, 2) === '$_') {
2255
                if (strpos($content, '_PHX_INTERNAL_') === false) {
2256
                    $value = $this->_getSGVar($call);
2257
                } else {
2258
                    $value = $s;
2259
                }
2260 View Code Duplication
                if (strpos($content, $s) !== false) {
2261
                    $content = str_replace($s, $value, $content);
2262
                } elseif($this->debug) {
2263
                    $this->addLog('evalSnippetsSGVar parse error', $_SERVER['REQUEST_URI'] . $s, 2);
2264
                }
2265
                continue;
2266
            }
2267
            $value = $this->_get_snip_result($call);
2268
            if (is_null($value)) {
2269
                continue;
2270
            }
2271
2272 View Code Duplication
            if (strpos($content, $s) !== false) {
2273
                $content = str_replace($s, $value, $content);
2274
            } elseif($this->debug) {
2275
                $this->addLog('evalSnippets parse error', $_SERVER['REQUEST_URI'] . $s, 2);
2276
            }
2277
        }
2278
2279
        if ($this->dumpSnippets) {
2280
            $this->snippetsCode .= '</fieldset><br />';
2281
        }
2282
2283
        return $content;
2284
    }
2285
2286
    /**
2287
     * @param $value
2288
     * @return mixed|string
2289
     */
2290
    public function _getSGVar($value)
0 ignored issues
show
Coding Style introduced by
_getSGVar uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
2291
    { // Get super globals
2292
        $key = $value;
2293
        $_ = $this->config['enable_filter'];
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $_. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
2294
        $this->config['enable_filter'] = 1;
2295
        list($key, $modifiers) = $this->splitKeyAndFilter($key);
2296
        $this->config['enable_filter'] = $_;
2297
        $key = str_replace(array('(', ')'), array("['", "']"), $key);
2298
        $key = rtrim($key, ';');
2299
        if (strpos($key, '$_SESSION') !== false) {
2300
            $_ = $_SESSION;
2301
            $key = str_replace('$_SESSION', '$_', $key);
2302
            if (isset($_['mgrFormValues'])) {
2303
                unset($_['mgrFormValues']);
2304
            }
2305
            if (isset($_['token'])) {
2306
                unset($_['token']);
2307
            }
2308
        }
2309
        if (strpos($key, '[') !== false) {
2310
            $value = $key ? eval("return {$key};") : '';
0 ignored issues
show
Coding Style introduced by
It is generally not recommended to use eval unless absolutely required.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
2311
        } elseif (0 < eval("return count({$key});")) {
0 ignored issues
show
Coding Style introduced by
It is generally not recommended to use eval unless absolutely required.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
2312
            $value = eval("return print_r({$key},true);");
0 ignored issues
show
Coding Style introduced by
It is generally not recommended to use eval unless absolutely required.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
2313
        } else {
2314
            $value = '';
2315
        }
2316
        if ($modifiers !== false) {
2317
            $value = $this->applyFilter($value, $modifiers, $key);
2318
        }
2319
        return $value;
2320
    }
2321
2322
    /**
2323
     * @param $piece
2324
     * @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...
2325
     */
2326
    private function _get_snip_result($piece)
0 ignored issues
show
Coding Style introduced by
This method is not in camel caps format.

This check looks for method names that are not written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection seeker becomes databaseConnectionSeeker.

Loading history...
2327
    {
2328
        if (ltrim($piece) !== $piece) {
2329
            return '';
2330
        }
2331
2332
        $eventtime = $this->dumpSnippets ? $this->getMicroTime() : 0;
2333
2334
        $snip_call = $this->_split_snip_call($piece);
2335
        $key = $snip_call['name'];
2336
2337
        list($key, $modifiers) = $this->splitKeyAndFilter($key);
2338
        $snip_call['name'] = $key;
2339
        $snippetObject = $this->_getSnippetObject($key);
2340
        if (is_null($snippetObject['content'])) {
2341
            return null;
2342
        }
2343
2344
        $this->currentSnippet = $snippetObject['name'];
2345
2346
        // current params
2347
        $params = $this->getParamsFromString($snip_call['params']);
2348
2349
        if (!isset($snippetObject['properties'])) {
2350
            $snippetObject['properties'] = array();
2351
        }
2352
        $default_params = $this->parseProperties($snippetObject['properties'], $this->currentSnippet, 'snippet');
2353
        $params = array_merge($default_params, $params);
2354
2355
        $value = $this->evalSnippet($snippetObject['content'], $params);
2356
        $this->currentSnippet = '';
2357
        if ($modifiers !== false) {
2358
            $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...
2359
        }
2360
2361
        if ($this->dumpSnippets) {
2362
            $eventtime = $this->getMicroTime() - $eventtime;
2363
            $eventtime = sprintf('%2.2f ms', $eventtime * 1000);
2364
            $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 2355 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...
2365
            $piece = str_replace("\t", '  ', $this->getPhpCompat()->htmlspecialchars($piece));
2366
            $print_r_params = str_replace("\t", '  ', $this->getPhpCompat()->htmlspecialchars('$modx->event->params = ' . print_r($params, true)));
2367
            $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>', $snippetObject['name'], $eventtime, $piece, $print_r_params, $code);
2368
            $this->snippetsTime[] = array('sname' => $key, 'time' => $eventtime);
2369
        }
2370
        return $value;
2371
    }
2372
2373
    /**
2374
     * @param string $string
2375
     * @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...
2376
     */
2377
    public function getParamsFromString($string = '')
2378
    {
2379
        if (empty($string)) {
2380
            return array();
2381
        }
2382
2383
        if (strpos($string, '&_PHX_INTERNAL_') !== false) {
2384
            $string = str_replace(array('&_PHX_INTERNAL_091_&', '&_PHX_INTERNAL_093_&'), array('[', ']'), $string);
2385
        }
2386
2387
        $_ = $this->documentOutput;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $_. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
2388
        $this->documentOutput = $string;
2389
        $this->invokeEvent('OnBeforeParseParams');
2390
        $string = $this->documentOutput;
2391
        $this->documentOutput = $_;
2392
2393
        $_tmp = $string;
2394
        $_tmp = ltrim($_tmp, '?&');
2395
        $temp_params = array();
2396
        $key = '';
2397
        $value = null;
2398
        while ($_tmp !== '') {
2399
            $bt = $_tmp;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $bt. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
2400
            $char = substr($_tmp, 0, 1);
2401
            $_tmp = substr($_tmp, 1);
2402
2403
            if ($char === '=') {
2404
                $_tmp = trim($_tmp);
2405
                $delim = substr($_tmp, 0, 1);
2406
                if (in_array($delim, array('"', "'", '`'))) {
2407
                    $null = null;
2408
                    //list(, $value, $_tmp)
0 ignored issues
show
Unused Code Comprehensibility introduced by
78% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
2409
                    list($null, $value, $_tmp) = explode($delim, $_tmp, 3);
2410
                    unset($null);
2411
2412
                    if (substr(trim($_tmp), 0, 2) === '//') {
2413
                        $_tmp = strstr(trim($_tmp), "\n");
2414
                    }
2415
                    $i = 0;
2416
                    while ($delim === '`' && substr(trim($_tmp), 0, 1) !== '&' && 1 < substr_count($_tmp, '`')) {
2417
                        list($inner, $outer, $_tmp) = explode('`', $_tmp, 3);
2418
                        $value .= "`{$inner}`{$outer}";
2419
                        $i++;
2420
                        if (100 < $i) {
2421
                            exit('The nest of values are hard to read. Please use three different quotes.');
0 ignored issues
show
Coding Style Compatibility introduced by
The method getParamsFromString() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
2422
                        }
2423
                    }
2424
                    if ($i && $delim === '`') {
2425
                        $value = rtrim($value, '`');
2426
                    }
2427
                } elseif (strpos($_tmp, '&') !== false) {
2428
                    list($value, $_tmp) = explode('&', $_tmp, 2);
2429
                    $value = trim($value);
2430
                } else {
2431
                    $value = $_tmp;
2432
                    $_tmp = '';
2433
                }
2434
            } elseif ($char === '&') {
2435
                if (trim($key) !== '') {
2436
                    $value = '1';
2437
                } else {
2438
                    continue;
2439
                }
2440
            } elseif ($_tmp === '') {
2441
                $key .= $char;
2442
                $value = '1';
2443
            } elseif ($key !== '' || trim($char) !== '') {
2444
                $key .= $char;
2445
            }
2446
2447
            if (isset($value) && !is_null($value)) {
2448
                if (strpos($key, 'amp;') !== false) {
2449
                    $key = str_replace('amp;', '', $key);
2450
                }
2451
                $key = trim($key);
2452 View Code Duplication
                if (strpos($value, '[!') !== false) {
2453
                    $value = str_replace(array('[!', '!]'), array('[[', ']]'), $value);
2454
                }
2455
                $value = $this->mergeDocumentContent($value);
2456
                $value = $this->mergeSettingsContent($value);
2457
                $value = $this->mergeChunkContent($value);
2458
                $value = $this->evalSnippets($value);
2459
                if (substr($value, 0, 6) !== '@CODE:') {
2460
                    $value = $this->mergePlaceholderContent($value);
2461
                }
2462
2463
                $temp_params[][$key] = $value;
2464
2465
                $key = '';
2466
                $value = null;
2467
2468
                $_tmp = ltrim($_tmp, " ,\t");
2469
                if (substr($_tmp, 0, 2) === '//') {
2470
                    $_tmp = strstr($_tmp, "\n");
2471
                }
2472
            }
2473
2474
            if ($_tmp === $bt) {
2475
                $key = trim($key);
2476
                if ($key !== '') {
2477
                    $temp_params[][$key] = '';
2478
                }
2479
                break;
2480
            }
2481
        }
2482
2483
        foreach ($temp_params as $p) {
2484
            $k = key($p);
2485
            if (substr($k, -2) === '[]') {
2486
                $k = substr($k, 0, -2);
2487
                $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...
2488
            } elseif (strpos($k, '[') !== false && substr($k, -1) === ']') {
2489
                list($k, $subk) = explode('[', $k, 2);
2490
                $subk = substr($subk, 0, -1);
2491
                $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...
2492
            } else {
2493
                $params[$k] = current($p);
2494
            }
2495
        }
2496
        return $params;
2497
    }
2498
2499
    /**
2500
     * @param $str
2501
     * @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...
2502
     */
2503
    public function _getSplitPosition($str)
2504
    {
2505
        $closeOpt = false;
2506
        $maybePos = false;
2507
        $inFilter = false;
2508
        $pos = false;
2509
        $total = strlen($str);
2510
        for ($i = 0; $i < $total; $i++) {
2511
            $c = substr($str, $i, 1);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $c. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
2512
            $cc = substr($str, $i, 2);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $cc. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
2513
            if (!$inFilter) {
2514
                if ($c === ':') {
2515
                    $inFilter = true;
2516
                } elseif ($c === '?') {
2517
                    $pos = $i;
2518
                } elseif ($c === ' ') {
2519
                    $maybePos = $i;
2520
                } 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...
2521
                    $pos = $maybePos;
2522
                } elseif ($c === "\n") {
2523
                    $pos = $i;
2524
                } else {
2525
                    $pos = false;
2526
                }
2527
            } else {
2528
                if ($cc == $closeOpt) {
2529
                    $closeOpt = false;
2530
                } elseif ($c == $closeOpt) {
2531
                    $closeOpt = false;
2532
                } 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...
2533
                    continue;
2534
                } elseif ($cc === "('") {
2535
                    $closeOpt = "')";
2536
                } elseif ($cc === '("') {
2537
                    $closeOpt = '")';
2538
                } elseif ($cc === '(`') {
2539
                    $closeOpt = '`)';
2540
                } elseif ($c === '(') {
2541
                    $closeOpt = ')';
2542
                } elseif ($c === '?') {
2543
                    $pos = $i;
2544
                } elseif ($c === ' ' && strpos($str, '?') === false) {
2545
                    $pos = $i;
2546
                } else {
2547
                    $pos = false;
2548
                }
2549
            }
2550
            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...
2551
                break;
2552
            }
2553
        }
2554
        return $pos;
2555
    }
2556
2557
    /**
2558
     * @param $call
2559
     * @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...
2560
     */
2561
    private function _split_snip_call($call)
0 ignored issues
show
Coding Style introduced by
This method is not in camel caps format.

This check looks for method names that are not written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection seeker becomes databaseConnectionSeeker.

Loading history...
2562
    {
2563
        $spacer = md5('dummy');
2564 View Code Duplication
        if (strpos($call, ']]>') !== false) {
2565
            $call = str_replace(']]>', "]{$spacer}]>", $call);
2566
        }
2567
2568
        $splitPosition = $this->_getSplitPosition($call);
2569
2570
        if ($splitPosition !== false) {
2571
            $name = substr($call, 0, $splitPosition);
2572
            $params = substr($call, $splitPosition + 1);
2573
        } else {
2574
            $name = $call;
2575
            $params = '';
2576
        }
2577
2578
        $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...
2579 View Code Duplication
        if (strpos($params, $spacer) !== false) {
2580
            $params = str_replace("]{$spacer}]>", ']]>', $params);
2581
        }
2582
        $snip['params'] = ltrim($params, "?& \t\n");
2583
2584
        return $snip;
2585
    }
2586
2587
    /**
2588
     * @param $snip_name
2589
     * @return mixed
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use null|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...
2590
     */
2591
    private function _getSnippetObject($snip_name)
0 ignored issues
show
Coding Style Naming introduced by
The parameter $snip_name is not named in camelCase.

This check marks parameter names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
2592
    {
2593
        if (isset($this->snippetCache[$snip_name])) {
2594
            $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...
2595
            $snippetObject['content'] = $this->snippetCache[$snip_name];
2596
            if (isset($this->snippetCache["{$snip_name}Props"])) {
2597
                if (!isset($this->snippetCache["{$snip_name}Props"])) {
2598
                    $this->snippetCache["{$snip_name}Props"] = '';
2599
                }
2600
                $snippetObject['properties'] = $this->snippetCache["{$snip_name}Props"];
2601
            }
2602
        } elseif (substr($snip_name, 0, 1) === '@' && isset($this->pluginEvent[trim($snip_name, '@')])) {
2603
            $snippetObject['name'] = trim($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...
2604
            $snippetObject['content'] = sprintf('$rs=$this->invokeEvent("%s",$params);echo trim(implode("",$rs));', trim($snip_name, '@'));
2605
            $snippetObject['properties'] = '';
2606
        } else {
2607
            $where = sprintf("name='%s' AND disabled=0", $this->getDatabase()->escape($snip_name));
2608
            $rs = $this->getDatabase()->select('name,snippet,properties', '[+prefix+]site_snippets', $where);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rs. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
2609
            $count = $this->getDatabase()->getRecordCount($rs);
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se...site_snippets', $where) on line 2608 can also be of type boolean; however, EvolutionCMS\Database::getRecordCount() does only seem to accept object<mysqli_result>, 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...
2610
            if (1 < $count) {
2611
                exit('Error $modx->_getSnippetObject()' . $snip_name);
0 ignored issues
show
Coding Style Compatibility introduced by
The method _getSnippetObject() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
2612
            }
2613
            if ($count) {
2614
                $row = $this->getDatabase()->getRow($rs);
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se...site_snippets', $where) on line 2608 can also be of type boolean; however, EvolutionCMS\Database::getRow() does only seem to accept object<mysqli_result>, 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...
2615
                $snip_content = $row['snippet'];
2616
                $snip_prop = $row['properties'];
2617
            } else {
2618
                $snip_content = null;
2619
                $snip_prop = '';
2620
            }
2621
            $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...
2622
            $snippetObject['content'] = $snip_content;
2623
            $snippetObject['properties'] = $snip_prop;
2624
            $this->snippetCache[$snip_name] = $snip_content;
2625
            $this->snippetCache["{$snip_name}Props"] = $snip_prop;
2626
        }
2627
        return $snippetObject;
2628
    }
2629
2630
    /**
2631
     * @param $text
2632
     * @return mixed
2633
     */
2634
    public function toAlias($text)
2635
    {
2636
        $suff = $this->config['friendly_url_suffix'];
2637
        return str_replace(array('.xml' . $suff, '.rss' . $suff, '.js' . $suff, '.css' . $suff, '.txt' . $suff, '.json' . $suff, '.pdf' . $suff), array('.xml', '.rss', '.js', '.css', '.txt', '.json', '.pdf'), $text);
2638
    }
2639
2640
    /**
2641
     * makeFriendlyURL
2642
     *
2643
     * @desc Create an URL.
2644
     *
2645
     * @param $pre {string} - Friendly URL Prefix. @required
2646
     * @param $suff {string} - Friendly URL Suffix. @required
2647
     * @param $alias {string} - Full document path. @required
2648
     * @param int $isfolder {0; 1}
2649
     * - Is it a folder? Default: 0.
2650
     * @param int $id {integer}
2651
     * - Document id. Default: 0.
2652
     * @return mixed|string {string} - Result URL.
2653
     * - Result URL.
2654
     */
2655
    public function makeFriendlyURL($pre, $suff, $alias, $isfolder = 0, $id = 0)
2656
    {
2657
        if ($id == $this->config['site_start'] && $this->config['seostrict'] === '1') {
2658
            $url = $this->config['base_url'];
2659
        } else {
2660
            $Alias = explode('/', $alias);
2661
            $alias = array_pop($Alias);
2662
            $dir = implode('/', $Alias);
2663
            unset($Alias);
2664
2665
            if ($this->config['make_folders'] === '1' && $isfolder == 1) {
2666
                $suff = '/';
2667
            }
2668
2669
            $url = ($dir != '' ? $dir . '/' : '') . $pre . $alias . $suff;
2670
        }
2671
2672
        $evtOut = $this->invokeEvent('OnMakeDocUrl', array(
2673
            'id' => $id,
2674
            'url' => $url
2675
        ));
2676
2677
        if (is_array($evtOut) && count($evtOut) > 0) {
2678
            $url = array_pop($evtOut);
2679
        }
2680
2681
        return $url;
2682
    }
2683
2684
    /**
2685
     * Convert URL tags [~...~] to URLs
2686
     *
2687
     * @param string $documentSource
2688
     * @return string
2689
     */
2690
    public function rewriteUrls($documentSource)
2691
    {
2692
        // rewrite the urls
2693
        if ($this->config['friendly_urls'] == 1) {
2694
            $aliases = array();
2695
            if (is_array($this->documentListing)) {
2696
                foreach ($this->documentListing as $path => $docid) { // This is big Loop on large site!
2697
                    $aliases[$docid] = $path;
2698
                    $isfolder[$docid] = $this->aliasListing[$docid]['isfolder'];
0 ignored issues
show
Coding Style Comprehensibility introduced by
$isfolder was never initialized. Although not strictly required by PHP, it is generally a good practice to add $isfolder = 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...
2699
                }
2700
            }
2701
2702
            if ($this->config['aliaslistingfolder'] == 1) {
2703
                preg_match_all('!\[\~([0-9]+)\~\]!ise', $documentSource, $match);
2704
                $ids = implode(',', array_unique($match['1']));
2705
                if ($ids) {
2706
                    $res = $this->getDatabase()->select("id,alias,isfolder,parent,alias_visible", $this->getFullTableName('site_content'), "id IN (" . $ids . ") AND isfolder = '0'");
2707
                    while ($row = $this->getDatabase()->getRow($res)) {
0 ignored issues
show
Bug introduced by
It seems like $res defined by $this->getDatabase()->se... AND isfolder = \'0\'') on line 2706 can also be of type boolean; however, EvolutionCMS\Database::getRow() does only seem to accept object<mysqli_result>, 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...
2708
                        if ($this->config['use_alias_path'] == '1' && $row['parent'] != 0) {
2709
                            $parent = $row['parent'];
2710
                            $path = $aliases[$parent];
2711
2712
                            while (isset($this->aliasListing[$parent]) && $this->aliasListing[$parent]['alias_visible'] == 0) {
2713
                                $path = $this->aliasListing[$parent]['path'];
2714
                                $parent = $this->aliasListing[$parent]['parent'];
2715
                            }
2716
2717
                            $aliases[$row['id']] = $path . '/' . $row['alias'];
2718
                        } else {
2719
                            $aliases[$row['id']] = $row['alias'];
2720
                        }
2721
                        $isfolder[$row['id']] = '0';
0 ignored issues
show
Bug introduced by
The variable $isfolder 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...
2722
                    }
2723
                }
2724
            }
2725
            $in = '!\[\~([0-9]+)\~\]!is';
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $in. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
2726
            $isfriendly = ($this->config['friendly_alias_urls'] == 1 ? 1 : 0);
2727
            $pref = $this->config['friendly_url_prefix'];
2728
            $suff = $this->config['friendly_url_suffix'];
2729
            $documentSource = preg_replace_callback($in, function ($m) use ($aliases, $isfolder, $isfriendly, $pref, $suff) {
0 ignored issues
show
Bug introduced by
The variable $isfolder 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...
Comprehensibility introduced by
Avoid variables with short names like $m. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
2730
                global $modx;
2731
                $thealias = $aliases[$m[1]];
2732
                $thefolder = $isfolder[$m[1]];
2733
                if ($isfriendly && isset($thealias)) {
2734
                    //found friendly url
2735
                    $out = ($modx->config['seostrict'] == '1' ? $modx->toAlias($modx->makeFriendlyURL($pref, $suff, $thealias, $thefolder, $m[1])) : $modx->makeFriendlyURL($pref, $suff, $thealias, $thefolder, $m[1]));
2736
                } else {
2737
                    //not found friendly url
2738
                    $out = $modx->makeFriendlyURL($pref, $suff, $m[1]);
2739
                }
2740
                return $out;
2741
            }, $documentSource);
2742
2743
        } else {
2744
            $in = '!\[\~([0-9]+)\~\]!is';
2745
            $out = "index.php?id=" . '\1';
2746
            $documentSource = preg_replace($in, $out, $documentSource);
2747
        }
2748
2749
        return $documentSource;
2750
    }
2751
2752
    public function sendStrictURI()
0 ignored issues
show
Coding Style introduced by
sendStrictURI uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
sendStrictURI uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
2753
    {
2754
        $q = $this->q;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $q. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
2755
        // FIX URLs
2756
        if (empty($this->documentIdentifier) || $this->config['seostrict'] == '0' || $this->config['friendly_urls'] == '0') {
2757
            return;
2758
        }
2759
        if ($this->config['site_status'] == 0) {
2760
            return;
2761
        }
2762
2763
        $scheme = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https' : 'http';
2764
        $len_base_url = strlen($this->config['base_url']);
2765
2766
        $url_path = $q;//LANG
2767
2768 View Code Duplication
        if (substr($url_path, 0, $len_base_url) === $this->config['base_url']) {
2769
            $url_path = substr($url_path, $len_base_url);
2770
        }
2771
2772
        $strictURL = $this->toAlias($this->makeUrl($this->documentIdentifier));
2773
2774 View Code Duplication
        if (substr($strictURL, 0, $len_base_url) === $this->config['base_url']) {
2775
            $strictURL = substr($strictURL, $len_base_url);
2776
        }
2777
        $http_host = $_SERVER['HTTP_HOST'];
2778
        $requestedURL = "{$scheme}://{$http_host}" . '/' . $q; //LANG
2779
2780
        $site_url = $this->config['site_url'];
2781
        $url_query_string = explode('?', $_SERVER['REQUEST_URI']);
2782
        // Strip conflicting id/q from query string
2783
        $qstring = !empty($url_query_string[1]) ? preg_replace("#(^|&)(q|id)=[^&]+#", '', $url_query_string[1]) : '';
2784
2785
        if ($this->documentIdentifier == $this->config['site_start']) {
2786
            if ($requestedURL != $this->config['site_url']) {
2787
                // Force redirect of site start
2788
                // $this->sendErrorPage();
0 ignored issues
show
Unused Code Comprehensibility introduced by
72% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
2789
                if ($qstring) {
2790
                    $url = "{$site_url}?{$qstring}";
2791
                } else {
2792
                    $url = $site_url;
2793
                }
2794
                if ($this->config['base_url'] != $_SERVER['REQUEST_URI']) {
2795
                    if (empty($_POST)) {
2796
                        if (($this->config['base_url'] . '?' . $qstring) != $_SERVER['REQUEST_URI']) {
2797
                            $this->sendRedirect($url, 0, 'REDIRECT_HEADER', 'HTTP/1.0 301 Moved Permanently');
2798
                            exit(0);
0 ignored issues
show
Coding Style Compatibility introduced by
The method sendStrictURI() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
2799
                        }
2800
                    }
2801
                }
2802
            }
2803
        } elseif ($url_path != $strictURL && $this->documentIdentifier != $this->config['error_page']) {
2804
            // Force page redirect
2805
            //$strictURL = ltrim($strictURL,'/');
0 ignored issues
show
Unused Code Comprehensibility introduced by
64% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
2806
            if (!empty($qstring)) {
2807
                $url = "{$site_url}{$strictURL}?{$qstring}";
2808
            } else {
2809
                $url = "{$site_url}{$strictURL}";
2810
            }
2811
            $this->sendRedirect($url, 0, 'REDIRECT_HEADER', 'HTTP/1.0 301 Moved Permanently');
2812
            exit(0);
0 ignored issues
show
Coding Style Compatibility introduced by
The method sendStrictURI() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
2813
        }
2814
        return;
2815
    }
2816
2817
    /**
2818
     * Get all db fields and TVs for a document/resource
2819
     *
2820
     * @param string $method
2821
     * @param mixed $identifier
2822
     * @param bool $isPrepareResponse
2823
     * @return array
2824
     */
2825
    public function getDocumentObject($method, $identifier, $isPrepareResponse = false)
0 ignored issues
show
Coding Style introduced by
getDocumentObject uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
2826
    {
2827
2828
        $cacheKey = md5(print_r(func_get_args(), true));
2829
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
2830
            return $this->tmpCache[__FUNCTION__][$cacheKey];
2831
        }
2832
2833
        $tblsc = $this->getFullTableName("site_content");
2834
        $tbldg = $this->getFullTableName("document_groups");
2835
2836
        // allow alias to be full path
2837
        if ($method == 'alias') {
2838
            $identifier = $this->cleanDocumentIdentifier($identifier);
2839
            $method = $this->documentMethod;
2840
        }
2841
        if ($method == 'alias' && $this->config['use_alias_path'] && array_key_exists($identifier, $this->documentListing)) {
2842
            $method = 'id';
2843
            $identifier = $this->documentListing[$identifier];
2844
        }
2845
2846
        $out = $this->invokeEvent('OnBeforeLoadDocumentObject', compact('method', 'identifier'));
2847
        if (is_array($out) && is_array($out[0])) {
2848
            $documentObject = $out[0];
2849
        } else {
2850
            // get document groups for current user
2851
            if ($docgrp = $this->getUserDocGroups()) {
2852
                $docgrp = implode(",", $docgrp);
2853
            }
2854
            // get document
2855
            $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
2856
            $rs = $this->getDatabase()->select('sc.*', "{$tblsc} sc
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rs. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
2857
                LEFT JOIN {$tbldg} dg ON dg.document = sc.id", "sc.{$method} = '{$identifier}' AND ({$access})", "", 1);
2858
            if ($this->getDatabase()->getRecordCount($rs) < 1) {
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se...ND ({$access})", '', 1) on line 2856 can also be of type boolean; however, EvolutionCMS\Database::getRecordCount() does only seem to accept object<mysqli_result>, 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...
2859
                $seclimit = 0;
2860
                if ($this->config['unauthorized_page']) {
2861
                    // method may still be alias, while identifier is not full path alias, e.g. id not found above
2862
                    if ($method === 'alias') {
2863
                        $secrs = $this->getDatabase()->select('count(dg.id)', "{$tbldg} as dg, {$tblsc} as sc", "dg.document = sc.id AND sc.alias = '{$identifier}'", '', 1);
2864
                    } else {
2865
                        $secrs = $this->getDatabase()->select('count(id)', $tbldg, "document = '{$identifier}'", '', 1);
2866
                    }
2867
                    // check if file is not public
2868
                    $seclimit = $this->getDatabase()->getValue($secrs);
0 ignored issues
show
Bug introduced by
It seems like $secrs can also be of type boolean; however, EvolutionCMS\Database::getValue() does only seem to accept object<mysqli_result>|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...
2869
                }
2870
                if ($seclimit > 0) {
2871
                    // match found but not publicly accessible, send the visitor to the unauthorized_page
2872
                    $this->sendUnauthorizedPage();
2873
                    exit; // stop here
0 ignored issues
show
Coding Style Compatibility introduced by
The method getDocumentObject() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
2874
                } else {
2875
                    $this->sendErrorPage();
2876
                    exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method getDocumentObject() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
2877
                }
2878
            }
2879
            # this is now the document :) #
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
2880
            $documentObject = $this->getDatabase()->getRow($rs);
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se...ND ({$access})", '', 1) on line 2856 can also be of type boolean; however, EvolutionCMS\Database::getRow() does only seem to accept object<mysqli_result>, 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...
2881
2882
            if ($isPrepareResponse === 'prepareResponse') {
2883
                $this->documentObject = &$documentObject;
2884
            }
2885
            $out = $this->invokeEvent('OnLoadDocumentObject', compact('method', 'identifier', 'documentObject'));
2886
            if (is_array($out) && is_array($out[0])) {
2887
                $documentObject = $out[0];
2888
            }
2889
            if ($documentObject['template']) {
2890
                // load TVs and merge with document - Orig by Apodigm - Docvars
2891
                $rs = $this->getDatabase()->select("tv.*, IF(tvc.value!='',tvc.value,tv.default_text) as value", $this->getFullTableName("site_tmplvars") . " tv
2892
                INNER JOIN " . $this->getFullTableName("site_tmplvar_templates") . " tvtpl ON tvtpl.tmplvarid = tv.id
2893
                LEFT JOIN " . $this->getFullTableName("site_tmplvar_contentvalues") . " tvc ON tvc.tmplvarid=tv.id AND tvc.contentid = '{$documentObject['id']}'", "tvtpl.templateid = '{$documentObject['template']}'");
2894
                $tmplvars = array();
2895
                while ($row = $this->getDatabase()->getRow($rs)) {
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se...tObject['template']}'") on line 2891 can also be of type boolean; however, EvolutionCMS\Database::getRow() does only seem to accept object<mysqli_result>, 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...
2896
                    $tmplvars[$row['name']] = array(
2897
                        $row['name'],
2898
                        $row['value'],
2899
                        $row['display'],
2900
                        $row['display_params'],
2901
                        $row['type']
2902
                    );
2903
                }
2904
                $documentObject = array_merge($documentObject, $tmplvars);
2905
            }
2906
            $out = $this->invokeEvent('OnAfterLoadDocumentObject', compact('method', 'identifier', 'documentObject'));
2907
            if (is_array($out) && array_key_exists(0, $out) !== false && is_array($out[0])) {
2908
                $documentObject = $out[0];
2909
            }
2910
        }
2911
2912
        $this->tmpCache[__FUNCTION__][$cacheKey] = $documentObject;
2913
2914
        return $documentObject;
2915
    }
2916
2917
    /**
2918
     * Parse a source string.
2919
     *
2920
     * Handles most MODX tags. Exceptions include:
2921
     *   - uncached snippet tags [!...!]
2922
     *   - URL tags [~...~]
2923
     *
2924
     * @param string $source
2925
     * @return string
2926
     */
2927
    public function parseDocumentSource($source)
2928
    {
2929
        // set the number of times we are to parse the document source
2930
        $this->minParserPasses = empty ($this->minParserPasses) ? 2 : $this->minParserPasses;
2931
        $this->maxParserPasses = empty ($this->maxParserPasses) ? 10 : $this->maxParserPasses;
2932
        $passes = $this->minParserPasses;
2933
        for ($i = 0; $i < $passes; $i++) {
2934
            // get source length if this is the final pass
2935
            if ($i == ($passes - 1)) {
2936
                $st = md5($source);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $st. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
2937
            }
2938
            if ($this->dumpSnippets == 1) {
2939
                $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>";
2940
            }
2941
2942
            // invoke OnParseDocument event
2943
            $this->documentOutput = $source; // store source code so plugins can
2944
            $this->invokeEvent("OnParseDocument"); // work on it via $modx->documentOutput
2945
            $source = $this->documentOutput;
2946
2947
            if ($this->config['enable_at_syntax']) {
2948
                $source = $this->ignoreCommentedTagsContent($source);
2949
                $source = $this->mergeConditionalTagsContent($source);
2950
            }
2951
2952
            $source = $this->mergeSettingsContent($source);
2953
            $source = $this->mergeDocumentContent($source);
2954
            $source = $this->mergeChunkContent($source);
2955
            $source = $this->evalSnippets($source);
2956
            $source = $this->mergePlaceholderContent($source);
2957
2958
            if ($this->dumpSnippets == 1) {
2959
                $this->snippetsCode .= "</fieldset><br />";
2960
            }
2961
            if ($i == ($passes - 1) && $i < ($this->maxParserPasses - 1)) {
2962
                // check if source content was changed
2963
                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...
2964
                    $passes++;
2965
                } // if content change then increase passes because
2966
            } // we have not yet reached maxParserPasses
2967
        }
2968
        return $source;
2969
    }
2970
2971
    /**
2972
     * Starts the parsing operations.
2973
     *
2974
     * - connects to the db
2975
     * - gets the settings (including system_settings)
2976
     * - gets the document/resource identifier as in the query string
2977
     * - finally calls prepareResponse()
2978
     */
2979
    public function executeParser()
2980
    {
2981
        if(MODX_CLI) {
2982
            throw new \RuntimeException('Call DocumentParser::executeParser on CLI mode');
2983
        }
2984
2985
        //error_reporting(0);
2986
        set_error_handler(array(
2987
            & $this,
2988
            "phpError"
2989
        ), E_ALL);
2990
        $this->getDatabase()->connect();
2991
2992
        // get the settings
2993
        if (empty ($this->config)) {
2994
            $this->getSettings();
2995
        }
2996
2997
        $this->_IIS_furl_fix(); // IIS friendly url fix
2998
2999
        // check site settings
3000
        if ($this->checkSiteStatus()) {
3001
            // make sure the cache doesn't need updating
3002
            $this->updatePubStatus();
3003
3004
            // find out which document we need to display
3005
            $this->documentMethod = filter_input(INPUT_GET, 'q') ? 'alias' : 'id';
3006
            $this->documentIdentifier = $this->getDocumentIdentifier($this->documentMethod);
3007
        } else {
3008
            header('HTTP/1.0 503 Service Unavailable');
3009
            $this->systemCacheKey = 'unavailable';
3010
            if (!$this->config['site_unavailable_page']) {
3011
                // display offline message
3012
                $this->documentContent = $this->config['site_unavailable_message'];
3013
                $this->outputContent();
3014
                exit; // stop processing here, as the site's offline
0 ignored issues
show
Coding Style Compatibility introduced by
The method executeParser() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
3015
            } else {
3016
                // setup offline page document settings
3017
                $this->documentMethod = 'id';
3018
                $this->documentIdentifier = $this->config['site_unavailable_page'];
3019
            }
3020
        }
3021
3022
        if ($this->documentMethod == "alias") {
3023
            $this->documentIdentifier = $this->cleanDocumentIdentifier($this->documentIdentifier);
3024
3025
            // Check use_alias_path and check if $this->virtualDir is set to anything, then parse the path
3026
            if ($this->config['use_alias_path'] == 1) {
3027
                $alias = (strlen($this->virtualDir) > 0 ? $this->virtualDir . '/' : '') . $this->documentIdentifier;
3028
                if (isset($this->documentListing[$alias])) {
3029
                    $this->documentIdentifier = $this->documentListing[$alias];
3030
                } else {
3031
                    //@TODO: check new $alias;
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
Unused Code Comprehensibility introduced by
45% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
3032
                    if ($this->config['aliaslistingfolder'] == 1) {
3033
                        $tbl_site_content = $this->getFullTableName('site_content');
3034
3035
                        $parentId = $this->getIdFromAlias($this->virtualDir);
3036
                        $parentId = ($parentId > 0) ? $parentId : '0';
3037
3038
                        $docAlias = $this->getDatabase()->escape($this->documentIdentifier);
3039
3040
                        $rs = $this->getDatabase()->select('id', $tbl_site_content, "deleted=0 and parent='{$parentId}' and alias='{$docAlias}'");
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rs. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
3041
                        if ($this->getDatabase()->getRecordCount($rs) == 0) {
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se...d alias='{$docAlias}'") on line 3040 can also be of type boolean; however, EvolutionCMS\Database::getRecordCount() does only seem to accept object<mysqli_result>, 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...
3042
                            $this->sendErrorPage();
3043
                        }
3044
                        $docId = $this->getDatabase()->getValue($rs);
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se...d alias='{$docAlias}'") on line 3040 can also be of type boolean; however, EvolutionCMS\Database::getValue() does only seem to accept object<mysqli_result>|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...
3045
3046
                        if (!$docId) {
3047
                            $alias = $this->q;
3048
                            if (!empty($this->config['friendly_url_suffix'])) {
3049
                                $pos = strrpos($alias, $this->config['friendly_url_suffix']);
3050
3051
                                if ($pos !== false) {
3052
                                    $alias = substr($alias, 0, $pos);
3053
                                }
3054
                            }
3055
                            $docId = $this->getIdFromAlias($alias);
3056
                        }
3057
3058
                        if ($docId > 0) {
3059
                            $this->documentIdentifier = $docId;
3060
                        } else {
3061
                            /*
0 ignored issues
show
Unused Code Comprehensibility introduced by
61% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
3062
                            $rs  = $this->getDatabase()->select('id', $tbl_site_content, "deleted=0 and alias='{$docAlias}'");
3063
                            if($this->getDatabase()->getRecordCount($rs)==0)
3064
                            {
3065
                                $rs  = $this->getDatabase()->select('id', $tbl_site_content, "deleted=0 and id='{$docAlias}'");
3066
                            }
3067
                            $docId = $this->getDatabase()->getValue($rs);
3068
3069
                            if ($docId > 0)
3070
                            {
3071
                                $this->documentIdentifier = $docId;
3072
3073
                            }else{
3074
                            */
3075
                            $this->sendErrorPage();
3076
                            //}
3077
                        }
3078
                    } else {
3079
                        $this->sendErrorPage();
3080
                    }
3081
                }
3082
            } else {
3083
                if (isset($this->documentListing[$this->documentIdentifier])) {
3084
                    $this->documentIdentifier = $this->documentListing[$this->documentIdentifier];
3085
                } else {
3086
                    $docAlias = $this->getDatabase()->escape($this->documentIdentifier);
3087
                    $rs = $this->getDatabase()->select('id', $this->getFullTableName('site_content'), "deleted=0 and alias='{$docAlias}'");
3088
                    $this->documentIdentifier = (int)$this->getDatabase()->getValue($rs);
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se...d alias='{$docAlias}'") on line 3087 can also be of type boolean; however, EvolutionCMS\Database::getValue() does only seem to accept object<mysqli_result>|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...
3089
                }
3090
            }
3091
            $this->documentMethod = 'id';
3092
        }
3093
3094
        //$this->_fixURI();
0 ignored issues
show
Unused Code Comprehensibility introduced by
84% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
3095
        // invoke OnWebPageInit event
3096
        $this->invokeEvent("OnWebPageInit");
3097
        // invoke OnLogPageView event
3098
        if ($this->config['track_visitors'] == 1) {
3099
            $this->invokeEvent("OnLogPageHit");
3100
        }
3101
        if ($this->config['seostrict'] === '1') {
3102
            $this->sendStrictURI();
3103
        }
3104
        $this->prepareResponse();
3105
    }
3106
3107
    /**
3108
     * @param $path
3109
     * @param null $suffix
3110
     * @return mixed
3111
     */
3112
    public function mb_basename($path, $suffix = null)
0 ignored issues
show
Coding Style introduced by
This method is not in camel caps format.

This check looks for method names that are not written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection seeker becomes databaseConnectionSeeker.

Loading history...
3113
    {
3114
        $exp = explode('/', $path);
3115
        return str_replace($suffix, '', end($exp));
3116
    }
3117
3118
    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...
Coding Style introduced by
_IIS_furl_fix uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
_IIS_furl_fix uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
_IIS_furl_fix uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
This method is not in camel caps format.

This check looks for method names that are not written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection seeker becomes databaseConnectionSeeker.

Loading history...
3119
    {
3120
        if ($this->config['friendly_urls'] != 1) {
3121
            return;
3122
        }
3123
3124
        if (strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') === false) {
3125
            return;
3126
        }
3127
3128
        $url = $_SERVER['QUERY_STRING'];
3129
        $err = substr($url, 0, 3);
3130
        if ($err !== '404' && $err !== '405') {
3131
            return;
3132
        }
3133
3134
        $k = array_keys($_GET);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $k. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
3135
        unset ($_GET[$k[0]]);
3136
        unset ($_REQUEST[$k[0]]); // remove 404,405 entry
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
3137
        $qp = parse_url(str_replace($this->config['site_url'], '', substr($url, 4)));
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $qp. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
3138
        $_SERVER['QUERY_STRING'] = $qp['query'];
3139
        if (!empty ($qp['query'])) {
3140
            parse_str($qp['query'], $qv);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $qv. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
3141
            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...
3142
                $_REQUEST[$n] = $_GET[$n] = $v;
3143
            }
3144
        }
3145
        $_SERVER['PHP_SELF'] = $this->config['base_url'] . $qp['path'];
3146
        $this->q = $qp['path'];
3147
        return $qp['path'];
3148
    }
3149
3150
    /**
3151
     * The next step called at the end of executeParser()
3152
     *
3153
     * - checks cache
3154
     * - checks if document/resource is deleted/unpublished
3155
     * - checks if resource is a weblink and redirects if so
3156
     * - gets template and parses it
3157
     * - ensures that postProcess is called when PHP is finished
3158
     */
3159
    public function prepareResponse()
3160
    {
3161
        // we now know the method and identifier, let's check the cache
3162
3163
        if ($this->config['enable_cache'] == 2 && $this->isLoggedIn()) {
3164
            $this->config['enable_cache'] = 0;
3165
        }
3166
3167
        if ($this->config['enable_cache']) {
3168
            $this->documentContent = $this->getDocumentObjectFromCache($this->documentIdentifier, true);
3169
        } else {
3170
            $this->documentContent = '';
3171
        }
3172
3173
        if ($this->documentContent == '') {
3174
            // get document object from DB
3175
            $this->documentObject = $this->getDocumentObject($this->documentMethod, $this->documentIdentifier, 'prepareResponse');
3176
3177
            // write the documentName to the object
3178
            $this->documentName = &$this->documentObject['pagetitle'];
3179
3180
            // check if we should not hit this document
3181
            if ($this->documentObject['donthit'] == 1) {
3182
                $this->config['track_visitors'] = 0;
3183
            }
3184
3185
            if ($this->documentObject['deleted'] == 1) {
3186
                $this->sendErrorPage();
3187
            } // validation routines
3188
            elseif ($this->documentObject['published'] == 0) {
3189
                $this->_sendErrorForUnpubPage();
3190
            } elseif ($this->documentObject['type'] == 'reference') {
3191
                $this->_sendRedirectForRefPage($this->documentObject['content']);
3192
            }
3193
3194
            // get the template and start parsing!
3195
            if (!$this->documentObject['template']) {
3196
                $templateCode = '[*content*]';
3197
            } // use blank template
3198
            else {
3199
                $templateCode = $this->_getTemplateCodeFromDB($this->documentObject['template']);
3200
            }
3201
3202
            if (substr($templateCode, 0, 8) === '@INCLUDE') {
3203
                $templateCode = $this->atBindInclude($templateCode);
3204
            }
3205
3206
3207
            $this->documentContent = &$templateCode;
3208
3209
            // invoke OnLoadWebDocument event
3210
            $this->invokeEvent('OnLoadWebDocument');
3211
3212
            // Parse document source
3213
            $this->documentContent = $this->parseDocumentSource($templateCode);
3214
3215
            $this->documentGenerated = 1;
3216
        } else {
3217
            $this->documentGenerated = 0;
3218
        }
3219
3220
        if ($this->config['error_page'] == $this->documentIdentifier && $this->config['error_page'] != $this->config['site_start']) {
3221
            header('HTTP/1.0 404 Not Found');
3222
        }
3223
3224
        register_shutdown_function(array(
3225
            &$this,
3226
            'postProcess'
3227
        )); // tell PHP to call postProcess when it shuts down
3228
        $this->outputContent();
3229
        //$this->postProcess();
0 ignored issues
show
Unused Code Comprehensibility introduced by
84% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
3230
    }
3231
3232
    public function _sendErrorForUnpubPage()
0 ignored issues
show
Coding Style introduced by
_sendErrorForUnpubPage uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
3233
    {
3234
        // Can't view unpublished pages !$this->checkPreview()
3235
        if (!$this->hasPermission('view_unpublished')) {
3236
            $this->sendErrorPage();
3237
        } else {
3238
            $udperms = new Legacy\Permissions();
3239
            $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...
3240
            $udperms->document = $this->documentIdentifier;
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->documentIdentifier can also be of type string or boolean. However, the property $document is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
3241
            $udperms->role = $_SESSION['mgrRole'];
3242
            // Doesn't have access to this document
3243
            if (!$udperms->checkPermissions()) {
3244
                $this->sendErrorPage();
3245
            }
3246
        }
3247
    }
3248
3249
    /**
3250
     * @param $url
3251
     */
3252
    public function _sendRedirectForRefPage($url)
3253
    {
3254
        // check whether it's a reference
3255
        if (preg_match('@^[1-9][0-9]*$@', $url)) {
3256
            $url = $this->makeUrl($url); // if it's a bare document id
3257
        } elseif (strpos($url, '[~') !== false) {
3258
            $url = $this->rewriteUrls($url); // if it's an internal docid tag, process it
3259
        }
3260
        $this->sendRedirect($url, 0, '', 'HTTP/1.0 301 Moved Permanently');
3261
        exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method _sendRedirectForRefPage() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
3262
    }
3263
3264
    /**
3265
     * @param $templateID
3266
     * @return mixed
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use boolean|string|integer|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...
3267
     */
3268
    public function _getTemplateCodeFromDB($templateID)
3269
    {
3270
        $rs = $this->getDatabase()->select('content', '[+prefix+]site_templates', "id = '{$templateID}'");
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rs. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
3271
        if ($this->getDatabase()->getRecordCount($rs) == 1) {
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se..."id = '{$templateID}'") on line 3270 can also be of type boolean; however, EvolutionCMS\Database::getRecordCount() does only seem to accept object<mysqli_result>, 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...
3272
            return $this->getDatabase()->getValue($rs);
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se..."id = '{$templateID}'") on line 3270 can also be of type boolean; however, EvolutionCMS\Database::getValue() does only seem to accept object<mysqli_result>|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...
3273
        } else {
3274
            $this->messageQuit('Incorrect number of templates returned from database');
3275
        }
3276
    }
3277
3278
    /**
3279
     * Returns an array of all parent record IDs for the id passed.
3280
     *
3281
     * @param int $id Docid to get parents for.
3282
     * @param int $height The maximum number of levels to go up, default 10.
3283
     * @return array
3284
     */
3285
    public function getParentIds($id, $height = 10)
3286
    {
3287
        $parents = array();
3288
        while ($id && $height--) {
3289
            $thisid = $id;
3290
            if ($this->config['aliaslistingfolder'] == 1) {
3291
                $id = isset($this->aliasListing[$id]['parent']) ? $this->aliasListing[$id]['parent'] : $this->getDatabase()->getValue("SELECT `parent` FROM " . $this->getFullTableName("site_content") . " WHERE `id` = '{$id}' LIMIT 0,1");
3292
                if (!$id || $id == '0') {
3293
                    break;
3294
                }
3295
            } else {
3296
                $id = $this->aliasListing[$id]['parent'];
3297
                if (!$id) {
3298
                    break;
3299
                }
3300
            }
3301
            $parents[$thisid] = $id;
3302
        }
3303
        return $parents;
3304
    }
3305
3306
    /**
3307
     * @param $id
3308
     * @param int $top
3309
     * @return mixed
3310
     */
3311
    public function getUltimateParentId($id, $top = 0)
3312
    {
3313
        $i = 0;
3314
        while ($id && $i < 20) {
3315
            if ($top == $this->aliasListing[$id]['parent']) {
3316
                break;
3317
            }
3318
            $id = $this->aliasListing[$id]['parent'];
3319
            $i++;
3320
        }
3321
        return $id;
3322
    }
3323
3324
    /**
3325
     * Returns an array of child IDs belonging to the specified parent.
3326
     *
3327
     * @param int $id The parent resource/document to start from
3328
     * @param int $depth How many levels deep to search for children, default: 10
3329
     * @param array $children Optional array of docids to merge with the result.
3330
     * @return array Contains the document Listing (tree) like the sitemap
3331
     */
3332
    public function getChildIds($id, $depth = 10, $children = array())
3333
    {
3334
3335
        $cacheKey = md5(print_r(func_get_args(), true));
3336
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3337
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3338
        }
3339
3340
        if ($this->config['aliaslistingfolder'] == 1) {
3341
3342
            $res = $this->getDatabase()->select("id,alias,isfolder,parent", $this->getFullTableName('site_content'), "parent IN (" . $id . ") AND deleted = '0'");
3343
            $idx = array();
3344
            while ($row = $this->getDatabase()->getRow($res)) {
0 ignored issues
show
Bug introduced by
It seems like $res defined by $this->getDatabase()->se...) AND deleted = \'0\'') on line 3342 can also be of type boolean; however, EvolutionCMS\Database::getRow() does only seem to accept object<mysqli_result>, 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...
3345
                $pAlias = '';
3346
                if (isset($this->aliasListing[$row['parent']])) {
3347
                    $pAlias .= !empty($this->aliasListing[$row['parent']]['path']) ? $this->aliasListing[$row['parent']]['path'] . '/' : '';
3348
                    $pAlias .= !empty($this->aliasListing[$row['parent']]['alias']) ? $this->aliasListing[$row['parent']]['alias'] . '/' : '';
3349
                };
3350
                $children[$pAlias . $row['alias']] = $row['id'];
3351
                if ($row['isfolder'] == 1) {
3352
                    $idx[] = $row['id'];
3353
                }
3354
            }
3355
            $depth--;
3356
            $idx = implode(',', $idx);
3357
            if (!empty($idx)) {
3358
                if ($depth) {
3359
                    $children = $this->getChildIds($idx, $depth, $children);
3360
                }
3361
            }
3362
            $this->tmpCache[__FUNCTION__][$cacheKey] = $children;
3363
            return $children;
3364
3365
        } else {
3366
3367
            // Initialise a static array to index parents->children
3368
            static $documentMap_cache = array();
3369
            if (!count($documentMap_cache)) {
3370
                foreach ($this->documentMap as $document) {
3371
                    foreach ($document as $p => $c) {
3372
                        $documentMap_cache[$p][] = $c;
3373
                    }
3374
                }
3375
            }
3376
3377
            // Get all the children for this parent node
3378
            if (isset($documentMap_cache[$id])) {
3379
                $depth--;
3380
3381
                foreach ($documentMap_cache[$id] as $childId) {
3382
                    $pkey = (strlen($this->aliasListing[$childId]['path']) ? "{$this->aliasListing[$childId]['path']}/" : '') . $this->aliasListing[$childId]['alias'];
3383
                    if (!strlen($pkey)) {
3384
                        $pkey = "{$childId}";
3385
                    }
3386
                    $children[$pkey] = $childId;
3387
3388
                    if ($depth && isset($documentMap_cache[$childId])) {
3389
                        $children += $this->getChildIds($childId, $depth);
3390
                    }
3391
                }
3392
            }
3393
            $this->tmpCache[__FUNCTION__][$cacheKey] = $children;
3394
            return $children;
3395
3396
        }
3397
    }
3398
3399
    /**
3400
     * Displays a javascript alert message in the web browser and quit
3401
     *
3402
     * @param string $msg Message to show
3403
     * @param string $url URL to redirect to
3404
     */
3405
    public function webAlertAndQuit($msg, $url = '')
3406
    {
3407
        global $modx_manager_charset;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
3408
        switch (true) {
3409
            case (0 === stripos($url, 'javascript:')):
3410
                $fnc = substr($url, 11);
3411
                break;
3412
            case $url === '#':
3413
                $fnc = '';
3414
                break;
3415
            case empty($url):
3416
                $fnc = 'history.back(-1);';
3417
                break;
3418
            default:
3419
                $fnc = "window.location.href='" . addslashes($url) . "';";
3420
        }
3421
3422
        echo "<html><head>
3423
            <title>MODX :: Alert</title>
3424
            <meta http-equiv=\"Content-Type\" content=\"text/html; charset={$modx_manager_charset};\">
3425
            <script>
3426
                function __alertQuit() {
3427
                    var el = document.querySelector('p');
3428
                    alert(el.innerHTML);
3429
                    el.remove();
3430
                    {$fnc}
3431
                }
3432
                window.setTimeout('__alertQuit();',100);
3433
            </script>
3434
            </head><body>
3435
            <p>{$msg}</p>
3436
            </body></html>";
3437
        exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method webAlertAndQuit() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
3438
    }
3439
3440
    /**
3441
     * Returns 1 if user has the currect permission
3442
     *
3443
     * @param string $pm Permission name
3444
     * @return int Why not bool?
3445
     */
3446
    public function hasPermission($pm)
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $pm. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
Coding Style introduced by
hasPermission uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
3447
    {
3448
        $state = 0;
3449
        $pms = $_SESSION['mgrPermissions'];
3450
        if ($pms) {
3451
            $state = ((bool)$pms[$pm] === true);
3452
        }
3453
        return (int)$state;
3454
    }
3455
3456
    /**
3457
     * Returns true if element is locked
3458
     *
3459
     * @param int $type Types: 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3460
     * @param int $id Element- / Resource-id
3461
     * @param bool $includeThisUser true = Return also info about actual user
3462
     * @return array lock-details or null
3463
     */
3464
    public function elementIsLocked($type, $id, $includeThisUser = false)
3465
    {
3466
        $id = (int)$id;
3467
        $type = (int)$type;
3468
        if (!$type || !$id) {
3469
            return null;
3470
        }
3471
3472
        // Build lockedElements-Cache at first call
3473
        $this->buildLockedElementsCache();
3474
3475
        if (!$includeThisUser && $this->lockedElements[$type][$id]['sid'] == $this->sid) {
3476
            return null;
3477
        }
3478
3479
        if (isset($this->lockedElements[$type][$id])) {
3480
            return $this->lockedElements[$type][$id];
3481
        } else {
3482
            return null;
3483
        }
3484
    }
3485
3486
    /**
3487
     * Returns Locked Elements as Array
3488
     *
3489
     * @param int $type Types: 0=all, 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3490
     * @param bool $minimumDetails true =
3491
     * @return array|mixed|null
3492
     */
3493
    public function getLockedElements($type = 0, $minimumDetails = false)
3494
    {
3495
        $this->buildLockedElementsCache();
3496
3497
        if (!$minimumDetails) {
3498
            $lockedElements = $this->lockedElements;
3499
        } else {
3500
            // Minimum details for HTML / Ajax-requests
3501
            $lockedElements = array();
3502
            foreach ($this->lockedElements as $elType => $elements) {
3503
                foreach ($elements as $elId => $el) {
3504
                    $lockedElements[$elType][$elId] = array(
3505
                        'username' => $el['username'],
3506
                        'lasthit_df' => $el['lasthit_df'],
3507
                        'state' => $this->determineLockState($el['internalKey'])
3508
                    );
3509
                }
3510
            }
3511
        }
3512
3513
        if ($type == 0) {
3514
            return $lockedElements;
3515
        }
3516
3517
        $type = (int)$type;
3518
        if (isset($lockedElements[$type])) {
3519
            return $lockedElements[$type];
3520
        } else {
3521
            return array();
3522
        }
3523
    }
3524
3525
    /**
3526
     * Builds the Locked Elements Cache once
3527
     */
3528
    public function buildLockedElementsCache()
3529
    {
3530
        if (is_null($this->lockedElements)) {
3531
            $this->lockedElements = array();
3532
            $this->cleanupExpiredLocks();
3533
3534
            $rs = $this->getDatabase()->select('sid,internalKey,elementType,elementId,lasthit,username', $this->getFullTableName('active_user_locks') . " ul
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rs. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
3535
                LEFT JOIN {$this->getFullTableName('manager_users')} mu on ul.internalKey = mu.id");
3536
            while ($row = $this->getDatabase()->getRow($rs)) {
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se...l.internalKey = mu.id") on line 3534 can also be of type boolean; however, EvolutionCMS\Database::getRow() does only seem to accept object<mysqli_result>, 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...
3537
                $this->lockedElements[$row['elementType']][$row['elementId']] = array(
3538
                    'sid' => $row['sid'],
3539
                    'internalKey' => $row['internalKey'],
3540
                    'username' => $row['username'],
3541
                    'elementType' => $row['elementType'],
3542
                    'elementId' => $row['elementId'],
3543
                    'lasthit' => $row['lasthit'],
3544
                    'lasthit_df' => $this->toDateFormat($row['lasthit']),
3545
                    'state' => $this->determineLockState($row['sid'])
3546
                );
3547
            }
3548
        }
3549
    }
3550
3551
    /**
3552
     * Cleans up the active user locks table
3553
     */
3554
    public function cleanupExpiredLocks()
3555
    {
3556
        // Clean-up active_user_sessions first
3557
        $timeout = (int)$this->config['session_timeout'] < 2 ? 120 : $this->config['session_timeout'] * 60; // session.js pings every 10min, updateMail() in mainMenu pings every minute, so 2min is minimum
3558
        $validSessionTimeLimit = $this->time - $timeout;
3559
        $this->getDatabase()->delete($this->getFullTableName('active_user_sessions'), "lasthit < {$validSessionTimeLimit}");
3560
3561
        // Clean-up active_user_locks
3562
        $rs = $this->getDatabase()->select('sid,internalKey', $this->getFullTableName('active_user_sessions'));
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rs. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
3563
        $count = $this->getDatabase()->getRecordCount($rs);
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se...active_user_sessions')) on line 3562 can also be of type boolean; however, EvolutionCMS\Database::getRecordCount() does only seem to accept object<mysqli_result>, 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...
3564
        if ($count) {
3565
            $rs = $this->getDatabase()->makeArray($rs);
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->makeArray($rs) on line 3565 can also be of type boolean; however, EvolutionCMS\Database::makeArray() does only seem to accept string|object<mysqli_result>, 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...
3566
            $userSids = array();
3567
            foreach ($rs as $row) {
3568
                $userSids[] = $row['sid'];
3569
            }
3570
            $userSids = "'" . implode("','", $userSids) . "'";
3571
            $this->getDatabase()->delete($this->getFullTableName('active_user_locks'), "sid NOT IN({$userSids})");
3572
        } else {
3573
            $this->getDatabase()->delete($this->getFullTableName('active_user_locks'));
3574
        }
3575
3576
    }
3577
3578
    /**
3579
     * Cleans up the active users table
3580
     */
3581
    public function cleanupMultipleActiveUsers()
3582
    {
3583
        $timeout = 20 * 60; // Delete multiple user-sessions after 20min
3584
        $validSessionTimeLimit = $this->time - $timeout;
3585
3586
        $activeUserSids = array();
3587
        $rs = $this->getDatabase()->select('sid', $this->getFullTableName('active_user_sessions'));
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rs. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
3588
        $count = $this->getDatabase()->getRecordCount($rs);
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se...active_user_sessions')) on line 3587 can also be of type boolean; however, EvolutionCMS\Database::getRecordCount() does only seem to accept object<mysqli_result>, 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...
3589
        if ($count) {
3590
            $rs = $this->getDatabase()->makeArray($rs);
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->makeArray($rs) on line 3590 can also be of type boolean; however, EvolutionCMS\Database::makeArray() does only seem to accept string|object<mysqli_result>, 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...
3591
            foreach ($rs as $row) {
3592
                $activeUserSids[] = $row['sid'];
3593
            }
3594
        }
3595
3596
        $rs = $this->getDatabase()->select("sid,internalKey,lasthit", "{$this->getFullTableName('active_users')}", "", "lasthit DESC");
3597
        if ($this->getDatabase()->getRecordCount($rs)) {
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se...}", '', 'lasthit DESC') on line 3596 can also be of type boolean; however, EvolutionCMS\Database::getRecordCount() does only seem to accept object<mysqli_result>, 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...
3598
            $rs = $this->getDatabase()->makeArray($rs);
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->makeArray($rs) on line 3598 can also be of type boolean; however, EvolutionCMS\Database::makeArray() does only seem to accept string|object<mysqli_result>, 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...
3599
            $internalKeyCount = array();
3600
            $deleteSids = '';
3601
            foreach ($rs as $row) {
3602
                if (!isset($internalKeyCount[$row['internalKey']])) {
3603
                    $internalKeyCount[$row['internalKey']] = 0;
3604
                }
3605
                $internalKeyCount[$row['internalKey']]++;
3606
3607
                if ($internalKeyCount[$row['internalKey']] > 1 && !in_array($row['sid'], $activeUserSids) && $row['lasthit'] < $validSessionTimeLimit) {
3608
                    $deleteSids .= $deleteSids == '' ? '' : ' OR ';
3609
                    $deleteSids .= "sid='{$row['sid']}'";
3610
                };
3611
3612
            }
3613
            if ($deleteSids) {
3614
                $this->getDatabase()->delete($this->getFullTableName('active_users'), $deleteSids);
3615
            }
3616
        }
3617
3618
    }
3619
3620
    /**
3621
     * Determines state of a locked element acc. to user-permissions
3622
     *
3623
     * @param $sid
3624
     * @return int $state States: 0=No display, 1=viewing this element, 2=locked, 3=show unlock-button
3625
     * @internal param int $internalKey : ID of User who locked actual element
3626
     */
3627
    public function determineLockState($sid)
3628
    {
3629
        $state = 0;
3630
        if ($this->hasPermission('display_locks')) {
3631
            if ($sid == $this->sid) {
3632
                $state = 1;
3633
            } else {
3634
                if ($this->hasPermission('remove_locks')) {
3635
                    $state = 3;
3636
                } else {
3637
                    $state = 2;
3638
                }
3639
            }
3640
        }
3641
        return $state;
3642
    }
3643
3644
    /**
3645
     * Locks an element
3646
     *
3647
     * @param int $type Types: 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3648
     * @param int $id Element- / Resource-id
3649
     * @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...
3650
     */
3651
    public function lockElement($type, $id)
0 ignored issues
show
Coding Style introduced by
lockElement uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
3652
    {
3653
        $userId = $this->isBackend() && $_SESSION['mgrInternalKey'] ? $_SESSION['mgrInternalKey'] : 0;
3654
        $type = (int)$type;
3655
        $id = (int)$id;
3656
        if (!$type || !$id || !$userId) {
3657
            return false;
3658
        }
3659
3660
        $sql = sprintf('REPLACE INTO %s (internalKey, elementType, elementId, lasthit, sid)
3661
                VALUES (%d, %d, %d, %d, \'%s\')', $this->getFullTableName('active_user_locks'), $userId, $type, $id, $this->time, $this->sid);
3662
        $this->getDatabase()->query($sql);
3663
    }
3664
3665
    /**
3666
     * Unlocks an element
3667
     *
3668
     * @param int $type Types: 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3669
     * @param int $id Element- / Resource-id
3670
     * @param bool $includeAllUsers true = Deletes not only own user-locks
3671
     * @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...
3672
     */
3673
    public function unlockElement($type, $id, $includeAllUsers = false)
0 ignored issues
show
Coding Style introduced by
unlockElement uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
3674
    {
3675
        $userId = $this->isBackend() && $_SESSION['mgrInternalKey'] ? $_SESSION['mgrInternalKey'] : 0;
3676
        $type = (int)$type;
3677
        $id = (int)$id;
3678
        if (!$type || !$id) {
3679
            return false;
3680
        }
3681
3682
        if (!$includeAllUsers) {
3683
            $sql = sprintf('DELETE FROM %s WHERE internalKey = %d AND elementType = %d AND elementId = %d;', $this->getFullTableName('active_user_locks'), $userId, $type, $id);
3684
        } else {
3685
            $sql = sprintf('DELETE FROM %s WHERE elementType = %d AND elementId = %d;', $this->getFullTableName('active_user_locks'), $type, $id);
3686
        }
3687
        $this->getDatabase()->query($sql);
3688
    }
3689
3690
    /**
3691
     * Updates table "active_user_sessions" with userid, lasthit, IP
3692
     */
3693
    public function updateValidatedUserSession()
0 ignored issues
show
Coding Style introduced by
updateValidatedUserSession uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
3694
    {
3695
        if (!$this->sid) {
3696
            return;
3697
        }
3698
3699
        // web users are stored with negative keys
3700
        $userId = $this->getLoginUserType() == 'manager' ? $this->getLoginUserID() : -$this->getLoginUserID();
3701
3702
        // Get user IP
3703 View Code Duplication
        if ($cip = getenv("HTTP_CLIENT_IP")) {
3704
            $ip = $cip;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ip. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
3705
        } elseif ($cip = getenv("HTTP_X_FORWARDED_FOR")) {
3706
            $ip = $cip;
3707
        } elseif ($cip = getenv("REMOTE_ADDR")) {
3708
            $ip = $cip;
3709
        } else {
3710
            $ip = "UNKNOWN";
3711
        }
3712
        $_SESSION['ip'] = $ip;
3713
3714
        $sql = sprintf('REPLACE INTO %s (internalKey, lasthit, ip, sid)
3715
            VALUES (%d, %d, \'%s\', \'%s\')', $this->getFullTableName('active_user_sessions'), $userId, $this->time, $ip, $this->sid);
3716
        $this->getDatabase()->query($sql);
3717
    }
3718
3719
    /**
3720
     * Add an a alert message to the system event log
3721
     *
3722
     * @param int $evtid Event ID
3723
     * @param int $type Types: 1 = information, 2 = warning, 3 = error
3724
     * @param string $msg Message to be logged
3725
     * @param string $source source of the event (module, snippet name, etc.)
3726
     *                       Default: Parser
3727
     */
3728
    public function logEvent($evtid, $type, $msg, $source = 'Parser')
0 ignored issues
show
Coding Style introduced by
logEvent uses the super-global variable $GLOBALS which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
logEvent uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
3729
    {
3730
        $msg = $this->getDatabase()->escape($msg);
3731
        if (strpos($GLOBALS['database_connection_charset'], 'utf8') === 0 && extension_loaded('mbstring')) {
3732
            $esc_source = mb_substr($source, 0, 50, "UTF-8");
3733
        } else {
3734
            $esc_source = substr($source, 0, 50);
3735
        }
3736
        $esc_source = $this->getDatabase()->escape($esc_source);
3737
3738
        $LoginUserID = $this->getLoginUserID();
3739
        if ($LoginUserID == '') {
3740
            $LoginUserID = 0;
3741
        }
3742
3743
        $usertype = $this->isFrontend() ? 1 : 0;
3744
        $evtid = (int)$evtid;
3745
        $type = (int)$type;
3746
3747
        // Types: 1 = information, 2 = warning, 3 = error
3748
        if ($type < 1) {
3749
            $type = 1;
3750
        } elseif ($type > 3) {
3751
            $type = 3;
3752
        }
3753
3754
        $this->getDatabase()->insert(array(
3755
            'eventid' => $evtid,
3756
            'type' => $type,
3757
            'createdon' => $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'],
3758
            'source' => $esc_source,
3759
            'description' => $msg,
3760
            'user' => $LoginUserID,
3761
            'usertype' => $usertype
3762
        ), $this->getFullTableName("event_log"));
3763
3764
        if (isset($this->config['send_errormail']) && $this->config['send_errormail'] !== '0') {
3765
            if ($this->config['send_errormail'] <= $type) {
3766
                $this->sendmail(array(
3767
                    'subject' => 'MODX System Error on ' . $this->config['site_name'],
3768
                    'body' => 'Source: ' . $source . ' - The details of the error could be seen in the MODX system events log.',
3769
                    'type' => 'text'
3770
                ));
3771
            }
3772
        }
3773
    }
3774
3775
    /**
3776
     * @param string|array $params
3777
     * @param string $msg
3778
     * @param array $files
3779
     * @return bool
3780
     * @throws \PHPMailer\PHPMailer\Exception
3781
     */
3782
    public function sendmail($params = array(), $msg = '', $files = array())
0 ignored issues
show
Coding Style introduced by
sendmail uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
3783
    {
3784
        if (\is_scalar($params)) {
3785
            if (strpos($params, '=') === false) {
3786
                if (strpos($params, '@') !== false) {
3787
                    $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...
Comprehensibility introduced by
Avoid variables with short names like $p. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
3788
                } else {
3789
                    $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...
3790
                }
3791
            } else {
3792
                $params_array = explode(',', $params);
3793
                foreach ($params_array as $k => $v) {
3794
                    $k = trim($k);
3795
                    $v = trim($v);
3796
                    $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...
3797
                }
3798
            }
3799
        } else {
3800
            $p = $params;
3801
            unset($params);
3802
        }
3803
        if (isset($p['sendto'])) {
3804
            $p['to'] = $p['sendto'];
3805
        }
3806
3807
        if (isset($p['to']) && preg_match('@^[0-9]+$@', $p['to'])) {
3808
            $userinfo = $this->getUserInfo($p['to']);
3809
            $p['to'] = $userinfo['email'];
3810
        }
3811
        if (isset($p['from']) && preg_match('@^[0-9]+$@', $p['from'])) {
3812
            $userinfo = $this->getUserInfo($p['from']);
3813
            $p['from'] = $userinfo['email'];
3814
            $p['fromname'] = $userinfo['username'];
3815
        }
3816
        if ($msg === '' && !isset($p['body'])) {
3817
            $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...
3818
        } elseif (is_string($msg) && 0 < strlen($msg)) {
3819
            $p['body'] = $msg;
3820
        }
3821
3822
        $sendto = (!isset($p['to'])) ? $this->config['emailsender'] : $p['to'];
3823
        $sendto = explode(',', $sendto);
3824
        foreach ($sendto as $address) {
3825
            list($name, $address) = $this->getMail()->address_split($address);
3826
            $this->getMail()->AddAddress($address, $name);
3827
        }
3828 View Code Duplication
        if (isset($p['cc'])) {
3829
            $p['cc'] = explode(',', $p['cc']);
3830
            foreach ($p['cc'] as $address) {
3831
                list($name, $address) = $this->getMail()->address_split($address);
3832
                $this->getMail()->AddCC($address, $name);
3833
            }
3834
        }
3835 View Code Duplication
        if (isset($p['bcc'])) {
3836
            $p['bcc'] = explode(',', $p['bcc']);
3837
            foreach ($p['bcc'] as $address) {
3838
                list($name, $address) = $this->getMail()->address_split($address);
3839
                $this->getMail()->AddBCC($address, $name);
3840
            }
3841
        }
3842
        if (isset($p['from']) && strpos($p['from'], '<') !== false && substr($p['from'], -1) === '>') {
3843
            list($p['fromname'], $p['from']) = $this->getMail()->address_split($p['from']);
3844
        }
3845
        $this->getMail()->setFrom(
3846
            isset($p['from']) ? $p['from'] : $this->config['emailsender'],
3847
            isset($p['fromname']) ? $p['fromname'] : $this->config['site_name']
3848
        );
3849
        $this->getMail()->Subject = (!isset($p['subject'])) ? $this->config['emailsubject'] : $p['subject'];
3850
        $this->getMail()->Body = $p['body'];
3851
        if (isset($p['type']) && $p['type'] === 'text') {
3852
            $this->getMail()->IsHTML(false);
3853
        }
3854
        if (!is_array($files)) {
3855
            $files = array();
3856
        }
3857
        foreach ($files as $f) {
3858
            if (file_exists(MODX_BASE_PATH . $f) && is_file(MODX_BASE_PATH . $f) && is_readable(MODX_BASE_PATH . $f)) {
3859
                $this->getMail()->AddAttachment(MODX_BASE_PATH . $f);
3860
            }
3861
        }
3862
        return $this->getMail()->send();
3863
    }
3864
3865
    /**
3866
     * @param string $target
3867
     * @param int $limit
3868
     * @param int $trim
3869
     */
3870
    public function rotate_log($target = 'event_log', $limit = 3000, $trim = 100)
0 ignored issues
show
Coding Style introduced by
This method is not in camel caps format.

This check looks for method names that are not written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection seeker becomes databaseConnectionSeeker.

Loading history...
3871
    {
3872
        if ($limit < $trim) {
3873
            $trim = $limit;
3874
        }
3875
3876
        $table_name = $this->getFullTableName($target);
3877
        $count = $this->getDatabase()->getValue($this->getDatabase()->select('COUNT(id)', $table_name));
0 ignored issues
show
Bug introduced by
It seems like $this->getDatabase()->se...OUNT(id)', $table_name) targeting EvolutionCMS\Database::select() can also be of type boolean; however, EvolutionCMS\Database::getValue() does only seem to accept object<mysqli_result>|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...
3878
        $over = $count - $limit;
3879
        if (0 < $over) {
3880
            $trim = ($over + $trim);
3881
            $this->getDatabase()->delete($table_name, '', '', $trim);
3882
        }
3883
        $this->getDatabase()->optimize($table_name);
3884
    }
3885
3886
    /**
3887
     * Returns true if we are currently in the manager backend
3888
     *
3889
     * @return boolean
3890
     */
3891
    public function isBackend()
3892
    {
3893
        return (defined('IN_MANAGER_MODE') && IN_MANAGER_MODE === true);
3894
    }
3895
3896
    /**
3897
     * Returns true if we are currently in the frontend
3898
     *
3899
     * @return boolean
3900
     */
3901
    public function isFrontend()
3902
    {
3903
        return ! $this->isBackend();
3904
    }
3905
3906
    /**
3907
     * Gets all child documents of the specified document, including those which are unpublished or deleted.
3908
     *
3909
     * @param int $id The Document identifier to start with
3910
     * @param string $sort Sort field
3911
     *                     Default: menuindex
3912
     * @param string $dir Sort direction, ASC and DESC is possible
3913
     *                    Default: ASC
3914
     * @param string $fields Default: id, pagetitle, description, parent, alias, menutitle
3915
     * @return array
3916
     */
3917 View Code Duplication
    public function getAllChildren($id = 0, $sort = 'menuindex', $dir = 'ASC', $fields = 'id, pagetitle, description, parent, alias, menutitle')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
Coding Style introduced by
getAllChildren uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
3918
    {
3919
3920
        $cacheKey = md5(print_r(func_get_args(), true));
3921
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3922
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3923
        }
3924
3925
        $tblsc = $this->getFullTableName("site_content");
3926
        $tbldg = $this->getFullTableName("document_groups");
3927
        // modify field names to use sc. table reference
3928
        $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3929
        $sort = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
3930
        // get document groups for current user
3931
        if ($docgrp = $this->getUserDocGroups()) {
3932
            $docgrp = implode(",", $docgrp);
3933
        }
3934
        // build query
3935
        $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
3936
        $result = $this->getDatabase()->select("DISTINCT {$fields}", "{$tblsc} sc
3937
                LEFT JOIN {$tbldg} dg on dg.document = sc.id", "sc.parent = '{$id}' AND ({$access}) GROUP BY sc.id", "{$sort} {$dir}");
3938
        $resourceArray = $this->getDatabase()->makeArray($result);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->getDatabase()->se....id", "{$sort} {$dir}") on line 3936 can also be of type boolean; however, EvolutionCMS\Database::makeArray() does only seem to accept string|object<mysqli_result>, 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...
3939
        $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
3940
        return $resourceArray;
3941
    }
3942
3943
    /**
3944
     * Gets all active child documents of the specified document, i.e. those which published and not deleted.
3945
     *
3946
     * @param int $id The Document identifier to start with
3947
     * @param string $sort Sort field
3948
     *                     Default: menuindex
3949
     * @param string $dir Sort direction, ASC and DESC is possible
3950
     *                    Default: ASC
3951
     * @param string $fields Default: id, pagetitle, description, parent, alias, menutitle
3952
     * @return array
3953
     */
3954 View Code Duplication
    public function getActiveChildren($id = 0, $sort = 'menuindex', $dir = 'ASC', $fields = 'id, pagetitle, description, parent, alias, menutitle')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
Coding Style introduced by
getActiveChildren uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
3955
    {
3956
        $cacheKey = md5(print_r(func_get_args(), true));
3957
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3958
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3959
        }
3960
3961
        $tblsc = $this->getFullTableName("site_content");
3962
        $tbldg = $this->getFullTableName("document_groups");
3963
3964
        // modify field names to use sc. table reference
3965
        $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3966
        $sort = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
3967
        // get document groups for current user
3968
        if ($docgrp = $this->getUserDocGroups()) {
3969
            $docgrp = implode(",", $docgrp);
3970
        }
3971
        // build query
3972
        $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
3973
        $result = $this->getDatabase()->select("DISTINCT {$fields}", "{$tblsc} sc
3974
                LEFT JOIN {$tbldg} dg on dg.document = sc.id", "sc.parent = '{$id}' AND sc.published=1 AND sc.deleted=0 AND ({$access}) GROUP BY sc.id", "{$sort} {$dir}");
3975
        $resourceArray = $this->getDatabase()->makeArray($result);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->getDatabase()->se....id", "{$sort} {$dir}") on line 3973 can also be of type boolean; however, EvolutionCMS\Database::makeArray() does only seem to accept string|object<mysqli_result>, 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...
3976
3977
        $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
3978
3979
        return $resourceArray;
3980
    }
3981
3982
    /**
3983
     * getDocumentChildren
3984
     * @version 1.1.1 (2014-02-19)
3985
     *
3986
     * @desc Returns the children of the selected document/folder as an associative array.
3987
     *
3988
     * @param $parentid {integer} - The parent document identifier. Default: 0 (site root).
3989
     * @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.
3990
     * @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.
3991
     * @param $fields {comma separated string; '*'} - Comma separated list of document fields to get. Default: '*' (all fields).
3992
     * @param $where {string} - Where condition in SQL style. Should include a leading 'AND '. Default: ''.
3993
     * @param $sort {comma separated string} - Should be a comma-separated list of field names on which to sort. Default: 'menuindex'.
3994
     * @param $dir {'ASC'; 'DESC'} - Sort direction, ASC and DESC is possible. Default: 'ASC'.
3995
     * @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).
3996
     *
3997
     * @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...
3998
     */
3999
    public function getDocumentChildren($parentid = 0, $published = 1, $deleted = 0, $fields = '*', $where = '', $sort = 'menuindex', $dir = 'ASC', $limit = '')
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...
Coding Style introduced by
getDocumentChildren uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
4000
    {
4001
4002
        $cacheKey = md5(print_r(func_get_args(), true));
4003
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
4004
            return $this->tmpCache[__FUNCTION__][$cacheKey];
4005
        }
4006
4007
        $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...
4008
        $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...
4009
4010
        if ($where != '') {
4011
            $where = 'AND ' . $where;
4012
        }
4013
4014
        // modify field names to use sc. table reference
4015
        $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
4016
        $sort = ($sort == '') ? '' : 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
4017
4018
        // get document groups for current user
4019
        if ($docgrp = $this->getUserDocGroups()) {
4020
            $docgrp = implode(',', $docgrp);
4021
        }
4022
4023
        // build query
4024
        $access = ($this->isFrontend() ? 'sc.privateweb=0' : '1="' . $_SESSION['mgrRole'] . '" OR sc.privatemgr=0') . (!$docgrp ? '' : ' OR dg.document_group IN (' . $docgrp . ')');
4025
4026
        $tblsc = $this->getFullTableName('site_content');
4027
        $tbldg = $this->getFullTableName('document_groups');
4028
4029
        $result = $this->getDatabase()->select("DISTINCT {$fields}", "{$tblsc} sc
4030
                LEFT JOIN {$tbldg} dg on dg.document = sc.id", "sc.parent = '{$parentid}' {$published} {$deleted} {$where} AND ({$access}) GROUP BY sc.id", ($sort ? "{$sort} {$dir}" : ""), $limit);
4031
4032
        $resourceArray = $this->getDatabase()->makeArray($result);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->getDatabase()->se...} {$dir}" : '', $limit) on line 4029 can also be of type boolean; however, EvolutionCMS\Database::makeArray() does only seem to accept string|object<mysqli_result>, 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...
4033
4034
        $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
4035
4036
        return $resourceArray;
4037
    }
4038
4039
    /**
4040
     * getDocuments
4041
     * @version 1.1.1 (2013-02-19)
4042
     *
4043
     * @desc Returns required documents (their fields).
4044
     *
4045
     * @param $ids {array; comma separated string} - Documents Ids to get. @required
4046
     * @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.
4047
     * @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.
4048
     * @param $fields {comma separated string; '*'} - Documents fields to get. Default: '*'.
4049
     * @param $where {string} - SQL WHERE clause. Default: ''.
4050
     * @param $sort {comma separated string} - A comma-separated list of field names to sort by. Default: 'menuindex'.
4051
     * @param $dir {'ASC'; 'DESC'} - Sorting direction. Default: 'ASC'.
4052
     * @param $limit {string} - SQL LIMIT (without 'LIMIT '). An empty string means no limit. Default: ''.
4053
     *
4054
     * @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...
4055
     */
4056
    public function getDocuments($ids = array(), $published = 1, $deleted = 0, $fields = '*', $where = '', $sort = 'menuindex', $dir = 'ASC', $limit = '')
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...
Coding Style introduced by
getDocuments uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
4057
    {
4058
4059
        $cacheKey = md5(print_r(func_get_args(), true));
4060
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
4061
            return $this->tmpCache[__FUNCTION__][$cacheKey];
4062
        }
4063
4064
        if (is_string($ids)) {
4065
            if (strpos($ids, ',') !== false) {
4066
                $ids = array_filter(array_map('intval', explode(',', $ids)));
4067
            } else {
4068
                $ids = array($ids);
4069
            }
4070
        }
4071
        if (count($ids) == 0) {
4072
            $this->tmpCache[__FUNCTION__][$cacheKey] = false;
4073
            return false;
4074
        } else {
4075
            // modify field names to use sc. table reference
4076
            $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
4077
            $sort = ($sort == '') ? '' : 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
4078
            if ($where != '') {
4079
                $where = 'AND ' . $where;
4080
            }
4081
4082
            $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...
4083
            $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...
4084
4085
            // get document groups for current user
4086
            if ($docgrp = $this->getUserDocGroups()) {
4087
                $docgrp = implode(',', $docgrp);
4088
            }
4089
4090
            $access = ($this->isFrontend() ? 'sc.privateweb=0' : '1="' . $_SESSION['mgrRole'] . '" OR sc.privatemgr=0') . (!$docgrp ? '' : ' OR dg.document_group IN (' . $docgrp . ')');
4091
4092
            $tblsc = $this->getFullTableName('site_content');
4093
            $tbldg = $this->getFullTableName('document_groups');
4094
4095
            $result = $this->getDatabase()->select("DISTINCT {$fields}", "{$tblsc} sc
4096
                    LEFT JOIN {$tbldg} dg on dg.document = sc.id", "(sc.id IN (" . implode(',', $ids) . ") {$published} {$deleted} {$where}) AND ({$access}) GROUP BY sc.id", ($sort ? "{$sort} {$dir}" : ""), $limit);
4097
4098
            $resourceArray = $this->getDatabase()->makeArray($result);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->getDatabase()->se...} {$dir}" : '', $limit) on line 4095 can also be of type boolean; however, EvolutionCMS\Database::makeArray() does only seem to accept string|object<mysqli_result>, 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...
4099
4100
            $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
4101
4102
            return $resourceArray;
4103
        }
4104
    }
4105
4106
    /**
4107
     * getDocument
4108
     * @version 1.0.1 (2014-02-19)
4109
     *
4110
     * @desc Returns required fields of a document.
4111
     *
4112
     * @param int $id {integer}
4113
     * - Id of a document which data has to be gained. @required
4114
     * @param string $fields {comma separated string; '*'}
4115
     * - Comma separated list of document fields to get. Default: '*'.
4116
     * @param int $published {0; 1; 'all'}
4117
     * - 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.
4118
     * @param int $deleted {0; 1; 'all'}
4119
     * - 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.
4120
     * @return bool {array; false} - Result array with fields or false.
4121
     * - Result array with fields or false.
4122
     */
4123 View Code Duplication
    public function getDocument($id = 0, $fields = '*', $published = 1, $deleted = 0)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
4124
    {
4125
        if ($id == 0) {
4126
            return false;
4127
        } else {
4128
            $docs = $this->getDocuments(array($id), $published, $deleted, $fields, '', '', '', 1);
4129
4130
            if ($docs != false) {
4131
                return $docs[0];
4132
            } else {
4133
                return false;
4134
            }
4135
        }
4136
    }
4137
4138
    /**
4139
     * @param string $field
4140
     * @param string $docid
4141
     * @return bool|mixed
4142
     */
4143
    public function getField($field = 'content', $docid = '')
4144
    {
4145
        if (empty($docid) && isset($this->documentIdentifier)) {
4146
            $docid = $this->documentIdentifier;
4147
        } elseif (!preg_match('@^[0-9]+$@', $docid)) {
4148
            $docid = $this->getIdFromAlias($docid);
4149
        }
4150
4151
        if (empty($docid)) {
4152
            return false;
4153
        }
4154
4155
        $cacheKey = md5(print_r(func_get_args(), true));
4156
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
4157
            return $this->tmpCache[__FUNCTION__][$cacheKey];
4158
        }
4159
4160
        $doc = $this->getDocumentObject('id', $docid);
4161
        if (is_array($doc[$field])) {
4162
            $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...
4163
            $content = $tvs[$field];
4164
        } else {
4165
            $content = $doc[$field];
4166
        }
4167
4168
        $this->tmpCache[__FUNCTION__][$cacheKey] = $content;
4169
4170
        return $content;
4171
    }
4172
4173
    /**
4174
     * Returns the page information as database row, the type of result is
4175
     * defined with the parameter $rowMode
4176
     *
4177
     * @param int $pageid The parent document identifier
4178
     *                    Default: -1 (no result)
4179
     * @param int $active Should we fetch only published and undeleted documents/resources?
4180
     *                     1 = yes, 0 = no
4181
     *                     Default: 1
4182
     * @param string $fields List of fields
4183
     *                       Default: id, pagetitle, description, alias
4184
     * @return boolean|array
4185
     */
4186
    public function getPageInfo($pageid = -1, $active = 1, $fields = 'id, pagetitle, description, alias')
0 ignored issues
show
Coding Style introduced by
getPageInfo uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
4187
    {
4188
4189
        $cacheKey = md5(print_r(func_get_args(), true));
4190
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
4191
            return $this->tmpCache[__FUNCTION__][$cacheKey];
4192
        }
4193
4194
        if ($pageid == 0) {
4195
            return false;
4196
        } else {
4197
            $tblsc = $this->getFullTableName("site_content");
4198
            $tbldg = $this->getFullTableName("document_groups");
4199
            $activeSql = $active == 1 ? "AND sc.published=1 AND sc.deleted=0" : "";
4200
            // modify field names to use sc. table reference
4201
            $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
4202
            // get document groups for current user
4203
            if ($docgrp = $this->getUserDocGroups()) {
4204
                $docgrp = implode(",", $docgrp);
4205
            }
4206
            $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
4207
            $result = $this->getDatabase()->select($fields, "{$tblsc} sc LEFT JOIN {$tbldg} dg on dg.document = sc.id", "(sc.id='{$pageid}' {$activeSql}) AND ({$access})", "", 1);
4208
            $pageInfo = $this->getDatabase()->getRow($result);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->getDatabase()->se...ND ({$access})", '', 1) on line 4207 can also be of type boolean; however, EvolutionCMS\Database::getRow() does only seem to accept object<mysqli_result>, 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...
4209
4210
            $this->tmpCache[__FUNCTION__][$cacheKey] = $pageInfo;
4211
4212
            return $pageInfo;
4213
        }
4214
    }
4215
4216
    /**
4217
     * Returns the parent document/resource of the given docid
4218
     *
4219
     * @param int $pid The parent docid. If -1, then fetch the current document/resource's parent
4220
     *                 Default: -1
4221
     * @param int $active Should we fetch only published and undeleted documents/resources?
4222
     *                     1 = yes, 0 = no
4223
     *                     Default: 1
4224
     * @param string $fields List of fields
4225
     *                       Default: id, pagetitle, description, alias
4226
     * @return boolean|array
4227
     */
4228
    public function getParent($pid = -1, $active = 1, $fields = 'id, pagetitle, description, alias, parent')
4229
    {
4230
        if ($pid == -1) {
4231
            $pid = $this->documentObject['parent'];
4232
            return ($pid == 0) ? false : $this->getPageInfo($pid, $active, $fields);
4233
        } else if ($pid == 0) {
4234
            return false;
4235
        } else {
4236
            // first get the child document
4237
            $child = $this->getPageInfo($pid, $active, "parent");
4238
            // now return the child's parent
4239
            $pid = ($child['parent']) ? $child['parent'] : 0;
4240
            return ($pid == 0) ? false : $this->getPageInfo($pid, $active, $fields);
4241
        }
4242
    }
4243
4244
    /**
4245
     * Returns the id of the current snippet.
4246
     *
4247
     * @return int
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|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...
4248
     */
4249
    public function getSnippetId()
4250
    {
4251
        if ($this->currentSnippet) {
4252
            $tbl = $this->getFullTableName("site_snippets");
4253
            $rs = $this->getDatabase()->select('id', $tbl, "name='" . $this->getDatabase()->escape($this->currentSnippet) . "'", '', 1);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rs. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
4254
            if ($snippetId = $this->getDatabase()->getValue($rs)) {
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se...Snippet) . '\'', '', 1) on line 4253 can also be of type boolean; however, EvolutionCMS\Database::getValue() does only seem to accept object<mysqli_result>|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...
4255
                return $snippetId;
4256
            }
4257
        }
4258
        return 0;
4259
    }
4260
4261
    /**
4262
     * Returns the name of the current snippet.
4263
     *
4264
     * @return string
4265
     */
4266
    public function getSnippetName()
4267
    {
4268
        return $this->currentSnippet;
4269
    }
4270
4271
    /**
4272
     * Clear the cache of MODX.
4273
     *
4274
     * @param string $type
4275
     * @param bool $report
4276
     * @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...
4277
     */
4278
    public function clearCache($type = '', $report = false)
4279
    {
4280
        $cache_dir = MODX_BASE_PATH . $this->getCacheFolder();
4281
        if (is_array($type)) {
4282
            foreach ($type as $_) {
4283
                $this->clearCache($_, $report);
4284
            }
4285
        } elseif ($type == 'full') {
4286
            $sync = new Cache();
4287
            $sync->setCachepath($cache_dir);
4288
            $sync->setReport($report);
4289
            $sync->emptyCache();
4290
        } elseif (preg_match('@^[1-9][0-9]*$@', $type)) {
4291
            $key = ($this->config['cache_type'] == 2) ? $this->makePageCacheKey($type) : $type;
4292
            $file_name = "docid_" . $key . "_*.pageCache.php";
4293
            $cache_path = $cache_dir . $file_name;
4294
            $files = glob($cache_path);
4295
            $files[] = $cache_dir . "docid_" . $key . ".pageCache.php";
4296
            foreach ($files as $file) {
4297
                if (!is_file($file)) {
4298
                    continue;
4299
                }
4300
                unlink($file);
4301
            }
4302
        } else {
4303
            $files = glob($cache_dir . '*');
4304
            foreach ($files as $file) {
4305
                $name = basename($file);
4306
                if (strpos($name, '.pageCache.php') === false) {
4307
                    continue;
4308
                }
4309
                if (!is_file($file)) {
4310
                    continue;
4311
                }
4312
                unlink($file);
4313
            }
4314
        }
4315
    }
4316
4317
    /**
4318
     * makeUrl
4319
     *
4320
     * @desc Create an URL for the given document identifier. The url prefix and postfix are used, when “friendly_url” is active.
4321
     *
4322
     * @param $id {integer} - The document identifier. @required
4323
     * @param string $alias {string}
4324
     * - The alias name for the document. Default: ''.
4325
     * @param string $args {string}
4326
     * - The paramaters to add to the URL. Default: ''.
4327
     * @param string $scheme {string}
4328
     * - With full as valus, the site url configuration is used. Default: ''.
4329
     * @return mixed|string {string} - Result URL.
4330
     * - Result URL.
4331
     */
4332
    public function makeUrl($id, $alias = '', $args = '', $scheme = '')
0 ignored issues
show
Coding Style introduced by
makeUrl uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
4333
    {
4334
        $url = '';
0 ignored issues
show
Unused Code introduced by
$url 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...
4335
        $virtualDir = isset($this->config['virtual_dir']) ? $this->config['virtual_dir'] : '';
4336
        $f_url_prefix = $this->config['friendly_url_prefix'];
4337
        $f_url_suffix = $this->config['friendly_url_suffix'];
4338
4339
        if (!is_numeric($id)) {
4340
            $this->messageQuit("`{$id}` is not numeric and may not be passed to makeUrl()");
4341
        }
4342
4343
        if ($args !== '') {
4344
            // add ? or & to $args if missing
4345
            $args = ltrim($args, '?&');
4346
            $_ = strpos($f_url_prefix, '?');
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $_. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
4347
4348
            if ($_ === false && $this->config['friendly_urls'] == 1) {
4349
                $args = "?{$args}";
4350
            } else {
4351
                $args = "&{$args}";
4352
            }
4353
        }
4354
4355
        if ($id != $this->config['site_start']) {
4356
            if ($this->config['friendly_urls'] == 1 && $alias == '') {
4357
                $alias = $id;
4358
                $alPath = '';
4359
4360
                if ($this->config['friendly_alias_urls'] == 1) {
4361
4362
                    if ($this->config['aliaslistingfolder'] == 1) {
4363
                        $al = $this->getAliasListing($id);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $al. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
4364
                    } else {
4365
                        $al = $this->aliasListing[$id];
4366
                    }
4367
4368
                    if ($al['isfolder'] === 1 && $this->config['make_folders'] === '1') {
4369
                        $f_url_suffix = '/';
4370
                    }
4371
4372
                    $alPath = !empty ($al['path']) ? $al['path'] . '/' : '';
4373
4374
                    if ($al && $al['alias']) {
4375
                        $alias = $al['alias'];
4376
                    }
4377
4378
                }
4379
4380
                $alias = $alPath . $f_url_prefix . $alias . $f_url_suffix;
4381
                $url = "{$alias}{$args}";
4382
            } else {
4383
                $url = "index.php?id={$id}{$args}";
4384
            }
4385
        } else {
4386
            $url = $args;
4387
        }
4388
4389
        $host = $this->config['base_url'];
4390
4391
        // check if scheme argument has been set
4392
        if ($scheme != '') {
4393
            // for backward compatibility - check if the desired scheme is different than the current scheme
4394
            if (is_numeric($scheme) && $scheme != $_SERVER['HTTPS']) {
4395
                $scheme = ($_SERVER['HTTPS'] ? 'http' : 'https');
4396
            }
4397
4398
            //TODO: check to make sure that $site_url incudes the url :port (e.g. :8080)
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
4399
            $host = $scheme == 'full' ? $this->config['site_url'] : $scheme . '://' . $_SERVER['HTTP_HOST'] . $host;
4400
        }
4401
4402
        //fix strictUrl by Bumkaka
4403
        if ($this->config['seostrict'] == '1') {
4404
            $url = $this->toAlias($url);
4405
        }
4406
4407
        if ($this->config['xhtml_urls']) {
4408
            $url = preg_replace("/&(?!amp;)/", "&amp;", $host . $virtualDir . $url);
4409
        } else {
4410
            $url = $host . $virtualDir . $url;
4411
        }
4412
4413
        $evtOut = $this->invokeEvent('OnMakeDocUrl', array(
4414
            'id' => $id,
4415
            'url' => $url
4416
        ));
4417
4418
        if (is_array($evtOut) && count($evtOut) > 0) {
4419
            $url = array_pop($evtOut);
4420
        }
4421
4422
        return $url;
4423
    }
4424
4425
    /**
4426
     * @param $id
4427
     * @return mixed
4428
     */
4429
    public function getAliasListing($id)
4430
    {
4431
        if (isset($this->aliasListing[$id])) {
4432
            $out = $this->aliasListing[$id];
4433
        } else {
4434
            $q = $this->getDatabase()->query("SELECT id,alias,isfolder,parent FROM " . $this->getFullTableName("site_content") . " WHERE id=" . (int)$id);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $q. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
4435
            if ($this->getDatabase()->getRecordCount($q) == '1') {
0 ignored issues
show
Bug introduced by
It seems like $q defined by $this->getDatabase()->qu...WHERE id=' . (int) $id) on line 4434 can also be of type boolean; however, EvolutionCMS\Database::getRecordCount() does only seem to accept object<mysqli_result>, 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...
4436
                $q = $this->getDatabase()->getRow($q);
0 ignored issues
show
Bug introduced by
It seems like $q defined by $this->getDatabase()->getRow($q) on line 4436 can also be of type boolean; however, EvolutionCMS\Database::getRow() does only seem to accept object<mysqli_result>, 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...
4437
                $this->aliasListing[$id] = array(
4438
                    'id' => (int)$q['id'],
4439
                    'alias' => $q['alias'] == '' ? $q['id'] : $q['alias'],
4440
                    'parent' => (int)$q['parent'],
4441
                    'isfolder' => (int)$q['isfolder'],
4442
                );
4443
                if ($this->aliasListing[$id]['parent'] > 0) {
4444
                    //fix alias_path_usage
4445
                    if ($this->config['use_alias_path'] == '1') {
4446
                        //&& $tmp['path'] != '' - fix error slash with epty path
4447
                        $tmp = $this->getAliasListing($this->aliasListing[$id]['parent']);
4448
                        $this->aliasListing[$id]['path'] = $tmp['path'] . ($tmp['alias_visible'] ? (($tmp['parent'] > 0 && $tmp['path'] != '') ? '/' : '') . $tmp['alias'] : '');
4449
                    } else {
4450
                        $this->aliasListing[$id]['path'] = '';
4451
                    }
4452
                }
4453
4454
                $out = $this->aliasListing[$id];
4455
            }
4456
        }
4457
        return $out;
0 ignored issues
show
Bug introduced by
The variable $out 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...
4458
    }
4459
4460
    /**
4461
     * Returns an entry from the config
4462
     *
4463
     * Note: most code accesses the config array directly and we will continue to support this.
4464
     *
4465
     * @param string $name
4466
     * @return bool|string
4467
     */
4468
    public function getConfig($name = '')
4469
    {
4470
        if (!empty ($this->config[$name])) {
4471
            return $this->config[$name];
4472
        } else {
4473
            return false;
4474
        }
4475
    }
4476
4477
    /**
4478
     * Returns the MODX version information as version, branch, release date and full application name.
4479
     *
4480
     * @param null $data
4481
     * @return array
4482
     */
4483
4484
    public function getVersionData($data = null)
4485
    {
4486
        $out = array();
4487
        if (empty($this->version) || !is_array($this->version)) {
4488
            //include for compatibility modx version < 1.0.10
4489
            include MODX_MANAGER_PATH . "includes/version.inc.php";
4490
            $this->version = array();
4491
            $this->version['version'] = isset($modx_version) ? $modx_version : '';
0 ignored issues
show
Bug introduced by
The variable $modx_version seems to never exist, and therefore isset should always return false. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
4492
            $this->version['branch'] = isset($modx_branch) ? $modx_branch : '';
0 ignored issues
show
Bug introduced by
The variable $modx_branch seems to never exist, and therefore isset should always return false. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
4493
            $this->version['release_date'] = isset($modx_release_date) ? $modx_release_date : '';
0 ignored issues
show
Bug introduced by
The variable $modx_release_date seems to never exist, and therefore isset should always return false. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
4494
            $this->version['full_appname'] = isset($modx_full_appname) ? $modx_full_appname : '';
0 ignored issues
show
Bug introduced by
The variable $modx_full_appname seems to never exist, and therefore isset should always return false. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
4495
            $this->version['new_version'] = isset($this->config['newversiontext']) ? $this->config['newversiontext'] : '';
4496
        }
4497
        return (!is_null($data) && is_array($this->version) && isset($this->version[$data])) ? $this->version[$data] : $this->version;
4498
    }
4499
4500
    /**
4501
     * Executes a snippet.
4502
     *
4503
     * @param string $snippetName
4504
     * @param array $params Default: Empty array
4505
     * @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...
4506
     */
4507
    public function runSnippet($snippetName, $params = array())
4508
    {
4509
        if (isset ($this->snippetCache[$snippetName])) {
4510
            $snippet = $this->snippetCache[$snippetName];
4511
            $properties = !empty($this->snippetCache[$snippetName . "Props"]) ? $this->snippetCache[$snippetName . "Props"] : '';
4512
        } else { // not in cache so let's check the db
4513
            $sql = "SELECT ss.`name`, ss.`snippet`, ss.`properties`, sm.properties as `sharedproperties` FROM " . $this->getFullTableName("site_snippets") . " as ss LEFT JOIN " . $this->getFullTableName('site_modules') . " as sm on sm.guid=ss.moduleguid WHERE ss.`name`='" . $this->getDatabase()->escape($snippetName) . "'  AND ss.disabled=0;";
4514
            $result = $this->getDatabase()->query($sql);
4515
            if ($this->getDatabase()->getRecordCount($result) == 1) {
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->getDatabase()->query($sql) on line 4514 can also be of type boolean; however, EvolutionCMS\Database::getRecordCount() does only seem to accept object<mysqli_result>, 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...
4516
                $row = $this->getDatabase()->getRow($result);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->getDatabase()->query($sql) on line 4514 can also be of type boolean; however, EvolutionCMS\Database::getRow() does only seem to accept object<mysqli_result>, 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...
4517
                $snippet = $this->snippetCache[$snippetName] = $row['snippet'];
4518
                $mergedProperties = array_merge($this->parseProperties($row['properties']), $this->parseProperties($row['sharedproperties']));
4519
                $properties = $this->snippetCache[$snippetName . "Props"] = json_encode($mergedProperties);
4520
            } else {
4521
                $snippet = $this->snippetCache[$snippetName] = "return false;";
4522
                $properties = $this->snippetCache[$snippetName . "Props"] = '';
4523
            }
4524
        }
4525
        // load default params/properties
4526
        $parameters = $this->parseProperties($properties, $snippetName, 'snippet');
4527
        $parameters = array_merge($parameters, $params);
4528
4529
        // run snippet
4530
        return $this->evalSnippet($snippet, $parameters);
4531
    }
4532
4533
    /**
4534
     * Returns the chunk content for the given chunk name
4535
     *
4536
     * @param string $chunkName
4537
     * @return boolean|string
4538
     */
4539
    public function getChunk($chunkName)
4540
    {
4541
        $out = null;
4542
        if (empty($chunkName)) {
4543
            return $out;
4544
        }
4545
        if (isset ($this->chunkCache[$chunkName])) {
4546
            $out = $this->chunkCache[$chunkName];
4547
        } else if (stripos($chunkName, '@FILE') === 0) {
4548
            $out = $this->chunkCache[$chunkName] = $this->atBindFileContent($chunkName);
4549
        } else {
4550
            $where = sprintf("`name`='%s' AND disabled=0", $this->getDatabase()->escape($chunkName));
4551
            $rs = $this->getDatabase()->select('snippet', '[+prefix+]site_htmlsnippets', $where);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rs. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
4552
            if ($this->getDatabase()->getRecordCount($rs) == 1) {
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se..._htmlsnippets', $where) on line 4551 can also be of type boolean; however, EvolutionCMS\Database::getRecordCount() does only seem to accept object<mysqli_result>, 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...
4553
                $row = $this->getDatabase()->getRow($rs);
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se..._htmlsnippets', $where) on line 4551 can also be of type boolean; however, EvolutionCMS\Database::getRow() does only seem to accept object<mysqli_result>, 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...
4554
                $out = $this->chunkCache[$chunkName] = $row['snippet'];
4555
            } else {
4556
                $out = $this->chunkCache[$chunkName] = null;
4557
            }
4558
        }
4559
        return $out;
4560
    }
4561
4562
    /**
4563
     * parseText
4564
     * @version 1.0 (2013-10-17)
4565
     *
4566
     * @desc Replaces placeholders in text with required values.
4567
     *
4568
     * @param string $tpl
4569
     * @param array $ph
4570
     * @param string $left
4571
     * @param string $right
4572
     * @param bool $execModifier
4573
     * @return string {string} - Parsed text.
4574
     * - Parsed text.
4575
     * @internal param $chunk {string} - String to parse. - String to parse. @required
4576
     * @internal param $chunkArr {array} - Array of values. Key — placeholder name, value — value. - Array of values. Key — placeholder name, value — value. @required
4577
     * @internal param $prefix {string} - Placeholders prefix. Default: '[+'. - Placeholders prefix. Default: '[+'.
4578
     * @internal param $suffix {string} - Placeholders suffix. Default: '+]'. - Placeholders suffix. Default: '+]'.
4579
     *
4580
     */
4581
    public function parseText($tpl = '', $ph = array(), $left = '[+', $right = '+]', $execModifier = true)
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ph. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
Coding Style introduced by
parseText uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
4582
    {
4583
        if (empty($ph) || empty($tpl)) {
4584
            return $tpl;
4585
        }
4586
4587 View Code Duplication
        if ($this->config['enable_at_syntax']) {
4588
            if (stripos($tpl, '<@LITERAL>') !== false) {
4589
                $tpl = $this->escapeLiteralTagsContent($tpl);
4590
            }
4591
        }
4592
4593
        $matches = $this->getTagsFromContent($tpl, $left, $right);
4594
        if (empty($matches)) {
4595
            return $tpl;
4596
        }
4597
4598
        foreach ($matches[1] as $i => $key) {
4599
4600
            if (strpos($key, ':') !== false && $execModifier) {
4601
                list($key, $modifiers) = $this->splitKeyAndFilter($key);
4602
            } else {
4603
                $modifiers = false;
4604
            }
4605
4606
            //          if(!isset($ph[$key])) continue;
0 ignored issues
show
Unused Code Comprehensibility introduced by
87% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
4607
            if (!array_key_exists($key, $ph)) {
4608
                continue;
4609
            } //NULL values must be saved in placeholders, if we got them from database string
4610
4611
            $value = $ph[$key];
4612
4613
            $s = &$matches[0][$i];
4614
            if ($modifiers !== false) {
4615
                if (strpos($modifiers, $left) !== false) {
4616
                    $modifiers = $this->parseText($modifiers, $ph, $left, $right);
4617
                }
4618
                $value = $this->applyFilter($value, $modifiers, $key);
4619
            }
4620 View Code Duplication
            if (strpos($tpl, $s) !== false) {
4621
                $tpl = str_replace($s, $value, $tpl);
4622
            } elseif($this->debug) {
4623
                $this->addLog('parseText parse error', $_SERVER['REQUEST_URI'] . $s, 2);
4624
            }
4625
        }
4626
4627
        return $tpl;
4628
    }
4629
4630
    /**
4631
     * parseChunk
4632
     * @version 1.1 (2013-10-17)
4633
     *
4634
     * @desc Replaces placeholders in a chunk with required values.
4635
     *
4636
     * @param $chunkName {string} - Name of chunk to parse. @required
4637
     * @param $chunkArr {array} - Array of values. Key — placeholder name, value — value. @required
4638
     * @param string $prefix {string}
4639
     * - Placeholders prefix. Default: '{'.
4640
     * @param string $suffix {string}
4641
     * - Placeholders suffix. Default: '}'.
4642
     * @return bool|mixed|string {string; false} - Parsed chunk or false if $chunkArr is not array.
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...
4643
     * - Parsed chunk or false if $chunkArr is not array.
4644
     */
4645
    public function parseChunk($chunkName, $chunkArr, $prefix = '{', $suffix = '}')
4646
    {
4647
        //TODO: Wouldn't it be more practical to return the contents of a chunk instead of false?
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
4648
        if (!is_array($chunkArr)) {
4649
            return false;
4650
        }
4651
4652
        return $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...
4653
    }
4654
4655
    /**
4656
     * getTpl
4657
     * get template for snippets
4658
     * @param $tpl {string}
4659
     * @return bool|string {string}
4660
     */
4661
    public function getTpl($tpl)
4662
    {
4663
        $template = $tpl;
4664
        if (preg_match("/^@([^:\s]+)[:\s]+(.+)$/s", trim($tpl), $match)) {
4665
            $command = strtoupper($match[1]);
4666
            $template = $match[2];
4667
        }
4668
        switch ($command) {
0 ignored issues
show
Bug introduced by
The variable $command 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...
4669
            case 'CODE':
4670
                break;
4671
            case 'FILE':
4672
                $template = file_get_contents(MODX_BASE_PATH . $template);
4673
                break;
4674
            case 'CHUNK':
4675
                $template = $this->getChunk($template);
4676
                break;
4677
            case 'DOCUMENT':
4678
                $doc = $this->getDocument($template, 'content', 'all');
4679
                $template = $doc['content'];
4680
                break;
4681
            case 'SELECT':
4682
                $this->getDatabase()->getValue($this->getDatabase()->query("SELECT {$template}"));
0 ignored issues
show
Bug introduced by
It seems like $this->getDatabase()->query("SELECT {$template}") targeting EvolutionCMS\Database::query() can also be of type boolean; however, EvolutionCMS\Database::getValue() does only seem to accept object<mysqli_result>|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...
4683
                break;
4684
            default:
4685
                if (!($template = $this->getChunk($tpl))) {
4686
                    $template = $tpl;
4687
                }
4688
        }
4689
        return $template;
4690
    }
4691
4692
    /**
4693
     * Returns the timestamp in the date format defined in $this->config['datetime_format']
4694
     *
4695
     * @param int $timestamp Default: 0
4696
     * @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.
4697
     * @return string
4698
     */
4699
    public function toDateFormat($timestamp = 0, $mode = '')
4700
    {
4701
        $timestamp = trim($timestamp);
4702
        if ($mode !== 'formatOnly' && empty($timestamp)) {
4703
            return '-';
4704
        }
4705
        $timestamp = (int)$timestamp;
4706
4707
        switch ($this->config['datetime_format']) {
4708
            case 'YYYY/mm/dd':
4709
                $dateFormat = '%Y/%m/%d';
4710
                break;
4711
            case 'dd-mm-YYYY':
4712
                $dateFormat = '%d-%m-%Y';
4713
                break;
4714
            case 'mm/dd/YYYY':
4715
                $dateFormat = '%m/%d/%Y';
4716
                break;
4717
            /*
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
4718
            case 'dd-mmm-YYYY':
4719
                $dateFormat = '%e-%b-%Y';
4720
                break;
4721
            */
4722
        }
4723
4724
        if (empty($mode)) {
4725
            $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...
4726
        } elseif ($mode == 'dateOnly') {
4727
            $strTime = strftime($dateFormat, $timestamp);
4728
        } elseif ($mode == 'formatOnly') {
4729
            $strTime = $dateFormat;
4730
        }
4731
        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...
4732
    }
4733
4734
    /**
4735
     * Make a timestamp from a string corresponding to the format in $this->config['datetime_format']
4736
     *
4737
     * @param string $str
4738
     * @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...
4739
     */
4740
    public function toTimeStamp($str)
4741
    {
4742
        $str = trim($str);
4743
        if (empty($str)) {
4744
            return '';
4745
        }
4746
4747
        switch ($this->config['datetime_format']) {
4748 View Code Duplication
            case 'YYYY/mm/dd':
4749
                if (!preg_match('/^[0-9]{4}\/[0-9]{2}\/[0-9]{2}[0-9 :]*$/', $str)) {
4750
                    return '';
4751
                }
4752
                list ($Y, $m, $d, $H, $M, $S) = sscanf($str, '%4d/%2d/%2d %2d:%2d:%2d');
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $Y. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
Comprehensibility introduced by
Avoid variables with short names like $m. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
Comprehensibility introduced by
Avoid variables with short names like $d. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
Comprehensibility introduced by
Avoid variables with short names like $H. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
Comprehensibility introduced by
Avoid variables with short names like $M. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
Comprehensibility introduced by
Avoid variables with short names like $S. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
4753
                break;
4754 View Code Duplication
            case 'dd-mm-YYYY':
4755
                if (!preg_match('/^[0-9]{2}-[0-9]{2}-[0-9]{4}[0-9 :]*$/', $str)) {
4756
                    return '';
4757
                }
4758
                list ($d, $m, $Y, $H, $M, $S) = sscanf($str, '%2d-%2d-%4d %2d:%2d:%2d');
4759
                break;
4760 View Code Duplication
            case 'mm/dd/YYYY':
4761
                if (!preg_match('/^[0-9]{2}\/[0-9]{2}\/[0-9]{4}[0-9 :]*$/', $str)) {
4762
                    return '';
4763
                }
4764
                list ($m, $d, $Y, $H, $M, $S) = sscanf($str, '%2d/%2d/%4d %2d:%2d:%2d');
4765
                break;
4766
            /*
0 ignored issues
show
Unused Code Comprehensibility introduced by
65% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
4767
            case 'dd-mmm-YYYY':
4768
                if (!preg_match('/^[0-9]{2}-[0-9a-z]+-[0-9]{4}[0-9 :]*$/i', $str)) {return '';}
4769
                list ($m, $d, $Y, $H, $M, $S) = sscanf($str, '%2d-%3s-%4d %2d:%2d:%2d');
4770
                break;
4771
            */
4772
        }
4773
        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...
4774
            $H = 0;
4775
            $M = 0;
4776
            $S = 0;
4777
        }
4778
        $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...
4779
        $timeStamp = (int)$timeStamp;
4780
        return $timeStamp;
4781
    }
4782
4783
    /**
4784
     * Get the TVs of a document's children. Returns an array where each element represents one child doc.
4785
     *
4786
     * Ignores deleted children. Gets all children - there is no where clause available.
4787
     *
4788
     * @param int $parentid The parent docid
4789
     *                 Default: 0 (site root)
4790
     * @param array $tvidnames . Which TVs to fetch - Can relate to the TV ids in the db (array elements should be numeric only)
4791
     *                                               or the TV names (array elements should be names only)
4792
     *                      Default: Empty array
4793
     * @param int $published Whether published or unpublished documents are in the result
4794
     *                      Default: 1
4795
     * @param string $docsort How to sort the result array (field)
4796
     *                      Default: menuindex
4797
     * @param ASC|string $docsortdir How to sort the result array (direction)
4798
     *                      Default: ASC
4799
     * @param string $tvfields Fields to fetch from site_tmplvars, default '*'
4800
     *                      Default: *
4801
     * @param string $tvsort How to sort each element of the result array i.e. how to sort the TVs (field)
4802
     *                      Default: rank
4803
     * @param string $tvsortdir How to sort each element of the result array i.e. how to sort the TVs (direction)
4804
     *                      Default: ASC
4805
     * @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...
4806
     */
4807
    public function getDocumentChildrenTVars($parentid = 0, $tvidnames = array(), $published = 1, $docsort = "menuindex", $docsortdir = "ASC", $tvfields = "*", $tvsort = "rank", $tvsortdir = "ASC")
4808
    {
4809
        $docs = $this->getDocumentChildren($parentid, $published, 0, '*', '', $docsort, $docsortdir);
4810
        if (!$docs) {
4811
            return false;
4812
        } else {
4813
            $result = array();
4814
            // get user defined template variables
4815
            if ($tvfields) {
4816
                $_ = array_filter(array_map('trim', explode(',', $tvfields)));
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $_. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
4817
                foreach ($_ as $i => $v) {
4818
                    if ($v === 'value') {
4819
                        unset($_[$i]);
4820
                    } else {
4821
                        $_[$i] = 'tv.' . $v;
4822
                    }
4823
                }
4824
                $fields = implode(',', $_);
4825
            } else {
4826
                $fields = "tv.*";
4827
            }
4828
4829
            if ($tvsort != '') {
4830
                $tvsort = 'tv.' . implode(',tv.', array_filter(array_map('trim', explode(',', $tvsort))));
4831
            }
4832 View Code Duplication
            if ($tvidnames == "*") {
4833
                $query = "tv.id<>0";
4834
            } else {
4835
                $query = (is_numeric($tvidnames[0]) ? "tv.id" : "tv.name") . " IN ('" . implode("','", $tvidnames) . "')";
4836
            }
4837
4838
            $this->getUserDocGroups();
4839
4840
            foreach ($docs as $doc) {
4841
4842
                $docid = $doc['id'];
4843
4844
                $rs = $this->getDatabase()->select("{$fields}, IF(tvc.value!='',tvc.value,tv.default_text) as value ", "[+prefix+]site_tmplvars tv
4845
                        INNER JOIN [+prefix+]site_tmplvar_templates tvtpl ON tvtpl.tmplvarid = tv.id
4846
                        LEFT JOIN [+prefix+]site_tmplvar_contentvalues tvc ON tvc.tmplvarid=tv.id AND tvc.contentid='{$docid}'", "{$query} AND tvtpl.templateid = '{$doc['template']}'", ($tvsort ? "{$tvsort} {$tvsortdir}" : ""));
4847
                $tvs = $this->getDatabase()->makeArray($rs);
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se...rt} {$tvsortdir}" : '') on line 4844 can also be of type boolean; however, EvolutionCMS\Database::makeArray() does only seem to accept string|object<mysqli_result>, 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...
4848
4849
                // get default/built-in template variables
4850
                ksort($doc);
4851
                foreach ($doc as $key => $value) {
4852
                    if ($tvidnames == '*' || in_array($key, $tvidnames)) {
4853
                        $tvs[] = array('name' => $key, 'value' => $value);
4854
                    }
4855
                }
4856
                if (is_array($tvs) && count($tvs)) {
4857
                    $result[] = $tvs;
4858
                }
4859
            }
4860
            return $result;
4861
        }
4862
    }
4863
4864
    /**
4865
     * getDocumentChildrenTVarOutput
4866
     * @version 1.1 (2014-02-19)
4867
     *
4868
     * @desc Returns an array where each element represents one child doc and contains the result from getTemplateVarOutput().
4869
     *
4870
     * @param int $parentid {integer}
4871
     * - Id of parent document. Default: 0 (site root).
4872
     * @param array $tvidnames {array; '*'}
4873
     * - Which TVs to fetch. In the form expected by getTemplateVarOutput(). Default: array().
4874
     * @param int $published {0; 1; 'all'}
4875
     * - 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.
4876
     * @param string $sortBy {string}
4877
     * - How to sort the result array (field). Default: 'menuindex'.
4878
     * @param string $sortDir {'ASC'; 'DESC'}
4879
     * - How to sort the result array (direction). Default: 'ASC'.
4880
     * @param string $where {string}
4881
     * - SQL WHERE condition (use only document fields, not TV). Default: ''.
4882
     * @param string $resultKey {string; false}
4883
     * - Field, which values are keys into result array. Use the “false”, that result array keys just will be numbered. Default: 'id'.
4884
     * @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...
4885
     * - Result array, or false.
4886
     */
4887
    public function getDocumentChildrenTVarOutput($parentid = 0, $tvidnames = array(), $published = 1, $sortBy = 'menuindex', $sortDir = 'ASC', $where = '', $resultKey = 'id')
4888
    {
4889
        $docs = $this->getDocumentChildren($parentid, $published, 0, 'id', $where, $sortBy, $sortDir);
4890
4891
        if (!$docs) {
4892
            return false;
4893
        } else {
4894
            $result = array();
4895
4896
            $unsetResultKey = false;
4897
4898
            if ($resultKey !== false) {
4899
                if (is_array($tvidnames)) {
4900
                    if (count($tvidnames) != 0 && !in_array($resultKey, $tvidnames)) {
4901
                        $tvidnames[] = $resultKey;
4902
                        $unsetResultKey = true;
4903
                    }
4904
                } else if ($tvidnames != '*' && $tvidnames != $resultKey) {
4905
                    $tvidnames = array($tvidnames, $resultKey);
4906
                    $unsetResultKey = true;
4907
                }
4908
            }
4909
4910
            for ($i = 0; $i < count($docs); $i++) {
4911
                $tvs = $this->getTemplateVarOutput($tvidnames, $docs[$i]['id'], $published);
4912
4913
                if ($tvs) {
4914
                    if ($resultKey !== false && array_key_exists($resultKey, $tvs)) {
4915
                        $result[$tvs[$resultKey]] = $tvs;
4916
4917
                        if ($unsetResultKey) {
4918
                            unset($result[$tvs[$resultKey]][$resultKey]);
4919
                        }
4920
                    } else {
4921
                        $result[] = $tvs;
4922
                    }
4923
                }
4924
            }
4925
4926
            return $result;
4927
        }
4928
    }
4929
4930
    /**
4931
     * Modified by Raymond for TV - Orig Modified by Apodigm - DocVars
4932
     * Returns a single site_content field or TV record from the db.
4933
     *
4934
     * If a site content field the result is an associative array of 'name' and 'value'.
4935
     *
4936
     * If a TV the result is an array representing a db row including the fields specified in $fields.
4937
     *
4938
     * @param string $idname Can be a TV id or name
4939
     * @param string $fields Fields to fetch from site_tmplvars. Default: *
4940
     * @param string|type $docid Docid. Defaults to empty string which indicates the current document.
4941
     * @param int $published Whether published or unpublished documents are in the result
4942
     *                        Default: 1
4943
     * @return bool
4944
     */
4945 View Code Duplication
    public function getTemplateVar($idname = "", $fields = "*", $docid = "", $published = 1)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
4946
    {
4947
        if ($idname == "") {
4948
            return false;
4949
        } else {
4950
            $result = $this->getTemplateVars(array($idname), $fields, $docid, $published, "", ""); //remove sorting for speed
4951
            return ($result != false) ? $result[0] : false;
4952
        }
4953
    }
4954
4955
    /**
4956
     * getTemplateVars
4957
     * @version 1.0.1 (2014-02-19)
4958
     *
4959
     * @desc Returns an array of site_content field fields and/or TV records from the db.
4960
     * Elements representing a site content field consist of an associative array of 'name' and 'value'.
4961
     * Elements representing a TV consist of an array representing a db row including the fields specified in $fields.
4962
     *
4963
     * @param $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
4964
     * @param $fields {comma separated string; '*'} - Fields names in the TV table of MODx database. Default: '*'
4965
     * @param $docid {integer; ''} - Id of a document to get. Default: an empty string which indicates the current document.
4966
     * @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.
4967
     * @param $sort {comma separated string} - Fields of the TV table to sort by. Default: 'rank'.
4968
     * @param $dir {'ASC'; 'DESC'} - How to sort the result array (direction). Default: 'ASC'.
4969
     *
4970
     * @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...
4971
     */
4972
    public function getTemplateVars($idnames = array(), $fields = '*', $docid = '', $published = 1, $sort = 'rank', $dir = 'ASC')
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...
4973
    {
4974
        $cacheKey = md5(print_r(func_get_args(), true));
4975
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
4976
            return $this->tmpCache[__FUNCTION__][$cacheKey];
4977
        }
4978
4979
        if (($idnames != '*' && !is_array($idnames)) || empty($idnames) ) {
4980
            return false;
4981
        } else {
4982
4983
            // get document record
4984
            if ($docid == '') {
4985
                $docid = $this->documentIdentifier;
4986
                $docRow = $this->documentObject;
4987
            } else {
4988
                $docRow = $this->getDocument($docid, '*', $published);
4989
4990
                if (!$docRow) {
4991
                    $this->tmpCache[__FUNCTION__][$cacheKey] = false;
4992
                    return false;
4993
                }
4994
            }
4995
4996
            // get user defined template variables
4997
            $fields = ($fields == '') ? 'tv.*' : 'tv.' . implode(',tv.', array_filter(array_map('trim', explode(',', $fields))));
4998
            $sort = ($sort == '') ? '' : 'tv.' . implode(',tv.', array_filter(array_map('trim', explode(',', $sort))));
4999
5000 View Code Duplication
            if ($idnames == '*') {
5001
                $query = 'tv.id<>0';
5002
            } else {
5003
                $query = (is_numeric($idnames[0]) ? 'tv.id' : 'tv.name') . " IN ('" . implode("','", $idnames) . "')";
5004
            }
5005
5006
            $rs = $this->getDatabase()->select("{$fields}, IF(tvc.value != '', tvc.value, tv.default_text) as value", $this->getFullTableName('site_tmplvars') . " tv
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rs. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
5007
                    INNER JOIN " . $this->getFullTableName('site_tmplvar_templates') . " tvtpl ON tvtpl.tmplvarid = tv.id
5008
                    LEFT JOIN " . $this->getFullTableName('site_tmplvar_contentvalues') . " tvc ON tvc.tmplvarid=tv.id AND tvc.contentid = '{$docid}'", "{$query} AND tvtpl.templateid = '{$docRow['template']}'", ($sort ? "{$sort} {$dir}" : ""));
5009
5010
            $result = $this->getDatabase()->makeArray($rs);
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se... "{$sort} {$dir}" : '') on line 5006 can also be of type boolean; however, EvolutionCMS\Database::makeArray() does only seem to accept string|object<mysqli_result>, 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...
5011
5012
            // get default/built-in template variables
5013
            if(is_array($docRow)){
5014
                ksort($docRow);
5015
5016
                foreach ($docRow as $key => $value) {
5017
                    if ($idnames == '*' || in_array($key, $idnames)) {
5018
                        array_push($result, array(
5019
                            'name' => $key,
5020
                            'value' => $value
5021
                        ));
5022
                    }
5023
                }
5024
            }
5025
5026
            $this->tmpCache[__FUNCTION__][$cacheKey] = $result;
5027
5028
            return $result;
5029
        }
5030
    }
5031
5032
    /**
5033
     * getTemplateVarOutput
5034
     * @version 1.0.1 (2014-02-19)
5035
     *
5036
     * @desc Returns an associative array containing TV rendered output values.
5037
     *
5038
     * @param array $idnames {array; '*'}
5039
     * - 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
5040
     * @param string $docid {integer; ''}
5041
     * - Id of a document to get. Default: an empty string which indicates the current document.
5042
     * @param int $published {0; 1; 'all'}
5043
     * - 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.
5044
     * @param string $sep {string}
5045
     * - Separator that is used while concatenating in getTVDisplayFormat(). Default: ''.
5046
     * @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...
5047
     * - Result array, or false.
5048
     */
5049
    public function getTemplateVarOutput($idnames = array(), $docid = '', $published = 1, $sep = '')
5050
    {
5051
        if (is_array($idnames) && empty($idnames) ) {
5052
            return false;
5053
        } else {
5054
            $output = array();
5055
            $vars = ($idnames == '*' || is_array($idnames)) ? $idnames : array($idnames);
5056
5057
            $docid = (int)$docid > 0 ? (int)$docid : $this->documentIdentifier;
5058
            // remove sort for speed
5059
            $result = $this->getTemplateVars($vars, '*', $docid, $published, '', '');
5060
5061
            if ($result == false) {
5062
                return false;
5063
            } else {
5064
                for ($i = 0; $i < count($result); $i++) {
5065
                    $row = $result[$i];
5066
5067
                    if (!isset($row['id']) or !$row['id']) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
5068
                        $output[$row['name']] = $row['value'];
5069
                    } else {
5070
                        $output[$row['name']] = getTVDisplayFormat($row['name'], $row['value'], $row['display'], $row['display_params'], $row['type'], $docid, $sep);
5071
                    }
5072
                }
5073
5074
                return $output;
5075
            }
5076
        }
5077
    }
5078
5079
    /**
5080
     * Returns the full table name based on db settings
5081
     *
5082
     * @param string $tbl Table name
5083
     * @return string Table name with prefix
5084
     */
5085
    public function getFullTableName($tbl)
5086
    {
5087
        return $this->getDatabase()->config['dbase'] . ".`" . $this->getDatabase()->config['table_prefix'] . $tbl . "`";
5088
    }
5089
5090
    /**
5091
     * Returns the placeholder value
5092
     *
5093
     * @param string $name Placeholder name
5094
     * @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...
5095
     */
5096
    public function getPlaceholder($name)
5097
    {
5098
        return isset($this->placeholders[$name]) ? $this->placeholders[$name] : null;
5099
    }
5100
5101
    /**
5102
     * Sets a value for a placeholder
5103
     *
5104
     * @param string $name The name of the placeholder
5105
     * @param string $value The value of the placeholder
5106
     */
5107
    public function setPlaceholder($name, $value)
5108
    {
5109
        $this->placeholders[$name] = $value;
5110
    }
5111
5112
    /**
5113
     * Set placeholders en masse via an array or object.
5114
     *
5115
     * @param object|array $subject
5116
     * @param string $prefix
5117
     */
5118
    public function toPlaceholders($subject, $prefix = '')
5119
    {
5120
        if (is_object($subject)) {
5121
            $subject = get_object_vars($subject);
5122
        }
5123
        if (is_array($subject)) {
5124
            foreach ($subject as $key => $value) {
5125
                $this->toPlaceholder($key, $value, $prefix);
5126
            }
5127
        }
5128
    }
5129
5130
    /**
5131
     * For use by toPlaceholders(); For setting an array or object element as placeholder.
5132
     *
5133
     * @param string $key
5134
     * @param object|array $value
5135
     * @param string $prefix
5136
     */
5137
    public function toPlaceholder($key, $value, $prefix = '')
5138
    {
5139
        if (is_array($value) || is_object($value)) {
5140
            $this->toPlaceholders($value, "{$prefix}{$key}.");
5141
        } else {
5142
            $this->setPlaceholder("{$prefix}{$key}", $value);
5143
        }
5144
    }
5145
5146
    /**
5147
     * Returns the manager relative URL/path with respect to the site root.
5148
     *
5149
     * @global string $base_url
5150
     * @return string The complete URL to the manager folder
5151
     */
5152
    public function getManagerPath()
5153
    {
5154
        return MODX_MANAGER_URL;
5155
    }
5156
5157
    /**
5158
     * Returns the cache relative URL/path with respect to the site root.
5159
     *
5160
     * @global string $base_url
5161
     * @return string The complete URL to the cache folder
5162
     */
5163
    public function getCachePath()
5164
    {
5165
        global $base_url;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
5166
        $pth = $base_url . $this->getCacheFolder();
5167
        return $pth;
5168
    }
5169
5170
    /**
5171
     * Sends a message to a user's message box.
5172
     *
5173
     * @param string $type Type of the message
5174
     * @param string $to The recipient of the message
5175
     * @param string $from The sender of the message
5176
     * @param string $subject The subject of the message
5177
     * @param string $msg The message body
5178
     * @param int $private Whether it is a private message, or not
5179
     *                     Default : 0
5180
     */
5181
    public function sendAlert($type, $to, $from, $subject, $msg, $private = 0)
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $to. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
Coding Style introduced by
sendAlert uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
5182
    {
5183
        $private = ($private) ? 1 : 0;
5184 View Code Duplication
        if (!is_numeric($to)) {
5185
            // Query for the To ID
5186
            $rs = $this->getDatabase()->select('id', $this->getFullTableName("manager_users"), "username='{$to}'");
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rs. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
5187
            $to = $this->getDatabase()->getValue($rs);
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se...'), "username='{$to}'") on line 5186 can also be of type boolean; however, EvolutionCMS\Database::getValue() does only seem to accept object<mysqli_result>|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...
5188
        }
5189 View Code Duplication
        if (!is_numeric($from)) {
5190
            // Query for the From ID
5191
            $rs = $this->getDatabase()->select('id', $this->getFullTableName("manager_users"), "username='{$from}'");
5192
            $from = $this->getDatabase()->getValue($rs);
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se..., "username='{$from}'") on line 5191 can also be of type boolean; however, EvolutionCMS\Database::getValue() does only seem to accept object<mysqli_result>|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...
5193
        }
5194
        // insert a new message into user_messages
5195
        $this->getDatabase()->insert(array(
5196
            'type' => $type,
5197
            'subject' => $subject,
5198
            'message' => $msg,
5199
            'sender' => $from,
5200
            'recipient' => $to,
5201
            'private' => $private,
5202
            'postdate' => $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'],
5203
            'messageread' => 0,
5204
        ), $this->getFullTableName('user_messages'));
5205
    }
5206
5207
    /**
5208
     * Returns current user id.
5209
     *
5210
     * @param string $context . Default is an empty string which indicates the method should automatically pick 'web (frontend) or 'mgr' (backend)
5211
     * @return string
5212
     */
5213 View Code Duplication
    public function getLoginUserID($context = '')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
Coding Style introduced by
getLoginUserID uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
5214
    {
5215
        $out = false;
5216
5217
        if (!empty($context)) {
5218
            if (is_scalar($context) && isset($_SESSION[$context . 'Validated'])) {
5219
                $out = $_SESSION[$context . 'InternalKey'];
5220
            }
5221
        } else {
5222
            switch (true) {
5223
                case ($this->isFrontend() && isset ($_SESSION['webValidated'])): {
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
5224
                    $out = $_SESSION['webInternalKey'];
5225
                    break;
5226
                }
5227
                case ($this->isBackend() && isset ($_SESSION['mgrValidated'])): {
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
5228
                    $out = $_SESSION['mgrInternalKey'];
5229
                    break;
5230
                }
5231
            }
5232
        }
5233
        return $out;
5234
    }
5235
5236
    /**
5237
     * Returns current user name
5238
     *
5239
     * @param string $context . Default is an empty string which indicates the method should automatically pick 'web (frontend) or 'mgr' (backend)
5240
     * @return string
5241
     */
5242 View Code Duplication
    public function getLoginUserName($context = '')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
Coding Style introduced by
getLoginUserName uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
5243
    {
5244
        $out = false;
5245
5246
        if (!empty($context)) {
5247
            if (is_scalar($context) && isset($_SESSION[$context . 'Validated'])) {
5248
                $out = $_SESSION[$context . 'Shortname'];
5249
            }
5250
        } else {
5251
            switch (true) {
5252
                case ($this->isFrontend() && isset ($_SESSION['webValidated'])): {
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
5253
                    $out = $_SESSION['webShortname'];
5254
                    break;
5255
                }
5256
                case ($this->isBackend() && isset ($_SESSION['mgrValidated'])): {
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
5257
                    $out = $_SESSION['mgrShortname'];
5258
                    break;
5259
                }
5260
            }
5261
        }
5262
        return $out;
5263
    }
5264
5265
    /**
5266
     * Returns current login user type - web or manager
5267
     *
5268
     * @return string
5269
     */
5270
    public function getLoginUserType()
0 ignored issues
show
Coding Style introduced by
getLoginUserType uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
5271
    {
5272
        if ($this->isFrontend() && isset ($_SESSION['webValidated'])) {
5273
            return 'web';
5274
        } elseif ($this->isBackend() && isset ($_SESSION['mgrValidated'])) {
5275
            return 'manager';
5276
        } else {
5277
            return '';
5278
        }
5279
    }
5280
5281
    /**
5282
     * Returns a user info record for the given manager user
5283
     *
5284
     * @param int $uid
5285
     * @return boolean|string
5286
     */
5287
    public function getUserInfo($uid)
5288
    {
5289
        if (isset($this->tmpCache[__FUNCTION__][$uid])) {
5290
            return $this->tmpCache[__FUNCTION__][$uid];
5291
        }
5292
5293
        $from = '[+prefix+]manager_users mu INNER JOIN [+prefix+]user_attributes mua ON mua.internalkey=mu.id';
5294
        $where = sprintf("mu.id='%s'", $this->getDatabase()->escape($uid));
5295
        $rs = $this->getDatabase()->select('mu.username, mu.password, mua.*', $from, $where, '', 1);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rs. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
5296
5297
        if (!$this->getDatabase()->getRecordCount($rs)) {
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se..., $from, $where, '', 1) on line 5295 can also be of type boolean; however, EvolutionCMS\Database::getRecordCount() does only seem to accept object<mysqli_result>, 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...
5298
            return $this->tmpCache[__FUNCTION__][$uid] = false;
5299
        }
5300
5301
        $row = $this->getDatabase()->getRow($rs);
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se..., $from, $where, '', 1) on line 5295 can also be of type boolean; however, EvolutionCMS\Database::getRow() does only seem to accept object<mysqli_result>, 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...
5302 View Code Duplication
        if (!isset($row['usertype']) || !$row['usertype']) {
5303
            $row['usertype'] = 'manager';
5304
        }
5305
5306
        $this->tmpCache[__FUNCTION__][$uid] = $row;
5307
5308
        return $row;
5309
    }
5310
5311
    /**
5312
     * Returns a record for the web user
5313
     *
5314
     * @param int $uid
5315
     * @return boolean|string
5316
     */
5317
    public function getWebUserInfo($uid)
5318
    {
5319
        $rs = $this->getDatabase()->select('wu.username, wu.password, wua.*', $this->getFullTableName("web_users") . " wu
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rs. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
5320
                INNER JOIN " . $this->getFullTableName("web_user_attributes") . " wua ON wua.internalkey=wu.id", "wu.id='{$uid}'");
5321
        if ($row = $this->getDatabase()->getRow($rs)) {
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se....id', "wu.id='{$uid}'") on line 5319 can also be of type boolean; however, EvolutionCMS\Database::getRow() does only seem to accept object<mysqli_result>, 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...
5322 View Code Duplication
            if (!isset($row['usertype']) or !$row["usertype"]) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
5323
                $row["usertype"] = "web";
5324
            }
5325
            return $row;
5326
        }
5327
    }
5328
5329
    /**
5330
     * Returns an array of document groups that current user is assigned to.
5331
     * This function will first return the web user doc groups when running from
5332
     * frontend otherwise it will return manager user's docgroup.
5333
     *
5334
     * @param boolean $resolveIds Set to true to return the document group names
5335
     *                            Default: false
5336
     * @return string|array
5337
     */
5338
    public function getUserDocGroups($resolveIds = false)
0 ignored issues
show
Coding Style introduced by
getUserDocGroups uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
5339
    {
5340
        if ($this->isFrontend() && isset($_SESSION['webDocgroups']) && isset($_SESSION['webValidated'])) {
5341
            $dg = $_SESSION['webDocgroups'];
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $dg. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
5342
            $dgn = isset($_SESSION['webDocgrpNames']) ? $_SESSION['webDocgrpNames'] : false;
5343
        } else if ($this->isBackend() && isset($_SESSION['mgrDocgroups']) && isset($_SESSION['mgrValidated'])) {
5344
            $dg = $_SESSION['mgrDocgroups'];
5345
            $dgn = isset($_SESSION['mgrDocgrpNames']) ? $_SESSION['mgrDocgrpNames'] : false;
5346
        } else {
5347
            $dg = '';
5348
        }
5349
        if (!$resolveIds) {
5350
            return $dg;
5351
        } else if (is_array($dgn)) {
5352
            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...
5353
        } else if (is_array($dg)) {
5354
            // resolve ids to names
5355
            $dgn = array();
5356
            $ds = $this->getDatabase()->select('name', $this->getFullTableName("documentgroup_names"), "id IN (" . implode(",", $dg) . ")");
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ds. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
5357
            while ($row = $this->getDatabase()->getRow($ds)) {
0 ignored issues
show
Bug introduced by
It seems like $ds defined by $this->getDatabase()->se...mplode(',', $dg) . ')') on line 5356 can also be of type boolean; however, EvolutionCMS\Database::getRow() does only seem to accept object<mysqli_result>, 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...
5358
                $dgn[] = $row['name'];
5359
            }
5360
            // cache docgroup names to session
5361
            if ($this->isFrontend()) {
5362
                $_SESSION['webDocgrpNames'] = $dgn;
5363
            } else {
5364
                $_SESSION['mgrDocgrpNames'] = $dgn;
5365
            }
5366
            return $dgn;
5367
        }
5368
    }
5369
5370
    /**
5371
     * Change current web user's password
5372
     *
5373
     * @todo Make password length configurable, allow rules for passwords and translation of messages
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
5374
     * @param string $oldPwd
5375
     * @param string $newPwd
5376
     * @return string|boolean Returns true if successful, oterhwise return error
5377
     *                        message
5378
     */
5379
    public function changeWebUserPassword($oldPwd, $newPwd)
0 ignored issues
show
Coding Style introduced by
changeWebUserPassword uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
5380
    {
5381
        $rt = false;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rt. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
5382
        if ($_SESSION["webValidated"] == 1) {
5383
            $tbl = $this->getFullTableName("web_users");
5384
            $ds = $this->getDatabase()->select('id, username, password', $tbl, "id='" . $this->getLoginUserID() . "'");
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ds. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
5385
            if ($row = $this->getDatabase()->getRow($ds)) {
0 ignored issues
show
Bug introduced by
It seems like $ds defined by $this->getDatabase()->se...etLoginUserID() . '\'') on line 5384 can also be of type boolean; however, EvolutionCMS\Database::getRow() does only seem to accept object<mysqli_result>, 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...
5386
                if ($row["password"] == md5($oldPwd)) {
5387
                    if (strlen($newPwd) < 6) {
5388
                        return "Password is too short!";
5389
                    } elseif ($newPwd == "") {
5390
                        return "You didn't specify a password for this user!";
5391
                    } else {
5392
                        $this->getDatabase()->update(array(
5393
                            'password' => $this->getDatabase()->escape($newPwd),
5394
                        ), $tbl, "id='" . $this->getLoginUserID() . "'");
5395
                        // invoke OnWebChangePassword event
5396
                        $this->invokeEvent("OnWebChangePassword", array(
5397
                            "userid" => $row["id"],
5398
                            "username" => $row["username"],
5399
                            "userpassword" => $newPwd
5400
                        ));
5401
                        return true;
5402
                    }
5403
                } else {
5404
                    return "Incorrect password.";
5405
                }
5406
            }
5407
        }
5408
        return $rt;
5409
    }
5410
5411
    /**
5412
     * Returns true if the current web user is a member the specified groups
5413
     *
5414
     * @param array $groupNames
5415
     * @return boolean
5416
     */
5417
    public function isMemberOfWebGroup($groupNames = array())
0 ignored issues
show
Coding Style introduced by
isMemberOfWebGroup uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
5418
    {
5419
        if (!is_array($groupNames)) {
5420
            return false;
5421
        }
5422
        // check cache
5423
        $grpNames = isset ($_SESSION['webUserGroupNames']) ? $_SESSION['webUserGroupNames'] : false;
5424
        if (!is_array($grpNames)) {
5425
            $rs = $this->getDatabase()->select('wgn.name', $this->getFullTableName("webgroup_names") . " wgn
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rs. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
5426
                    INNER JOIN " . $this->getFullTableName("web_groups") . " wg ON wg.webgroup=wgn.id AND wg.webuser='" . $this->getLoginUserID() . "'");
5427
            $grpNames = $this->getDatabase()->getColumn("name", $rs);
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se...etLoginUserID() . '\'') on line 5425 can also be of type boolean; however, EvolutionCMS\Database::getColumn() does only seem to accept object<mysqli_result>|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...
5428
            // save to cache
5429
            $_SESSION['webUserGroupNames'] = $grpNames;
5430
        }
5431
        foreach ($groupNames as $k => $v) {
5432
            if (in_array(trim($v), $grpNames)) {
5433
                return true;
5434
            }
5435
        }
5436
        return false;
5437
    }
5438
5439
    /**
5440
     * Registers Client-side CSS scripts - these scripts are loaded at inside
5441
     * the <head> tag
5442
     *
5443
     * @param string $src
5444
     * @param string $media Default: Empty string
5445
     * @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...
5446
     */
5447
    public function regClientCSS($src, $media = '')
5448
    {
5449
        if (empty($src) || isset ($this->loadedjscripts[$src])) {
5450
            return '';
5451
        }
5452
        $nextpos = max(array_merge(array(0), array_keys($this->sjscripts))) + 1;
5453
        $this->loadedjscripts[$src]['startup'] = true;
5454
        $this->loadedjscripts[$src]['version'] = '0';
5455
        $this->loadedjscripts[$src]['pos'] = $nextpos;
5456
        if (strpos(strtolower($src), "<style") !== false || strpos(strtolower($src), "<link") !== false) {
5457
            $this->sjscripts[$nextpos] = $src;
5458
        } else {
5459
            $this->sjscripts[$nextpos] = "\t" . '<link rel="stylesheet" type="text/css" href="' . $src . '" ' . ($media ? 'media="' . $media . '" ' : '') . '/>';
5460
        }
5461
    }
5462
5463
    /**
5464
     * Registers Startup Client-side JavaScript - these scripts are loaded at inside the <head> tag
5465
     *
5466
     * @param string $src
5467
     * @param array $options Default: 'name'=>'', 'version'=>'0', 'plaintext'=>false
5468
     */
5469
    public function regClientStartupScript($src, $options = array('name' => '', 'version' => '0', 'plaintext' => false))
5470
    {
5471
        $this->regClientScript($src, $options, true);
5472
    }
5473
5474
    /**
5475
     * Registers Client-side JavaScript these scripts are loaded at the end of the page unless $startup is true
5476
     *
5477
     * @param string $src
5478
     * @param array $options Default: 'name'=>'', 'version'=>'0', 'plaintext'=>false
5479
     * @param boolean $startup Default: false
5480
     * @return string
5481
     */
5482
    public function regClientScript($src, $options = array('name' => '', 'version' => '0', 'plaintext' => false), $startup = false)
5483
    {
5484
        if (empty($src)) {
5485
            return '';
5486
        } // nothing to register
5487
        if (!is_array($options)) {
5488
            if (is_bool($options))  // backward compatibility with old plaintext parameter
5489
            {
5490
                $options = array('plaintext' => $options);
5491
            } elseif (is_string($options)) // Also allow script name as 2nd param
5492
            {
5493
                $options = array('name' => $options);
5494
            } else {
5495
                $options = array();
5496
            }
5497
        }
5498
        $name = isset($options['name']) ? strtolower($options['name']) : '';
5499
        $version = isset($options['version']) ? $options['version'] : '0';
5500
        $plaintext = isset($options['plaintext']) ? $options['plaintext'] : false;
5501
        $key = !empty($name) ? $name : $src;
5502
        unset($overwritepos); // probably unnecessary--just making sure
5503
5504
        $useThisVer = true;
5505
        if (isset($this->loadedjscripts[$key])) { // a matching script was found
5506
            // if existing script is a startup script, make sure the candidate is also a startup script
5507
            if ($this->loadedjscripts[$key]['startup']) {
5508
                $startup = true;
5509
            }
5510
5511
            if (empty($name)) {
5512
                $useThisVer = false; // if the match was based on identical source code, no need to replace the old one
5513
            } else {
5514
                $useThisVer = version_compare($this->loadedjscripts[$key]['version'], $version, '<');
5515
            }
5516
5517
            if ($useThisVer) {
5518
                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...
5519
                    // remove old script from the bottom of the page (new one will be at the top)
5520
                    unset($this->jscripts[$this->loadedjscripts[$key]['pos']]);
5521
                } else {
5522
                    // overwrite the old script (the position may be important for dependent scripts)
5523
                    $overwritepos = $this->loadedjscripts[$key]['pos'];
5524
                }
5525
            } else { // Use the original version
5526
                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...
5527
                    // need to move the exisiting script to the head
5528
                    $version = $this->loadedjscripts[$key][$version];
5529
                    $src = $this->jscripts[$this->loadedjscripts[$key]['pos']];
5530
                    unset($this->jscripts[$this->loadedjscripts[$key]['pos']]);
5531
                } else {
5532
                    return ''; // the script is already in the right place
5533
                }
5534
            }
5535
        }
5536
5537
        if ($useThisVer && $plaintext != true && (strpos(strtolower($src), "<script") === false)) {
5538
            $src = "\t" . '<script type="text/javascript" src="' . $src . '"></script>';
5539
        }
5540
        if ($startup) {
5541
            $pos = isset($overwritepos) ? $overwritepos : max(array_merge(array(0), array_keys($this->sjscripts))) + 1;
5542
            $this->sjscripts[$pos] = $src;
5543
        } else {
5544
            $pos = isset($overwritepos) ? $overwritepos : max(array_merge(array(0), array_keys($this->jscripts))) + 1;
5545
            $this->jscripts[$pos] = $src;
5546
        }
5547
        $this->loadedjscripts[$key]['version'] = $version;
5548
        $this->loadedjscripts[$key]['startup'] = $startup;
5549
        $this->loadedjscripts[$key]['pos'] = $pos;
5550
        return '';
5551
    }
5552
5553
    /**
5554
     * Returns all registered JavaScripts
5555
     *
5556
     * @return string
5557
     */
5558
    public function regClientStartupHTMLBlock($html)
5559
    {
5560
        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...
5561
    }
5562
5563
    /**
5564
     * Returns all registered startup scripts
5565
     *
5566
     * @return string
5567
     */
5568
    public function regClientHTMLBlock($html)
5569
    {
5570
        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...
5571
    }
5572
5573
    /**
5574
     * Remove unwanted html tags and snippet, settings and tags
5575
     *
5576
     * @param string $html
5577
     * @param string $allowed Default: Empty string
5578
     * @return string
5579
     */
5580
    public function stripTags($html, $allowed = "")
5581
    {
5582
        $t = strip_tags($html, $allowed);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $t. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
5583
        $t = preg_replace('~\[\*(.*?)\*\]~', "", $t); //tv
5584
        $t = preg_replace('~\[\[(.*?)\]\]~', "", $t); //snippet
5585
        $t = preg_replace('~\[\!(.*?)\!\]~', "", $t); //snippet
5586
        $t = preg_replace('~\[\((.*?)\)\]~', "", $t); //settings
5587
        $t = preg_replace('~\[\+(.*?)\+\]~', "", $t); //placeholders
5588
        $t = preg_replace('~{{(.*?)}}~', "", $t); //chunks
5589
        return $t;
5590
    }
5591
5592
    /**
5593
     * Add an event listener to a plugin - only for use within the current execution cycle
5594
     *
5595
     * @param string $evtName
5596
     * @param string $pluginName
5597
     * @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...
5598
     */
5599
    public function addEventListener($evtName, $pluginName)
5600
    {
5601
        if (!$evtName || !$pluginName) {
5602
            return false;
5603
        }
5604
        if (!array_key_exists($evtName, $this->pluginEvent)) {
5605
            $this->pluginEvent[$evtName] = array();
5606
        }
5607
        return array_push($this->pluginEvent[$evtName], $pluginName); // return array count
5608
    }
5609
5610
    /**
5611
     * Remove event listener - only for use within the current execution cycle
5612
     *
5613
     * @param string $evtName
5614
     * @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...
5615
     */
5616
    public function removeEventListener($evtName)
5617
    {
5618
        if (!$evtName) {
5619
            return false;
5620
        }
5621
        unset ($this->pluginEvent[$evtName]);
5622
    }
5623
5624
    /**
5625
     * Remove all event listeners - only for use within the current execution cycle
5626
     */
5627
    public function removeAllEventListener()
5628
    {
5629
        unset ($this->pluginEvent);
5630
        $this->pluginEvent = array();
5631
    }
5632
5633
    /**
5634
     * Invoke an event.
5635
     *
5636
     * @param string $evtName
5637
     * @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.
5638
     * @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...
5639
     */
5640
    public function invokeEvent($evtName, $extParams = array())
5641
    {
5642
        if (!$evtName) {
5643
            return false;
5644
        }
5645
        if (!isset ($this->pluginEvent[$evtName])) {
5646
            return false;
5647
        }
5648
5649
        $results = null;
5650
        foreach ($this->pluginEvent[$evtName] as $pluginName) { // start for loop
5651
            if ($this->dumpPlugins) {
5652
                $eventtime = $this->getMicroTime();
5653
            }
5654
            // reset event object
5655
            $e = &$this->event;
5656
            $e->_resetEventObject();
5657
            $e->name = $evtName;
5658
            $e->activePlugin = $pluginName;
5659
5660
            // get plugin code
5661
            $_ = $this->getPluginCode($pluginName);
5662
            $pluginCode = $_['code'];
5663
            $pluginProperties = $_['props'];
5664
5665
            // load default params/properties
5666
            $parameter = $this->parseProperties($pluginProperties);
5667
            if (!is_array($parameter)) {
5668
                $parameter = array();
5669
            }
5670
            if (!empty($extParams)) {
5671
                $parameter = array_merge($parameter, $extParams);
5672
            }
5673
5674
            // eval plugin
5675
            $this->evalPlugin($pluginCode, $parameter);
5676
5677
            if (class_exists('PHxParser')) {
5678
                $this->config['enable_filter'] = 0;
5679
            }
5680
5681
            if ($this->dumpPlugins) {
5682
                $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...
5683
                $this->pluginsCode .= sprintf('<fieldset><legend><b>%s / %s</b> (%2.2f ms)</legend>', $evtName, $pluginName, $eventtime * 1000);
5684
                foreach ($parameter as $k => $v) {
5685
                    $this->pluginsCode .= "{$k} => " . print_r($v, true) . '<br>';
5686
                }
5687
                $this->pluginsCode .= '</fieldset><br />';
5688
                $this->pluginsTime["{$evtName} / {$pluginName}"] += $eventtime;
5689
            }
5690
            if ($e->getOutput() != '') {
5691
                $results[] = $e->getOutput();
5692
            }
5693
            if ($e->_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...
5694
                break;
5695
            }
5696
        }
5697
5698
        $e->activePlugin = '';
0 ignored issues
show
Bug introduced by
The variable $e 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...
5699
        return $results;
5700
    }
5701
5702
    /**
5703
     * Returns plugin-code and properties
5704
     *
5705
     * @param string $pluginName
5706
     * @return array Associative array consisting of 'code' and 'props'
5707
     */
5708
    public function getPluginCode($pluginName)
5709
    {
5710
        $plugin = array();
5711
        if (isset ($this->pluginCache[$pluginName])) {
5712
            $pluginCode = $this->pluginCache[$pluginName];
5713
            $pluginProperties = isset($this->pluginCache[$pluginName . "Props"]) ? $this->pluginCache[$pluginName . "Props"] : '';
5714
        } else {
5715
            $pluginName = $this->getDatabase()->escape($pluginName);
5716
            $result = $this->getDatabase()->select('name, plugincode, properties', $this->getFullTableName("site_plugins"), "name='{$pluginName}' AND disabled=0");
5717
            if ($row = $this->getDatabase()->getRow($result)) {
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->getDatabase()->se...Name}' AND disabled=0") on line 5716 can also be of type boolean; however, EvolutionCMS\Database::getRow() does only seem to accept object<mysqli_result>, 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...
5718
                $pluginCode = $this->pluginCache[$row['name']] = $row['plugincode'];
5719
                $pluginProperties = $this->pluginCache[$row['name'] . "Props"] = $row['properties'];
5720
            } else {
5721
                $pluginCode = $this->pluginCache[$pluginName] = "return false;";
5722
                $pluginProperties = '';
5723
            }
5724
        }
5725
        $plugin['code'] = $pluginCode;
5726
        $plugin['props'] = $pluginProperties;
5727
5728
        return $plugin;
5729
    }
5730
5731
    /**
5732
     * Parses a resource property string and returns the result as an array
5733
     *
5734
     * @param string|array $propertyString
5735
     * @param string|null $elementName
5736
     * @param string|null $elementType
5737
     * @return array Associative array in the form property name => property value
5738
     */
5739
    public function parseProperties($propertyString, $elementName = null, $elementType = null)
5740
    {
5741
        $property = array();
5742
5743
        if(\is_scalar($propertyString)) {
5744
            $propertyString = trim($propertyString);
5745
            $propertyString = str_replace('{}', '', $propertyString);
5746
            $propertyString = str_replace('} {', ',', $propertyString);
5747
            if (!empty($propertyString) && $propertyString != '{}') {
5748
                $jsonFormat = $this->isJson($propertyString, true);
5749
                // old format
5750
                if ($jsonFormat === false) {
5751
                    $props = explode('&', $propertyString);
5752
                    foreach ($props as $prop) {
5753
5754
                        if (empty($prop)) {
5755
                            continue;
5756
                        } elseif (strpos($prop, '=') === false) {
5757
                            $property[trim($prop)] = '';
5758
                            continue;
5759
                        }
5760
5761
                        $_ = explode('=', $prop, 2);
5762
                        $key = trim($_[0]);
5763
                        $p = explode(';', trim($_[1]));
5764
                        switch ($p[1]) {
5765
                            case 'list':
5766
                            case 'list-multi':
5767
                            case 'checkbox':
5768
                            case 'radio':
5769
                                $value = !isset($p[3]) ? '' : $p[3];
5770
                                break;
5771
                            default:
5772
                                $value = !isset($p[2]) ? '' : $p[2];
5773
                        }
5774
                        if (!empty($key)) {
5775
                            $property[$key] = $value;
5776
                        }
5777
                    }
5778
                    // new json-format
5779
                } else if (!empty($jsonFormat)) {
5780
                    foreach ($jsonFormat as $key => $row) {
5781
                        if (!empty($key)) {
5782 View Code Duplication
                            if (is_array($row)) {
5783
                                if (isset($row[0]['value'])) {
5784
                                    $value = $row[0]['value'];
5785
                                }
5786
                            } else {
5787
                                $value = $row;
5788
                            }
5789
                            if (isset($value) && $value !== '') {
5790
                                $property[$key] = $value;
5791
                            }
5792
                        }
5793
                    }
5794
                }
5795
            }
5796
        }
5797
        elseif(\is_array($propertyString)) {
5798
            $property = $propertyString;
5799
        }
5800
        if (!empty($elementName) && !empty($elementType)) {
5801
            $out = $this->invokeEvent('OnParseProperties', array(
5802
                'element' => $elementName,
5803
                'type' => $elementType,
5804
                'args' => $property
5805
            ));
5806
            if (is_array($out)) {
5807
                $out = array_pop($out);
5808
            }
5809
            if (is_array($out)) {
5810
                $property = $out;
5811
            }
5812
        }
5813
5814
        return $property;
5815
    }
5816
5817
    /**
5818
     * Parses docBlock from a file and returns the result as an array
5819
     *
5820
     * @param string $element_dir
5821
     * @param string $filename
5822
     * @param boolean $escapeValues
5823
     * @return array Associative array in the form property name => property value
5824
     */
5825
    public function parseDocBlockFromFile($element_dir, $filename, $escapeValues = false)
0 ignored issues
show
Coding Style Naming introduced by
The parameter $element_dir is not named in camelCase.

This check marks parameter names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
5826
    {
5827
        $params = array();
5828
        $fullpath = $element_dir . '/' . $filename;
5829
        if (is_readable($fullpath)) {
5830
            $tpl = @fopen($fullpath, "r");
5831
            if ($tpl) {
5832
                $params['filename'] = $filename;
5833
                $docblock_start_found = false;
5834
                $name_found = false;
5835
                $description_found = false;
5836
                $docblock_end_found = false;
5837
                $arrayParams = array('author', 'documentation', 'reportissues', 'link');
5838
5839
                while (!feof($tpl)) {
5840
                    $line = fgets($tpl);
5841
                    $r = $this->parseDocBlockLine($line, $docblock_start_found, $name_found, $description_found, $docblock_end_found);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $r. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
5842
                    $docblock_start_found = $r['docblock_start_found'];
5843
                    $name_found = $r['name_found'];
5844
                    $description_found = $r['description_found'];
5845
                    $docblock_end_found = $r['docblock_end_found'];
5846
                    $param = $r['param'];
5847
                    $val = $r['val'];
5848
                    if (!$docblock_end_found) {
5849
                        break;
5850
                    }
5851
                    if (!$docblock_start_found || !$name_found || !$description_found || empty($param)) {
5852
                        continue;
5853
                    }
5854 View Code Duplication
                    if (!empty($param)) {
5855
                        if (in_array($param, $arrayParams)) {
5856
                            if (!isset($params[$param])) {
5857
                                $params[$param] = array();
5858
                            }
5859
                            $params[$param][] = $escapeValues ? $this->getDatabase()->escape($val) : $val;
5860
                        } else {
5861
                            $params[$param] = $escapeValues ? $this->getDatabase()->escape($val) : $val;
5862
                        }
5863
                    }
5864
                }
5865
                @fclose($tpl);
5866
            }
5867
        }
5868
        return $params;
5869
    }
5870
5871
    /**
5872
     * Parses docBlock from string and returns the result as an array
5873
     *
5874
     * @param string $string
5875
     * @param boolean $escapeValues
5876
     * @return array Associative array in the form property name => property value
5877
     */
5878
    public function parseDocBlockFromString($string, $escapeValues = false)
5879
    {
5880
        $params = array();
5881
        if (!empty($string)) {
5882
            $string = str_replace('\r\n', '\n', $string);
5883
            $exp = explode('\n', $string);
5884
            $docblock_start_found = false;
5885
            $name_found = false;
5886
            $description_found = false;
5887
            $docblock_end_found = false;
5888
            $arrayParams = array('author', 'documentation', 'reportissues', 'link');
5889
5890
            foreach ($exp as $line) {
5891
                $r = $this->parseDocBlockLine($line, $docblock_start_found, $name_found, $description_found, $docblock_end_found);
5892
                $docblock_start_found = $r['docblock_start_found'];
5893
                $name_found = $r['name_found'];
5894
                $description_found = $r['description_found'];
5895
                $docblock_end_found = $r['docblock_end_found'];
5896
                $param = $r['param'];
5897
                $val = $r['val'];
5898
                if (!$docblock_start_found) {
5899
                    continue;
5900
                }
5901
                if ($docblock_end_found) {
5902
                    break;
5903
                }
5904 View Code Duplication
                if (!empty($param)) {
5905
                    if (in_array($param, $arrayParams)) {
5906
                        if (!isset($params[$param])) {
5907
                            $params[$param] = array();
5908
                        }
5909
                        $params[$param][] = $escapeValues ? $this->getDatabase()->escape($val) : $val;
5910
                    } else {
5911
                        $params[$param] = $escapeValues ? $this->getDatabase()->escape($val) : $val;
5912
                    }
5913
                }
5914
            }
5915
        }
5916
        return $params;
5917
    }
5918
5919
    /**
5920
     * Parses docBlock of a component´s source-code and returns the result as an array
5921
     * (modified parseDocBlock() from modules/stores/setup.info.php by Bumkaka & Dmi3yy)
5922
     *
5923
     * @param string $line
5924
     * @param boolean $docblock_start_found
5925
     * @param boolean $name_found
5926
     * @param boolean $description_found
5927
     * @param boolean $docblock_end_found
5928
     * @return array Associative array in the form property name => property value
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,boolean|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...
5929
     */
5930
    public function parseDocBlockLine($line, $docblock_start_found, $name_found, $description_found, $docblock_end_found)
0 ignored issues
show
Coding Style Naming introduced by
The parameter $docblock_start_found is not named in camelCase.

This check marks parameter names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
Coding Style Naming introduced by
The parameter $name_found is not named in camelCase.

This check marks parameter names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
Coding Style Naming introduced by
The parameter $description_found is not named in camelCase.

This check marks parameter names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
Coding Style Naming introduced by
The parameter $docblock_end_found is not named in camelCase.

This check marks parameter names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
5931
    {
5932
        $param = '';
5933
        $val = '';
5934
        $ma = null;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ma. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
5935
        if (!$docblock_start_found) {
5936
            // find docblock start
5937
            if (strpos($line, '/**') !== false) {
5938
                $docblock_start_found = true;
5939
            }
5940
        } elseif (!$name_found) {
5941
            // find name
5942
            if (preg_match("/^\s+\*\s+(.+)/", $line, $ma)) {
5943
                $param = 'name';
5944
                $val = trim($ma[1]);
5945
                $name_found = !empty($val);
5946
            }
5947
        } elseif (!$description_found) {
5948
            // find description
5949
            if (preg_match("/^\s+\*\s+(.+)/", $line, $ma)) {
5950
                $param = 'description';
5951
                $val = trim($ma[1]);
5952
                $description_found = !empty($val);
5953
            }
5954
        } else {
5955
            if (preg_match("/^\s+\*\s+\@([^\s]+)\s+(.+)/", $line, $ma)) {
5956
                $param = trim($ma[1]);
5957
                $val = trim($ma[2]);
5958
                if (!empty($param) && !empty($val)) {
5959
                    if ($param == 'internal') {
5960
                        $ma = null;
5961
                        if (preg_match("/\@([^\s]+)\s+(.+)/", $val, $ma)) {
5962
                            $param = trim($ma[1]);
5963
                            $val = trim($ma[2]);
5964
                        }
5965
                    }
5966
                }
5967
            } elseif (preg_match("/^\s*\*\/\s*$/", $line)) {
5968
                $docblock_end_found = true;
5969
            }
5970
        }
5971
        return array(
5972
            'docblock_start_found' => $docblock_start_found,
5973
            'name_found' => $name_found,
5974
            'description_found' => $description_found,
5975
            'docblock_end_found' => $docblock_end_found,
5976
            'param' => $param,
5977
            'val' => $val
5978
        );
5979
    }
5980
5981
    /**
5982
     * Renders docBlock-parameters into human readable list
5983
     *
5984
     * @param array $parsed
5985
     * @return string List in HTML-format
5986
     */
5987
    public function convertDocBlockIntoList($parsed)
5988
    {
5989
        global $_lang;
5990
5991
        // Replace special placeholders & make URLs + Emails clickable
5992
        $ph = array('site_url' => MODX_SITE_URL);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ph. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
5993
        $regexUrl = "/((http|https|ftp|ftps)\:\/\/[^\/]+(\/[^\s]+[^,.?!:;\s])?)/";
5994
        $regexEmail = '#([0-9a-z]([-_.]?[0-9a-z])*@[0-9a-z]([-.]?[0-9a-z])*\\.[a-wyz][a-z](fo|g|l|m|mes|o|op|pa|ro|seum|t|u|v|z)?)#i';
5995
        $emailSubject = isset($parsed['name']) ? '?subject=' . $parsed['name'] : '';
5996
        $emailSubject .= isset($parsed['version']) ? ' v' . $parsed['version'] : '';
5997
        foreach ($parsed as $key => $val) {
5998
            if (is_array($val)) {
5999
                foreach ($val as $key2 => $val2) {
6000
                    $val2 = $this->parseText($val2, $ph);
6001 View Code Duplication
                    if (preg_match($regexUrl, $val2, $url)) {
6002
                        $val2 = preg_replace($regexUrl, "<a href=\"{$url[0]}\" target=\"_blank\">{$url[0]}</a> ", $val2);
6003
                    }
6004 View Code Duplication
                    if (preg_match($regexEmail, $val2, $url)) {
6005
                        $val2 = preg_replace($regexEmail, '<a href="mailto:\\1' . $emailSubject . '">\\1</a>', $val2);
6006
                    }
6007
                    $parsed[$key][$key2] = $val2;
6008
                }
6009
            } else {
6010
                $val = $this->parseText($val, $ph);
6011 View Code Duplication
                if (preg_match($regexUrl, $val, $url)) {
6012
                    $val = preg_replace($regexUrl, "<a href=\"{$url[0]}\" target=\"_blank\">{$url[0]}</a> ", $val);
6013
                }
6014 View Code Duplication
                if (preg_match($regexEmail, $val, $url)) {
6015
                    $val = preg_replace($regexEmail, '<a href="mailto:\\1' . $emailSubject . '">\\1</a>', $val);
6016
                }
6017
                $parsed[$key] = $val;
6018
            }
6019
        }
6020
6021
        $arrayParams = array(
6022
            'documentation' => $_lang['documentation'],
6023
            'reportissues' => $_lang['report_issues'],
6024
            'link' => $_lang['further_info'],
6025
            'author' => $_lang['author_infos']
6026
        );
6027
6028
        $nl = "\n";
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $nl. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
6029
        $list = isset($parsed['logo']) ? '<img src="' . $this->config['base_url'] . ltrim($parsed['logo'], "/") . '" style="float:right;max-width:100px;height:auto;" />' . $nl : '';
6030
        $list .= '<p>' . $nl;
6031
        $list .= isset($parsed['name']) ? '<strong>' . $parsed['name'] . '</strong><br/>' . $nl : '';
6032
        $list .= isset($parsed['description']) ? $parsed['description'] . $nl : '';
6033
        $list .= '</p><br/>' . $nl;
6034
        $list .= isset($parsed['version']) ? '<p><strong>' . $_lang['version'] . ':</strong> ' . $parsed['version'] . '</p>' . $nl : '';
6035
        $list .= isset($parsed['license']) ? '<p><strong>' . $_lang['license'] . ':</strong> ' . $parsed['license'] . '</p>' . $nl : '';
6036
        $list .= isset($parsed['lastupdate']) ? '<p><strong>' . $_lang['last_update'] . ':</strong> ' . $parsed['lastupdate'] . '</p>' . $nl : '';
6037
        $list .= '<br/>' . $nl;
6038
        $first = true;
6039
        foreach ($arrayParams as $param => $label) {
6040
            if (isset($parsed[$param])) {
6041
                if ($first) {
6042
                    $list .= '<p><strong>' . $_lang['references'] . '</strong></p>' . $nl;
6043
                    $list .= '<ul class="docBlockList">' . $nl;
6044
                    $first = false;
6045
                }
6046
                $list .= '    <li><strong>' . $label . '</strong>' . $nl;
6047
                $list .= '        <ul>' . $nl;
6048
                foreach ($parsed[$param] as $val) {
6049
                    $list .= '            <li>' . $val . '</li>' . $nl;
6050
                }
6051
                $list .= '        </ul></li>' . $nl;
6052
            }
6053
        }
6054
        $list .= !$first ? '</ul>' . $nl : '';
6055
6056
        return $list;
6057
    }
6058
6059
    /**
6060
     * @param string $string
6061
     * @return string
6062
     */
6063
    public function removeSanitizeSeed($string = '')
6064
    {
6065
        global $sanitize_seed;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
6066
6067
        if (!$string || strpos($string, $sanitize_seed) === false) {
6068
            return $string;
6069
        }
6070
6071
        return str_replace($sanitize_seed, '', $string);
6072
    }
6073
6074
    /**
6075
     * @param string $content
6076
     * @return string
6077
     */
6078
    public function cleanUpMODXTags($content = '')
6079
    {
6080
        if ($this->minParserPasses < 1) {
6081
            return $content;
6082
        }
6083
6084
        $enable_filter = $this->config['enable_filter'];
6085
        $this->config['enable_filter'] = 1;
6086
        $_ = array('[* *]', '[( )]', '{{ }}', '[[ ]]', '[+ +]');
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $_. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
6087
        foreach ($_ as $brackets) {
6088
            list($left, $right) = explode(' ', $brackets);
6089
            if (strpos($content, $left) !== false) {
6090
                if ($left === '[*') {
6091
                    $content = $this->mergeDocumentContent($content);
6092
                } elseif ($left === '[(') {
6093
                    $content = $this->mergeSettingsContent($content);
6094
                } elseif ($left === '{{') {
6095
                    $content = $this->mergeChunkContent($content);
6096
                } elseif ($left === '[[') {
6097
                    $content = $this->evalSnippets($content);
6098
                }
6099
            }
6100
        }
6101
        foreach ($_ as $brackets) {
6102
            list($left, $right) = explode(' ', $brackets);
6103
            if (strpos($content, $left) !== false) {
6104
                $matches = $this->getTagsFromContent($content, $left, $right);
6105
                $content = str_replace($matches[0], '', $content);
6106
            }
6107
        }
6108
        $this->config['enable_filter'] = $enable_filter;
6109
        return $content;
6110
    }
6111
6112
    /**
6113
     * @param string $str
6114
     * @param string $allowable_tags
6115
     * @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...
6116
     */
6117
    public function strip_tags($str = '', $allowable_tags = '')
0 ignored issues
show
Coding Style Naming introduced by
The parameter $allowable_tags is not named in camelCase.

This check marks parameter names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
Coding Style introduced by
This method is not in camel caps format.

This check looks for method names that are not written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection seeker becomes databaseConnectionSeeker.

Loading history...
6118
    {
6119
        $str = strip_tags($str, $allowable_tags);
6120
        modx_sanitize_gpc($str);
6121
        return $str;
6122
    }
6123
6124
    /**
6125
     * {@inheritdoc}
6126
     */
6127
    public function addSnippet($name, $phpCode, $namespace = '#', array $defaultParams = array())
6128
    {
6129
        $this->snippetCache[$namespace . $name] = $phpCode;
6130
        $this->snippetCache[$namespace . $name . 'Props'] = $defaultParams;
6131
    }
6132
6133
    /**
6134
     * {@inheritdoc}
6135
     */
6136
    public function addChunk($name, $text, $namespace = '#')
6137
    {
6138
        $this->chunkCache[$namespace . $name] = $text;
6139
    }
6140
6141
    /**
6142
     * {@inheritdoc}
6143
     */
6144
    public function findElements($type, $scanPath, array $ext)
6145
    {
6146
        $out = array();
6147
6148
        if (! is_dir($scanPath) || empty($ext)) {
6149
            return $out;
6150
        }
6151
        $iterator = new \RecursiveIteratorIterator(
6152
            new \RecursiveDirectoryIterator($scanPath, \RecursiveDirectoryIterator::SKIP_DOTS),
6153
            \RecursiveIteratorIterator::SELF_FIRST
6154
        );
6155
        foreach ($iterator as $item) {
6156
            /**
6157
             * @var \SplFileInfo $item
6158
             */
6159
            if ($item->isFile() && $item->isReadable() && \in_array($item->getExtension(), $ext)) {
6160
                $name = $item->getBasename('.' . $item->getExtension());
6161
                $path = ltrim(str_replace(
6162
                    array(rtrim($scanPath, '//'), '/'),
6163
                    array('', '\\'),
6164
                    $item->getPath() . '/'
6165
                ), '\\');
6166
6167
                if (!empty($path)) {
6168
                    $name = $path . $name;
6169
                }
6170
                switch ($type) {
6171
                    case 'chunk':
6172
                        $out[$name] = file_get_contents($item->getRealPath());
6173
                        break;
6174
                    case 'snippet':
6175
                        $out[$name] = "return require '" . $item->getRealPath() . "';";
6176
                        break;
6177
                    default:
6178
                        throw new \Exception;
6179
                }
6180
            }
6181
        }
6182
6183
        return $out;
6184
    }
6185
6186
    /**
6187
     * @param string $phpcode
6188
     * @param string $evalmode
6189
     * @param string $safe_functions
6190
     * @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...
6191
     */
6192
    public function safeEval($phpcode = '', $evalmode = '', $safe_functions = '')
0 ignored issues
show
Coding Style introduced by
safeEval uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
safeEval uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style Naming introduced by
The parameter $safe_functions is not named in camelCase.

This check marks parameter names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
6193
    {
6194
        if ($evalmode == '') {
6195
            $evalmode = $this->config['allow_eval'];
6196
        }
6197
        if ($safe_functions == '') {
6198
            $safe_functions = $this->config['safe_functions_at_eval'];
6199
        }
6200
6201
        modx_sanitize_gpc($phpcode);
6202
6203
        switch ($evalmode) {
6204
            case 'with_scan'         :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
6205
                $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...
6206
                break;
6207
            case 'with_scan_at_post' :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
6208
                $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...
6209
                break;
6210
            case 'everytime_eval'    :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
6211
                $isSafe = true;
6212
                break; // Should debug only
6213
            case 'dont_eval'         :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
6214
            default                  :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a DEFAULT statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in the default statement.

switch ($expr) {
    default : //wrong
        doSomething();
        break;
}

switch ($expr) {
    default: //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
6215
                return $phpcode;
6216
        }
6217
6218
        if (!$isSafe) {
6219
            $msg = $phpcode . "\n" . $this->currentSnippet . "\n" . print_r($_SERVER, true);
6220
            $title = sprintf('Unknown eval was executed (%s)', $this->getPhpCompat()->htmlspecialchars(substr(trim($phpcode), 0, 50)));
6221
            $this->messageQuit($title, '', true, '', '', 'Parser', $msg);
6222
            return;
6223
        }
6224
6225
        ob_start();
6226
        $return = eval($phpcode);
0 ignored issues
show
Coding Style introduced by
It is generally not recommended to use eval unless absolutely required.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
6227
        $echo = ob_get_clean();
6228
6229
        if (is_array($return)) {
6230
            return 'array()';
6231
        }
6232
6233
        $output = $echo . $return;
6234
        modx_sanitize_gpc($output);
6235
        return $this->getPhpCompat()->htmlspecialchars($output); // Maybe, all html tags are dangerous
6236
    }
6237
6238
    /**
6239
     * @param string $phpcode
6240
     * @param string $safe_functions
6241
     * @return bool
6242
     */
6243
    public function isSafeCode($phpcode = '', $safe_functions = '')
0 ignored issues
show
Coding Style Naming introduced by
The parameter $safe_functions is not named in camelCase.

This check marks parameter names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
6244
    { // return true or false
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
6245
        if ($safe_functions == '') {
6246
            return false;
6247
        }
6248
6249
        $safe = explode(',', $safe_functions);
6250
6251
        $phpcode = rtrim($phpcode, ';') . ';';
6252
        $tokens = token_get_all('<?php ' . $phpcode);
6253
        foreach ($tokens as $i => $token) {
6254
            if (!is_array($token)) {
6255
                continue;
6256
            }
6257
            $tokens[$i]['token_name'] = token_name($token[0]);
6258
        }
6259
        foreach ($tokens as $token) {
6260
            if (!is_array($token)) {
6261
                continue;
6262
            }
6263
            switch ($token['token_name']) {
6264
                case 'T_STRING':
6265
                    if (!in_array($token[1], $safe)) {
6266
                        return false;
6267
                    }
6268
                    break;
6269
                case 'T_VARIABLE':
6270
                    if ($token[1] == '$GLOBALS') {
6271
                        return false;
6272
                    }
6273
                    break;
6274
                case 'T_EVAL':
6275
                    return false;
6276
            }
6277
        }
6278
        return true;
6279
    }
6280
6281
    /**
6282
     * @param string $str
6283
     * @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...
6284
     */
6285
    public function atBindFileContent($str = '')
6286
    {
6287
6288
        $search_path = array('assets/tvs/', 'assets/chunks/', 'assets/templates/', $this->config['rb_base_url'] . 'files/', '');
6289
6290
        if (stripos($str, '@FILE') !== 0) {
6291
            return $str;
6292
        }
6293 View Code Duplication
        if (strpos($str, "\n") !== false) {
6294
            $str = substr($str, 0, strpos("\n", $str));
6295
        }
6296
6297
        if ($this->getExtFromFilename($str) === '.php') {
6298
            return 'Could not retrieve PHP file.';
6299
        }
6300
6301
        $str = substr($str, 6);
6302
        $str = trim($str);
6303
        if (strpos($str, '\\') !== false) {
6304
            $str = str_replace('\\', '/', $str);
6305
        }
6306
        $str = ltrim($str, '/');
6307
6308
        $errorMsg = sprintf("Could not retrieve string '%s'.", $str);
6309
6310
        foreach ($search_path as $path) {
6311
            $file_path = MODX_BASE_PATH . $path . $str;
6312
            if (strpos($file_path, MODX_MANAGER_PATH) === 0) {
6313
                return $errorMsg;
6314
            } elseif (is_file($file_path)) {
6315
                break;
6316
            } else {
6317
                $file_path = false;
6318
            }
6319
        }
6320
6321
        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...
6322
            return $errorMsg;
6323
        }
6324
6325
        $content = (string)file_get_contents($file_path);
6326
        if ($content === false) {
6327
            return $errorMsg;
6328
        }
6329
6330
        return $content;
6331
    }
6332
6333
    /**
6334
     * @param $str
6335
     * @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...
6336
     */
6337
    public function getExtFromFilename($str)
6338
    {
6339
        $str = strtolower(trim($str));
6340
        $pos = strrpos($str, '.');
6341
        if ($pos === false) {
6342
            return false;
6343
        } else {
6344
            return substr($str, $pos);
6345
        }
6346
    }
6347
    /***************************************************************************************/
6348
    /* End of API functions                                       */
6349
    /***************************************************************************************/
6350
6351
    /**
6352
     * PHP error handler set by http://www.php.net/manual/en/function.set-error-handler.php
6353
     *
6354
     * Checks the PHP error and calls messageQuit() unless:
6355
     *  - error_reporting() returns 0, or
6356
     *  - the PHP error level is 0, or
6357
     *  - the PHP error level is 8 (E_NOTICE) and stopOnNotice is false
6358
     *
6359
     * @param int $nr The PHP error level as per http://www.php.net/manual/en/errorfunc.constants.php
6360
     * @param string $text Error message
6361
     * @param string $file File where the error was detected
6362
     * @param string $line Line number within $file
6363
     * @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...
6364
     */
6365
    public function phpError($nr, $text, $file, $line)
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $nr. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
6366
    {
6367
        if (error_reporting() == 0 || $nr == 0) {
6368
            return true;
6369
        }
6370
        if ($this->stopOnNotice == 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...
6371
            switch ($nr) {
6372
                case E_NOTICE:
6373
                    if ($this->error_reporting <= 2) {
6374
                        return true;
6375
                    }
6376
                    $isError = false;
6377
                    $msg = 'PHP Minor Problem (this message show logged in only)';
6378
                    break;
6379
                case E_STRICT:
6380 View Code Duplication
                case E_DEPRECATED:
6381
                    if ($this->error_reporting <= 1) {
6382
                        return true;
6383
                    }
6384
                    $isError = true;
6385
                    $msg = 'PHP Strict Standards Problem';
6386
                    break;
6387 View Code Duplication
                default:
6388
                    if ($this->error_reporting === 0) {
6389
                        return true;
6390
                    }
6391
                    $isError = true;
6392
                    $msg = 'PHP Parse Error';
6393
            }
6394
        }
6395
        if (is_readable($file)) {
6396
            $source = file($file);
6397
            $source = $this->getPhpCompat()->htmlspecialchars($source[$line - 1]);
6398
        } else {
6399
            $source = "";
6400
        } //Error $nr in $file at $line: <div><code>$source</code></div>
6401
6402
        $this->messageQuit($msg, '', $isError, $nr, $file, $source, $text, $line);
0 ignored issues
show
Bug introduced by
The variable $msg 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 $isError 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...
6403
    }
6404
6405
    /**
6406
     * @param string $msg
6407
     * @param string $query
6408
     * @param bool $is_error
6409
     * @param string $nr
6410
     * @param string $file
6411
     * @param string $source
6412
     * @param string $text
6413
     * @param string $line
6414
     * @param string $output
6415
     * @return bool
0 ignored issues
show
Documentation introduced by
Should the return type not be null|boolean?

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...
6416
     */
6417
    public function messageQuit($msg = 'unspecified error', $query = '', $is_error = true, $nr = '', $file = '', $source = '', $text = '', $line = '', $output = '')
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $nr. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
Coding Style introduced by
messageQuit uses the super-global variable $GLOBALS which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
messageQuit uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
messageQuit uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style Naming introduced by
The parameter $is_error is not named in camelCase.

This check marks parameter names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
6418
    {
6419
6420
        if (0 < $this->messageQuitCount) {
6421
            return;
6422
        }
6423
        $this->messageQuitCount++;
6424
        $MakeTable = new Support\MakeTable();
6425
        $MakeTable->setTableClass('grid');
6426
        $MakeTable->setRowRegularClass('gridItem');
6427
        $MakeTable->setRowAlternateClass('gridAltItem');
6428
        $MakeTable->setColumnWidths(array('100px'));
6429
6430
        $table = array();
6431
6432
        $version = isset ($GLOBALS['modx_version']) ? $GLOBALS['modx_version'] : '';
6433
        $release_date = isset ($GLOBALS['release_date']) ? $GLOBALS['release_date'] : '';
6434
        $request_uri = "http://" . $_SERVER['HTTP_HOST'] . ($_SERVER["SERVER_PORT"] == 80 ? "" : (":" . $_SERVER["SERVER_PORT"])) . $_SERVER['REQUEST_URI'];
6435
        $request_uri = $this->getPhpCompat()->htmlspecialchars($request_uri, ENT_QUOTES, $this->config['modx_charset']);
6436
        $ua = $this->getPhpCompat()->htmlspecialchars($_SERVER['HTTP_USER_AGENT'], ENT_QUOTES, $this->config['modx_charset']);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ua. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
6437
        $referer = $this->getPhpCompat()->htmlspecialchars($_SERVER['HTTP_REFERER'], ENT_QUOTES, $this->config['modx_charset']);
6438
        if ($is_error) {
6439
            $str = '<h2 style="color:red">&laquo; Evo Parse Error &raquo;</h2>';
6440
            if ($msg != 'PHP Parse Error') {
6441
                $str .= '<h3 style="color:red">' . $msg . '</h3>';
6442
            }
6443
        } else {
6444
            $str = '<h2 style="color:#003399">&laquo; Evo Debug/ stop message &raquo;</h2>';
6445
            $str .= '<h3 style="color:#003399">' . $msg . '</h3>';
6446
        }
6447
6448
        if (!empty ($query)) {
6449
            $str .= '<div style="font-weight:bold;border:1px solid #ccc;padding:8px;color:#333;background-color:#ffffcd;margin-bottom:15px;">SQL &gt; <span id="sqlHolder">' . $query . '</span></div>';
6450
        }
6451
6452
        $errortype = array(
6453
            E_ERROR => "ERROR",
6454
            E_WARNING => "WARNING",
6455
            E_PARSE => "PARSING ERROR",
6456
            E_NOTICE => "NOTICE",
6457
            E_CORE_ERROR => "CORE ERROR",
6458
            E_CORE_WARNING => "CORE WARNING",
6459
            E_COMPILE_ERROR => "COMPILE ERROR",
6460
            E_COMPILE_WARNING => "COMPILE WARNING",
6461
            E_USER_ERROR => "USER ERROR",
6462
            E_USER_WARNING => "USER WARNING",
6463
            E_USER_NOTICE => "USER NOTICE",
6464
            E_STRICT => "STRICT NOTICE",
6465
            E_RECOVERABLE_ERROR => "RECOVERABLE ERROR",
6466
            E_DEPRECATED => "DEPRECATED",
6467
            E_USER_DEPRECATED => "USER DEPRECATED"
6468
        );
6469
6470
        if (!empty($nr) || !empty($file)) {
6471
            if ($text != '') {
6472
                $str .= '<div style="font-weight:bold;border:1px solid #ccc;padding:8px;color:#333;background-color:#ffffcd;margin-bottom:15px;">Error : ' . $text . '</div>';
6473
            }
6474
            if ($output != '') {
6475
                $str .= '<div style="font-weight:bold;border:1px solid #ccc;padding:8px;color:#333;background-color:#ffffcd;margin-bottom:15px;">' . $output . '</div>';
6476
            }
6477
            if ($nr !== '') {
6478
                $table[] = array('ErrorType[num]', $errortype [$nr] . "[" . $nr . "]");
6479
            }
6480
            if ($file) {
6481
                $table[] = array('File', $file);
6482
            }
6483
            if ($line) {
6484
                $table[] = array('Line', $line);
6485
            }
6486
6487
        }
6488
6489
        if ($source != '') {
6490
            $table[] = array("Source", $source);
6491
        }
6492
6493
        if (!empty($this->currentSnippet)) {
6494
            $table[] = array('Current Snippet', $this->currentSnippet);
6495
        }
6496
6497
        if (!empty($this->event->activePlugin)) {
6498
            $table[] = array('Current Plugin', $this->event->activePlugin . '(' . $this->event->name . ')');
6499
        }
6500
6501
        $str .= $MakeTable->create($table, array('Error information', ''));
6502
        $str .= "<br />";
6503
6504
        $table = array();
6505
        $table[] = array('REQUEST_URI', $request_uri);
6506
6507
        if ($this->getManagerApi()->action) {
6508
            include_once(MODX_MANAGER_PATH . 'includes/actionlist.inc.php');
6509
            global $action_list;
6510
            $actionName = (isset($action_list[$this->getManagerApi()->action])) ? " - {$action_list[$this->getManagerApi()->action]}" : '';
6511
6512
            $table[] = array('Manager action', $this->getManagerApi()->action . $actionName);
6513
        }
6514
6515
        if (preg_match('@^[0-9]+@', $this->documentIdentifier)) {
6516
            $resource = $this->getDocumentObject('id', $this->documentIdentifier);
6517
            $url = $this->makeUrl($this->documentIdentifier, '', '', 'full');
6518
            $table[] = array('Resource', '[' . $this->documentIdentifier . '] <a href="' . $url . '" target="_blank">' . $resource['pagetitle'] . '</a>');
6519
        }
6520
        $table[] = array('Referer', $referer);
6521
        $table[] = array('User Agent', $ua);
6522
        $table[] = array('IP', $_SERVER['REMOTE_ADDR']);
6523
        $table[] = array('Current time', date("Y-m-d H:i:s", $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time']));
6524
        $str .= $MakeTable->create($table, array('Basic info', ''));
6525
        $str .= "<br />";
6526
6527
        $table = array();
6528
        $table[] = array('MySQL', '[^qt^] ([^q^] Requests)');
6529
        $table[] = array('PHP', '[^p^]');
6530
        $table[] = array('Total', '[^t^]');
6531
        $table[] = array('Memory', '[^m^]');
6532
        $str .= $MakeTable->create($table, array('Benchmarks', ''));
6533
        $str .= "<br />";
6534
6535
        $totalTime = ($this->getMicroTime() - $this->tstart);
6536
6537
        $mem = memory_get_peak_usage(true);
6538
        $total_mem = $mem - $this->mstart;
6539
        $total_mem = ($total_mem / 1024 / 1024) . ' mb';
6540
6541
        $queryTime = $this->queryTime;
6542
        $phpTime = $totalTime - $queryTime;
6543
        $queries = isset ($this->executedQueries) ? $this->executedQueries : 0;
6544
        $queryTime = sprintf("%2.4f s", $queryTime);
6545
        $totalTime = sprintf("%2.4f s", $totalTime);
6546
        $phpTime = sprintf("%2.4f s", $phpTime);
6547
6548
        $str = str_replace('[^q^]', $queries, $str);
6549
        $str = str_replace('[^qt^]', $queryTime, $str);
6550
        $str = str_replace('[^p^]', $phpTime, $str);
6551
        $str = str_replace('[^t^]', $totalTime, $str);
6552
        $str = str_replace('[^m^]', $total_mem, $str);
6553
6554
        if (isset($php_errormsg) && !empty($php_errormsg)) {
6555
            $str = "<b>{$php_errormsg}</b><br />\n{$str}";
6556
        }
6557
        $str .= $this->get_backtrace(debug_backtrace());
6558
        // Log error
6559
        if (!empty($this->currentSnippet)) {
6560
            $source = 'Snippet - ' . $this->currentSnippet;
6561
        } elseif (!empty($this->event->activePlugin)) {
6562
            $source = 'Plugin - ' . $this->event->activePlugin;
6563
        } elseif ($source !== '') {
6564
            $source = 'Parser - ' . $source;
6565
        } elseif ($query !== '') {
6566
            $source = 'SQL Query';
6567
        } else {
6568
            $source = 'Parser';
6569
        }
6570
        if ($msg) {
6571
            $source .= ' / ' . $msg;
6572
        }
6573
        if (isset($actionName) && !empty($actionName)) {
6574
            $source .= $actionName;
6575
        }
6576 View Code Duplication
        switch ($nr) {
6577
            case E_DEPRECATED :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
6578
            case E_USER_DEPRECATED :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
6579
            case E_STRICT :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
6580
            case E_NOTICE :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
6581
            case E_USER_NOTICE :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
6582
                $error_level = 2;
6583
                break;
6584
            default:
6585
                $error_level = 3;
6586
        }
6587
        $this->logEvent(0, $error_level, $str, $source);
6588
6589
        if ($error_level === 2 && $this->error_reporting !== '99') {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of $this->error_reporting (integer) and '99' (string) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
6590
            return true;
6591
        }
6592
        if ($this->error_reporting === '99' && !isset($_SESSION['mgrValidated'])) {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $this->error_reporting (integer) and '99' (string) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
6593
            return true;
6594
        }
6595
6596
        // Set 500 response header
6597
        if ($error_level !== 2) {
6598
            header('HTTP/1.1 500 Internal Server Error');
6599
        }
6600
6601
        // Display error
6602
        if (isset($_SESSION['mgrValidated'])) {
6603
            echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><html><head><title>EVO Content Manager ' . $version . ' &raquo; ' . $release_date . '</title>
6604
                 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
6605
                 <link rel="stylesheet" type="text/css" href="' . $this->config['site_manager_url'] . 'media/style/' . $this->config['manager_theme'] . '/style.css" />
6606
                 <style type="text/css">body { padding:10px; } td {font:inherit;}</style>
6607
                 </head><body>
6608
                 ' . $str . '</body></html>';
6609
6610
        } else {
6611
            echo 'Error';
6612
        }
6613
        ob_end_flush();
6614
        exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method messageQuit() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
6615
    }
6616
6617
    /**
6618
     * @param $backtrace
6619
     * @return string
6620
     */
6621
    public function get_backtrace($backtrace)
0 ignored issues
show
Coding Style introduced by
This method is not in camel caps format.

This check looks for method names that are not written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection seeker becomes databaseConnectionSeeker.

Loading history...
6622
    {
6623
        $MakeTable = new Support\MakeTable();
6624
        $MakeTable->setTableClass('grid');
6625
        $MakeTable->setRowRegularClass('gridItem');
6626
        $MakeTable->setRowAlternateClass('gridAltItem');
6627
        $table = array();
6628
        $backtrace = array_reverse($backtrace);
6629
        foreach ($backtrace as $key => $val) {
6630
            $key++;
6631
            if (substr($val['function'], 0, 11) === 'messageQuit') {
6632
                break;
6633
            } elseif (substr($val['function'], 0, 8) === 'phpError') {
6634
                break;
6635
            }
6636
            $path = str_replace('\\', '/', $val['file']);
6637
            if (strpos($path, MODX_BASE_PATH) === 0) {
6638
                $path = substr($path, strlen(MODX_BASE_PATH));
6639
            }
6640
            switch ($val['type']) {
6641
                case '->':
6642
                case '::':
6643
                    $functionName = $val['function'] = $val['class'] . $val['type'] . $val['function'];
6644
                    break;
6645
                default:
6646
                    $functionName = $val['function'];
6647
            }
6648
            $tmp = 1;
6649
            $_ = (!empty($val['args'])) ? count($val['args']) : 0;
6650
            $args = array_pad(array(), $_, '$var');
6651
            $args = implode(", ", $args);
6652
            $modx = &$this;
6653
            $args = preg_replace_callback('/\$var/', function () use ($modx, &$tmp, $val) {
6654
                $arg = $val['args'][$tmp - 1];
6655
                switch (true) {
6656
                    case is_null($arg): {
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
6657
                        $out = 'NULL';
6658
                        break;
6659
                    }
6660
                    case is_numeric($arg): {
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
6661
                        $out = $arg;
6662
                        break;
6663
                    }
6664
                    case is_scalar($arg): {
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
6665
                        $out = strlen($arg) > 20 ? 'string $var' . $tmp : ("'" . $this->getPhpCompat()->htmlspecialchars(str_replace("'", "\\'", $arg)) . "'");
6666
                        break;
6667
                    }
6668
                    case is_bool($arg): {
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
6669
                        $out = $arg ? 'TRUE' : 'FALSE';
6670
                        break;
6671
                    }
6672
                    case is_array($arg): {
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
6673
                        $out = 'array $var' . $tmp;
6674
                        break;
6675
                    }
6676
                    case is_object($arg): {
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
6677
                        $out = get_class($arg) . ' $var' . $tmp;
6678
                        break;
6679
                    }
6680
                    default: {
0 ignored issues
show
Coding Style introduced by
DEFAULT statements must be defined using a colon

As per the PSR-2 coding standard, default statements should not be wrapped in curly braces.

switch ($expr) {
    default: { //wrong
        doSomething();
        break;
    }
}

switch ($expr) {
    default: //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
6681
                        $out = '$var' . $tmp;
6682
                    }
6683
                }
6684
                $tmp++;
6685
                return $out;
6686
            }, $args);
6687
            $line = array(
6688
                "<strong>" . $functionName . "</strong>(" . $args . ")",
6689
                $path . " on line " . $val['line']
6690
            );
6691
            $table[] = array(implode("<br />", $line));
6692
        }
6693
        return $MakeTable->create($table, array('Backtrace'));
6694
    }
6695
6696
    /**
6697
     * @return string
6698
     */
6699
    public function getRegisteredClientScripts()
6700
    {
6701
        return implode("\n", $this->jscripts);
6702
    }
6703
6704
    /**
6705
     * @return string
6706
     */
6707
    public function getRegisteredClientStartupScripts()
6708
    {
6709
        return implode("\n", $this->sjscripts);
6710
    }
6711
6712
    /**
6713
     * Format alias to be URL-safe. Strip invalid characters.
6714
     *
6715
     * @param string $alias Alias to be formatted
6716
     * @return string Safe alias
6717
     */
6718
    public function stripAlias($alias)
6719
    {
6720
        // let add-ons overwrite the default behavior
6721
        $results = $this->invokeEvent('OnStripAlias', array('alias' => $alias));
6722
        if (!empty($results)) {
6723
            // if multiple plugins are registered, only the last one is used
6724
            return end($results);
6725
        } else {
6726
            // default behavior: strip invalid characters and replace spaces with dashes.
6727
            $alias = strip_tags($alias); // strip HTML
6728
            $alias = preg_replace('/[^\.A-Za-z0-9 _-]/', '', $alias); // strip non-alphanumeric characters
6729
            $alias = preg_replace('/\s+/', '-', $alias); // convert white-space to dash
6730
            $alias = preg_replace('/-+/', '-', $alias);  // convert multiple dashes to one
6731
            $alias = trim($alias, '-'); // trim excess
6732
            return $alias;
6733
        }
6734
    }
6735
6736
    /**
6737
     * @param $size
6738
     * @return string
6739
     */
6740
    public function nicesize($size)
6741
    {
6742
        $sizes = array('Tb' => 1099511627776, 'Gb' => 1073741824, 'Mb' => 1048576, 'Kb' => 1024, 'b' => 1);
6743
        $precisions = count($sizes) - 1;
6744
        foreach ($sizes as $unit => $bytes) {
6745
            if ($size >= $bytes) {
6746
                return number_format($size / $bytes, $precisions) . ' ' . $unit;
6747
            }
6748
            $precisions--;
6749
        }
6750
        return '0 b';
6751
    }
6752
6753
    /**
6754
     * @param $parentid
6755
     * @param $alias
6756
     * @return bool
6757
     */
6758
    public function getHiddenIdFromAlias($parentid, $alias)
6759
    {
6760
        $table = $this->getFullTableName('site_content');
6761
        $query = $this->getDatabase()->query("SELECT sc.id, children.id AS child_id, children.alias, COUNT(children2.id) AS children_count
6762
            FROM {$table} sc
6763
            JOIN {$table} children ON children.parent = sc.id
6764
            LEFT JOIN {$table} children2 ON children2.parent = children.id
6765
            WHERE sc.parent = {$parentid} AND sc.alias_visible = '0' GROUP BY children.id;");
6766
6767
        while ($child = $this->getDatabase()->getRow($query)) {
0 ignored issues
show
Bug introduced by
It seems like $query defined by $this->getDatabase()->qu...GROUP BY children.id;") on line 6761 can also be of type boolean; however, EvolutionCMS\Database::getRow() does only seem to accept object<mysqli_result>, 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...
6768
            if ($child['alias'] == $alias || $child['child_id'] == $alias) {
6769
                return $child['child_id'];
6770
            }
6771
6772
            if ($child['children_count'] > 0) {
6773
                $id = $this->getHiddenIdFromAlias($child['id'], $alias);
6774
                if ($id) {
6775
                    return $id;
6776
                }
6777
            }
6778
        }
6779
6780
        return false;
6781
    }
6782
6783
    /**
6784
     * @param $alias
6785
     * @return bool|int
6786
     */
6787
    public function getIdFromAlias($alias)
6788
    {
6789
        if (isset($this->documentListing[$alias])) {
6790
            return $this->documentListing[$alias];
6791
        }
6792
6793
        $tbl_site_content = $this->getFullTableName('site_content');
6794
        if ($this->config['use_alias_path'] == 1) {
6795
            if ($alias == '.') {
6796
                return 0;
6797
            }
6798
6799
            if (strpos($alias, '/') !== false) {
6800
                $_a = explode('/', $alias);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $_a. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
6801
            } else {
6802
                $_a[] = $alias;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$_a was never initialized. Although not strictly required by PHP, it is generally a good practice to add $_a = 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...
6803
            }
6804
            $id = 0;
6805
6806
            foreach ($_a as $alias) {
6807
                if ($id === false) {
6808
                    break;
6809
                }
6810
                $alias = $this->getDatabase()->escape($alias);
6811
                $rs = $this->getDatabase()->select('id', $tbl_site_content, "deleted=0 and parent='{$id}' and alias='{$alias}'");
6812
                if ($this->getDatabase()->getRecordCount($rs) == 0) {
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se... and alias='{$alias}'") on line 6811 can also be of type boolean; however, EvolutionCMS\Database::getRecordCount() does only seem to accept object<mysqli_result>, 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...
6813
                    $rs = $this->getDatabase()->select('id', $tbl_site_content, "deleted=0 and parent='{$id}' and id='{$alias}'");
6814
                }
6815
                $next = $this->getDatabase()->getValue($rs);
0 ignored issues
show
Bug introduced by
It seems like $rs can also be of type boolean; however, EvolutionCMS\Database::getValue() does only seem to accept object<mysqli_result>|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...
6816
                $id = !$next ? $this->getHiddenIdFromAlias($id, $alias) : $next;
6817
            }
6818
        } else {
6819
            $rs = $this->getDatabase()->select('id', $tbl_site_content, "deleted=0 and alias='{$alias}'", 'parent, menuindex');
6820
            $id = $this->getDatabase()->getValue($rs);
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se...", 'parent, menuindex') on line 6819 can also be of type boolean; however, EvolutionCMS\Database::getValue() does only seem to accept object<mysqli_result>|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...
6821
            if (!$id) {
6822
                $id = false;
6823
            }
6824
        }
6825
        return $id;
6826
    }
6827
6828
    /**
6829
     * @param string $str
6830
     * @return bool|mixed|string
6831
     */
6832
    public function atBindInclude($str = '')
6833
    {
6834
        if (strpos($str, '@INCLUDE') !== 0) {
6835
            return $str;
6836
        }
6837 View Code Duplication
        if (strpos($str, "\n") !== false) {
6838
            $str = substr($str, 0, strpos("\n", $str));
6839
        }
6840
6841
        $str = substr($str, 9);
6842
        $str = trim($str);
6843
        $str = str_replace('\\', '/', $str);
6844
        $str = ltrim($str, '/');
6845
6846
        $tpl_dir = 'assets/templates/';
6847
6848
        if (strpos($str, MODX_MANAGER_PATH) === 0) {
6849
            return false;
6850
        } elseif (is_file(MODX_BASE_PATH . $str)) {
6851
            $file_path = MODX_BASE_PATH . $str;
6852
        } elseif (is_file(MODX_BASE_PATH . "{$tpl_dir}{$str}")) {
6853
            $file_path = MODX_BASE_PATH . $tpl_dir . $str;
6854
        } else {
6855
            return false;
6856
        }
6857
6858
        if (!$file_path || !is_file($file_path)) {
6859
            return false;
6860
        }
6861
6862
        ob_start();
6863
        $modx = &$this;
6864
        $result = include($file_path);
6865
        if ($result === 1) {
6866
            $result = '';
6867
        }
6868
        $content = ob_get_clean();
6869
        if (!$content && $result) {
6870
            $content = $result;
6871
        }
6872
        return $content;
6873
    }
6874
6875
    // php compat
6876
6877
    /**
6878
     * @param $str
6879
     * @param int $flags
6880
     * @param string $encode
6881
     * @return mixed
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use null|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...
6882
     */
6883
    public function htmlspecialchars($str, $flags = ENT_COMPAT, $encode = '')
6884
    {
6885
        return $this->getPhpCompat()->htmlspecialchars($str, $flags, $encode);
6886
    }
6887
6888
    /**
6889
     * @param $string
6890
     * @param bool $returnData
6891
     * @return bool|mixed
6892
     */
6893
    public function isJson($string, $returnData = false)
6894
    {
6895
        $data = json_decode($string, true);
6896
        return (json_last_error() == JSON_ERROR_NONE) ? ($returnData ? $data : true) : false;
6897
    }
6898
6899
    /**
6900
     * @param $key
6901
     * @return array
6902
     */
6903
    public function splitKeyAndFilter($key)
6904
    {
6905
        if ($this->config['enable_filter'] == 1 && strpos($key, ':') !== false && stripos($key, '@FILE') !== 0) {
6906
            list($key, $modifiers) = explode(':', $key, 2);
6907
        } else {
6908
            $modifiers = false;
6909
        }
6910
6911
        $key = trim($key);
6912
        if ($modifiers !== false) {
6913
            $modifiers = trim($modifiers);
6914
        }
6915
6916
        return array($key, $modifiers);
6917
    }
6918
6919
    /**
6920
     * @param string $value
6921
     * @param bool $modifiers
6922
     * @param string $key
6923
     * @return string
6924
     */
6925
    public function applyFilter($value = '', $modifiers = false, $key = '')
6926
    {
6927
        if ($modifiers === false || $modifiers == 'raw') {
6928
            return $value;
6929
        }
6930
        if ($modifiers !== false) {
6931
            $modifiers = trim($modifiers);
6932
        }
6933
6934
        return $this->getModifiers()->phxFilter($key, $value, $modifiers);
6935
    }
6936
6937
    // End of class.
6938
6939
6940
    /**
6941
     * Get Clean Query String
6942
     *
6943
     * Fixes the issue where passing an array into the q get variable causes errors
6944
     *
6945
     */
6946
    private static function _getCleanQueryString()
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...
Coding Style introduced by
_getCleanQueryString uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
6947
    {
6948
        $q = MODX_CLI ? null : $_GET['q'];
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $q. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
6949
6950
        //Return null if the query doesn't exist
6951
        if (empty($q)) {
6952
            return null;
6953
        }
6954
6955
        //If we have a string, return it
6956
        if (is_string($q)) {
6957
            return $q;
6958
        }
6959
6960
        //If we have an array, return the first element
6961
        if (is_array($q)) {
6962
            return $q[0];
6963
        }
6964
    }
6965
6966
    /**
6967
     * @param string $title
6968
     * @param string $msg
6969
     * @param int $type
6970
     */
6971
    public function addLog($title = 'no title', $msg = '', $type = 1)
0 ignored issues
show
Coding Style introduced by
addLog uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
6972
    {
6973
        if ($title === '') {
6974
            $title = 'no title';
6975
        }
6976
        if (is_array($msg)) {
6977
            $msg = '<pre>' . print_r($msg, true) . '</pre>';
6978
        } elseif ($msg === '') {
6979
            $msg = $_SERVER['REQUEST_URI'];
6980
        }
6981
        $this->logEvent(0, $type, $msg, $title);
6982
    }
6983
6984
}
6985