Completed
Push — develop ( 4e806c...6a553b )
by Agel_Nash
10:03
created

Core::getDocumentObjectFromCache()   F

Complexity

Conditions 16
Paths 236

Size

Total Lines 85
Code Lines 53

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 16
eloc 53
nc 236
nop 2
dl 0
loc 85
rs 3.9504
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 $configGlobal = null; // contains backup of settings overwritten by user-settings
102
    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...
103
    public $result;
104
    public $sql;
105
    public $table_prefix;
106
    public $debug = false;
107
    public $documentIdentifier;
108
    public $documentMethod;
109
    public $documentGenerated;
110
    public $documentContent;
111
    public $documentOutput;
112
    public $tstart;
113
    public $mstart;
114
    public $minParserPasses;
115
    public $maxParserPasses;
116
    public $documentObject;
117
    public $templateObject;
118
    public $snippetObjects;
119
    public $stopOnNotice = false;
120
    public $executedQueries;
121
    public $queryTime;
122
    public $currentSnippet;
123
    public $documentName;
124
    public $aliases;
125
    public $visitor;
126
    public $entrypage;
127
    public $documentListing;
128
    /**
129
     * feed the parser the execution start time
130
     * @var bool
131
     */
132
    public $dumpSnippets = false;
133
    public $snippetsCode;
134
    public $snippetsTime = array();
135
    public $chunkCache;
136
    public $snippetCache;
137
    public $contentTypes;
138
    public $dumpSQL = false;
139
    public $queryCode;
140
    public $virtualDir;
141
    public $placeholders;
142
    public $sjscripts = array();
143
    public $jscripts = array();
144
    public $loadedjscripts = array();
145
    public $documentMap;
146
    public $forwards = 3;
147
    public $error_reporting = 1;
148
    public $dumpPlugins = false;
149
    public $pluginsCode;
150
    public $pluginsTime = array();
151
    public $pluginCache = array();
152
    public $aliasListing;
153
    public $lockedElements = null;
154
    public $tmpCache = array();
155
    private $version = array();
156
    public $extensions = array();
157
    public $cacheKey = null;
158
    public $recentUpdate = 0;
159
    public $useConditional = false;
160
    protected $systemCacheKey = null;
161
    public $snipLapCount = 0;
162
    public $messageQuitCount;
163
    public $time;
164
    public $sid;
165
    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...
166
    public $decoded_request_uri;
167
    /**
168
     * @var Legacy\DeprecatedCore
169
     * @deprecated use ->getDeprecatedCore()
170
     */
171
    public $old;
172
173
    /**
174
     * Hold the class instance.
175
     * @var self
176
     */
177
    private static $instance = null;
178
179
    private $services;
180
    private $serviceStore = [];
181
182
    /**
183
     * @var array
184
     * $this->{$key}
185
     */
186
    public $providerAliases = [
187
        'db' => Interfaces\DatabaseInterface::class,
188
        'mail' => Interfaces\MailInterface::class,
189
        'phpcompat' => Interfaces\PhpCompatInterface::class,
190
        'phpass' => Interfaces\PasswordHashInterface::class,
191
        'table' => Interfaces\MakeTableInterface::class,
192
        'export' => Interfaces\ExportSiteInerface::class,
193
        'manager' => Interfaces\ManagerApiInterface::class,
194
        'filter' => Interfaces\ModifiersInterface::class
195
    ];
196
197
    /**
198
     * @var array
199
     * $this->loadExtension($key)
200
     */
201
    public $extensionAlias = [
202
        'DBAPI' => Interfaces\DatabaseInterface::class,
203
        'MODxMailer' => Interfaces\MailInterface::class,
204
        'PHPCOMPAT' => Interfaces\PhpCompatInterface::class,
205
        'phpass' => Interfaces\PasswordHashInterface::class,
206
        'makeTable' => Interfaces\MakeTableInterface::class,
207
        'EXPORT_SITE' => Interfaces\ExportSiteInerface::class,
208
        'ManagerAPI' => Interfaces\ManagerApiInterface::class,
209
        'MODIFIERS' => Interfaces\ModifiersInterface::class
210
    ];
211
212
    /**
213
     * @param array $services
214
     * @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...
215
     */
216
    public function __construct(array $services = array())
217
    {
218
        self::$instance = $this;
219
        if (empty($services)) {
220
            $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...
221
        }
222
        $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...
223
224
        $this->initialize();
225
    }
226
227
    /**
228
     * {@inheritDoc}
229
     */
230
    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...
231
    {
232
        if (!$this->hasService($name)) {
233
            throw new Exceptions\ServiceNotFoundException('Service not found: '.$name);
234
        }
235
        // If we haven't created it, create it and save to store
236
        if (!isset($this->serviceStore[$name])) {
237
            $this->serviceStore[$name] = $this->createService($name);
238
        }
239
        // Return service from store
240
        return $this->serviceStore[$name];
241
    }
242
    /**
243
     * {@inheritDoc}
244
     */
245
    public function hasService($name)
246
    {
247
        return isset($this->services[$name]);
248
    }
249
250
    /**
251
     * Attempt to create a service.
252
     *
253
     * @param string $name The service name.
254
     *
255
     * @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...
256
     *
257
     * @throws Exceptions\ContainerException On failure.
258
     */
259
    private function createService($name)
260
    {
261
        $entry = &$this->services[$name];
262
        if (!is_array($entry) || !isset($entry['class'])) {
263
            throw new Exceptions\ContainerException($name.' service entry must be an array containing a \'class\' key');
264
        } elseif (!class_exists($entry['class'])) {
265
            throw new Exceptions\ContainerException($name.' service class does not exist: '.$entry['class']);
266
        } elseif (isset($entry['lock'])) {
267
            throw new Exceptions\ContainerException($name.' contains circular reference');
268
        }
269
        $entry['lock'] = true;
270
        $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...
271
        $reflector = new \ReflectionClass($entry['class']);
272
        $service = $reflector->newInstanceArgs($arguments);
273
        if (isset($entry['calls'])) {
274
            $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...
275
        }
276
277
        if ($alias = $this->checkServiceAlias($name)) {
278
            $this->{$alias} = $service;
279
        }
280
281
        return $service;
282
    }
283
284
    private function checkServiceAlias($name){
285
        foreach ($this->providerAliases as $alias => $interface) {
286
            if($name === $interface) {
287
                return $alias;
288
            }
289
        }
290
291
        return false;
292
    }
293
294
    /**
295
     * Resolve argument definitions into an array of arguments.
296
     *
297
     * @param array  $argumentDefinitions The service arguments definition.
298
     *
299
     * @return array The service constructor arguments.
300
     *
301
     * @throws Exceptions\ContainerException On failure.
302
     */
303
    private function resolveServiceArguments(array $argumentDefinitions)
304
    {
305
        $arguments = [];
306
        foreach ($argumentDefinitions as $argumentDefinition) {
307
            if ($argumentDefinition instanceof Interfaces\ServiceProviderInterface) {
308
                $argumentServiceName = $argumentDefinition->getName();
309
                $arguments[] = $this->getService($argumentServiceName);
310
            } else {
311
                $arguments[] = $argumentDefinition;
312
            }
313
        }
314
        return $arguments;
315
    }
316
    /**
317
     * Initialize a service using the call definitions.
318
     *
319
     * @param object $service         The service.
320
     * @param string $name            The service name.
321
     * @param array  $callDefinitions The service calls definition.
322
     *
323
     * @throws Exceptions\ContainerException On failure.
324
     */
325
    private function initializeService($service, $name, array $callDefinitions)
326
    {
327
        foreach ($callDefinitions as $callDefinition) {
328
            if (!is_array($callDefinition) || !isset($callDefinition['method'])) {
329
                throw new Exceptions\ContainerException($name.' service calls must be arrays containing a \'method\' key');
330
            } elseif (!is_callable([$service, $callDefinition['method']])) {
331
                throw new Exceptions\ContainerException($name.' service asks for call to uncallable method: '.$callDefinition['method']);
332
            }
333
            $arguments = isset($callDefinition['arguments']) ? $this->resolveServiceArguments($callDefinition['arguments']) : [];
334
335
            call_user_func_array([$service, $callDefinition['method']], $arguments);
336
        }
337
    }
338
339
    /**
340
     * @return Database
341
     * @throws Exceptions\ServiceNotFoundException
342
     */
343
    public function getDatabase()
344
    {
345
        return $this->getService(Interfaces\DatabaseInterface::class);
346
    }
347
348
    /**
349
     * @return Mail
350
     * @throws Exceptions\ServiceNotFoundException
351
     */
352
    public function getMail()
353
    {
354
        return $this->getService(Interfaces\MailInterface::class);
355
    }
356
357
    /**
358
     * @return Legacy\PhpCompat
359
     * @throws Exceptions\ServiceNotFoundException
360
     */
361
    public function getPhpCompat()
362
    {
363
        return $this->getService(Interfaces\PhpCompatInterface::class);
364
    }
365
366
    /**
367
     * @return Legacy\PasswordHash
368
     * @throws Exceptions\ServiceNotFoundException
369
     */
370
    public function getPasswordHash()
371
    {
372
        return $this->getService(Interfaces\PasswordHashInterface::class);
373
    }
374
375
    /**
376
     * @return Support\MakeTable
377
     * @throws Exceptions\ServiceNotFoundException
378
     */
379
    public function getMakeTable()
380
    {
381
        return $this->getService(Interfaces\MakeTableInterface::class);
382
    }
383
384
    /**
385
     * @return Legacy\ExportSite
386
     * @throws Exceptions\ServiceNotFoundException
387
     */
388
    public function getExportSite()
389
    {
390
        return $this->getService(Interfaces\ExportSiteInerface::class);
391
    }
392
393
    /**
394
     * @return mixed
395
     * @throws Exceptions\ServiceNotFoundException
396
     */
397
    public function getDeprecatedCore()
398
    {
399
        return $this->getService(Interfaces\DeprecatedCoreInterface::class);
400
    }
401
402
    /**
403
     * @return mixed
404
     * @throws Exceptions\ServiceNotFoundException
405
     */
406
    public function getManagerApi()
407
    {
408
        return $this->getService(Interfaces\ManagerApiInterface::class);
409
    }
410
411
    /**
412
     * @return mixed
413
     * @throws Exceptions\ServiceNotFoundException
414
     */
415
    public function getModifiers()
416
    {
417
        return $this->getService(Interfaces\ModifiersInterface::class);
418
    }
419
420
    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...
421
    {
422
        if ($this->isLoggedIn()) {
423
            ini_set('display_errors', 1);
424
        }
425
        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...
426
        if (substr(PHP_OS, 0, 3) === 'WIN' && $database_server === 'localhost') {
427
            $database_server = '127.0.0.1';
428
        }
429
        // events
430
        $this->event = new Event();
431
        $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...
432
        // set track_errors ini variable
433
        @ ini_set("track_errors", "1"); // enable error tracking in $php_errormsg
434
        $this->time = $_SERVER['REQUEST_TIME']; // for having global timestamp
435
436
        $this->q = self::_getCleanQueryString();
437
    }
438
439
    final public function __clone()
440
    {
441
    }
442
443
    /**
444
     * @param array $services
445
     * @return self
446
     */
447
    public static function getInstance(array $services = array())
448
    {
449
        if (self::$instance === null) {
450
            self::$instance = new static($services);
451
        }
452
        return self::$instance;
453
    }
454
455
    /**
456
     * @param $method_name
457
     * @param $arguments
458
     * @return mixed
459
     */
460
    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...
461
    {
462
        $old = $this->getDeprecatedCore();
463
        //////////@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...
464
        if (method_exists($old, $method_name)) {
465
            $error_type = 1;
466
        } else {
467
            $error_type = 3;
468
        }
469
470
        if (!isset($this->config['error_reporting']) || 1 < $this->config['error_reporting']) {
471
            if ($error_type == 1) {
472
                $title = 'Call deprecated method';
473
                $msg = $this->getPhpCompat()->htmlspecialchars("\$modx->{$method_name}() is deprecated function");
474
            } else {
475
                $title = 'Call undefined method';
476
                $msg = $this->getPhpCompat()->htmlspecialchars("\$modx->{$method_name}() is undefined function");
477
            }
478
            $info = debug_backtrace();
479
            $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...
480
            if (!empty($this->currentSnippet)) {
481
                $m[] = 'Snippet - ' . $this->currentSnippet;
482
            } elseif (!empty($this->event->activePlugin)) {
483
                $m[] = 'Plugin - ' . $this->event->activePlugin;
484
            }
485
            $m[] = $this->decoded_request_uri;
486
            $m[] = str_replace('\\', '/', $info[0]['file']) . '(line:' . $info[0]['line'] . ')';
487
            $msg = implode('<br />', $m);
488
            $this->logEvent(0, $error_type, $msg, $title);
489
        }
490
        if (method_exists($old, $method_name)) {
491
            return call_user_func_array(array($old, $method_name), $arguments);
492
        }
493
    }
494
495
    /**
496
     * @param string $connector
497
     * @return bool
498
     */
499
    public function checkSQLconnect($connector = 'db')
500
    {
501
        $flag = false;
502
        if (is_scalar($connector) && !empty($connector) && isset($this->{$connector}) && $this->{$connector} instanceof Interfaces\DatabaseInterface) {
503
            $flag = (bool)$this->{$connector}->conn;
504
        }
505
        return $flag;
506
    }
507
508
    /**
509
     * Loads an extension from the extenders folder.
510
     * You can load any extension creating a boot file:
511
     * MODX_MANAGER_PATH."includes/extenders/ex_{$extname}.inc.php"
512
     * $extname - extension name in lowercase
513
     *
514
     * @deprecated use getService
515
     * @param $extname
516
     * @param bool $reload
517
     * @return bool
518
     */
519
    public function loadExtension($extname, $reload = true)
520
    {
521
        $out = false;
522
        if (isset($this->extensionAlias[$extname])) {
523
            $out = $this->getService($this->extensionAlias[$extname]);
524
        } else {
525
            $flag = ($reload || !in_array($extname, $this->extensions));
526
            if ($this->checkSQLconnect('db') && $flag) {
527
                $evtOut = $this->invokeEvent('OnBeforeLoadExtension', array('name' => $extname, 'reload' => $reload));
528
                if (is_array($evtOut) && count($evtOut) > 0) {
529
                    $out = array_pop($evtOut);
530
                }
531
            }
532
            if (!$out && $flag) {
533
                $extname = trim(str_replace(array('..', '/', '\\'), '', strtolower($extname)));
534
                $filename = MODX_MANAGER_PATH . "includes/extenders/ex_{$extname}.inc.php";
535
                $out = is_file($filename) ? include $filename : false;
536
            }
537
            if ($out && !in_array($extname, $this->extensions)) {
538
                $this->extensions[] = $extname;
539
            }
540
        }
541
        return $out;
542
    }
543
544
    /**
545
     * Returns the current micro time
546
     *
547
     * @return float
548
     */
549
    public function getMicroTime()
550
    {
551
        list ($usec, $sec) = explode(' ', microtime());
552
        return ((float)$usec + (float)$sec);
553
    }
554
555
    /**
556
     * Redirect
557
     *
558
     * @param string $url
559
     * @param int $count_attempts
560
     * @param string $type $type
561
     * @param string $responseCode
562
     * @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...
563
     * @global string $base_url
564
     * @global string $site_url
565
     */
566
    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...
567
    {
568
        $header = '';
569
        if (empty ($url)) {
570
            return false;
571
        }
572
        if ($count_attempts == 1) {
573
            // append the redirect count string to the url
574
            $currentNumberOfRedirects = isset ($_REQUEST['err']) ? $_REQUEST['err'] : 0;
575
            if ($currentNumberOfRedirects > 3) {
576
                $this->messageQuit('Redirection attempt failed - please ensure the document you\'re trying to redirect to exists. <p>Redirection URL: <i>' . $url . '</i></p>');
577
            } else {
578
                $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...
579
                if (strpos($url, "?") > 0) {
580
                    $url .= "&err=$currentNumberOfRedirects";
581
                } else {
582
                    $url .= "?err=$currentNumberOfRedirects";
583
                }
584
            }
585
        }
586
        if ($type == 'REDIRECT_REFRESH') {
587
            $header = 'Refresh: 0;URL=' . $url;
588
        } elseif ($type == 'REDIRECT_META') {
589
            $header = '<META HTTP-EQUIV="Refresh" CONTENT="0; URL=' . $url . '" />';
590
            echo $header;
591
            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...
592
        } elseif ($type == 'REDIRECT_HEADER' || empty ($type)) {
593
            // check if url has /$base_url
594
            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...
595
            if (substr($url, 0, strlen($base_url)) == $base_url) {
596
                // append $site_url to make it work with Location:
597
                $url = $site_url . substr($url, strlen($base_url));
598
            }
599
            if (strpos($url, "\n") === false) {
600
                $header = 'Location: ' . $url;
601
            } else {
602
                $this->messageQuit('No newline allowed in redirect url.');
603
            }
604
        }
605
        if ($responseCode && (strpos($responseCode, '30') !== false)) {
606
            header($responseCode);
607
        }
608
609
        if(!empty($header)) {
610
            header($header);
611
        }
612
613
        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...
614
    }
615
616
    /**
617
     * Forward to another page
618
     *
619
     * @param int|string $id
620
     * @param string $responseCode
621
     */
622
    public function sendForward($id, $responseCode = '')
623
    {
624
        if ($this->forwards > 0) {
625
            $this->forwards = $this->forwards - 1;
626
            $this->documentIdentifier = $id;
627
            $this->documentMethod = 'id';
628
            if ($responseCode) {
629
                header($responseCode);
630
            }
631
            $this->prepareResponse();
632
            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...
633
        } else {
634
            $this->messageQuit("Internal Server Error id={$id}");
635
            header('HTTP/1.0 500 Internal Server Error');
636
            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...
637
        }
638
    }
639
640
    /**
641
     * Redirect to the error page, by calling sendForward(). This is called for example when the page was not found.
642
     * @param bool $noEvent
643
     */
644
    public function sendErrorPage($noEvent = false)
645
    {
646
        $this->systemCacheKey = 'notfound';
647
        if (!$noEvent) {
648
            // invoke OnPageNotFound event
649
            $this->invokeEvent('OnPageNotFound');
650
        }
651
        $url = $this->config['error_page'] ? $this->config['error_page'] : $this->config['site_start'];
652
653
        $this->sendForward($url, 'HTTP/1.0 404 Not Found');
654
        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...
655
    }
656
657
    /**
658
     * @param bool $noEvent
659
     */
660
    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...
661
    {
662
        // invoke OnPageUnauthorized event
663
        $_REQUEST['refurl'] = $this->documentIdentifier;
664
        $this->systemCacheKey = 'unauth';
665
        if (!$noEvent) {
666
            $this->invokeEvent('OnPageUnauthorized');
667
        }
668
        if ($this->config['unauthorized_page']) {
669
            $unauthorizedPage = $this->config['unauthorized_page'];
670
        } elseif ($this->config['error_page']) {
671
            $unauthorizedPage = $this->config['error_page'];
672
        } else {
673
            $unauthorizedPage = $this->config['site_start'];
674
        }
675
        $this->sendForward($unauthorizedPage, 'HTTP/1.1 401 Unauthorized');
676
        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...
677
    }
678
679
    /**
680
     * Get MODX settings including, but not limited to, the system_settings table
681
     */
682
    public function getSettings()
683
    {
684
        if (!isset($this->config['site_name'])) {
685
            $this->recoverySiteCache();
686
        }
687
688
        // setup default site id - new installation should generate a unique id for the site.
689
        if (!isset($this->config['site_id'])) {
690
            $this->config['site_id'] = "MzGeQ2faT4Dw06+U49x3";
691
        }
692
693
        // store base_url and base_path inside config array
694
        $this->config['base_url'] = MODX_BASE_URL;
695
        $this->config['base_path'] = MODX_BASE_PATH;
696
        $this->config['site_url'] = MODX_SITE_URL;
697
        $this->config['valid_hostnames'] = MODX_SITE_HOSTNAMES;
698
        $this->config['site_manager_url'] = MODX_MANAGER_URL;
699
        $this->config['site_manager_path'] = MODX_MANAGER_PATH;
700
        $this->error_reporting = $this->config['error_reporting'];
701
        $this->config['filemanager_path'] = str_replace('[(base_path)]', MODX_BASE_PATH, $this->config['filemanager_path']);
702
        $this->config['rb_base_dir'] = str_replace('[(base_path)]', MODX_BASE_PATH, $this->config['rb_base_dir']);
703
704
        if (!isset($this->config['enable_at_syntax'])) {
705
            $this->config['enable_at_syntax'] = 1;
706
        } // @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...
707
708
        // now merge user settings into evo-configuration
709
        $this->getUserSettings();
710
    }
711
712
    private function recoverySiteCache()
713
    {
714
        $site_cache_dir = MODX_BASE_PATH . $this->getCacheFolder();
715
        $site_cache_path = $site_cache_dir . 'siteCache.idx.php';
716
717
        if (is_file($site_cache_path)) {
718
            include($site_cache_path);
719
        }
720
        if (isset($this->config['site_name'])) {
721
            return;
722
        }
723
724
        $cache = new Cache();
725
        $cache->setCachepath($site_cache_dir);
726
        $cache->setReport(false);
727
        $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...
728
729
        clearstatcache();
730
        if (is_file($site_cache_path)) {
731
            include($site_cache_path);
732
        }
733
        if (isset($this->config['site_name'])) {
734
            return;
735
        }
736
737
        $rs = $this->getDatabase()->select(
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...
738
            'setting_name, setting_value',
739
            $this->getDatabase()->getFullTableName('system_settings')
740
        );
741
        while ($row = $this->getDatabase()->getRow($rs)) {
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se...ame('system_settings')) on line 737 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...
742
            $this->config[$row['setting_name']] = $row['setting_value'];
743
        }
744
745
        if (!$this->config['enable_filter']) {
746
            return;
747
        }
748
749
        $where = "plugincode LIKE '%phx.parser.class.inc.php%OnParseDocument();%' AND disabled != 1";
750
        $rs = $this->getDatabase()->select('id', $this->getDatabase()->getFullTableName('site_plugins'), $where);
751
        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 750 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...
752
            $this->config['enable_filter'] = '0';
753
        }
754
    }
755
756
    /**
757
     * Get user settings and merge into MODX configuration
758
     * @return array
759
     */
760
    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...
761
    {
762
        $tbl_web_user_settings = $this->getDatabase()->getFullTableName('web_user_settings');
763
        $tbl_user_settings = $this->getDatabase()->getFullTableName('user_settings');
764
765
        // load user setting if user is logged in
766
        $usrSettings = array();
767
        if ($id = $this->getLoginUserID()) {
768
            $usrType = $this->getLoginUserType();
769
            if (isset ($usrType) && $usrType == 'manager') {
770
                $usrType = 'mgr';
771
            }
772
773
            if ($usrType == 'mgr' && $this->isBackend()) {
774
                // invoke the OnBeforeManagerPageInit event, only if in backend
775
                $this->invokeEvent("OnBeforeManagerPageInit");
776
            }
777
778
            if (isset ($_SESSION[$usrType . 'UsrConfigSet'])) {
779
                $usrSettings = &$_SESSION[$usrType . 'UsrConfigSet'];
780
            } else {
781
                if ($usrType == 'web') {
782
                    $from = $tbl_web_user_settings;
783
                    $where = "webuser='{$id}'";
784
                } else {
785
                    $from = $tbl_user_settings;
786
                    $where = "user='{$id}'";
787
                }
788
789
                $which_browser_default = $this->configGlobal['which_browser'] ? $this->configGlobal['which_browser'] : $this->config['which_browser'];
790
791
                $result = $this->getDatabase()->select('setting_name, setting_value', $from, $where);
792
                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 791 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...
793 View Code Duplication
                    if ($row['setting_name'] == 'which_browser' && $row['setting_value'] == 'default') {
794
                        $row['setting_value'] = $which_browser_default;
795
                    }
796
                    $usrSettings[$row['setting_name']] = $row['setting_value'];
797
                }
798
                if (isset ($usrType)) {
799
                    $_SESSION[$usrType . 'UsrConfigSet'] = $usrSettings;
800
                } // store user settings in session
801
            }
802
        }
803
        if ($this->isFrontend() && $mgrid = $this->getLoginUserID('mgr')) {
804
            $musrSettings = array();
805
            if (isset ($_SESSION['mgrUsrConfigSet'])) {
806
                $musrSettings = &$_SESSION['mgrUsrConfigSet'];
807
            } else {
808
                if ($result = $this->getDatabase()->select('setting_name, setting_value', $tbl_user_settings, "user='{$mgrid}'")) {
809
                    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 808 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...
810
                        $musrSettings[$row['setting_name']] = $row['setting_value'];
811
                    }
812
                    $_SESSION['mgrUsrConfigSet'] = $musrSettings; // store user settings in session
813
                }
814
            }
815
            if (!empty ($musrSettings)) {
816
                $usrSettings = array_merge($musrSettings, $usrSettings);
817
            }
818
        }
819
        // save global values before overwriting/merging array
820
        foreach ($usrSettings as $param => $value) {
821
            if (isset($this->config[$param])) {
822
                $this->configGlobal[$param] = $this->config[$param];
823
            }
824
        }
825
826
        $this->config = array_merge($this->config, $usrSettings);
827
        $this->config['filemanager_path'] = str_replace('[(base_path)]', MODX_BASE_PATH, $this->config['filemanager_path']);
828
        $this->config['rb_base_dir'] = str_replace('[(base_path)]', MODX_BASE_PATH, $this->config['rb_base_dir']);
829
830
        return $usrSettings;
831
    }
832
833
    /**
834
     * Returns the document identifier of the current request
835
     *
836
     * @param string $method id and alias are allowed
837
     * @return int
838
     */
839
    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...
840
    {
841
        // function to test the query and find the retrieval method
842
        if ($method === 'alias') {
843
            return $this->getDatabase()->escape($_REQUEST['q']);
844
        }
845
846
        $id_ = filter_input(INPUT_GET, 'id');
847
        if ($id_) {
848
            if (preg_match('@^[1-9][0-9]*$@', $id_)) {
849
                return $id_;
850
            } else {
851
                $this->sendErrorPage();
852
            }
853
        } elseif (strpos($_SERVER['REQUEST_URI'], 'index.php/') !== false) {
854
            $this->sendErrorPage();
855
        } else {
856
            return $this->config['site_start'];
857
        }
858
    }
859
860
    /**
861
     * Check for manager or webuser login session since v1.2
862
     *
863
     * @param string $context
864
     * @return bool
865
     */
866
    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...
867
    {
868
        if (substr($context, 0, 1) == 'm') {
869
            $_ = '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...
870
        } else {
871
            $_ = 'webValidated';
872
        }
873
874
        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...
875
            return true;
876
        } else {
877
            return false;
878
        }
879
    }
880
881
    /**
882
     * Check for manager login session
883
     *
884
     * @return boolean
885
     */
886
    public function checkSession()
887
    {
888
        return $this->isLoggedin();
889
    }
890
891
    /**
892
     * Checks, if a the result is a preview
893
     *
894
     * @return boolean
895
     */
896
    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...
897
    {
898
        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...
899
            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...
900
                return true;
901
            } else {
902
                return false;
903
            }
904
        } else {
905
            return false;
906
        }
907
    }
908
909
    /**
910
     * check if site is offline
911
     *
912
     * @return boolean
913
     */
914
    public function checkSiteStatus()
915
    {
916
        if ($this->config['site_status']) {
917
            return true;
918
        }  // site online
919
        elseif ($this->isLoggedin()) {
920
            return true;
921
        }  // site offline but launched via the manager
922
        else {
923
            return false;
924
        } // site is offline
925
    }
926
927
    /**
928
     * Create a 'clean' document identifier with path information, friendly URL suffix and prefix.
929
     *
930
     * @param string $qOrig
931
     * @return string
932
     */
933
    public function cleanDocumentIdentifier($qOrig)
934
    {
935
        if (!$qOrig) {
936
            $qOrig = $this->config['site_start'];
937
        }
938
        $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...
939
940
        $pre = $this->config['friendly_url_prefix'];
941
        $suf = $this->config['friendly_url_suffix'];
942
        $pre = preg_quote($pre, '/');
943
        $suf = preg_quote($suf, '/');
944 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...
945
            $q = $_[1];
946
        }
947 View Code Duplication
        if ($suf && preg_match('@(.*)' . $suf . '$@', $q, $_)) {
948
            $q = $_[1];
949
        }
950
951
        /* First remove any / before or after */
952
        $q = trim($q, '/');
953
954
        /* Save path if any */
955
        /* FS#476 and FS#308: only return virtualDir if friendly paths are enabled */
956
        if ($this->config['use_alias_path'] == 1) {
957
            $_ = strrpos($q, '/');
958
            $this->virtualDir = $_ !== false ? substr($q, 0, $_) : '';
959
            if ($_ !== false) {
960
                $q = preg_replace('@.*/@', '', $q);
961
            }
962
        } else {
963
            $this->virtualDir = '';
964
        }
965
966
        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 */
967
            /* FS#476 and FS#308: check that id is valid in terms of virtualDir structure */
968
            if ($this->config['use_alias_path'] == 1) {
969
                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))))) {
970
                    $this->documentMethod = 'id';
971
                    return $q;
972
                } else { /* not a valid id in terms of virtualDir, treat as alias */
973
                    $this->documentMethod = 'alias';
974
                    return $q;
975
                }
976
            } else {
977
                $this->documentMethod = 'id';
978
                return $q;
979
            }
980
        } else { /* we didn't get an ID back, so instead we assume it's an alias */
981
            if ($this->config['friendly_alias_urls'] != 1) {
982
                $q = $qOrig;
983
            }
984
            $this->documentMethod = 'alias';
985
            return $q;
986
        }
987
    }
988
989
    /**
990
     * @return string
991
     */
992
    public function getCacheFolder()
993
    {
994
        return "assets/cache/";
995
    }
996
997
    /**
998
     * @param $key
999
     * @return string
1000
     */
1001
    public function getHashFile($key)
1002
    {
1003
        return $this->getCacheFolder() . "docid_" . $key . ".pageCache.php";
1004
    }
1005
1006
    /**
1007
     * @param $id
1008
     * @return array|mixed|null|string
1009
     */
1010
    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...
1011
        $hash = $id;
1012
        $tmp = null;
1013
        $params = array();
1014
        if(!empty($this->systemCacheKey)){
1015
            $hash = $this->systemCacheKey;
1016
        }else {
1017
            if (!empty($_GET)) {
1018
                // Sort GET parameters so that the order of parameters on the HTTP request don't affect the generated cache ID.
1019
                $params = $_GET;
1020
                ksort($params);
1021
                $hash .= '_'.md5(http_build_query($params));
1022
            }
1023
        }
1024
        $evtOut = $this->invokeEvent("OnMakePageCacheKey", array ("hash" => $hash, "id" => $id, 'params' => $params));
1025
        if (is_array($evtOut) && count($evtOut) > 0){
1026
            $tmp = array_pop($evtOut);
1027
        }
1028
        return empty($tmp) ? $hash : $tmp;
1029
    }
1030
1031
    /**
1032
     * @param $id
1033
     * @param bool $loading
1034
     * @return string
1035
     */
1036
    public function checkCache($id, $loading = false)
1037
    {
1038
        return $this->getDocumentObjectFromCache($id, $loading);
1039
    }
1040
1041
    /**
1042
     * Check the cache for a specific document/resource
1043
     *
1044
     * @param int $id
1045
     * @param bool $loading
1046
     * @return string
1047
     */
1048
    public function getDocumentObjectFromCache($id, $loading = false)
1049
    {
1050
        $key = ($this->config['cache_type'] == 2) ? $this->makePageCacheKey($id) : $id;
1051
        if ($loading) {
1052
            $this->cacheKey = $key;
1053
        }
1054
1055
        $cache_path = $this->getHashFile($key);
1056
1057
        if (!is_file($cache_path)) {
1058
            $this->documentGenerated = 1;
1059
            return '';
1060
        }
1061
        $content = file_get_contents($cache_path, false);
1062
        if (substr($content, 0, 5) === '<?php') {
1063
            $content = substr($content, strpos($content, '?>') + 2);
1064
        } // remove php header
1065
        $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...
1066
        if (count($a) == 1) {
1067
            $result = $a[0];
1068
        } // return only document content
1069
        else {
1070
            $docObj = unserialize($a[0]); // rebuild document object
1071
            // check page security
1072
            if ($docObj['privateweb'] && isset ($docObj['__MODxDocGroups__'])) {
1073
                $pass = false;
1074
                $usrGrps = $this->getUserDocGroups();
1075
                $docGrps = explode(',', $docObj['__MODxDocGroups__']);
1076
                // check is user has access to doc groups
1077
                if (is_array($usrGrps)) {
1078
                    foreach ($usrGrps as $k => $v) {
1079
                        if (!in_array($v, $docGrps)) {
1080
                            continue;
1081
                        }
1082
                        $pass = true;
1083
                        break;
1084
                    }
1085
                }
1086
                // diplay error pages if user has no access to cached doc
1087
                if (!$pass) {
1088
                    if ($this->config['unauthorized_page']) {
1089
                        // check if file is not public
1090
                        $rs = $this->getDatabase()->select(
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...
1091
                            'count(id)',
1092
                            $this->getDatabase()->getFullTableName('document_groups'),
1093
                            "document='{$id}'",
1094
                            '',
1095
                            '1'
1096
                        );
1097
                        $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 1090 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...
1098
                    } else {
1099
                        $total = 0;
1100
                    }
1101
1102
                    if ($total > 0) {
1103
                        $this->sendUnauthorizedPage();
1104
                    } else {
1105
                        $this->sendErrorPage();
1106
                    }
1107
1108
                    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...
1109
                }
1110
            }
1111
            // Grab the Scripts
1112
            if (isset($docObj['__MODxSJScripts__'])) {
1113
                $this->sjscripts = $docObj['__MODxSJScripts__'];
1114
            }
1115
            if (isset($docObj['__MODxJScripts__'])) {
1116
                $this->jscripts = $docObj['__MODxJScripts__'];
1117
            }
1118
1119
            // Remove intermediate variables
1120
            unset($docObj['__MODxDocGroups__'], $docObj['__MODxSJScripts__'], $docObj['__MODxJScripts__']);
1121
1122
            $this->documentObject = $docObj;
1123
1124
            $result = $a[1]; // return document content
1125
        }
1126
1127
        $this->documentGenerated = 0;
1128
        // invoke OnLoadWebPageCache  event
1129
        $this->documentContent = $result;
1130
        $this->invokeEvent('OnLoadWebPageCache');
1131
        return $result;
1132
    }
1133
1134
    /**
1135
     * Final processing and output of the document/resource.
1136
     *
1137
     * - runs uncached snippets
1138
     * - add javascript to <head>
1139
     * - removes unused placeholders
1140
     * - converts URL tags [~...~] to URLs
1141
     *
1142
     * @param boolean $noEvent Default: false
1143
     */
1144
    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...
1145
    {
1146
        $this->documentOutput = $this->documentContent;
1147
1148
        if ($this->documentGenerated == 1 && $this->documentObject['cacheable'] == 1 && $this->documentObject['type'] == 'document' && $this->documentObject['published'] == 1) {
1149
            if (!empty($this->sjscripts)) {
1150
                $this->documentObject['__MODxSJScripts__'] = $this->sjscripts;
1151
            }
1152
            if (!empty($this->jscripts)) {
1153
                $this->documentObject['__MODxJScripts__'] = $this->jscripts;
1154
            }
1155
        }
1156
1157
        // check for non-cached snippet output
1158
        if (strpos($this->documentOutput, '[!') > -1) {
1159
            $this->recentUpdate = $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'];
1160
1161
            $this->documentOutput = str_replace('[!', '[[', $this->documentOutput);
1162
            $this->documentOutput = str_replace('!]', ']]', $this->documentOutput);
1163
1164
            // Parse document source
1165
            $this->documentOutput = $this->parseDocumentSource($this->documentOutput);
1166
        }
1167
1168
        // Moved from prepareResponse() by sirlancelot
1169
        // Insert Startup jscripts & CSS scripts into template - template must have a <head> tag
1170
        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...
1171
            // change to just before closing </head>
1172
            // $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...
1173
            $this->documentOutput = preg_replace("/(<\/head>)/i", $js . "\n\\1", $this->documentOutput);
1174
        }
1175
1176
        // Insert jscripts & html block into template - template must have a </body> tag
1177
        if ($js = $this->getRegisteredClientScripts()) {
1178
            $this->documentOutput = preg_replace("/(<\/body>)/i", $js . "\n\\1", $this->documentOutput);
1179
        }
1180
        // End fix by sirlancelot
1181
1182
        $this->documentOutput = $this->cleanUpMODXTags($this->documentOutput);
1183
1184
        $this->documentOutput = $this->rewriteUrls($this->documentOutput);
1185
1186
        // send out content-type and content-disposition headers
1187
        if (IN_PARSER_MODE == "true") {
1188
            $type = !empty ($this->contentTypes[$this->documentIdentifier]) ? $this->contentTypes[$this->documentIdentifier] : "text/html";
1189
            header('Content-Type: ' . $type . '; charset=' . $this->config['modx_charset']);
1190
            //            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...
1191
            //                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...
1192
            if (!$this->checkPreview() && $this->documentObject['content_dispo'] == 1) {
1193
                if ($this->documentObject['alias']) {
1194
                    $name = $this->documentObject['alias'];
1195
                } else {
1196
                    // strip title of special characters
1197
                    $name = $this->documentObject['pagetitle'];
1198
                    $name = strip_tags($name);
1199
                    $name = $this->cleanUpMODXTags($name);
1200
                    $name = strtolower($name);
1201
                    $name = preg_replace('/&.+?;/', '', $name); // kill entities
1202
                    $name = preg_replace('/[^\.%a-z0-9 _-]/', '', $name);
1203
                    $name = preg_replace('/\s+/', '-', $name);
1204
                    $name = preg_replace('|-+|', '-', $name);
1205
                    $name = trim($name, '-');
1206
                }
1207
                $header = 'Content-Disposition: attachment; filename=' . $name;
1208
                header($header);
1209
            }
1210
        }
1211
        $this->setConditional();
1212
1213
        $stats = $this->getTimerStats($this->tstart);
1214
1215
        $out =& $this->documentOutput;
1216
        $out = str_replace("[^q^]", $stats['queries'], $out);
1217
        $out = str_replace("[^qt^]", $stats['queryTime'], $out);
1218
        $out = str_replace("[^p^]", $stats['phpTime'], $out);
1219
        $out = str_replace("[^t^]", $stats['totalTime'], $out);
1220
        $out = str_replace("[^s^]", $stats['source'], $out);
1221
        $out = str_replace("[^m^]", $stats['phpMemory'], $out);
1222
        //$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...
1223
1224
        // invoke OnWebPagePrerender event
1225
        if (!$noEvent) {
1226
            $evtOut = $this->invokeEvent('OnWebPagePrerender', array('documentOutput' => $this->documentOutput));
1227
            if (is_array($evtOut) && count($evtOut) > 0) {
1228
                $this->documentOutput = $evtOut['0'];
1229
            }
1230
        }
1231
1232
        $this->documentOutput = $this->removeSanitizeSeed($this->documentOutput);
1233
1234
        if (strpos($this->documentOutput, '\{') !== false) {
1235
            $this->documentOutput = $this->RecoveryEscapedTags($this->documentOutput);
1236
        } elseif (strpos($this->documentOutput, '\[') !== false) {
1237
            $this->documentOutput = $this->RecoveryEscapedTags($this->documentOutput);
1238
        }
1239
1240
        echo $this->documentOutput;
1241
1242
        if ($this->dumpSQL) {
1243
            echo $this->queryCode;
1244
        }
1245
        if ($this->dumpSnippets) {
1246
            $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...
1247
            $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...
1248
            foreach ($this->snippetsTime as $s => $v) {
1249
                $t = $v['time'];
1250
                $sname = $v['sname'];
1251
                $sc .= sprintf("%s. %s (%s)<br>", $s, $sname, sprintf("%2.2f ms", $t)); // currentSnippet
1252
                $tt += $t;
1253
            }
1254
            echo "<fieldset><legend><b>Snippets</b> (" . count($this->snippetsTime) . " / " . sprintf("%2.2f ms", $tt) . ")</legend>{$sc}</fieldset><br />";
1255
            echo $this->snippetsCode;
1256
        }
1257
        if ($this->dumpPlugins) {
1258
            $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...
1259
            $tt = 0;
1260
            foreach ($this->pluginsTime as $s => $t) {
1261
                $ps .= "$s (" . sprintf("%2.2f ms", $t * 1000) . ")<br>";
1262
                $tt += $t;
1263
            }
1264
            echo "<fieldset><legend><b>Plugins</b> (" . count($this->pluginsTime) . " / " . sprintf("%2.2f ms", $tt * 1000) . ")</legend>{$ps}</fieldset><br />";
1265
            echo $this->pluginsCode;
1266
        }
1267
1268
        ob_end_flush();
1269
    }
1270
1271
    /**
1272
     * @param $contents
1273
     * @return mixed
1274
     */
1275
    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...
1276
    {
1277
        list($sTags, $rTags) = $this->getTagsForEscape();
1278
        return str_replace($rTags, $sTags, $contents);
1279
    }
1280
1281
    /**
1282
     * @param string $tags
1283
     * @return array[]
1284
     */
1285
    public function getTagsForEscape($tags = '{{,}},[[,]],[!,!],[*,*],[(,)],[+,+],[~,~],[^,^]')
1286
    {
1287
        $srcTags = explode(',', $tags);
1288
        $repTags = array();
1289
        foreach ($srcTags as $tag) {
1290
            $repTags[] = '\\' . $tag[0] . '\\' . $tag[1];
1291
        }
1292
        return array($srcTags, $repTags);
1293
    }
1294
1295
    /**
1296
     * @param $tstart
1297
     * @return array
1298
     */
1299
    public function getTimerStats($tstart)
1300
    {
1301
        $stats = array();
1302
1303
        $stats['totalTime'] = ($this->getMicroTime() - $tstart);
1304
        $stats['queryTime'] = $this->queryTime;
1305
        $stats['phpTime'] = $stats['totalTime'] - $stats['queryTime'];
1306
1307
        $stats['queryTime'] = sprintf("%2.4f s", $stats['queryTime']);
1308
        $stats['totalTime'] = sprintf("%2.4f s", $stats['totalTime']);
1309
        $stats['phpTime'] = sprintf("%2.4f s", $stats['phpTime']);
1310
        $stats['source'] = $this->documentGenerated == 1 ? "database" : "cache";
1311
        $stats['queries'] = isset ($this->executedQueries) ? $this->executedQueries : 0;
1312
        $stats['phpMemory'] = (memory_get_peak_usage(true) / 1024 / 1024) . " mb";
1313
1314
        return $stats;
1315
    }
1316
1317
    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...
1318
    {
1319
        if (!empty($_POST) || (defined('MODX_API_MODE') && MODX_API_MODE) || $this->getLoginUserID('mgr') || !$this->useConditional || empty($this->recentUpdate)) {
1320
            return;
1321
        }
1322
        $last_modified = gmdate('D, d M Y H:i:s T', $this->recentUpdate);
1323
        $etag = md5($last_modified);
1324
        $HTTP_IF_MODIFIED_SINCE = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? $_SERVER['HTTP_IF_MODIFIED_SINCE'] : false;
1325
        $HTTP_IF_NONE_MATCH = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? $_SERVER['HTTP_IF_NONE_MATCH'] : false;
1326
        header('Pragma: no-cache');
1327
1328
        if ($HTTP_IF_MODIFIED_SINCE == $last_modified || strpos($HTTP_IF_NONE_MATCH, $etag) !== false) {
1329
            header('HTTP/1.1 304 Not Modified');
1330
            header('Content-Length: 0');
1331
            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...
1332
        } else {
1333
            header("Last-Modified: {$last_modified}");
1334
            header("ETag: '{$etag}'");
1335
        }
1336
    }
1337
1338
    /**
1339
     * Checks the publish state of page
1340
     */
1341
    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...
1342
    {
1343
        $cacheRefreshTime = 0;
1344
        $recent_update = 0;
1345
        @include(MODX_BASE_PATH . $this->getCacheFolder() . 'sitePublishing.idx.php');
1346
        $this->recentUpdate = $recent_update;
1347
1348
        $timeNow = $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'];
1349
        if ($timeNow < $cacheRefreshTime || $cacheRefreshTime == 0) {
1350
            return;
1351
        }
1352
1353
        // now, check for documents that need publishing
1354
        $field = array('published' => 1, 'publishedon' => $timeNow);
1355
        $where = "pub_date <= {$timeNow} AND pub_date!=0 AND published=0";
1356
        $result_pub = $this->getDatabase()->select(
1357
            'id',
1358
            $this->getDatabase()->getFullTableName('site_content'),
1359
            $where
1360
        );
1361
        $this->getDatabase()->update($field, $this->getDatabase()->getFullTableName('site_content'), $where);
1362 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 1356 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...
1363
            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 1356 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...
1364
                $this->invokeEvent("OnDocUnPublished", array(
1365
                    "docid" => $row_pub['id']
1366
                ));
1367
            }
1368
        }
1369
1370
        // now, check for documents that need un-publishing
1371
        $field = array('published' => 0, 'publishedon' => 0);
1372
        $where = "unpub_date <= {$timeNow} AND unpub_date!=0 AND published=1";
1373
        $result_unpub = $this->getDatabase()->select(
1374
            'id',
1375
            $this->getDatabase()->getFullTableName('site_content'),
1376
            $where
1377
        );
1378
        $this->getDatabase()->update($field, $this->getDatabase()->getFullTableName('site_content'), $where);
1379 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 1373 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...
1380
            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 1373 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...
1381
                $this->invokeEvent("OnDocUnPublished", array(
1382
                    "docid" => $row_unpub['id']
1383
                ));
1384
            }
1385
        }
1386
1387
        $this->recentUpdate = $timeNow;
1388
1389
        // clear the cache
1390
        $this->clearCache('full');
1391
    }
1392
1393
    public function checkPublishStatus()
1394
    {
1395
        $this->updatePubStatus();
1396
    }
1397
1398
    /**
1399
     * Final jobs.
1400
     *
1401
     * - cache page
1402
     */
1403
    public function postProcess()
1404
    {
1405
        // if the current document was generated, cache it!
1406
        $cacheable = ($this->config['enable_cache'] && $this->documentObject['cacheable']) ? 1 : 0;
1407
        if ($cacheable && $this->documentGenerated && $this->documentObject['type'] == 'document' && $this->documentObject['published']) {
1408
            // invoke OnBeforeSaveWebPageCache event
1409
            $this->invokeEvent("OnBeforeSaveWebPageCache");
1410
1411
            if (!empty($this->cacheKey) && is_scalar($this->cacheKey)) {
1412
                // get and store document groups inside document object. Document groups will be used to check security on cache pages
1413
                $where = "document='{$this->documentIdentifier}'";
1414
                $rs = $this->getDatabase()->select(
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...
1415
                    'document_group',
1416
                    $this->getDatabase()->getFullTableName('document_groups'),
1417
                    $where
1418
                );
1419
                $docGroups = $this->getDatabase()->getColumn('document_group', $rs);
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se...ument_groups'), $where) on line 1414 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...
1420
1421
                // Attach Document Groups and Scripts
1422
                if (is_array($docGroups)) {
1423
                    $this->documentObject['__MODxDocGroups__'] = implode(",", $docGroups);
1424
                }
1425
1426
                $docObjSerial = serialize($this->documentObject);
1427
                $cacheContent = $docObjSerial . "<!--__MODxCacheSpliter__-->" . $this->documentContent;
1428
                $page_cache_path = MODX_BASE_PATH . $this->getHashFile($this->cacheKey);
1429
                file_put_contents($page_cache_path, "<?php die('Unauthorized access.'); ?>$cacheContent");
1430
            }
1431
        }
1432
1433
        // Useful for example to external page counters/stats packages
1434
        $this->invokeEvent('OnWebPageComplete');
1435
1436
        // end post processing
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
        $_ = $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...
1448
        if (empty($_)) {
1449
            return array();
1450
        }
1451
        foreach ($_ as $v) {
1452
            $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...
1453
            $tags[1][] = $v;
1454
        }
1455
        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...
1456
    }
1457
1458
    /**
1459
     * @param $content
1460
     * @param string $left
1461
     * @param string $right
1462
     * @return array
1463
     */
1464
    public function _getTagsFromContent($content, $left = '[+', $right = '+]')
1465
    {
1466
        if (strpos($content, $left) === false) {
1467
            return array();
1468
        }
1469
        $spacer = md5('<<<EVO>>>');
1470
        if($left==='{{' && strpos($content,';}}')!==false)  $content = str_replace(';}}', sprintf(';}%s}',   $spacer),$content);
1471
        if($left==='{{' && strpos($content,'{{}}')!==false) $content = str_replace('{{}}',sprintf('{%$1s{}%$1s}',$spacer),$content);
1472
        if($left==='[[' && strpos($content,']]]]')!==false) $content = str_replace(']]]]',sprintf(']]%s]]',  $spacer),$content);
1473
        if($left==='[[' && strpos($content,']]]')!==false)  $content = str_replace(']]]', sprintf(']%s]]',   $spacer),$content);
1474
1475
        $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...
1476
        $pos[']]>'] = strpos($content, ']]>');
1477
1478
        if ($pos['<![CDATA['] !== false && $pos[']]>'] !== false) {
1479
            $content = substr($content, 0, $pos['<![CDATA[']) . substr($content, $pos[']]>'] + 3);
1480
        }
1481
1482
        $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...
1483
        $piece = array();
1484
        foreach ($lp as $lc => $lv) {
1485
            if ($lc !== 0) {
1486
                $piece[] = $left;
1487
            }
1488
            if (strpos($lv, $right) === false) {
1489
                $piece[] = $lv;
1490
            } else {
1491
                $rp = explode($right, $lv);
1492
                foreach ($rp as $rc => $rv) {
1493
                    if ($rc !== 0) {
1494
                        $piece[] = $right;
1495
                    }
1496
                    $piece[] = $rv;
1497
                }
1498
            }
1499
        }
1500
        $lc = 0;
1501
        $rc = 0;
1502
        $fetch = '';
1503
        $tags = array();
1504
        foreach ($piece as $v) {
1505
            if ($v === $left) {
1506
                if (0 < $lc) {
1507
                    $fetch .= $left;
1508
                }
1509
                $lc++;
1510
            } elseif ($v === $right) {
1511
                if ($lc === 0) {
1512
                    continue;
1513
                }
1514
                $rc++;
1515
                if ($lc === $rc) {
1516
                    // #1200 Enable modifiers in Wayfinder - add nested placeholders to $tags like for $fetch = "phx:input=`[+wf.linktext+]`:test"
1517
                    if (strpos($fetch, $left) !== false) {
1518
                        $nested = $this->_getTagsFromContent($fetch, $left, $right);
1519
                        foreach ($nested as $tag) {
1520
                            if (!in_array($tag, $tags)) {
1521
                                $tags[] = $tag;
1522
                            }
1523
                        }
1524
                    }
1525
1526
                    if (!in_array($fetch, $tags)) {  // Avoid double Matches
1527
                        $tags[] = $fetch; // Fetch
1528
                    };
1529
                    $fetch = ''; // and reset
1530
                    $lc = 0;
1531
                    $rc = 0;
1532
                } else {
1533
                    $fetch .= $right;
1534
                }
1535
            } else {
1536
                if (0 < $lc) {
1537
                    $fetch .= $v;
1538
                } else {
1539
                    continue;
1540
                }
1541
            }
1542
        }
1543
        foreach($tags as $i=>$tag) {
1544
            if(strpos($tag,$spacer)!==false) $tags[$i] = str_replace($spacer, '', $tag);
1545
        }
1546
        return $tags;
1547
    }
1548
1549
    /**
1550
     * Merge content fields and TVs
1551
     *
1552
     * @param $content
1553
     * @param bool $ph
1554
     * @return string
1555
     * @internal param string $template
1556
     */
1557
    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...
1558
    {
1559 View Code Duplication
        if ($this->config['enable_at_syntax']) {
1560
            if (stripos($content, '<@LITERAL>') !== false) {
1561
                $content = $this->escapeLiteralTagsContent($content);
1562
            }
1563
        }
1564
        if (strpos($content, '[*') === false) {
1565
            return $content;
1566
        }
1567
        if (!isset($this->documentIdentifier)) {
1568
            return $content;
1569
        }
1570
        if (!isset($this->documentObject) || empty($this->documentObject)) {
1571
            return $content;
1572
        }
1573
1574
        if (!$ph) {
1575
            $ph = $this->documentObject;
1576
        }
1577
1578
        $matches = $this->getTagsFromContent($content, '[*', '*]');
1579
        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...
1580
            return $content;
1581
        }
1582
1583
        foreach ($matches[1] as $i => $key) {
1584
            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...
1585
            if (substr($key, 0, 1) == '#') {
1586
                $key = substr($key, 1);
1587
            } // remove # for QuickEdit format
1588
1589
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1590
            if (strpos($key, '@') !== false) {
1591
                list($key, $context) = explode('@', $key, 2);
1592
            } else {
1593
                $context = false;
1594
            }
1595
1596
            // 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...
1597
            if ($context) {
1598
                $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...
1599
            } else {
1600
                $value = isset($ph[$key]) ? $ph[$key] : '';
1601
            }
1602
1603 View Code Duplication
            if (is_array($value)) {
1604
                $value = getTVDisplayFormat($value[0], $value[1], $value[2], $value[3], $value[4]);
1605
            }
1606
1607
            $s = &$matches[0][$i];
1608
            if ($modifiers !== false) {
1609
                $value = $this->applyFilter($value, $modifiers, $key);
1610
            }
1611
1612 View Code Duplication
            if (strpos($content, $s) !== false) {
1613
                $content = str_replace($s, $value, $content);
1614
            } elseif($this->debug) {
1615
                $this->addLog('mergeDocumentContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1616
            }
1617
        }
1618
1619
        return $content;
1620
    }
1621
1622
    /**
1623
     * @param $key
1624
     * @param bool|int $parent
1625
     * @return bool|mixed|string
1626
     */
1627
    public function _contextValue($key, $parent = false)
1628
    {
1629
        if (preg_match('/@\d+\/u/', $key)) {
1630
            $key = str_replace(array('@', '/u'), array('@u(', ')'), $key);
1631
        }
1632
        list($key, $str) = explode('@', $key, 2);
1633
1634
        if (strpos($str, '(')) {
1635
            list($context, $option) = explode('(', $str, 2);
1636
        } else {
1637
            list($context, $option) = array($str, false);
1638
        }
1639
1640
        if ($option) {
1641
            $option = trim($option, ')(\'"`');
1642
        }
1643
1644
        switch (strtolower($context)) {
1645
            case 'site_start':
1646
                $docid = $this->config['site_start'];
1647
                break;
1648
            case 'parent':
1649
            case 'p':
1650
                $docid = $parent;
1651
                if ($docid == 0) {
1652
                    $docid = $this->config['site_start'];
1653
                }
1654
                break;
1655
            case 'ultimateparent':
1656
            case 'uparent':
1657
            case 'up':
1658
            case 'u':
1659 View Code Duplication
                if (strpos($str, '(') !== false) {
1660
                    $top = substr($str, strpos($str, '('));
1661
                    $top = trim($top, '()"\'');
1662
                } else {
1663
                    $top = 0;
1664
                }
1665
                $docid = $this->getUltimateParentId($this->documentIdentifier, $top);
1666
                break;
1667
            case 'alias':
1668
                $str = substr($str, strpos($str, '('));
1669
                $str = trim($str, '()"\'');
1670
                $docid = $this->getIdFromAlias($str);
1671
                break;
1672 View Code Duplication
            case 'prev':
1673
                if (!$option) {
1674
                    $option = 'menuindex,ASC';
1675
                } elseif (strpos($option, ',') === false) {
1676
                    $option .= ',ASC';
1677
                }
1678
                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...
1679
                $children = $this->getActiveChildren($parent, $by, $dir);
1680
                $find = false;
1681
                $prev = false;
1682
                foreach ($children as $row) {
1683
                    if ($row['id'] == $this->documentIdentifier) {
1684
                        $find = true;
1685
                        break;
1686
                    }
1687
                    $prev = $row;
1688
                }
1689
                if ($find) {
1690
                    if (isset($prev[$key])) {
1691
                        return $prev[$key];
1692
                    } else {
1693
                        $docid = $prev['id'];
1694
                    }
1695
                } else {
1696
                    $docid = '';
1697
                }
1698
                break;
1699 View Code Duplication
            case 'next':
1700
                if (!$option) {
1701
                    $option = 'menuindex,ASC';
1702
                } elseif (strpos($option, ',') === false) {
1703
                    $option .= ',ASC';
1704
                }
1705
                list($by, $dir) = explode(',', $option, 2);
1706
                $children = $this->getActiveChildren($parent, $by, $dir);
1707
                $find = false;
1708
                $next = false;
1709
                foreach ($children as $row) {
1710
                    if ($find) {
1711
                        $next = $row;
1712
                        break;
1713
                    }
1714
                    if ($row['id'] == $this->documentIdentifier) {
1715
                        $find = true;
1716
                    }
1717
                }
1718
                if ($find) {
1719
                    if (isset($next[$key])) {
1720
                        return $next[$key];
1721
                    } else {
1722
                        $docid = $next['id'];
1723
                    }
1724
                } else {
1725
                    $docid = '';
1726
                }
1727
                break;
1728
            default:
1729
                $docid = $str;
1730
        }
1731
        if (preg_match('@^[1-9][0-9]*$@', $docid)) {
1732
            $value = $this->getField($key, $docid);
1733
        } else {
1734
            $value = '';
1735
        }
1736
        return $value;
1737
    }
1738
1739
    /**
1740
     * Merge system settings
1741
     *
1742
     * @param $content
1743
     * @param bool|array $ph
1744
     * @return string
1745
     * @internal param string $template
1746
     */
1747
    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...
1748
    {
1749 View Code Duplication
        if ($this->config['enable_at_syntax']) {
1750
            if (stripos($content, '<@LITERAL>') !== false) {
1751
                $content = $this->escapeLiteralTagsContent($content);
1752
            }
1753
        }
1754
        if (strpos($content, '[(') === false) {
1755
            return $content;
1756
        }
1757
1758
        if (empty($ph)) {
1759
            $ph = $this->config;
1760
        }
1761
1762
        $matches = $this->getTagsFromContent($content, '[(', ')]');
1763
        if (empty($matches)) {
1764
            return $content;
1765
        }
1766
1767
        foreach ($matches[1] as $i => $key) {
1768
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1769
1770
            if (isset($ph[$key])) {
1771
                $value = $ph[$key];
1772
            } else {
1773
                continue;
1774
            }
1775
1776
            if ($modifiers !== false) {
1777
                $value = $this->applyFilter($value, $modifiers, $key);
1778
            }
1779
            $s = &$matches[0][$i];
1780 View Code Duplication
            if (strpos($content, $s) !== false) {
1781
                $content = str_replace($s, $value, $content);
1782
            } elseif($this->debug) {
1783
                $this->addLog('mergeSettingsContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1784
            }
1785
        }
1786
        return $content;
1787
    }
1788
1789
    /**
1790
     * Merge chunks
1791
     *
1792
     * @param string $content
1793
     * @param bool|array $ph
1794
     * @return string
1795
     */
1796
    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...
1797
    {
1798
        if ($this->config['enable_at_syntax']) {
1799
            if (strpos($content, '{{ ') !== false) {
1800
                $content = str_replace(array('{{ ', ' }}'), array('\{\{ ', ' \}\}'), $content);
1801
            }
1802
            if (stripos($content, '<@LITERAL>') !== false) {
1803
                $content = $this->escapeLiteralTagsContent($content);
1804
            }
1805
        }
1806
        if (strpos($content, '{{') === false) {
1807
            return $content;
1808
        }
1809
1810
        if (empty($ph)) {
1811
            $ph = $this->chunkCache;
1812
        }
1813
1814
        $matches = $this->getTagsFromContent($content, '{{', '}}');
1815
        if (empty($matches)) {
1816
            return $content;
1817
        }
1818
1819
        foreach ($matches[1] as $i => $key) {
1820
            $snip_call = $this->_split_snip_call($key);
1821
            $key = $snip_call['name'];
1822
            $params = $this->getParamsFromString($snip_call['params']);
1823
1824
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1825
1826
            if (!isset($ph[$key])) {
1827
                $ph[$key] = $this->getChunk($key);
1828
            }
1829
            $value = $ph[$key];
1830
1831
            if (is_null($value)) {
1832
                continue;
1833
            }
1834
1835
            $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 1822 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...
1836
            $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 1822 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...
1837
            if ($this->config['enable_at_syntax']) {
1838
                $value = $this->mergeConditionalTagsContent($value);
1839
            }
1840
            $value = $this->mergeDocumentContent($value);
1841
            $value = $this->mergeSettingsContent($value);
1842
            $value = $this->mergeChunkContent($value);
1843
1844
            if ($modifiers !== false) {
1845
                $value = $this->applyFilter($value, $modifiers, $key);
1846
            }
1847
1848
            $s = &$matches[0][$i];
1849 View Code Duplication
            if (strpos($content, $s) !== false) {
1850
                $content = str_replace($s, $value, $content);
1851
            } elseif($this->debug) {
1852
                $this->addLog('mergeChunkContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1853
            }
1854
        }
1855
        return $content;
1856
    }
1857
1858
    /**
1859
     * Merge placeholder values
1860
     *
1861
     * @param string $content
1862
     * @param bool|array $ph
1863
     * @return string
1864
     */
1865
    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...
1866
    {
1867
1868 View Code Duplication
        if ($this->config['enable_at_syntax']) {
1869
            if (stripos($content, '<@LITERAL>') !== false) {
1870
                $content = $this->escapeLiteralTagsContent($content);
1871
            }
1872
        }
1873
        if (strpos($content, '[+') === false) {
1874
            return $content;
1875
        }
1876
1877
        if (empty($ph)) {
1878
            $ph = $this->placeholders;
1879
        }
1880
1881
        if ($this->config['enable_at_syntax']) {
1882
            $content = $this->mergeConditionalTagsContent($content);
1883
        }
1884
1885
        $content = $this->mergeDocumentContent($content);
1886
        $content = $this->mergeSettingsContent($content);
1887
        $matches = $this->getTagsFromContent($content, '[+', '+]');
1888
        if (empty($matches)) {
1889
            return $content;
1890
        }
1891
        foreach ($matches[1] as $i => $key) {
1892
1893
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1894
1895
            if (isset($ph[$key])) {
1896
                $value = $ph[$key];
1897
            } elseif ($key === 'phx') {
1898
                $value = '';
1899
            } else {
1900
                continue;
1901
            }
1902
1903
            if ($modifiers !== false) {
1904
                $modifiers = $this->mergePlaceholderContent($modifiers);
1905
                $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...
1906
            }
1907
            $s = &$matches[0][$i];
1908 View Code Duplication
            if (strpos($content, $s) !== false) {
1909
                $content = str_replace($s, $value, $content);
1910
            } elseif($this->debug) {
1911
                $this->addLog('mergePlaceholderContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1912
            }
1913
        }
1914
        return $content;
1915
    }
1916
1917
    /**
1918
     * @param $content
1919
     * @param string $iftag
1920
     * @param string $elseiftag
1921
     * @param string $elsetag
1922
     * @param string $endiftag
1923
     * @return mixed|string
1924
     */
1925
    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...
1926
    {
1927
        if (strpos($content, '@IF') !== false) {
1928
            $content = $this->_prepareCTag($content, $iftag, $elseiftag, $elsetag, $endiftag);
1929
        }
1930
1931
        if (strpos($content, $iftag) === false) {
1932
            return $content;
1933
        }
1934
1935
        $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...
1936
        $content = str_replace(array('<?php', '<?=', '<?', '?>'), array("{$sp}b", "{$sp}p", "{$sp}s", "{$sp}e"), $content);
1937
1938
        $pieces = explode('<@IF:', $content);
1939 View Code Duplication
        foreach ($pieces as $i => $split) {
1940
            if ($i === 0) {
1941
                $content = $split;
1942
                continue;
1943
            }
1944
            list($cmd, $text) = explode('>', $split, 2);
1945
            $cmd = str_replace("'", "\'", $cmd);
1946
            $content .= "<?php if(\$this->_parseCTagCMD('" . $cmd . "')): ?>";
1947
            $content .= $text;
1948
        }
1949
        $pieces = explode('<@ELSEIF:', $content);
1950 View Code Duplication
        foreach ($pieces as $i => $split) {
1951
            if ($i === 0) {
1952
                $content = $split;
1953
                continue;
1954
            }
1955
            list($cmd, $text) = explode('>', $split, 2);
1956
            $cmd = str_replace("'", "\'", $cmd);
1957
            $content .= "<?php elseif(\$this->_parseCTagCMD('" . $cmd . "')): ?>";
1958
            $content .= $text;
1959
        }
1960
1961
        $content = str_replace(array('<@ELSE>', '<@ENDIF>'), array('<?php else:?>', '<?php endif;?>'), $content);
1962
        ob_start();
1963
        $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...
1964
        $content = ob_get_clean();
1965
        $content = str_replace(array("{$sp}b", "{$sp}p", "{$sp}s", "{$sp}e"), array('<?php', '<?=', '<?', '?>'), $content);
1966
1967
        return $content;
1968
    }
1969
1970
    /**
1971
     * @param $content
1972
     * @param string $iftag
1973
     * @param string $elseiftag
1974
     * @param string $elsetag
1975
     * @param string $endiftag
1976
     * @return mixed
1977
     */
1978
    private function _prepareCTag($content, $iftag = '<@IF:', $elseiftag = '<@ELSEIF:', $elsetag = '<@ELSE>', $endiftag = '<@ENDIF>')
1979
    {
1980
        if (strpos($content, '<!--@IF ') !== false) {
1981
            $content = str_replace('<!--@IF ', $iftag, $content);
1982
        } // for jp
1983
        if (strpos($content, '<!--@IF:') !== false) {
1984
            $content = str_replace('<!--@IF:', $iftag, $content);
1985
        }
1986
        if (strpos($content, $iftag) === false) {
1987
            return $content;
1988
        }
1989
        if (strpos($content, '<!--@ELSEIF:') !== false) {
1990
            $content = str_replace('<!--@ELSEIF:', $elseiftag, $content);
1991
        } // for jp
1992
        if (strpos($content, '<!--@ELSE-->') !== false) {
1993
            $content = str_replace('<!--@ELSE-->', $elsetag, $content);
1994
        }  // for jp
1995
        if (strpos($content, '<!--@ENDIF-->') !== false) {
1996
            $content = str_replace('<!--@ENDIF-->', $endiftag, $content);
1997
        }    // for jp
1998
        if (strpos($content, '<@ENDIF-->') !== false) {
1999
            $content = str_replace('<@ENDIF-->', $endiftag, $content);
2000
        }
2001
        $tags = array($iftag, $elseiftag, $elsetag, $endiftag);
2002
        $content = str_ireplace($tags, $tags, $content); // Change to capital letters
2003
        return $content;
2004
    }
2005
2006
    /**
2007
     * @param $cmd
2008
     * @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...
2009
     */
2010
    private function _parseCTagCMD($cmd)
2011
    {
2012
        $cmd = trim($cmd);
2013
        $reverse = substr($cmd, 0, 1) === '!' ? true : false;
2014
        if ($reverse) {
2015
            $cmd = ltrim($cmd, '!');
2016
        }
2017
        if (strpos($cmd, '[!') !== false) {
2018
            $cmd = str_replace(array('[!', '!]'), array('[[', ']]'), $cmd);
2019
        }
2020
        $safe = 0;
2021
        while ($safe < 20) {
2022
            $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...
2023
            if (strpos($cmd, '[*') !== false) {
2024
                $cmd = $this->mergeDocumentContent($cmd);
2025
            }
2026
            if (strpos($cmd, '[(') !== false) {
2027
                $cmd = $this->mergeSettingsContent($cmd);
2028
            }
2029
            if (strpos($cmd, '{{') !== false) {
2030
                $cmd = $this->mergeChunkContent($cmd);
2031
            }
2032
            if (strpos($cmd, '[[') !== false) {
2033
                $cmd = $this->evalSnippets($cmd);
2034
            }
2035
            if (strpos($cmd, '[+') !== false && strpos($cmd, '[[') === false) {
2036
                $cmd = $this->mergePlaceholderContent($cmd);
2037
            }
2038
            if ($bt === md5($cmd)) {
2039
                break;
2040
            }
2041
            $safe++;
2042
        }
2043
        $cmd = ltrim($cmd);
2044
        $cmd = rtrim($cmd, '-');
2045
        $cmd = str_ireplace(array(' and ', ' or '), array('&&', '||'), $cmd);
2046
2047
        if (!preg_match('@^[0-9]*$@', $cmd) && preg_match('@^[0-9<= \-\+\*/\(\)%!&|]*$@', $cmd)) {
2048
            $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...
2049
        } else {
2050
            $_ = 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...
2051
            foreach ($_ as $left) {
2052
                if (strpos($cmd, $left) !== false) {
2053
                    $cmd = 0;
2054
                    break;
2055
                }
2056
            }
2057
        }
2058
        $cmd = trim($cmd);
2059
        if (!preg_match('@^[0-9]+$@', $cmd)) {
2060
            $cmd = empty($cmd) ? 0 : 1;
2061
        } elseif ($cmd <= 0) {
2062
            $cmd = 0;
2063
        }
2064
2065
        if ($reverse) {
2066
            $cmd = !$cmd;
2067
        }
2068
2069
        return $cmd;
2070
    }
2071
2072
    /**
2073
     * Remove Comment-Tags from output like <!--@- Comment -@-->
2074
     * @param $content
2075
     * @param string $left
2076
     * @param string $right
2077
     * @return mixed
2078
     */
2079
    function ignoreCommentedTagsContent($content, $left = '<!--@-', $right = '-@-->')
2080
    {
2081
        if (strpos($content, $left) === false) {
2082
            return $content;
2083
        }
2084
2085
        $matches = $this->getTagsFromContent($content, $left, $right);
2086
        if (!empty($matches)) {
2087
            foreach ($matches[0] as $i => $v) {
2088
                $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...
2089
            }
2090
            $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...
2091
            if (strpos($content, $left) !== false) {
2092
                $content = str_replace($matches[0], '', $content);
2093
            }
2094
        }
2095
        return $content;
2096
    }
2097
2098
    /**
2099
     * @param $content
2100
     * @param string $left
2101
     * @param string $right
2102
     * @return mixed
2103
     */
2104
    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...
2105
    {
2106
        if (stripos($content, $left) === false) {
2107
            return $content;
2108
        }
2109
2110
        $matches = $this->getTagsFromContent($content, $left, $right);
2111
        if (empty($matches)) {
2112
            return $content;
2113
        }
2114
2115
        list($sTags, $rTags) = $this->getTagsForEscape();
2116
        foreach ($matches[1] as $i => $v) {
2117
            $v = str_ireplace($sTags, $rTags, $v);
2118
            $s = &$matches[0][$i];
2119 View Code Duplication
            if (strpos($content, $s) !== false) {
2120
                $content = str_replace($s, $v, $content);
2121
            } elseif($this->debug) {
2122
                $this->addLog('ignoreCommentedTagsContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
2123
            }
2124
        }
2125
        return $content;
2126
    }
2127
2128
    /**
2129
     * Detect PHP error according to MODX error level
2130
     *
2131
     * @param integer $error PHP error level
2132
     * @return boolean Error detected
2133
     */
2134
2135
    public function detectError($error)
2136
    {
2137
        $detected = false;
2138
        if ($this->config['error_reporting'] == 99 && $error) {
2139
            $detected = true;
2140
        } elseif ($this->config['error_reporting'] == 2 && ($error & ~E_NOTICE)) {
2141
            $detected = true;
2142
        } elseif ($this->config['error_reporting'] == 1 && ($error & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT)) {
2143
            $detected = true;
2144
        }
2145
        return $detected;
2146
    }
2147
2148
    /**
2149
     * Run a plugin
2150
     *
2151
     * @param string $pluginCode Code to run
2152
     * @param array $params
2153
     */
2154
    public function evalPlugin($pluginCode, $params)
2155
    {
2156
        $modx = &$this;
2157
        $modx->event->params = &$params; // store params inside event object
2158
        if (is_array($params)) {
2159
            extract($params, EXTR_SKIP);
2160
        }
2161
        /* 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...
2162
        // 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.
2163
        // Related to https://github.com/modxcms/evolution/issues/1130
2164
        $lock_file_path = MODX_BASE_PATH . 'assets/cache/lock_' . str_replace(' ','-',strtolower($this->event->activePlugin)) . '.pageCache.php';
2165
        if($this->isBackend()) {
2166
            if(is_file($lock_file_path)) {
2167
                $msg = sprintf("Plugin parse error, Temporarily disabled '%s'.", $this->event->activePlugin);
2168
                $this->logEvent(0, 3, $msg, $msg);
2169
                return;
2170
            }
2171
            elseif(stripos($this->event->activePlugin,'ElementsInTree')===false) touch($lock_file_path);
2172
        }*/
2173
        ob_start();
2174
        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...
2175
        $msg = ob_get_contents();
2176
        ob_end_clean();
2177
        // When reached here, no fatal error occured so the lock should be removed.
2178
        /*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...
2179
2180 View Code Duplication
        if ((0 < $this->config['error_reporting']) && $msg && isset($php_errormsg)) {
2181
            $error_info = error_get_last();
2182
            if ($this->detectError($error_info['type'])) {
2183
                $msg = ($msg === false) ? 'ob_get_contents() error' : $msg;
2184
                $this->messageQuit('PHP Parse Error', '', true, $error_info['type'], $error_info['file'], 'Plugin', $error_info['message'], $error_info['line'], $msg);
2185
                if ($this->isBackend()) {
2186
                    $this->event->alert('An error occurred while loading. Please see the event log for more information.<p>' . $msg . '</p>');
2187
                }
2188
            }
2189
        } else {
2190
            echo $msg;
2191
        }
2192
        unset($modx->event->params);
2193
    }
2194
2195
    /**
2196
     * Run a snippet
2197
     *
2198
     * @param $phpcode
2199
     * @param array $params
2200
     * @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...
2201
     * @internal param string $snippet Code to run
2202
     */
2203
    public function evalSnippet($phpcode, $params)
2204
    {
2205
        $modx = &$this;
2206
        /*
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...
2207
        if(isset($params) && is_array($params)) {
2208
            foreach($params as $k=>$v) {
2209
                $v = strtolower($v);
2210
                if($v==='false')    $params[$k] = false;
2211
                elseif($v==='true') $params[$k] = true;
2212
            }
2213
        }*/
2214
        $modx->event->params = &$params; // store params inside event object
2215
        if (is_array($params)) {
2216
            extract($params, EXTR_SKIP);
2217
        }
2218
        ob_start();
2219
        if (strpos($phpcode, ';') !== false) {
2220
            if (substr($phpcode, 0, 5) === '<?php') {
2221
                $phpcode = substr($phpcode, 5);
2222
            }
2223
            $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...
2224
        } else {
2225
            $return = call_user_func_array($phpcode, array($params));
2226
        }
2227
        $echo = ob_get_contents();
2228
        ob_end_clean();
2229 View Code Duplication
        if ((0 < $this->config['error_reporting']) && isset($php_errormsg)) {
2230
            $error_info = error_get_last();
2231
            if ($this->detectError($error_info['type'])) {
2232
                $echo = ($echo === false) ? 'ob_get_contents() error' : $echo;
2233
                $this->messageQuit('PHP Parse Error', '', true, $error_info['type'], $error_info['file'], 'Snippet', $error_info['message'], $error_info['line'], $echo);
2234
                if ($this->isBackend()) {
2235
                    $this->event->alert('An error occurred while loading. Please see the event log for more information<p>' . $echo . $return . '</p>');
2236
                }
2237
            }
2238
        }
2239
        unset($modx->event->params);
2240
        if (is_array($return) || is_object($return)) {
2241
            return $return;
2242
        } else {
2243
            return $echo . $return;
2244
        }
2245
    }
2246
2247
    /**
2248
     * Run snippets as per the tags in $documentSource and replace the tags with the returned values.
2249
     *
2250
     * @param $content
2251
     * @return string
2252
     * @internal param string $documentSource
2253
     */
2254
    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...
2255
    {
2256
        if (strpos($content, '[[') === false) {
2257
            return $content;
2258
        }
2259
2260
        $matches = $this->getTagsFromContent($content, '[[', ']]');
2261
2262
        if (empty($matches)) {
2263
            return $content;
2264
        }
2265
2266
        $this->snipLapCount++;
2267
        if ($this->dumpSnippets) {
2268
            $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);
2269
        }
2270
2271
        foreach ($matches[1] as $i => $call) {
2272
            $s = &$matches[0][$i];
2273
            if (substr($call, 0, 2) === '$_') {
2274
                if (strpos($content, '_PHX_INTERNAL_') === false) {
2275
                    $value = $this->_getSGVar($call);
2276
                } else {
2277
                    $value = $s;
2278
                }
2279 View Code Duplication
                if (strpos($content, $s) !== false) {
2280
                    $content = str_replace($s, $value, $content);
2281
                } elseif($this->debug) {
2282
                    $this->addLog('evalSnippetsSGVar parse error', $_SERVER['REQUEST_URI'] . $s, 2);
2283
                }
2284
                continue;
2285
            }
2286
            $value = $this->_get_snip_result($call);
2287
            if (is_null($value)) {
2288
                continue;
2289
            }
2290
2291 View Code Duplication
            if (strpos($content, $s) !== false) {
2292
                $content = str_replace($s, $value, $content);
2293
            } elseif($this->debug) {
2294
                $this->addLog('evalSnippets parse error', $_SERVER['REQUEST_URI'] . $s, 2);
2295
            }
2296
        }
2297
2298
        if ($this->dumpSnippets) {
2299
            $this->snippetsCode .= '</fieldset><br />';
2300
        }
2301
2302
        return $content;
2303
    }
2304
2305
    /**
2306
     * @param $value
2307
     * @return mixed|string
2308
     */
2309
    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...
2310
    { // Get super globals
2311
        $key = $value;
2312
        $_ = $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...
2313
        $this->config['enable_filter'] = 1;
2314
        list($key, $modifiers) = $this->splitKeyAndFilter($key);
2315
        $this->config['enable_filter'] = $_;
2316
        $key = str_replace(array('(', ')'), array("['", "']"), $key);
2317
        $key = rtrim($key, ';');
2318
        if (strpos($key, '$_SESSION') !== false) {
2319
            $_ = $_SESSION;
2320
            $key = str_replace('$_SESSION', '$_', $key);
2321
            if (isset($_['mgrFormValues'])) {
2322
                unset($_['mgrFormValues']);
2323
            }
2324
            if (isset($_['token'])) {
2325
                unset($_['token']);
2326
            }
2327
        }
2328
        if (strpos($key, '[') !== false) {
2329
            $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...
2330
        } 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...
2331
            $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...
2332
        } else {
2333
            $value = '';
2334
        }
2335
        if ($modifiers !== false) {
2336
            $value = $this->applyFilter($value, $modifiers, $key);
2337
        }
2338
        return $value;
2339
    }
2340
2341
    /**
2342
     * @param $piece
2343
     * @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...
2344
     */
2345
    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...
2346
    {
2347
        if (ltrim($piece) !== $piece) {
2348
            return '';
2349
        }
2350
2351
        $eventtime = $this->dumpSnippets ? $this->getMicroTime() : 0;
2352
2353
        $snip_call = $this->_split_snip_call($piece);
2354
        $key = $snip_call['name'];
2355
2356
        list($key, $modifiers) = $this->splitKeyAndFilter($key);
2357
        $snip_call['name'] = $key;
2358
        $snippetObject = $this->_getSnippetObject($key);
2359
        if (is_null($snippetObject['content'])) {
2360
            return null;
2361
        }
2362
2363
        $this->currentSnippet = $snippetObject['name'];
2364
2365
        // current params
2366
        $params = $this->getParamsFromString($snip_call['params']);
2367
2368
        if (!isset($snippetObject['properties'])) {
2369
            $snippetObject['properties'] = array();
2370
        }
2371
        $default_params = $this->parseProperties($snippetObject['properties'], $this->currentSnippet, 'snippet');
2372
        $params = array_merge($default_params, $params);
2373
2374
        $value = $this->evalSnippet($snippetObject['content'], $params);
2375
        $this->currentSnippet = '';
2376
        if ($modifiers !== false) {
2377
            $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...
2378
        }
2379
2380
        if ($this->dumpSnippets) {
2381
            $eventtime = $this->getMicroTime() - $eventtime;
2382
            $eventtime = sprintf('%2.2f ms', $eventtime * 1000);
2383
            $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 2374 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...
2384
            $piece = str_replace("\t", '  ', $this->getPhpCompat()->htmlspecialchars($piece));
2385
            $print_r_params = str_replace("\t", '  ', $this->getPhpCompat()->htmlspecialchars('$modx->event->params = ' . print_r($params, true)));
2386
            $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);
2387
            $this->snippetsTime[] = array('sname' => $key, 'time' => $eventtime);
2388
        }
2389
        return $value;
2390
    }
2391
2392
    /**
2393
     * @param string $string
2394
     * @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...
2395
     */
2396
    public function getParamsFromString($string = '')
2397
    {
2398
        if (empty($string)) {
2399
            return array();
2400
        }
2401
2402
        if (strpos($string, '&_PHX_INTERNAL_') !== false) {
2403
            $string = str_replace(array('&_PHX_INTERNAL_091_&', '&_PHX_INTERNAL_093_&'), array('[', ']'), $string);
2404
        }
2405
2406
        $_ = $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...
2407
        $this->documentOutput = $string;
2408
        $this->invokeEvent('OnBeforeParseParams');
2409
        $string = $this->documentOutput;
2410
        $this->documentOutput = $_;
2411
2412
        $_tmp = $string;
2413
        $_tmp = ltrim($_tmp, '?&');
2414
        $temp_params = array();
2415
        $key = '';
2416
        $value = null;
2417
        while ($_tmp !== '') {
2418
            $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...
2419
            $char = substr($_tmp, 0, 1);
2420
            $_tmp = substr($_tmp, 1);
2421
2422
            if ($char === '=') {
2423
                $_tmp = trim($_tmp);
2424
                $delim = substr($_tmp, 0, 1);
2425
                if (in_array($delim, array('"', "'", '`'))) {
2426
                    $null = null;
2427
                    //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...
2428
                    list($null, $value, $_tmp) = explode($delim, $_tmp, 3);
2429
                    unset($null);
2430
2431
                    if (substr(trim($_tmp), 0, 2) === '//') {
2432
                        $_tmp = strstr(trim($_tmp), "\n");
2433
                    }
2434
                    $i = 0;
2435
                    while ($delim === '`' && substr(trim($_tmp), 0, 1) !== '&' && 1 < substr_count($_tmp, '`')) {
2436
                        list($inner, $outer, $_tmp) = explode('`', $_tmp, 3);
2437
                        $value .= "`{$inner}`{$outer}";
2438
                        $i++;
2439
                        if (100 < $i) {
2440
                            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...
2441
                        }
2442
                    }
2443
                    if ($i && $delim === '`') {
2444
                        $value = rtrim($value, '`');
2445
                    }
2446
                } elseif (strpos($_tmp, '&') !== false) {
2447
                    list($value, $_tmp) = explode('&', $_tmp, 2);
2448
                    $value = trim($value);
2449
                } else {
2450
                    $value = $_tmp;
2451
                    $_tmp = '';
2452
                }
2453
            } elseif ($char === '&') {
2454
                if (trim($key) !== '') {
2455
                    $value = '1';
2456
                } else {
2457
                    continue;
2458
                }
2459
            } elseif ($_tmp === '') {
2460
                $key .= $char;
2461
                $value = '1';
2462
            } elseif ($key !== '' || trim($char) !== '') {
2463
                $key .= $char;
2464
            }
2465
2466
            if (isset($value) && !is_null($value)) {
2467
                if (strpos($key, 'amp;') !== false) {
2468
                    $key = str_replace('amp;', '', $key);
2469
                }
2470
                $key = trim($key);
2471 View Code Duplication
                if (strpos($value, '[!') !== false) {
2472
                    $value = str_replace(array('[!', '!]'), array('[[', ']]'), $value);
2473
                }
2474
                $value = $this->mergeDocumentContent($value);
2475
                $value = $this->mergeSettingsContent($value);
2476
                $value = $this->mergeChunkContent($value);
2477
                $value = $this->evalSnippets($value);
2478
                if (substr($value, 0, 6) !== '@CODE:') {
2479
                    $value = $this->mergePlaceholderContent($value);
2480
                }
2481
2482
                $temp_params[][$key] = $value;
2483
2484
                $key = '';
2485
                $value = null;
2486
2487
                $_tmp = ltrim($_tmp, " ,\t");
2488
                if (substr($_tmp, 0, 2) === '//') {
2489
                    $_tmp = strstr($_tmp, "\n");
2490
                }
2491
            }
2492
2493
            if ($_tmp === $bt) {
2494
                $key = trim($key);
2495
                if ($key !== '') {
2496
                    $temp_params[][$key] = '';
2497
                }
2498
                break;
2499
            }
2500
        }
2501
2502
        foreach ($temp_params as $p) {
2503
            $k = key($p);
2504
            if (substr($k, -2) === '[]') {
2505
                $k = substr($k, 0, -2);
2506
                $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...
2507
            } elseif (strpos($k, '[') !== false && substr($k, -1) === ']') {
2508
                list($k, $subk) = explode('[', $k, 2);
2509
                $subk = substr($subk, 0, -1);
2510
                $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...
2511
            } else {
2512
                $params[$k] = current($p);
2513
            }
2514
        }
2515
        return $params;
2516
    }
2517
2518
    /**
2519
     * @param $str
2520
     * @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...
2521
     */
2522
    public function _getSplitPosition($str)
2523
    {
2524
        $closeOpt = false;
2525
        $maybePos = false;
2526
        $inFilter = false;
2527
        $pos = false;
2528
        $total = strlen($str);
2529
        for ($i = 0; $i < $total; $i++) {
2530
            $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...
2531
            $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...
2532
            if (!$inFilter) {
2533
                if ($c === ':') {
2534
                    $inFilter = true;
2535
                } elseif ($c === '?') {
2536
                    $pos = $i;
2537
                } elseif ($c === ' ') {
2538
                    $maybePos = $i;
2539
                } 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...
2540
                    $pos = $maybePos;
2541
                } elseif ($c === "\n") {
2542
                    $pos = $i;
2543
                } else {
2544
                    $pos = false;
2545
                }
2546
            } else {
2547
                if ($cc == $closeOpt) {
2548
                    $closeOpt = false;
2549
                } elseif ($c == $closeOpt) {
2550
                    $closeOpt = false;
2551
                } 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...
2552
                    continue;
2553
                } elseif ($cc === "('") {
2554
                    $closeOpt = "')";
2555
                } elseif ($cc === '("') {
2556
                    $closeOpt = '")';
2557
                } elseif ($cc === '(`') {
2558
                    $closeOpt = '`)';
2559
                } elseif ($c === '(') {
2560
                    $closeOpt = ')';
2561
                } elseif ($c === '?') {
2562
                    $pos = $i;
2563
                } elseif ($c === ' ' && strpos($str, '?') === false) {
2564
                    $pos = $i;
2565
                } else {
2566
                    $pos = false;
2567
                }
2568
            }
2569
            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...
2570
                break;
2571
            }
2572
        }
2573
        return $pos;
2574
    }
2575
2576
    /**
2577
     * @param $call
2578
     * @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...
2579
     */
2580
    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...
2581
    {
2582
        $spacer = md5('dummy');
2583 View Code Duplication
        if (strpos($call, ']]>') !== false) {
2584
            $call = str_replace(']]>', "]{$spacer}]>", $call);
2585
        }
2586
2587
        $splitPosition = $this->_getSplitPosition($call);
2588
2589
        if ($splitPosition !== false) {
2590
            $name = substr($call, 0, $splitPosition);
2591
            $params = substr($call, $splitPosition + 1);
2592
        } else {
2593
            $name = $call;
2594
            $params = '';
2595
        }
2596
2597
        $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...
2598 View Code Duplication
        if (strpos($params, $spacer) !== false) {
2599
            $params = str_replace("]{$spacer}]>", ']]>', $params);
2600
        }
2601
        $snip['params'] = ltrim($params, "?& \t\n");
2602
2603
        return $snip;
2604
    }
2605
2606
    /**
2607
     * @param $snip_name
2608
     * @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...
2609
     */
2610
    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...
2611
    {
2612
        if (isset($this->snippetCache[$snip_name])) {
2613
            $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...
2614
            $snippetObject['content'] = $this->snippetCache[$snip_name];
2615
            if (isset($this->snippetCache["{$snip_name}Props"])) {
2616
                if (!isset($this->snippetCache["{$snip_name}Props"])) {
2617
                    $this->snippetCache["{$snip_name}Props"] = '';
2618
                }
2619
                $snippetObject['properties'] = $this->snippetCache["{$snip_name}Props"];
2620
            }
2621
        } elseif (substr($snip_name, 0, 1) === '@' && isset($this->pluginEvent[trim($snip_name, '@')])) {
2622
            $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...
2623
            $snippetObject['content'] = sprintf('$rs=$this->invokeEvent("%s",$params);echo trim(implode("",$rs));', trim($snip_name, '@'));
2624
            $snippetObject['properties'] = '';
2625
        } else {
2626
            $where = sprintf("name='%s' AND disabled=0", $this->getDatabase()->escape($snip_name));
2627
            $rs = $this->getDatabase()->select(
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...
2628
                '`name`, `snippet`, `properties`',
2629
                $this->getDatabase()->getFullTableName('site_snippets'),
2630
                $where
2631
            );
2632
            $count = $this->getDatabase()->getRecordCount($rs);
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se...ite_snippets'), $where) on line 2627 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...
2633
            if (1 < $count) {
2634
                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...
2635
            }
2636
            if ($count) {
2637
                $row = $this->getDatabase()->getRow($rs);
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se...ite_snippets'), $where) on line 2627 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...
2638
                $snip_content = $row['snippet'];
2639
                $snip_prop = $row['properties'];
2640
            } else {
2641
                $snip_content = null;
2642
                $snip_prop = '';
2643
            }
2644
            $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...
2645
            $snippetObject['content'] = $snip_content;
2646
            $snippetObject['properties'] = $snip_prop;
2647
            $this->snippetCache[$snip_name] = $snip_content;
2648
            $this->snippetCache["{$snip_name}Props"] = $snip_prop;
2649
        }
2650
        return $snippetObject;
2651
    }
2652
2653
    /**
2654
     * @param $text
2655
     * @return mixed
2656
     */
2657
    public function toAlias($text)
2658
    {
2659
        $suff = $this->config['friendly_url_suffix'];
2660
        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);
2661
    }
2662
2663
    /**
2664
     * makeFriendlyURL
2665
     *
2666
     * @desc Create an URL.
2667
     *
2668
     * @param $pre {string} - Friendly URL Prefix. @required
2669
     * @param $suff {string} - Friendly URL Suffix. @required
2670
     * @param $alias {string} - Full document path. @required
2671
     * @param int $isfolder {0; 1}
2672
     * - Is it a folder? Default: 0.
2673
     * @param int $id {integer}
2674
     * - Document id. Default: 0.
2675
     * @return mixed|string {string} - Result URL.
2676
     * - Result URL.
2677
     */
2678
    public function makeFriendlyURL($pre, $suff, $alias, $isfolder = 0, $id = 0)
2679
    {
2680
        if ($id == $this->config['site_start'] && $this->config['seostrict'] === '1') {
2681
            $url = $this->config['base_url'];
2682
        } else {
2683
            $Alias = explode('/', $alias);
2684
            $alias = array_pop($Alias);
2685
            $dir = implode('/', $Alias);
2686
            unset($Alias);
2687
2688
            if ($this->config['make_folders'] === '1' && $isfolder == 1) {
2689
                $suff = '/';
2690
            }
2691
2692
            $url = ($dir != '' ? $dir . '/' : '') . $pre . $alias . $suff;
2693
        }
2694
2695
        $evtOut = $this->invokeEvent('OnMakeDocUrl', array(
2696
            'id' => $id,
2697
            'url' => $url
2698
        ));
2699
2700
        if (is_array($evtOut) && count($evtOut) > 0) {
2701
            $url = array_pop($evtOut);
2702
        }
2703
2704
        return $url;
2705
    }
2706
2707
    /**
2708
     * Convert URL tags [~...~] to URLs
2709
     *
2710
     * @param string $documentSource
2711
     * @return string
2712
     */
2713
    public function rewriteUrls($documentSource)
2714
    {
2715
        // rewrite the urls
2716
        if ($this->config['friendly_urls'] == 1) {
2717
            $aliases = array();
2718
            if (is_array($this->documentListing)) {
2719
                foreach ($this->documentListing as $path => $docid) { // This is big Loop on large site!
2720
                    $aliases[$docid] = $path;
2721
                    $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...
2722
                }
2723
            }
2724
2725
            if ($this->config['aliaslistingfolder'] == 1) {
2726
                preg_match_all('!\[\~([0-9]+)\~\]!ise', $documentSource, $match);
2727
                $ids = implode(',', array_unique($match['1']));
2728
                if ($ids) {
2729
                    $res = $this->getDatabase()->select("id,alias,isfolder,parent,alias_visible", $this->getDatabase()->getFullTableName('site_content'), "id IN (" . $ids . ") AND isfolder = '0'");
2730
                    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 2729 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...
2731
                        if ($this->config['use_alias_path'] == '1' && $row['parent'] != 0) {
2732
                            $parent = $row['parent'];
2733
                            $path = $aliases[$parent];
2734
2735
                            while (isset($this->aliasListing[$parent]) && $this->aliasListing[$parent]['alias_visible'] == 0) {
2736
                                $path = $this->aliasListing[$parent]['path'];
2737
                                $parent = $this->aliasListing[$parent]['parent'];
2738
                            }
2739
2740
                            $aliases[$row['id']] = $path . '/' . $row['alias'];
2741
                        } else {
2742
                            $aliases[$row['id']] = $row['alias'];
2743
                        }
2744
                        $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...
2745
                    }
2746
                }
2747
            }
2748
            $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...
2749
            $isfriendly = ($this->config['friendly_alias_urls'] == 1 ? 1 : 0);
2750
            $pref = $this->config['friendly_url_prefix'];
2751
            $suff = $this->config['friendly_url_suffix'];
2752
            $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...
2753
                global $modx;
2754
                $thealias = $aliases[$m[1]];
2755
                $thefolder = $isfolder[$m[1]];
2756
                if ($isfriendly && isset($thealias)) {
2757
                    //found friendly url
2758
                    $out = ($modx->config['seostrict'] == '1' ? $modx->toAlias($modx->makeFriendlyURL($pref, $suff, $thealias, $thefolder, $m[1])) : $modx->makeFriendlyURL($pref, $suff, $thealias, $thefolder, $m[1]));
2759
                } else {
2760
                    //not found friendly url
2761
                    $out = $modx->makeFriendlyURL($pref, $suff, $m[1]);
2762
                }
2763
                return $out;
2764
            }, $documentSource);
2765
2766
        } else {
2767
            $in = '!\[\~([0-9]+)\~\]!is';
2768
            $out = "index.php?id=" . '\1';
2769
            $documentSource = preg_replace($in, $out, $documentSource);
2770
        }
2771
2772
        return $documentSource;
2773
    }
2774
2775
    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...
2776
    {
2777
        $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...
2778
        // FIX URLs
2779
        if (empty($this->documentIdentifier) || $this->config['seostrict'] == '0' || $this->config['friendly_urls'] == '0') {
2780
            return;
2781
        }
2782
        if ($this->config['site_status'] == 0) {
2783
            return;
2784
        }
2785
2786
        $scheme = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https' : 'http';
2787
        $len_base_url = strlen($this->config['base_url']);
2788
2789
        $url_path = $q;//LANG
2790
2791 View Code Duplication
        if (substr($url_path, 0, $len_base_url) === $this->config['base_url']) {
2792
            $url_path = substr($url_path, $len_base_url);
2793
        }
2794
2795
        $strictURL = $this->toAlias($this->makeUrl($this->documentIdentifier));
2796
2797 View Code Duplication
        if (substr($strictURL, 0, $len_base_url) === $this->config['base_url']) {
2798
            $strictURL = substr($strictURL, $len_base_url);
2799
        }
2800
        $http_host = $_SERVER['HTTP_HOST'];
2801
        $requestedURL = "{$scheme}://{$http_host}" . '/' . $q; //LANG
2802
2803
        $site_url = $this->config['site_url'];
2804
        $url_query_string = explode('?', $_SERVER['REQUEST_URI']);
2805
        // Strip conflicting id/q from query string
2806
        $qstring = !empty($url_query_string[1]) ? preg_replace("#(^|&)(q|id)=[^&]+#", '', $url_query_string[1]) : '';
2807
2808
        if ($this->documentIdentifier == $this->config['site_start']) {
2809
            if ($requestedURL != $this->config['site_url']) {
2810
                // Force redirect of site start
2811
                // $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...
2812
                if ($qstring) {
2813
                    $url = "{$site_url}?{$qstring}";
2814
                } else {
2815
                    $url = $site_url;
2816
                }
2817
                if ($this->config['base_url'] != $_SERVER['REQUEST_URI']) {
2818
                    if (empty($_POST)) {
2819
                        if (($this->config['base_url'] . '?' . $qstring) != $_SERVER['REQUEST_URI']) {
2820
                            $this->sendRedirect($url, 0, 'REDIRECT_HEADER', 'HTTP/1.0 301 Moved Permanently');
2821
                            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...
2822
                        }
2823
                    }
2824
                }
2825
            }
2826
        } elseif ($url_path != $strictURL && $this->documentIdentifier != $this->config['error_page']) {
2827
            // Force page redirect
2828
            //$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...
2829
            if (!empty($qstring)) {
2830
                $url = "{$site_url}{$strictURL}?{$qstring}";
2831
            } else {
2832
                $url = "{$site_url}{$strictURL}";
2833
            }
2834
            $this->sendRedirect($url, 0, 'REDIRECT_HEADER', 'HTTP/1.0 301 Moved Permanently');
2835
            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...
2836
        }
2837
        return;
2838
    }
2839
2840
    /**
2841
     * Get all db fields and TVs for a document/resource
2842
     *
2843
     * @param string $method
2844
     * @param mixed $identifier
2845
     * @param bool $isPrepareResponse
2846
     * @return array
2847
     */
2848
    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...
2849
    {
2850
2851
        $cacheKey = md5(print_r(func_get_args(), true));
2852
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
2853
            return $this->tmpCache[__FUNCTION__][$cacheKey];
2854
        }
2855
2856
        $tblsc = $this->getDatabase()->getFullTableName("site_content");
2857
        $tbldg = $this->getDatabase()->getFullTableName("document_groups");
2858
2859
        // allow alias to be full path
2860
        if ($method == 'alias') {
2861
            $identifier = $this->cleanDocumentIdentifier($identifier);
2862
            $method = $this->documentMethod;
2863
        }
2864
        if ($method == 'alias' && $this->config['use_alias_path'] && array_key_exists($identifier, $this->documentListing)) {
2865
            $method = 'id';
2866
            $identifier = $this->documentListing[$identifier];
2867
        }
2868
2869
        $out = $this->invokeEvent('OnBeforeLoadDocumentObject', compact('method', 'identifier'));
2870
        if (is_array($out) && is_array($out[0])) {
2871
            $documentObject = $out[0];
2872
        } else {
2873
            // get document groups for current user
2874
            if ($docgrp = $this->getUserDocGroups()) {
2875
                $docgrp = implode(",", $docgrp);
2876
            }
2877
            // get document
2878
            $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
2879
            $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...
2880
                LEFT JOIN {$tbldg} dg ON dg.document = sc.id", "sc.{$method} = '{$identifier}' AND ({$access})", "", 1);
2881
            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 2879 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...
2882
                $seclimit = 0;
2883
                if ($this->config['unauthorized_page']) {
2884
                    // method may still be alias, while identifier is not full path alias, e.g. id not found above
2885
                    if ($method === 'alias') {
2886
                        $secrs = $this->getDatabase()->select('count(dg.id)', "{$tbldg} as dg, {$tblsc} as sc", "dg.document = sc.id AND sc.alias = '{$identifier}'", '', 1);
2887
                    } else {
2888
                        $secrs = $this->getDatabase()->select('count(id)', $tbldg, "document = '{$identifier}'", '', 1);
2889
                    }
2890
                    // check if file is not public
2891
                    $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...
2892
                }
2893
                if ($seclimit > 0) {
2894
                    // match found but not publicly accessible, send the visitor to the unauthorized_page
2895
                    $this->sendUnauthorizedPage();
2896
                    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...
2897
                } else {
2898
                    $this->sendErrorPage();
2899
                    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...
2900
                }
2901
            }
2902
            # 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...
2903
            $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 2879 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...
2904
2905
            if ($isPrepareResponse === 'prepareResponse') {
2906
                $this->documentObject = &$documentObject;
2907
            }
2908
            $out = $this->invokeEvent('OnLoadDocumentObject', compact('method', 'identifier', 'documentObject'));
2909
            if (is_array($out) && is_array($out[0])) {
2910
                $documentObject = $out[0];
2911
            }
2912
            if ($documentObject['template']) {
2913
                // load TVs and merge with document - Orig by Apodigm - Docvars
2914
                $rs = $this->getDatabase()->select("tv.*, IF(tvc.value!='',tvc.value,tv.default_text) as value", $this->getDatabase()->getFullTableName("site_tmplvars") . " tv
2915
                INNER JOIN " . $this->getDatabase()->getFullTableName("site_tmplvar_templates") . " tvtpl ON tvtpl.tmplvarid = tv.id
2916
                LEFT JOIN " . $this->getDatabase()->getFullTableName("site_tmplvar_contentvalues") . " tvc ON tvc.tmplvarid=tv.id AND tvc.contentid = '{$documentObject['id']}'", "tvtpl.templateid = '{$documentObject['template']}'");
2917
                $tmplvars = array();
2918
                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 2914 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...
2919
                    $tmplvars[$row['name']] = array(
2920
                        $row['name'],
2921
                        $row['value'],
2922
                        $row['display'],
2923
                        $row['display_params'],
2924
                        $row['type']
2925
                    );
2926
                }
2927
                $documentObject = array_merge($documentObject, $tmplvars);
2928
            }
2929
            $out = $this->invokeEvent('OnAfterLoadDocumentObject', compact('method', 'identifier', 'documentObject'));
2930
            if (is_array($out) && array_key_exists(0, $out) !== false && is_array($out[0])) {
2931
                $documentObject = $out[0];
2932
            }
2933
        }
2934
2935
        $this->tmpCache[__FUNCTION__][$cacheKey] = $documentObject;
2936
2937
        return $documentObject;
2938
    }
2939
2940
    /**
2941
     * Parse a source string.
2942
     *
2943
     * Handles most MODX tags. Exceptions include:
2944
     *   - uncached snippet tags [!...!]
2945
     *   - URL tags [~...~]
2946
     *
2947
     * @param string $source
2948
     * @return string
2949
     */
2950
    public function parseDocumentSource($source)
2951
    {
2952
        // set the number of times we are to parse the document source
2953
        $this->minParserPasses = empty ($this->minParserPasses) ? 2 : $this->minParserPasses;
2954
        $this->maxParserPasses = empty ($this->maxParserPasses) ? 10 : $this->maxParserPasses;
2955
        $passes = $this->minParserPasses;
2956
        for ($i = 0; $i < $passes; $i++) {
2957
            // get source length if this is the final pass
2958
            if ($i == ($passes - 1)) {
2959
                $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...
2960
            }
2961
            if ($this->dumpSnippets == 1) {
2962
                $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>";
2963
            }
2964
2965
            // invoke OnParseDocument event
2966
            $this->documentOutput = $source; // store source code so plugins can
2967
            $this->invokeEvent("OnParseDocument"); // work on it via $modx->documentOutput
2968
            $source = $this->documentOutput;
2969
2970
            if ($this->config['enable_at_syntax']) {
2971
                $source = $this->ignoreCommentedTagsContent($source);
2972
                $source = $this->mergeConditionalTagsContent($source);
2973
            }
2974
2975
            $source = $this->mergeSettingsContent($source);
2976
            $source = $this->mergeDocumentContent($source);
2977
            $source = $this->mergeChunkContent($source);
2978
            $source = $this->evalSnippets($source);
2979
            $source = $this->mergePlaceholderContent($source);
2980
2981
            if ($this->dumpSnippets == 1) {
2982
                $this->snippetsCode .= "</fieldset><br />";
2983
            }
2984
            if ($i == ($passes - 1) && $i < ($this->maxParserPasses - 1)) {
2985
                // check if source content was changed
2986
                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...
2987
                    $passes++;
2988
                } // if content change then increase passes because
2989
            } // we have not yet reached maxParserPasses
2990
        }
2991
        return $source;
2992
    }
2993
2994
    /**
2995
     * Starts the parsing operations.
2996
     *
2997
     * - connects to the db
2998
     * - gets the settings (including system_settings)
2999
     * - gets the document/resource identifier as in the query string
3000
     * - finally calls prepareResponse()
3001
     */
3002
    public function executeParser()
3003
    {
3004
        if(MODX_CLI) {
3005
            throw new \RuntimeException('Call DocumentParser::executeParser on CLI mode');
3006
        }
3007
3008
        //error_reporting(0);
3009
        set_error_handler(array(
3010
            & $this,
3011
            "phpError"
3012
        ), E_ALL);
3013
        $this->getDatabase()->connect();
3014
3015
        // get the settings
3016
        if (empty ($this->config)) {
3017
            $this->getSettings();
3018
        }
3019
3020
        $this->_IIS_furl_fix(); // IIS friendly url fix
3021
3022
        // check site settings
3023
        if ($this->checkSiteStatus()) {
3024
            // make sure the cache doesn't need updating
3025
            $this->updatePubStatus();
3026
3027
            // find out which document we need to display
3028
            $this->documentMethod = filter_input(INPUT_GET, 'q') ? 'alias' : 'id';
3029
            $this->documentIdentifier = $this->getDocumentIdentifier($this->documentMethod);
3030
        } else {
3031
            header('HTTP/1.0 503 Service Unavailable');
3032
            $this->systemCacheKey = 'unavailable';
3033
            if (!$this->config['site_unavailable_page']) {
3034
                // display offline message
3035
                $this->documentContent = $this->config['site_unavailable_message'];
3036
                $this->outputContent();
3037
                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...
3038
            } else {
3039
                // setup offline page document settings
3040
                $this->documentMethod = 'id';
3041
                $this->documentIdentifier = $this->config['site_unavailable_page'];
3042
            }
3043
        }
3044
3045
        if ($this->documentMethod == "alias") {
3046
            $this->documentIdentifier = $this->cleanDocumentIdentifier($this->documentIdentifier);
3047
3048
            // Check use_alias_path and check if $this->virtualDir is set to anything, then parse the path
3049
            if ($this->config['use_alias_path'] == 1) {
3050
                $alias = (strlen($this->virtualDir) > 0 ? $this->virtualDir . '/' : '') . $this->documentIdentifier;
3051
                if (isset($this->documentListing[$alias])) {
3052
                    $this->documentIdentifier = $this->documentListing[$alias];
3053
                } else {
3054
                    //@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...
3055
                    if ($this->config['aliaslistingfolder'] == 1) {
3056
                        $tbl_site_content = $this->getDatabase()->getFullTableName('site_content');
3057
3058
                        $parentId = $this->getIdFromAlias($this->virtualDir);
3059
                        $parentId = ($parentId > 0) ? $parentId : '0';
3060
3061
                        $docAlias = $this->getDatabase()->escape($this->documentIdentifier);
3062
3063
                        $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...
3064
                        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 3063 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...
3065
                            $this->sendErrorPage();
3066
                        }
3067
                        $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 3063 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...
3068
3069
                        if (!$docId) {
3070
                            $alias = $this->q;
3071
                            if (!empty($this->config['friendly_url_suffix'])) {
3072
                                $pos = strrpos($alias, $this->config['friendly_url_suffix']);
3073
3074
                                if ($pos !== false) {
3075
                                    $alias = substr($alias, 0, $pos);
3076
                                }
3077
                            }
3078
                            $docId = $this->getIdFromAlias($alias);
3079
                        }
3080
3081
                        if ($docId > 0) {
3082
                            $this->documentIdentifier = $docId;
3083
                        } else {
3084
                            /*
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...
3085
                            $rs  = $this->getDatabase()->select('id', $tbl_site_content, "deleted=0 and alias='{$docAlias}'");
3086
                            if($this->getDatabase()->getRecordCount($rs)==0)
3087
                            {
3088
                                $rs  = $this->getDatabase()->select('id', $tbl_site_content, "deleted=0 and id='{$docAlias}'");
3089
                            }
3090
                            $docId = $this->getDatabase()->getValue($rs);
3091
3092
                            if ($docId > 0)
3093
                            {
3094
                                $this->documentIdentifier = $docId;
3095
3096
                            }else{
3097
                            */
3098
                            $this->sendErrorPage();
3099
                            //}
3100
                        }
3101
                    } else {
3102
                        $this->sendErrorPage();
3103
                    }
3104
                }
3105
            } else {
3106
                if (isset($this->documentListing[$this->documentIdentifier])) {
3107
                    $this->documentIdentifier = $this->documentListing[$this->documentIdentifier];
3108
                } else {
3109
                    $docAlias = $this->getDatabase()->escape($this->documentIdentifier);
3110
                    $rs = $this->getDatabase()->select('id', $this->getDatabase()->getFullTableName('site_content'), "deleted=0 and alias='{$docAlias}'");
3111
                    $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 3110 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...
3112
                }
3113
            }
3114
            $this->documentMethod = 'id';
3115
        }
3116
3117
        //$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...
3118
        // invoke OnWebPageInit event
3119
        $this->invokeEvent("OnWebPageInit");
3120
        // invoke OnLogPageView event
3121
        if ($this->config['track_visitors'] == 1) {
3122
            $this->invokeEvent("OnLogPageHit");
3123
        }
3124
        if ($this->config['seostrict'] === '1') {
3125
            $this->sendStrictURI();
3126
        }
3127
        $this->prepareResponse();
3128
    }
3129
3130
    /**
3131
     * @param $path
3132
     * @param null $suffix
3133
     * @return mixed
3134
     */
3135
    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...
3136
    {
3137
        $exp = explode('/', $path);
3138
        return str_replace($suffix, '', end($exp));
3139
    }
3140
3141
    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...
3142
    {
3143
        if ($this->config['friendly_urls'] != 1) {
3144
            return;
3145
        }
3146
3147
        if (strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') === false) {
3148
            return;
3149
        }
3150
3151
        $url = $_SERVER['QUERY_STRING'];
3152
        $err = substr($url, 0, 3);
3153
        if ($err !== '404' && $err !== '405') {
3154
            return;
3155
        }
3156
3157
        $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...
3158
        unset ($_GET[$k[0]]);
3159
        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...
3160
        $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...
3161
        $_SERVER['QUERY_STRING'] = $qp['query'];
3162
        if (!empty ($qp['query'])) {
3163
            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...
3164
            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...
3165
                $_REQUEST[$n] = $_GET[$n] = $v;
3166
            }
3167
        }
3168
        $_SERVER['PHP_SELF'] = $this->config['base_url'] . $qp['path'];
3169
        $this->q = $qp['path'];
3170
        return $qp['path'];
3171
    }
3172
3173
    /**
3174
     * The next step called at the end of executeParser()
3175
     *
3176
     * - checks cache
3177
     * - checks if document/resource is deleted/unpublished
3178
     * - checks if resource is a weblink and redirects if so
3179
     * - gets template and parses it
3180
     * - ensures that postProcess is called when PHP is finished
3181
     */
3182
    public function prepareResponse()
3183
    {
3184
        // we now know the method and identifier, let's check the cache
3185
3186
        if ($this->config['enable_cache'] == 2 && $this->isLoggedIn()) {
3187
            $this->config['enable_cache'] = 0;
3188
        }
3189
3190
        if ($this->config['enable_cache']) {
3191
            $this->documentContent = $this->getDocumentObjectFromCache($this->documentIdentifier, true);
3192
        } else {
3193
            $this->documentContent = '';
3194
        }
3195
3196
        if ($this->documentContent == '') {
3197
            // get document object from DB
3198
            $this->documentObject = $this->getDocumentObject($this->documentMethod, $this->documentIdentifier, 'prepareResponse');
3199
3200
            // write the documentName to the object
3201
            $this->documentName = &$this->documentObject['pagetitle'];
3202
3203
            // check if we should not hit this document
3204
            if ($this->documentObject['donthit'] == 1) {
3205
                $this->config['track_visitors'] = 0;
3206
            }
3207
3208
            if ($this->documentObject['deleted'] == 1) {
3209
                $this->sendErrorPage();
3210
            } // validation routines
3211
            elseif ($this->documentObject['published'] == 0) {
3212
                $this->_sendErrorForUnpubPage();
3213
            } elseif ($this->documentObject['type'] == 'reference') {
3214
                $this->_sendRedirectForRefPage($this->documentObject['content']);
3215
            }
3216
3217
            // get the template and start parsing!
3218
            if (!$this->documentObject['template']) {
3219
                $templateCode = '[*content*]';
3220
            } // use blank template
3221
            else {
3222
                $templateCode = $this->_getTemplateCodeFromDB($this->documentObject['template']);
3223
            }
3224
3225
            if (substr($templateCode, 0, 8) === '@INCLUDE') {
3226
                $templateCode = $this->atBindInclude($templateCode);
3227
            }
3228
3229
3230
            $this->documentContent = &$templateCode;
3231
3232
            // invoke OnLoadWebDocument event
3233
            $this->invokeEvent('OnLoadWebDocument');
3234
3235
            // Parse document source
3236
            $this->documentContent = $this->parseDocumentSource($templateCode);
3237
3238
            $this->documentGenerated = 1;
3239
        } else {
3240
            $this->documentGenerated = 0;
3241
        }
3242
3243
        if ($this->config['error_page'] == $this->documentIdentifier && $this->config['error_page'] != $this->config['site_start']) {
3244
            header('HTTP/1.0 404 Not Found');
3245
        }
3246
3247
        register_shutdown_function(array(
3248
            &$this,
3249
            'postProcess'
3250
        )); // tell PHP to call postProcess when it shuts down
3251
        $this->outputContent();
3252
        //$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...
3253
    }
3254
3255
    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...
3256
    {
3257
        // Can't view unpublished pages !$this->checkPreview()
3258
        if (!$this->hasPermission('view_unpublished')) {
3259
            $this->sendErrorPage();
3260
        } else {
3261
            $udperms = new Legacy\Permissions();
3262
            $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...
3263
            $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...
3264
            $udperms->role = $_SESSION['mgrRole'];
3265
            // Doesn't have access to this document
3266
            if (!$udperms->checkPermissions()) {
3267
                $this->sendErrorPage();
3268
            }
3269
        }
3270
    }
3271
3272
    /**
3273
     * @param $url
3274
     */
3275
    public function _sendRedirectForRefPage($url)
3276
    {
3277
        // check whether it's a reference
3278
        if (preg_match('@^[1-9][0-9]*$@', $url)) {
3279
            $url = $this->makeUrl($url); // if it's a bare document id
3280
        } elseif (strpos($url, '[~') !== false) {
3281
            $url = $this->rewriteUrls($url); // if it's an internal docid tag, process it
3282
        }
3283
        $this->sendRedirect($url, 0, '', 'HTTP/1.0 301 Moved Permanently');
3284
        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...
3285
    }
3286
3287
    /**
3288
     * @param $templateID
3289
     * @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...
3290
     */
3291
    public function _getTemplateCodeFromDB($templateID)
3292
    {
3293
        $rs = $this->getDatabase()->select(
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...
3294
            'content',
3295
            $this->getDatabase()->getFullTableName('site_templates'),
3296
            "id = '{$templateID}'"
3297
        );
3298
        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 3293 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...
3299
            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 3293 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...
3300
        } else {
3301
            $this->messageQuit('Incorrect number of templates returned from database');
3302
        }
3303
    }
3304
3305
    /**
3306
     * Returns an array of all parent record IDs for the id passed.
3307
     *
3308
     * @param int $id Docid to get parents for.
3309
     * @param int $height The maximum number of levels to go up, default 10.
3310
     * @return array
3311
     */
3312
    public function getParentIds($id, $height = 10)
3313
    {
3314
        $parents = array();
3315
        while ($id && $height--) {
3316
            $thisid = $id;
3317
            if ($this->config['aliaslistingfolder'] == 1) {
3318
                $id = isset($this->aliasListing[$id]['parent']) ? $this->aliasListing[$id]['parent'] : $this->getDatabase()->getValue("SELECT `parent` FROM " . $this->getDatabase()->getFullTableName("site_content") . " WHERE `id` = '{$id}' LIMIT 0,1");
3319
                if (!$id || $id == '0') {
3320
                    break;
3321
                }
3322
            } else {
3323
                $id = $this->aliasListing[$id]['parent'];
3324
                if (!$id) {
3325
                    break;
3326
                }
3327
            }
3328
            $parents[$thisid] = $id;
3329
        }
3330
        return $parents;
3331
    }
3332
3333
    /**
3334
     * @param $id
3335
     * @param int $top
3336
     * @return mixed
3337
     */
3338
    public function getUltimateParentId($id, $top = 0)
3339
    {
3340
        $i = 0;
3341
        while ($id && $i < 20) {
3342
            if ($top == $this->aliasListing[$id]['parent']) {
3343
                break;
3344
            }
3345
            $id = $this->aliasListing[$id]['parent'];
3346
            $i++;
3347
        }
3348
        return $id;
3349
    }
3350
3351
    /**
3352
     * Returns an array of child IDs belonging to the specified parent.
3353
     *
3354
     * @param int $id The parent resource/document to start from
3355
     * @param int $depth How many levels deep to search for children, default: 10
3356
     * @param array $children Optional array of docids to merge with the result.
3357
     * @return array Contains the document Listing (tree) like the sitemap
3358
     */
3359
    public function getChildIds($id, $depth = 10, $children = array())
3360
    {
3361
3362
        $cacheKey = md5(print_r(func_get_args(), true));
3363
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3364
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3365
        }
3366
3367
        if ($this->config['aliaslistingfolder'] == 1) {
3368
3369
            $res = $this->getDatabase()->select("id,alias,isfolder,parent", $this->getDatabase()->getFullTableName('site_content'), "parent IN (" . $id . ") AND deleted = '0'");
3370
            $idx = array();
3371
            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 3369 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...
3372
                $pAlias = '';
3373
                if (isset($this->aliasListing[$row['parent']])) {
3374
                    $pAlias .= !empty($this->aliasListing[$row['parent']]['path']) ? $this->aliasListing[$row['parent']]['path'] . '/' : '';
3375
                    $pAlias .= !empty($this->aliasListing[$row['parent']]['alias']) ? $this->aliasListing[$row['parent']]['alias'] . '/' : '';
3376
                };
3377
                $children[$pAlias . $row['alias']] = $row['id'];
3378
                if ($row['isfolder'] == 1) {
3379
                    $idx[] = $row['id'];
3380
                }
3381
            }
3382
            $depth--;
3383
            $idx = implode(',', $idx);
3384
            if (!empty($idx)) {
3385
                if ($depth) {
3386
                    $children = $this->getChildIds($idx, $depth, $children);
3387
                }
3388
            }
3389
            $this->tmpCache[__FUNCTION__][$cacheKey] = $children;
3390
            return $children;
3391
3392
        } else {
3393
3394
            // Initialise a static array to index parents->children
3395
            static $documentMap_cache = array();
3396
            if (!count($documentMap_cache)) {
3397
                foreach ($this->documentMap as $document) {
3398
                    foreach ($document as $p => $c) {
3399
                        $documentMap_cache[$p][] = $c;
3400
                    }
3401
                }
3402
            }
3403
3404
            // Get all the children for this parent node
3405
            if (isset($documentMap_cache[$id])) {
3406
                $depth--;
3407
3408
                foreach ($documentMap_cache[$id] as $childId) {
3409
                    $pkey = (strlen($this->aliasListing[$childId]['path']) ? "{$this->aliasListing[$childId]['path']}/" : '') . $this->aliasListing[$childId]['alias'];
3410
                    if (!strlen($pkey)) {
3411
                        $pkey = "{$childId}";
3412
                    }
3413
                    $children[$pkey] = $childId;
3414
3415
                    if ($depth && isset($documentMap_cache[$childId])) {
3416
                        $children += $this->getChildIds($childId, $depth);
3417
                    }
3418
                }
3419
            }
3420
            $this->tmpCache[__FUNCTION__][$cacheKey] = $children;
3421
            return $children;
3422
3423
        }
3424
    }
3425
3426
    /**
3427
     * Displays a javascript alert message in the web browser and quit
3428
     *
3429
     * @param string $msg Message to show
3430
     * @param string $url URL to redirect to
3431
     */
3432
    public function webAlertAndQuit($msg, $url = '')
3433
    {
3434
        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...
3435
        switch (true) {
3436
            case (0 === stripos($url, 'javascript:')):
3437
                $fnc = substr($url, 11);
3438
                break;
3439
            case $url === '#':
3440
                $fnc = '';
3441
                break;
3442
            case empty($url):
3443
                $fnc = 'history.back(-1);';
3444
                break;
3445
            default:
3446
                $fnc = "window.location.href='" . addslashes($url) . "';";
3447
        }
3448
3449
        echo "<html><head>
3450
            <title>MODX :: Alert</title>
3451
            <meta http-equiv=\"Content-Type\" content=\"text/html; charset={$modx_manager_charset};\">
3452
            <script>
3453
                function __alertQuit() {
3454
                    var el = document.querySelector('p');
3455
                    alert(el.innerHTML);
3456
                    el.remove();
3457
                    {$fnc}
3458
                }
3459
                window.setTimeout('__alertQuit();',100);
3460
            </script>
3461
            </head><body>
3462
            <p>{$msg}</p>
3463
            </body></html>";
3464
        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...
3465
    }
3466
3467
    /**
3468
     * Returns 1 if user has the currect permission
3469
     *
3470
     * @param string $pm Permission name
3471
     * @return int Why not bool?
3472
     */
3473
    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...
3474
    {
3475
        $state = 0;
3476
        $pms = $_SESSION['mgrPermissions'];
3477
        if ($pms) {
3478
            $state = ((bool)$pms[$pm] === true);
3479
        }
3480
        return (int)$state;
3481
    }
3482
3483
    /**
3484
     * Returns true if element is locked
3485
     *
3486
     * @param int $type Types: 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3487
     * @param int $id Element- / Resource-id
3488
     * @param bool $includeThisUser true = Return also info about actual user
3489
     * @return array lock-details or null
3490
     */
3491
    public function elementIsLocked($type, $id, $includeThisUser = false)
3492
    {
3493
        $id = (int)$id;
3494
        $type = (int)$type;
3495
        if (!$type || !$id) {
3496
            return null;
3497
        }
3498
3499
        // Build lockedElements-Cache at first call
3500
        $this->buildLockedElementsCache();
3501
3502
        if (!$includeThisUser && $this->lockedElements[$type][$id]['sid'] == $this->sid) {
3503
            return null;
3504
        }
3505
3506
        if (isset($this->lockedElements[$type][$id])) {
3507
            return $this->lockedElements[$type][$id];
3508
        } else {
3509
            return null;
3510
        }
3511
    }
3512
3513
    /**
3514
     * Returns Locked Elements as Array
3515
     *
3516
     * @param int $type Types: 0=all, 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3517
     * @param bool $minimumDetails true =
3518
     * @return array|mixed|null
3519
     */
3520
    public function getLockedElements($type = 0, $minimumDetails = false)
3521
    {
3522
        $this->buildLockedElementsCache();
3523
3524
        if (!$minimumDetails) {
3525
            $lockedElements = $this->lockedElements;
3526
        } else {
3527
            // Minimum details for HTML / Ajax-requests
3528
            $lockedElements = array();
3529
            foreach ($this->lockedElements as $elType => $elements) {
3530
                foreach ($elements as $elId => $el) {
3531
                    $lockedElements[$elType][$elId] = array(
3532
                        'username' => $el['username'],
3533
                        'lasthit_df' => $el['lasthit_df'],
3534
                        'state' => $this->determineLockState($el['internalKey'])
3535
                    );
3536
                }
3537
            }
3538
        }
3539
3540
        if ($type == 0) {
3541
            return $lockedElements;
3542
        }
3543
3544
        $type = (int)$type;
3545
        if (isset($lockedElements[$type])) {
3546
            return $lockedElements[$type];
3547
        } else {
3548
            return array();
3549
        }
3550
    }
3551
3552
    /**
3553
     * Builds the Locked Elements Cache once
3554
     */
3555
    public function buildLockedElementsCache()
3556
    {
3557
        if (is_null($this->lockedElements)) {
3558
            $this->lockedElements = array();
3559
            $this->cleanupExpiredLocks();
3560
3561
            $rs = $this->getDatabase()->select('sid,internalKey,elementType,elementId,lasthit,username', $this->getDatabase()->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...
3562
                LEFT JOIN {$this->getDatabase()->getFullTableName('manager_users')} mu on ul.internalKey = mu.id");
3563
            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 3561 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...
3564
                $this->lockedElements[$row['elementType']][$row['elementId']] = array(
3565
                    'sid' => $row['sid'],
3566
                    'internalKey' => $row['internalKey'],
3567
                    'username' => $row['username'],
3568
                    'elementType' => $row['elementType'],
3569
                    'elementId' => $row['elementId'],
3570
                    'lasthit' => $row['lasthit'],
3571
                    'lasthit_df' => $this->toDateFormat($row['lasthit']),
3572
                    'state' => $this->determineLockState($row['sid'])
3573
                );
3574
            }
3575
        }
3576
    }
3577
3578
    /**
3579
     * Cleans up the active user locks table
3580
     */
3581
    public function cleanupExpiredLocks()
3582
    {
3583
        // Clean-up active_user_sessions first
3584
        $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
3585
        $validSessionTimeLimit = $this->time - $timeout;
3586
        $this->getDatabase()->delete($this->getDatabase()->getFullTableName('active_user_sessions'), "lasthit < {$validSessionTimeLimit}");
3587
3588
        // Clean-up active_user_locks
3589
        $rs = $this->getDatabase()->select('sid,internalKey', $this->getDatabase()->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...
3590
        $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 3589 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...
3591
        if ($count) {
3592
            $rs = $this->getDatabase()->makeArray($rs);
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->makeArray($rs) on line 3592 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...
3593
            $userSids = array();
3594
            foreach ($rs as $row) {
3595
                $userSids[] = $row['sid'];
3596
            }
3597
            $userSids = "'" . implode("','", $userSids) . "'";
3598
            $this->getDatabase()->delete($this->getDatabase()->getFullTableName('active_user_locks'), "sid NOT IN({$userSids})");
3599
        } else {
3600
            $this->getDatabase()->delete($this->getDatabase()->getFullTableName('active_user_locks'));
3601
        }
3602
3603
    }
3604
3605
    /**
3606
     * Cleans up the active users table
3607
     */
3608
    public function cleanupMultipleActiveUsers()
3609
    {
3610
        $timeout = 20 * 60; // Delete multiple user-sessions after 20min
3611
        $validSessionTimeLimit = $this->time - $timeout;
3612
3613
        $activeUserSids = array();
3614
        $rs = $this->getDatabase()->select('sid', $this->getDatabase()->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...
3615
        $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 3614 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...
3616
        if ($count) {
3617
            $rs = $this->getDatabase()->makeArray($rs);
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->makeArray($rs) on line 3617 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...
3618
            foreach ($rs as $row) {
3619
                $activeUserSids[] = $row['sid'];
3620
            }
3621
        }
3622
3623
        $rs = $this->getDatabase()->select("sid,internalKey,lasthit", "{$this->getDatabase()->getFullTableName('active_users')}", "", "lasthit DESC");
3624
        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 3623 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...
3625
            $rs = $this->getDatabase()->makeArray($rs);
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->makeArray($rs) on line 3625 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...
3626
            $internalKeyCount = array();
3627
            $deleteSids = '';
3628
            foreach ($rs as $row) {
3629
                if (!isset($internalKeyCount[$row['internalKey']])) {
3630
                    $internalKeyCount[$row['internalKey']] = 0;
3631
                }
3632
                $internalKeyCount[$row['internalKey']]++;
3633
3634
                if ($internalKeyCount[$row['internalKey']] > 1 && !in_array($row['sid'], $activeUserSids) && $row['lasthit'] < $validSessionTimeLimit) {
3635
                    $deleteSids .= $deleteSids == '' ? '' : ' OR ';
3636
                    $deleteSids .= "sid='{$row['sid']}'";
3637
                };
3638
3639
            }
3640
            if ($deleteSids) {
3641
                $this->getDatabase()->delete($this->getDatabase()->getFullTableName('active_users'), $deleteSids);
3642
            }
3643
        }
3644
3645
    }
3646
3647
    /**
3648
     * Determines state of a locked element acc. to user-permissions
3649
     *
3650
     * @param $sid
3651
     * @return int $state States: 0=No display, 1=viewing this element, 2=locked, 3=show unlock-button
3652
     * @internal param int $internalKey : ID of User who locked actual element
3653
     */
3654
    public function determineLockState($sid)
3655
    {
3656
        $state = 0;
3657
        if ($this->hasPermission('display_locks')) {
3658
            if ($sid == $this->sid) {
3659
                $state = 1;
3660
            } else {
3661
                if ($this->hasPermission('remove_locks')) {
3662
                    $state = 3;
3663
                } else {
3664
                    $state = 2;
3665
                }
3666
            }
3667
        }
3668
        return $state;
3669
    }
3670
3671
    /**
3672
     * Locks an element
3673
     *
3674
     * @param int $type Types: 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3675
     * @param int $id Element- / Resource-id
3676
     * @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...
3677
     */
3678
    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...
3679
    {
3680
        $userId = $this->isBackend() && $_SESSION['mgrInternalKey'] ? $_SESSION['mgrInternalKey'] : 0;
3681
        $type = (int)$type;
3682
        $id = (int)$id;
3683
        if (!$type || !$id || !$userId) {
3684
            return false;
3685
        }
3686
3687
        $sql = sprintf('REPLACE INTO %s (internalKey, elementType, elementId, lasthit, sid)
3688
                VALUES (%d, %d, %d, %d, \'%s\')', $this->getDatabase()->getFullTableName('active_user_locks'), $userId, $type, $id, $this->time, $this->sid);
3689
        $this->getDatabase()->query($sql);
3690
    }
3691
3692
    /**
3693
     * Unlocks an element
3694
     *
3695
     * @param int $type Types: 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3696
     * @param int $id Element- / Resource-id
3697
     * @param bool $includeAllUsers true = Deletes not only own user-locks
3698
     * @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...
3699
     */
3700
    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...
3701
    {
3702
        $userId = $this->isBackend() && $_SESSION['mgrInternalKey'] ? $_SESSION['mgrInternalKey'] : 0;
3703
        $type = (int)$type;
3704
        $id = (int)$id;
3705
        if (!$type || !$id) {
3706
            return false;
3707
        }
3708
3709
        if (!$includeAllUsers) {
3710
            $sql = sprintf('DELETE FROM %s WHERE internalKey = %d AND elementType = %d AND elementId = %d;', $this->getDatabase()->getFullTableName('active_user_locks'), $userId, $type, $id);
3711
        } else {
3712
            $sql = sprintf('DELETE FROM %s WHERE elementType = %d AND elementId = %d;', $this->getDatabase()->getFullTableName('active_user_locks'), $type, $id);
3713
        }
3714
        $this->getDatabase()->query($sql);
3715
    }
3716
3717
    /**
3718
     * Updates table "active_user_sessions" with userid, lasthit, IP
3719
     */
3720
    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...
3721
    {
3722
        if (!$this->sid) {
3723
            return;
3724
        }
3725
3726
        // web users are stored with negative keys
3727
        $userId = $this->getLoginUserType() == 'manager' ? $this->getLoginUserID() : -$this->getLoginUserID();
3728
3729
        // Get user IP
3730 View Code Duplication
        if ($cip = getenv("HTTP_CLIENT_IP")) {
3731
            $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...
3732
        } elseif ($cip = getenv("HTTP_X_FORWARDED_FOR")) {
3733
            $ip = $cip;
3734
        } elseif ($cip = getenv("REMOTE_ADDR")) {
3735
            $ip = $cip;
3736
        } else {
3737
            $ip = "UNKNOWN";
3738
        }
3739
        $_SESSION['ip'] = $ip;
3740
3741
        $sql = sprintf('REPLACE INTO %s (internalKey, lasthit, ip, sid)
3742
            VALUES (%d, %d, \'%s\', \'%s\')', $this->getDatabase()->getFullTableName('active_user_sessions'), $userId, $this->time, $ip, $this->sid);
3743
        $this->getDatabase()->query($sql);
3744
    }
3745
3746
    /**
3747
     * Add an a alert message to the system event log
3748
     *
3749
     * @param int $evtid Event ID
3750
     * @param int $type Types: 1 = information, 2 = warning, 3 = error
3751
     * @param string $msg Message to be logged
3752
     * @param string $source source of the event (module, snippet name, etc.)
3753
     *                       Default: Parser
3754
     */
3755
    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...
3756
    {
3757
        $msg = $this->getDatabase()->escape($msg);
3758
        if (strpos($GLOBALS['database_connection_charset'], 'utf8') === 0 && extension_loaded('mbstring')) {
3759
            $esc_source = mb_substr($source, 0, 50, "UTF-8");
3760
        } else {
3761
            $esc_source = substr($source, 0, 50);
3762
        }
3763
        $esc_source = $this->getDatabase()->escape($esc_source);
3764
3765
        $LoginUserID = $this->getLoginUserID();
3766
        if ($LoginUserID == '') {
3767
            $LoginUserID = 0;
3768
        }
3769
3770
        $usertype = $this->isFrontend() ? 1 : 0;
3771
        $evtid = (int)$evtid;
3772
        $type = (int)$type;
3773
3774
        // Types: 1 = information, 2 = warning, 3 = error
3775
        if ($type < 1) {
3776
            $type = 1;
3777
        } elseif ($type > 3) {
3778
            $type = 3;
3779
        }
3780
3781
        $this->getDatabase()->insert(array(
3782
            'eventid' => $evtid,
3783
            'type' => $type,
3784
            'createdon' => $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'],
3785
            'source' => $esc_source,
3786
            'description' => $msg,
3787
            'user' => $LoginUserID,
3788
            'usertype' => $usertype
3789
        ), $this->getDatabase()->getFullTableName("event_log"));
3790
3791
        if (isset($this->config['send_errormail']) && $this->config['send_errormail'] !== '0') {
3792
            if ($this->config['send_errormail'] <= $type) {
3793
                $this->sendmail(array(
3794
                    'subject' => 'MODX System Error on ' . $this->config['site_name'],
3795
                    'body' => 'Source: ' . $source . ' - The details of the error could be seen in the MODX system events log.',
3796
                    'type' => 'text'
3797
                ));
3798
            }
3799
        }
3800
    }
3801
3802
    /**
3803
     * @param string|array $params
3804
     * @param string $msg
3805
     * @param array $files
3806
     * @return bool
3807
     * @throws \PHPMailer\PHPMailer\Exception
3808
     */
3809
    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...
3810
    {
3811
        if (\is_scalar($params)) {
3812
            if (strpos($params, '=') === false) {
3813
                if (strpos($params, '@') !== false) {
3814
                    $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...
3815
                } else {
3816
                    $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...
3817
                }
3818
            } else {
3819
                $params_array = explode(',', $params);
3820
                foreach ($params_array as $k => $v) {
3821
                    $k = trim($k);
3822
                    $v = trim($v);
3823
                    $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...
3824
                }
3825
            }
3826
        } else {
3827
            $p = $params;
3828
            unset($params);
3829
        }
3830
        if (isset($p['sendto'])) {
3831
            $p['to'] = $p['sendto'];
3832
        }
3833
3834
        if (isset($p['to']) && preg_match('@^[0-9]+$@', $p['to'])) {
3835
            $userinfo = $this->getUserInfo($p['to']);
3836
            $p['to'] = $userinfo['email'];
3837
        }
3838
        if (isset($p['from']) && preg_match('@^[0-9]+$@', $p['from'])) {
3839
            $userinfo = $this->getUserInfo($p['from']);
3840
            $p['from'] = $userinfo['email'];
3841
            $p['fromname'] = $userinfo['username'];
3842
        }
3843
        if ($msg === '' && !isset($p['body'])) {
3844
            $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...
3845
        } elseif (is_string($msg) && 0 < strlen($msg)) {
3846
            $p['body'] = $msg;
3847
        }
3848
3849
        $sendto = (!isset($p['to'])) ? $this->config['emailsender'] : $p['to'];
3850
        $sendto = explode(',', $sendto);
3851
        foreach ($sendto as $address) {
3852
            list($name, $address) = $this->getMail()->address_split($address);
3853
            $this->getMail()->AddAddress($address, $name);
3854
        }
3855 View Code Duplication
        if (isset($p['cc'])) {
3856
            $p['cc'] = explode(',', $p['cc']);
3857
            foreach ($p['cc'] as $address) {
3858
                list($name, $address) = $this->getMail()->address_split($address);
3859
                $this->getMail()->AddCC($address, $name);
3860
            }
3861
        }
3862 View Code Duplication
        if (isset($p['bcc'])) {
3863
            $p['bcc'] = explode(',', $p['bcc']);
3864
            foreach ($p['bcc'] as $address) {
3865
                list($name, $address) = $this->getMail()->address_split($address);
3866
                $this->getMail()->AddBCC($address, $name);
3867
            }
3868
        }
3869
        if (isset($p['from']) && strpos($p['from'], '<') !== false && substr($p['from'], -1) === '>') {
3870
            list($p['fromname'], $p['from']) = $this->getMail()->address_split($p['from']);
3871
        }
3872
        $this->getMail()->setFrom(
3873
            isset($p['from']) ? $p['from'] : $this->config['emailsender'],
3874
            isset($p['fromname']) ? $p['fromname'] : $this->config['site_name']
3875
        );
3876
        $this->getMail()->Subject = (!isset($p['subject'])) ? $this->config['emailsubject'] : $p['subject'];
3877
        $this->getMail()->Body = $p['body'];
3878
        if (isset($p['type']) && $p['type'] === 'text') {
3879
            $this->getMail()->IsHTML(false);
3880
        }
3881
        if (!is_array($files)) {
3882
            $files = array();
3883
        }
3884
        foreach ($files as $f) {
3885
            if (file_exists(MODX_BASE_PATH . $f) && is_file(MODX_BASE_PATH . $f) && is_readable(MODX_BASE_PATH . $f)) {
3886
                $this->getMail()->AddAttachment(MODX_BASE_PATH . $f);
3887
            }
3888
        }
3889
        return $this->getMail()->send();
3890
    }
3891
3892
    /**
3893
     * @param string $target
3894
     * @param int $limit
3895
     * @param int $trim
3896
     */
3897
    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...
3898
    {
3899
        if ($limit < $trim) {
3900
            $trim = $limit;
3901
        }
3902
3903
        $table_name = $this->getDatabase()->getFullTableName($target);
3904
        $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...
3905
        $over = $count - $limit;
3906
        if (0 < $over) {
3907
            $trim = ($over + $trim);
3908
            $this->getDatabase()->delete($table_name, '', '', $trim);
3909
        }
3910
        $this->getDatabase()->optimize($table_name);
3911
    }
3912
3913
    /**
3914
     * Returns true if we are currently in the manager backend
3915
     *
3916
     * @return boolean
3917
     */
3918
    public function isBackend()
3919
    {
3920
        return (defined('IN_MANAGER_MODE') && IN_MANAGER_MODE === true);
3921
    }
3922
3923
    /**
3924
     * Returns true if we are currently in the frontend
3925
     *
3926
     * @return boolean
3927
     */
3928
    public function isFrontend()
3929
    {
3930
        return ! $this->isBackend();
3931
    }
3932
3933
    /**
3934
     * Gets all child documents of the specified document, including those which are unpublished or deleted.
3935
     *
3936
     * @param int $id The Document identifier to start with
3937
     * @param string $sort Sort field
3938
     *                     Default: menuindex
3939
     * @param string $dir Sort direction, ASC and DESC is possible
3940
     *                    Default: ASC
3941
     * @param string $fields Default: id, pagetitle, description, parent, alias, menutitle
3942
     * @return array
3943
     */
3944 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...
3945
    {
3946
3947
        $cacheKey = md5(print_r(func_get_args(), true));
3948
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3949
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3950
        }
3951
3952
        $tblsc = $this->getDatabase()->getFullTableName("site_content");
3953
        $tbldg = $this->getDatabase()->getFullTableName("document_groups");
3954
        // modify field names to use sc. table reference
3955
        $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3956
        $sort = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
3957
        // get document groups for current user
3958
        if ($docgrp = $this->getUserDocGroups()) {
3959
            $docgrp = implode(",", $docgrp);
3960
        }
3961
        // build query
3962
        $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
3963
        $result = $this->getDatabase()->select("DISTINCT {$fields}", "{$tblsc} sc
3964
                LEFT JOIN {$tbldg} dg on dg.document = sc.id", "sc.parent = '{$id}' AND ({$access}) GROUP BY sc.id", "{$sort} {$dir}");
3965
        $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 3963 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...
3966
        $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
3967
        return $resourceArray;
3968
    }
3969
3970
    /**
3971
     * Gets all active child documents of the specified document, i.e. those which published and not deleted.
3972
     *
3973
     * @param int $id The Document identifier to start with
3974
     * @param string $sort Sort field
3975
     *                     Default: menuindex
3976
     * @param string $dir Sort direction, ASC and DESC is possible
3977
     *                    Default: ASC
3978
     * @param string $fields Default: id, pagetitle, description, parent, alias, menutitle
3979
     * @return array
3980
     */
3981 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...
3982
    {
3983
        $cacheKey = md5(print_r(func_get_args(), true));
3984
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3985
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3986
        }
3987
3988
        $tblsc = $this->getDatabase()->getFullTableName("site_content");
3989
        $tbldg = $this->getDatabase()->getFullTableName("document_groups");
3990
3991
        // modify field names to use sc. table reference
3992
        $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3993
        $sort = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
3994
        // get document groups for current user
3995
        if ($docgrp = $this->getUserDocGroups()) {
3996
            $docgrp = implode(",", $docgrp);
3997
        }
3998
        // build query
3999
        $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
4000
        $result = $this->getDatabase()->select("DISTINCT {$fields}", "{$tblsc} sc
4001
                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}");
4002
        $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 4000 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...
4003
4004
        $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
4005
4006
        return $resourceArray;
4007
    }
4008
4009
    /**
4010
     * getDocumentChildren
4011
     * @version 1.1.1 (2014-02-19)
4012
     *
4013
     * @desc Returns the children of the selected document/folder as an associative array.
4014
     *
4015
     * @param $parentid {integer} - The parent document identifier. Default: 0 (site root).
4016
     * @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.
4017
     * @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.
4018
     * @param $fields {comma separated string; '*'} - Comma separated list of document fields to get. Default: '*' (all fields).
4019
     * @param $where {string} - Where condition in SQL style. Should include a leading 'AND '. Default: ''.
4020
     * @param $sort {comma separated string} - Should be a comma-separated list of field names on which to sort. Default: 'menuindex'.
4021
     * @param $dir {'ASC'; 'DESC'} - Sort direction, ASC and DESC is possible. Default: 'ASC'.
4022
     * @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).
4023
     *
4024
     * @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...
4025
     */
4026
    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...
4027
    {
4028
4029
        $cacheKey = md5(print_r(func_get_args(), true));
4030
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
4031
            return $this->tmpCache[__FUNCTION__][$cacheKey];
4032
        }
4033
4034
        $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...
4035
        $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...
4036
4037
        if ($where != '') {
4038
            $where = 'AND ' . $where;
4039
        }
4040
4041
        // modify field names to use sc. table reference
4042
        $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
4043
        $sort = ($sort == '') ? '' : 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
4044
4045
        // get document groups for current user
4046
        if ($docgrp = $this->getUserDocGroups()) {
4047
            $docgrp = implode(',', $docgrp);
4048
        }
4049
4050
        // build query
4051
        $access = ($this->isFrontend() ? 'sc.privateweb=0' : '1="' . $_SESSION['mgrRole'] . '" OR sc.privatemgr=0') . (!$docgrp ? '' : ' OR dg.document_group IN (' . $docgrp . ')');
4052
4053
        $tblsc = $this->getDatabase()->getFullTableName('site_content');
4054
        $tbldg = $this->getDatabase()->getFullTableName('document_groups');
4055
4056
        $result = $this->getDatabase()->select("DISTINCT {$fields}", "{$tblsc} sc
4057
                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);
4058
4059
        $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 4056 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...
4060
4061
        $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
4062
4063
        return $resourceArray;
4064
    }
4065
4066
    /**
4067
     * getDocuments
4068
     * @version 1.1.1 (2013-02-19)
4069
     *
4070
     * @desc Returns required documents (their fields).
4071
     *
4072
     * @param $ids {array; comma separated string} - Documents Ids to get. @required
4073
     * @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.
4074
     * @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.
4075
     * @param $fields {comma separated string; '*'} - Documents fields to get. Default: '*'.
4076
     * @param $where {string} - SQL WHERE clause. Default: ''.
4077
     * @param $sort {comma separated string} - A comma-separated list of field names to sort by. Default: 'menuindex'.
4078
     * @param $dir {'ASC'; 'DESC'} - Sorting direction. Default: 'ASC'.
4079
     * @param $limit {string} - SQL LIMIT (without 'LIMIT '). An empty string means no limit. Default: ''.
4080
     *
4081
     * @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...
4082
     */
4083
    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...
4084
    {
4085
4086
        $cacheKey = md5(print_r(func_get_args(), true));
4087
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
4088
            return $this->tmpCache[__FUNCTION__][$cacheKey];
4089
        }
4090
4091
        if (is_string($ids)) {
4092
            if (strpos($ids, ',') !== false) {
4093
                $ids = array_filter(array_map('intval', explode(',', $ids)));
4094
            } else {
4095
                $ids = array($ids);
4096
            }
4097
        }
4098
        if (count($ids) == 0) {
4099
            $this->tmpCache[__FUNCTION__][$cacheKey] = false;
4100
            return false;
4101
        } else {
4102
            // modify field names to use sc. table reference
4103
            $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
4104
            $sort = ($sort == '') ? '' : 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
4105
            if ($where != '') {
4106
                $where = 'AND ' . $where;
4107
            }
4108
4109
            $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...
4110
            $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...
4111
4112
            // get document groups for current user
4113
            if ($docgrp = $this->getUserDocGroups()) {
4114
                $docgrp = implode(',', $docgrp);
4115
            }
4116
4117
            $access = ($this->isFrontend() ? 'sc.privateweb=0' : '1="' . $_SESSION['mgrRole'] . '" OR sc.privatemgr=0') . (!$docgrp ? '' : ' OR dg.document_group IN (' . $docgrp . ')');
4118
4119
            $tblsc = $this->getDatabase()->getFullTableName('site_content');
4120
            $tbldg = $this->getDatabase()->getFullTableName('document_groups');
4121
4122
            $result = $this->getDatabase()->select("DISTINCT {$fields}", "{$tblsc} sc
4123
                    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);
4124
4125
            $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 4122 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...
4126
4127
            $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
4128
4129
            return $resourceArray;
4130
        }
4131
    }
4132
4133
    /**
4134
     * getDocument
4135
     * @version 1.0.1 (2014-02-19)
4136
     *
4137
     * @desc Returns required fields of a document.
4138
     *
4139
     * @param int $id {integer}
4140
     * - Id of a document which data has to be gained. @required
4141
     * @param string $fields {comma separated string; '*'}
4142
     * - Comma separated list of document fields to get. Default: '*'.
4143
     * @param int $published {0; 1; 'all'}
4144
     * - 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.
4145
     * @param int $deleted {0; 1; 'all'}
4146
     * - 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.
4147
     * @return bool {array; false} - Result array with fields or false.
4148
     * - Result array with fields or false.
4149
     */
4150 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...
4151
    {
4152
        if ($id == 0) {
4153
            return false;
4154
        } else {
4155
            $docs = $this->getDocuments(array($id), $published, $deleted, $fields, '', '', '', 1);
4156
4157
            if ($docs != false) {
4158
                return $docs[0];
4159
            } else {
4160
                return false;
4161
            }
4162
        }
4163
    }
4164
4165
    /**
4166
     * @param string $field
4167
     * @param string $docid
4168
     * @return bool|mixed
4169
     */
4170
    public function getField($field = 'content', $docid = '')
4171
    {
4172
        if (empty($docid) && isset($this->documentIdentifier)) {
4173
            $docid = $this->documentIdentifier;
4174
        } elseif (!preg_match('@^[0-9]+$@', $docid)) {
4175
            $docid = $this->getIdFromAlias($docid);
4176
        }
4177
4178
        if (empty($docid)) {
4179
            return false;
4180
        }
4181
4182
        $cacheKey = md5(print_r(func_get_args(), true));
4183
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
4184
            return $this->tmpCache[__FUNCTION__][$cacheKey];
4185
        }
4186
4187
        $doc = $this->getDocumentObject('id', $docid);
4188
        if (is_array($doc[$field])) {
4189
            $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...
4190
            $content = $tvs[$field];
4191
        } else {
4192
            $content = $doc[$field];
4193
        }
4194
4195
        $this->tmpCache[__FUNCTION__][$cacheKey] = $content;
4196
4197
        return $content;
4198
    }
4199
4200
    /**
4201
     * Returns the page information as database row, the type of result is
4202
     * defined with the parameter $rowMode
4203
     *
4204
     * @param int $pageid The parent document identifier
4205
     *                    Default: -1 (no result)
4206
     * @param int $active Should we fetch only published and undeleted documents/resources?
4207
     *                     1 = yes, 0 = no
4208
     *                     Default: 1
4209
     * @param string $fields List of fields
4210
     *                       Default: id, pagetitle, description, alias
4211
     * @return boolean|array
4212
     */
4213
    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...
4214
    {
4215
4216
        $cacheKey = md5(print_r(func_get_args(), true));
4217
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
4218
            return $this->tmpCache[__FUNCTION__][$cacheKey];
4219
        }
4220
4221
        if ($pageid == 0) {
4222
            return false;
4223
        } else {
4224
            $tblsc = $this->getDatabase()->getFullTableName("site_content");
4225
            $tbldg = $this->getDatabase()->getFullTableName("document_groups");
4226
            $activeSql = $active == 1 ? "AND sc.published=1 AND sc.deleted=0" : "";
4227
            // modify field names to use sc. table reference
4228
            $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
4229
            // get document groups for current user
4230
            if ($docgrp = $this->getUserDocGroups()) {
4231
                $docgrp = implode(",", $docgrp);
4232
            }
4233
            $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
4234
            $result = $this->getDatabase()->select($fields, "{$tblsc} sc LEFT JOIN {$tbldg} dg on dg.document = sc.id", "(sc.id='{$pageid}' {$activeSql}) AND ({$access})", "", 1);
4235
            $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 4234 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...
4236
4237
            $this->tmpCache[__FUNCTION__][$cacheKey] = $pageInfo;
4238
4239
            return $pageInfo;
4240
        }
4241
    }
4242
4243
    /**
4244
     * Returns the parent document/resource of the given docid
4245
     *
4246
     * @param int $pid The parent docid. If -1, then fetch the current document/resource's parent
4247
     *                 Default: -1
4248
     * @param int $active Should we fetch only published and undeleted documents/resources?
4249
     *                     1 = yes, 0 = no
4250
     *                     Default: 1
4251
     * @param string $fields List of fields
4252
     *                       Default: id, pagetitle, description, alias
4253
     * @return boolean|array
4254
     */
4255
    public function getParent($pid = -1, $active = 1, $fields = 'id, pagetitle, description, alias, parent')
4256
    {
4257
        if ($pid == -1) {
4258
            $pid = $this->documentObject['parent'];
4259
            return ($pid == 0) ? false : $this->getPageInfo($pid, $active, $fields);
4260
        } else if ($pid == 0) {
4261
            return false;
4262
        } else {
4263
            // first get the child document
4264
            $child = $this->getPageInfo($pid, $active, "parent");
4265
            // now return the child's parent
4266
            $pid = ($child['parent']) ? $child['parent'] : 0;
4267
            return ($pid == 0) ? false : $this->getPageInfo($pid, $active, $fields);
4268
        }
4269
    }
4270
4271
    /**
4272
     * Returns the id of the current snippet.
4273
     *
4274
     * @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...
4275
     */
4276
    public function getSnippetId()
4277
    {
4278
        if ($this->currentSnippet) {
4279
            $tbl = $this->getDatabase()->getFullTableName("site_snippets");
4280
            $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...
4281
            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 4280 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...
4282
                return $snippetId;
4283
            }
4284
        }
4285
        return 0;
4286
    }
4287
4288
    /**
4289
     * Returns the name of the current snippet.
4290
     *
4291
     * @return string
4292
     */
4293
    public function getSnippetName()
4294
    {
4295
        return $this->currentSnippet;
4296
    }
4297
4298
    /**
4299
     * Clear the cache of MODX.
4300
     *
4301
     * @param string $type
4302
     * @param bool $report
4303
     * @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...
4304
     */
4305
    public function clearCache($type = '', $report = false)
4306
    {
4307
        $cache_dir = MODX_BASE_PATH . $this->getCacheFolder();
4308
        if (is_array($type)) {
4309
            foreach ($type as $_) {
4310
                $this->clearCache($_, $report);
4311
            }
4312
        } elseif ($type == 'full') {
4313
            $sync = new Cache();
4314
            $sync->setCachepath($cache_dir);
4315
            $sync->setReport($report);
4316
            $sync->emptyCache();
4317
        } elseif (preg_match('@^[1-9][0-9]*$@', $type)) {
4318
            $key = ($this->config['cache_type'] == 2) ? $this->makePageCacheKey($type) : $type;
4319
            $file_name = "docid_" . $key . "_*.pageCache.php";
4320
            $cache_path = $cache_dir . $file_name;
4321
            $files = glob($cache_path);
4322
            $files[] = $cache_dir . "docid_" . $key . ".pageCache.php";
4323
            foreach ($files as $file) {
4324
                if (!is_file($file)) {
4325
                    continue;
4326
                }
4327
                unlink($file);
4328
            }
4329
        } else {
4330
            $files = glob($cache_dir . '*');
4331
            foreach ($files as $file) {
4332
                $name = basename($file);
4333
                if (strpos($name, '.pageCache.php') === false) {
4334
                    continue;
4335
                }
4336
                if (!is_file($file)) {
4337
                    continue;
4338
                }
4339
                unlink($file);
4340
            }
4341
        }
4342
    }
4343
4344
    /**
4345
     * makeUrl
4346
     *
4347
     * @desc Create an URL for the given document identifier. The url prefix and postfix are used, when “friendly_url” is active.
4348
     *
4349
     * @param $id {integer} - The document identifier. @required
4350
     * @param string $alias {string}
4351
     * - The alias name for the document. Default: ''.
4352
     * @param string $args {string}
4353
     * - The paramaters to add to the URL. Default: ''.
4354
     * @param string $scheme {string}
4355
     * - With full as valus, the site url configuration is used. Default: ''.
4356
     * @return mixed|string {string} - Result URL.
4357
     * - Result URL.
4358
     */
4359
    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...
4360
    {
4361
        $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...
4362
        $virtualDir = isset($this->config['virtual_dir']) ? $this->config['virtual_dir'] : '';
4363
        $f_url_prefix = $this->config['friendly_url_prefix'];
4364
        $f_url_suffix = $this->config['friendly_url_suffix'];
4365
4366
        if (!is_numeric($id)) {
4367
            $this->messageQuit("`{$id}` is not numeric and may not be passed to makeUrl()");
4368
        }
4369
4370
        if ($args !== '') {
4371
            // add ? or & to $args if missing
4372
            $args = ltrim($args, '?&');
4373
            $_ = 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...
4374
4375
            if ($_ === false && $this->config['friendly_urls'] == 1) {
4376
                $args = "?{$args}";
4377
            } else {
4378
                $args = "&{$args}";
4379
            }
4380
        }
4381
4382
        if ($id != $this->config['site_start']) {
4383
            if ($this->config['friendly_urls'] == 1 && $alias == '') {
4384
                $alias = $id;
4385
                $alPath = '';
4386
4387
                if ($this->config['friendly_alias_urls'] == 1) {
4388
4389
                    if ($this->config['aliaslistingfolder'] == 1) {
4390
                        $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...
4391
                    } else {
4392
                        $al = $this->aliasListing[$id];
4393
                    }
4394
4395
                    if ($al['isfolder'] === 1 && $this->config['make_folders'] === '1') {
4396
                        $f_url_suffix = '/';
4397
                    }
4398
4399
                    $alPath = !empty ($al['path']) ? $al['path'] . '/' : '';
4400
4401
                    if ($al && $al['alias']) {
4402
                        $alias = $al['alias'];
4403
                    }
4404
4405
                }
4406
4407
                $alias = $alPath . $f_url_prefix . $alias . $f_url_suffix;
4408
                $url = "{$alias}{$args}";
4409
            } else {
4410
                $url = "index.php?id={$id}{$args}";
4411
            }
4412
        } else {
4413
            $url = $args;
4414
        }
4415
4416
        $host = $this->config['base_url'];
4417
4418
        // check if scheme argument has been set
4419
        if ($scheme != '') {
4420
            // for backward compatibility - check if the desired scheme is different than the current scheme
4421
            if (is_numeric($scheme) && $scheme != $_SERVER['HTTPS']) {
4422
                $scheme = ($_SERVER['HTTPS'] ? 'http' : 'https');
4423
            }
4424
4425
            //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...
4426
            $host = $scheme == 'full' ? $this->config['site_url'] : $scheme . '://' . $_SERVER['HTTP_HOST'] . $host;
4427
        }
4428
4429
        //fix strictUrl by Bumkaka
4430
        if ($this->config['seostrict'] == '1') {
4431
            $url = $this->toAlias($url);
4432
        }
4433
4434
        if ($this->config['xhtml_urls']) {
4435
            $url = preg_replace("/&(?!amp;)/", "&amp;", $host . $virtualDir . $url);
4436
        } else {
4437
            $url = $host . $virtualDir . $url;
4438
        }
4439
4440
        $evtOut = $this->invokeEvent('OnMakeDocUrl', array(
4441
            'id' => $id,
4442
            'url' => $url
4443
        ));
4444
4445
        if (is_array($evtOut) && count($evtOut) > 0) {
4446
            $url = array_pop($evtOut);
4447
        }
4448
4449
        return $url;
4450
    }
4451
4452
    /**
4453
     * @param $id
4454
     * @return mixed
4455
     */
4456
    public function getAliasListing($id)
4457
    {
4458
        if (isset($this->aliasListing[$id])) {
4459
            $out = $this->aliasListing[$id];
4460
        } else {
4461
            $q = $this->getDatabase()->query("SELECT id,alias,isfolder,parent FROM " . $this->getDatabase()->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...
4462
            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 4461 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...
4463
                $q = $this->getDatabase()->getRow($q);
0 ignored issues
show
Bug introduced by
It seems like $q defined by $this->getDatabase()->getRow($q) on line 4463 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...
4464
                $this->aliasListing[$id] = array(
4465
                    'id' => (int)$q['id'],
4466
                    'alias' => $q['alias'] == '' ? $q['id'] : $q['alias'],
4467
                    'parent' => (int)$q['parent'],
4468
                    'isfolder' => (int)$q['isfolder'],
4469
                );
4470
                if ($this->aliasListing[$id]['parent'] > 0) {
4471
                    //fix alias_path_usage
4472
                    if ($this->config['use_alias_path'] == '1') {
4473
                        //&& $tmp['path'] != '' - fix error slash with epty path
4474
                        $tmp = $this->getAliasListing($this->aliasListing[$id]['parent']);
4475
                        $this->aliasListing[$id]['path'] = $tmp['path'] . ($tmp['alias_visible'] ? (($tmp['parent'] > 0 && $tmp['path'] != '') ? '/' : '') . $tmp['alias'] : '');
4476
                    } else {
4477
                        $this->aliasListing[$id]['path'] = '';
4478
                    }
4479
                }
4480
4481
                $out = $this->aliasListing[$id];
4482
            }
4483
        }
4484
        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...
4485
    }
4486
4487
    /**
4488
     * Returns an entry from the config
4489
     *
4490
     * Note: most code accesses the config array directly and we will continue to support this.
4491
     *
4492
     * @param string $name
4493
     * @param mixed $default
4494
     * @return bool|string
4495
     */
4496
    public function getConfig($name = '', $default = null)
4497
    {
4498
        return get_by_key($this->config, $name, $default);
4499
    }
4500
4501
    /**
4502
     * Returns the MODX version information as version, branch, release date and full application name.
4503
     *
4504
     * @param null $data
4505
     * @return array
4506
     */
4507
4508
    public function getVersionData($data = null)
4509
    {
4510
        $out = array();
4511
        if (empty($this->version) || !is_array($this->version)) {
4512
            //include for compatibility modx version < 1.0.10
4513
            include MODX_MANAGER_PATH . "includes/version.inc.php";
4514
            $this->version = array();
4515
            $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...
4516
            $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...
4517
            $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...
4518
            $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...
4519
            $this->version['new_version'] = isset($this->config['newversiontext']) ? $this->config['newversiontext'] : '';
4520
        }
4521
        return (!is_null($data) && is_array($this->version) && isset($this->version[$data])) ? $this->version[$data] : $this->version;
4522
    }
4523
4524
    /**
4525
     * Executes a snippet.
4526
     *
4527
     * @param string $snippetName
4528
     * @param array $params Default: Empty array
4529
     * @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...
4530
     */
4531
    public function runSnippet($snippetName, $params = array())
4532
    {
4533
        if (isset ($this->snippetCache[$snippetName])) {
4534
            $snippet = $this->snippetCache[$snippetName];
4535
            $properties = !empty($this->snippetCache[$snippetName . "Props"]) ? $this->snippetCache[$snippetName . "Props"] : '';
4536
        } else { // not in cache so let's check the db
4537
            $sql = "SELECT ss.`name`, ss.`snippet`, ss.`properties`, sm.properties as `sharedproperties` FROM " . $this->getDatabase()->getFullTableName("site_snippets") . " as ss LEFT JOIN " . $this->getDatabase()->getFullTableName('site_modules') . " as sm on sm.guid=ss.moduleguid WHERE ss.`name`='" . $this->getDatabase()->escape($snippetName) . "'  AND ss.disabled=0;";
4538
            $result = $this->getDatabase()->query($sql);
4539
            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 4538 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...
4540
                $row = $this->getDatabase()->getRow($result);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->getDatabase()->query($sql) on line 4538 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...
4541
                $snippet = $this->snippetCache[$snippetName] = $row['snippet'];
4542
                $mergedProperties = array_merge($this->parseProperties($row['properties']), $this->parseProperties($row['sharedproperties']));
4543
                $properties = $this->snippetCache[$snippetName . "Props"] = json_encode($mergedProperties);
4544
            } else {
4545
                $snippet = $this->snippetCache[$snippetName] = "return false;";
4546
                $properties = $this->snippetCache[$snippetName . "Props"] = '';
4547
            }
4548
        }
4549
        // load default params/properties
4550
        $parameters = $this->parseProperties($properties, $snippetName, 'snippet');
4551
        $parameters = array_merge($parameters, $params);
4552
4553
        // run snippet
4554
        return $this->evalSnippet($snippet, $parameters);
4555
    }
4556
4557
    /**
4558
     * Returns the chunk content for the given chunk name
4559
     *
4560
     * @param string $chunkName
4561
     * @return boolean|string
4562
     */
4563
    public function getChunk($chunkName)
4564
    {
4565
        $out = null;
4566
        if (empty($chunkName)) {
4567
            return $out;
4568
        }
4569
        if (isset ($this->chunkCache[$chunkName])) {
4570
            $out = $this->chunkCache[$chunkName];
4571
        } else if (stripos($chunkName, '@FILE') === 0) {
4572
            $out = $this->chunkCache[$chunkName] = $this->atBindFileContent($chunkName);
4573
        } else {
4574
            $where = sprintf("`name`='%s' AND disabled=0", $this->getDatabase()->escape($chunkName));
4575
            $rs = $this->getDatabase()->select(
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...
4576
                'snippet',
4577
                $this->getDatabase()->getFullTableName('site_htmlsnippets'),
4578
                $where
4579
            );
4580
            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 4575 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...
4581
                $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 4575 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...
4582
                $out = $this->chunkCache[$chunkName] = $row['snippet'];
4583
            } else {
4584
                $out = $this->chunkCache[$chunkName] = null;
4585
            }
4586
        }
4587
        return $out;
4588
    }
4589
4590
    /**
4591
     * parseText
4592
     * @version 1.0 (2013-10-17)
4593
     *
4594
     * @desc Replaces placeholders in text with required values.
4595
     *
4596
     * @param string $tpl
4597
     * @param array $ph
4598
     * @param string $left
4599
     * @param string $right
4600
     * @param bool $execModifier
4601
     * @return string {string} - Parsed text.
4602
     * - Parsed text.
4603
     * @internal param $chunk {string} - String to parse. - String to parse. @required
4604
     * @internal param $chunkArr {array} - Array of values. Key — placeholder name, value — value. - Array of values. Key — placeholder name, value — value. @required
4605
     * @internal param $prefix {string} - Placeholders prefix. Default: '[+'. - Placeholders prefix. Default: '[+'.
4606
     * @internal param $suffix {string} - Placeholders suffix. Default: '+]'. - Placeholders suffix. Default: '+]'.
4607
     *
4608
     */
4609
    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...
4610
    {
4611
        if (empty($ph) || empty($tpl)) {
4612
            return $tpl;
4613
        }
4614
4615 View Code Duplication
        if ($this->config['enable_at_syntax']) {
4616
            if (stripos($tpl, '<@LITERAL>') !== false) {
4617
                $tpl = $this->escapeLiteralTagsContent($tpl);
4618
            }
4619
        }
4620
4621
        $matches = $this->getTagsFromContent($tpl, $left, $right);
4622
        if (empty($matches)) {
4623
            return $tpl;
4624
        }
4625
4626
        foreach ($matches[1] as $i => $key) {
4627
4628
            if (strpos($key, ':') !== false && $execModifier) {
4629
                list($key, $modifiers) = $this->splitKeyAndFilter($key);
4630
            } else {
4631
                $modifiers = false;
4632
            }
4633
4634
            //          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...
4635
            if (!array_key_exists($key, $ph)) {
4636
                continue;
4637
            } //NULL values must be saved in placeholders, if we got them from database string
4638
4639
            $value = $ph[$key];
4640
4641
            $s = &$matches[0][$i];
4642
            if ($modifiers !== false) {
4643
                if (strpos($modifiers, $left) !== false) {
4644
                    $modifiers = $this->parseText($modifiers, $ph, $left, $right);
4645
                }
4646
                $value = $this->applyFilter($value, $modifiers, $key);
4647
            }
4648 View Code Duplication
            if (strpos($tpl, $s) !== false) {
4649
                $tpl = str_replace($s, $value, $tpl);
4650
            } elseif($this->debug) {
4651
                $this->addLog('parseText parse error', $_SERVER['REQUEST_URI'] . $s, 2);
4652
            }
4653
        }
4654
4655
        return $tpl;
4656
    }
4657
4658
    /**
4659
     * parseChunk
4660
     * @version 1.1 (2013-10-17)
4661
     *
4662
     * @desc Replaces placeholders in a chunk with required values.
4663
     *
4664
     * @param $chunkName {string} - Name of chunk to parse. @required
4665
     * @param $chunkArr {array} - Array of values. Key — placeholder name, value — value. @required
4666
     * @param string $prefix {string}
4667
     * - Placeholders prefix. Default: '{'.
4668
     * @param string $suffix {string}
4669
     * - Placeholders suffix. Default: '}'.
4670
     * @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...
4671
     * - Parsed chunk or false if $chunkArr is not array.
4672
     */
4673
    public function parseChunk($chunkName, $chunkArr, $prefix = '{', $suffix = '}')
4674
    {
4675
        //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...
4676
        if (!is_array($chunkArr)) {
4677
            return false;
4678
        }
4679
4680
        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...
4681
    }
4682
4683
    /**
4684
     * getTpl
4685
     * get template for snippets
4686
     * @param $tpl {string}
4687
     * @return bool|string {string}
4688
     */
4689
    public function getTpl($tpl)
4690
    {
4691
        $template = $tpl;
4692
        if (preg_match("/^@([^:\s]+)[:\s]+(.+)$/s", trim($tpl), $match)) {
4693
            $command = strtoupper($match[1]);
4694
            $template = $match[2];
4695
        }
4696
        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...
4697
            case 'CODE':
4698
                break;
4699
            case 'FILE':
4700
                $template = file_get_contents(MODX_BASE_PATH . $template);
4701
                break;
4702
            case 'CHUNK':
4703
                $template = $this->getChunk($template);
4704
                break;
4705
            case 'DOCUMENT':
4706
                $doc = $this->getDocument($template, 'content', 'all');
4707
                $template = $doc['content'];
4708
                break;
4709
            case 'SELECT':
4710
                $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...
4711
                break;
4712
            default:
4713
                if (!($template = $this->getChunk($tpl))) {
4714
                    $template = $tpl;
4715
                }
4716
        }
4717
        return $template;
4718
    }
4719
4720
    /**
4721
     * Returns the timestamp in the date format defined in $this->config['datetime_format']
4722
     *
4723
     * @param int $timestamp Default: 0
4724
     * @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.
4725
     * @return string
4726
     */
4727
    public function toDateFormat($timestamp = 0, $mode = '')
4728
    {
4729
        $timestamp = trim($timestamp);
4730
        if ($mode !== 'formatOnly' && empty($timestamp)) {
4731
            return '-';
4732
        }
4733
        $timestamp = (int)$timestamp;
4734
4735
        switch ($this->config['datetime_format']) {
4736
            case 'YYYY/mm/dd':
4737
                $dateFormat = '%Y/%m/%d';
4738
                break;
4739
            case 'dd-mm-YYYY':
4740
                $dateFormat = '%d-%m-%Y';
4741
                break;
4742
            case 'mm/dd/YYYY':
4743
                $dateFormat = '%m/%d/%Y';
4744
                break;
4745
            /*
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...
4746
            case 'dd-mmm-YYYY':
4747
                $dateFormat = '%e-%b-%Y';
4748
                break;
4749
            */
4750
        }
4751
4752
        if (empty($mode)) {
4753
            $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...
4754
        } elseif ($mode == 'dateOnly') {
4755
            $strTime = strftime($dateFormat, $timestamp);
4756
        } elseif ($mode == 'formatOnly') {
4757
            $strTime = $dateFormat;
4758
        }
4759
        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...
4760
    }
4761
4762
    /**
4763
     * Make a timestamp from a string corresponding to the format in $this->config['datetime_format']
4764
     *
4765
     * @param string $str
4766
     * @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...
4767
     */
4768
    public function toTimeStamp($str)
4769
    {
4770
        $str = trim($str);
4771
        if (empty($str)) {
4772
            return '';
4773
        }
4774
4775
        switch ($this->config['datetime_format']) {
4776 View Code Duplication
            case 'YYYY/mm/dd':
4777
                if (!preg_match('/^[0-9]{4}\/[0-9]{2}\/[0-9]{2}[0-9 :]*$/', $str)) {
4778
                    return '';
4779
                }
4780
                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...
4781
                break;
4782 View Code Duplication
            case 'dd-mm-YYYY':
4783
                if (!preg_match('/^[0-9]{2}-[0-9]{2}-[0-9]{4}[0-9 :]*$/', $str)) {
4784
                    return '';
4785
                }
4786
                list ($d, $m, $Y, $H, $M, $S) = sscanf($str, '%2d-%2d-%4d %2d:%2d:%2d');
4787
                break;
4788 View Code Duplication
            case 'mm/dd/YYYY':
4789
                if (!preg_match('/^[0-9]{2}\/[0-9]{2}\/[0-9]{4}[0-9 :]*$/', $str)) {
4790
                    return '';
4791
                }
4792
                list ($m, $d, $Y, $H, $M, $S) = sscanf($str, '%2d/%2d/%4d %2d:%2d:%2d');
4793
                break;
4794
            /*
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...
4795
            case 'dd-mmm-YYYY':
4796
                if (!preg_match('/^[0-9]{2}-[0-9a-z]+-[0-9]{4}[0-9 :]*$/i', $str)) {return '';}
4797
                list ($m, $d, $Y, $H, $M, $S) = sscanf($str, '%2d-%3s-%4d %2d:%2d:%2d');
4798
                break;
4799
            */
4800
        }
4801
        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...
4802
            $H = 0;
4803
            $M = 0;
4804
            $S = 0;
4805
        }
4806
        $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...
4807
        $timeStamp = (int)$timeStamp;
4808
        return $timeStamp;
4809
    }
4810
4811
    /**
4812
     * Get the TVs of a document's children. Returns an array where each element represents one child doc.
4813
     *
4814
     * Ignores deleted children. Gets all children - there is no where clause available.
4815
     *
4816
     * @param int $parentid The parent docid
4817
     *                 Default: 0 (site root)
4818
     * @param array $tvidnames . Which TVs to fetch - Can relate to the TV ids in the db (array elements should be numeric only)
4819
     *                                               or the TV names (array elements should be names only)
4820
     *                      Default: Empty array
4821
     * @param int $published Whether published or unpublished documents are in the result
4822
     *                      Default: 1
4823
     * @param string $docsort How to sort the result array (field)
4824
     *                      Default: menuindex
4825
     * @param ASC|string $docsortdir How to sort the result array (direction)
4826
     *                      Default: ASC
4827
     * @param string $tvfields Fields to fetch from site_tmplvars, default '*'
4828
     *                      Default: *
4829
     * @param string $tvsort How to sort each element of the result array i.e. how to sort the TVs (field)
4830
     *                      Default: rank
4831
     * @param string $tvsortdir How to sort each element of the result array i.e. how to sort the TVs (direction)
4832
     *                      Default: ASC
4833
     * @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...
4834
     */
4835
    public function getDocumentChildrenTVars($parentid = 0, $tvidnames = array(), $published = 1, $docsort = "menuindex", $docsortdir = "ASC", $tvfields = "*", $tvsort = "rank", $tvsortdir = "ASC")
4836
    {
4837
        $docs = $this->getDocumentChildren($parentid, $published, 0, '*', '', $docsort, $docsortdir);
4838
        if (!$docs) {
4839
            return false;
4840
        } else {
4841
            $result = array();
4842
            // get user defined template variables
4843
            if ($tvfields) {
4844
                $_ = 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...
4845
                foreach ($_ as $i => $v) {
4846
                    if ($v === 'value') {
4847
                        unset($_[$i]);
4848
                    } else {
4849
                        $_[$i] = 'tv.' . $v;
4850
                    }
4851
                }
4852
                $fields = implode(',', $_);
4853
            } else {
4854
                $fields = "tv.*";
4855
            }
4856
4857
            if ($tvsort != '') {
4858
                $tvsort = 'tv.' . implode(',tv.', array_filter(array_map('trim', explode(',', $tvsort))));
4859
            }
4860 View Code Duplication
            if ($tvidnames == "*") {
4861
                $query = "tv.id<>0";
4862
            } else {
4863
                $query = (is_numeric($tvidnames[0]) ? "tv.id" : "tv.name") . " IN ('" . implode("','", $tvidnames) . "')";
4864
            }
4865
4866
            $this->getUserDocGroups();
4867
4868
            foreach ($docs as $doc) {
4869
4870
                $docid = $doc['id'];
4871
4872
                $rs = $this->getDatabase()->select(
4873
                    "{$fields}, IF(tvc.value!='',tvc.value,tv.default_text) as value ",
4874
4875
                    $this->getDatabase()->getFullTableName("site_tmplvars") .
4876
                    " tv INNER JOIN " . $this->getDatabase()->getFullTableName("site_tmplvar_templates") .
4877
                    " tvtpl ON tvtpl.tmplvarid = tv.id LEFT JOIN " .
4878
                    $this->getDatabase()->getFullTableName("site_tmplvar_contentvalues") .
4879
                    " tvc ON tvc.tmplvarid=tv.id AND tvc.contentid='{$docid}'",
4880
4881
                    "{$query} AND tvtpl.templateid = '{$doc['template']}'",
4882
                    ($tvsort ? "{$tvsort} {$tvsortdir}" : "")
4883
                );
4884
                $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 4872 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...
4885
4886
                // get default/built-in template variables
4887
                ksort($doc);
4888
                foreach ($doc as $key => $value) {
4889
                    if ($tvidnames == '*' || in_array($key, $tvidnames)) {
4890
                        $tvs[] = array('name' => $key, 'value' => $value);
4891
                    }
4892
                }
4893
                if (is_array($tvs) && count($tvs)) {
4894
                    $result[] = $tvs;
4895
                }
4896
            }
4897
            return $result;
4898
        }
4899
    }
4900
4901
    /**
4902
     * getDocumentChildrenTVarOutput
4903
     * @version 1.1 (2014-02-19)
4904
     *
4905
     * @desc Returns an array where each element represents one child doc and contains the result from getTemplateVarOutput().
4906
     *
4907
     * @param int $parentid {integer}
4908
     * - Id of parent document. Default: 0 (site root).
4909
     * @param array $tvidnames {array; '*'}
4910
     * - Which TVs to fetch. In the form expected by getTemplateVarOutput(). Default: array().
4911
     * @param int $published {0; 1; 'all'}
4912
     * - 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.
4913
     * @param string $sortBy {string}
4914
     * - How to sort the result array (field). Default: 'menuindex'.
4915
     * @param string $sortDir {'ASC'; 'DESC'}
4916
     * - How to sort the result array (direction). Default: 'ASC'.
4917
     * @param string $where {string}
4918
     * - SQL WHERE condition (use only document fields, not TV). Default: ''.
4919
     * @param string $resultKey {string; false}
4920
     * - Field, which values are keys into result array. Use the “false”, that result array keys just will be numbered. Default: 'id'.
4921
     * @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...
4922
     * - Result array, or false.
4923
     */
4924
    public function getDocumentChildrenTVarOutput($parentid = 0, $tvidnames = array(), $published = 1, $sortBy = 'menuindex', $sortDir = 'ASC', $where = '', $resultKey = 'id')
4925
    {
4926
        $docs = $this->getDocumentChildren($parentid, $published, 0, 'id', $where, $sortBy, $sortDir);
4927
4928
        if (!$docs) {
4929
            return false;
4930
        } else {
4931
            $result = array();
4932
4933
            $unsetResultKey = false;
4934
4935
            if ($resultKey !== false) {
4936
                if (is_array($tvidnames)) {
4937
                    if (count($tvidnames) != 0 && !in_array($resultKey, $tvidnames)) {
4938
                        $tvidnames[] = $resultKey;
4939
                        $unsetResultKey = true;
4940
                    }
4941
                } else if ($tvidnames != '*' && $tvidnames != $resultKey) {
4942
                    $tvidnames = array($tvidnames, $resultKey);
4943
                    $unsetResultKey = true;
4944
                }
4945
            }
4946
4947
            for ($i = 0; $i < count($docs); $i++) {
4948
                $tvs = $this->getTemplateVarOutput($tvidnames, $docs[$i]['id'], $published);
4949
4950
                if ($tvs) {
4951
                    if ($resultKey !== false && array_key_exists($resultKey, $tvs)) {
4952
                        $result[$tvs[$resultKey]] = $tvs;
4953
4954
                        if ($unsetResultKey) {
4955
                            unset($result[$tvs[$resultKey]][$resultKey]);
4956
                        }
4957
                    } else {
4958
                        $result[] = $tvs;
4959
                    }
4960
                }
4961
            }
4962
4963
            return $result;
4964
        }
4965
    }
4966
4967
    /**
4968
     * Modified by Raymond for TV - Orig Modified by Apodigm - DocVars
4969
     * Returns a single site_content field or TV record from the db.
4970
     *
4971
     * If a site content field the result is an associative array of 'name' and 'value'.
4972
     *
4973
     * If a TV the result is an array representing a db row including the fields specified in $fields.
4974
     *
4975
     * @param string $idname Can be a TV id or name
4976
     * @param string $fields Fields to fetch from site_tmplvars. Default: *
4977
     * @param string|type $docid Docid. Defaults to empty string which indicates the current document.
4978
     * @param int $published Whether published or unpublished documents are in the result
4979
     *                        Default: 1
4980
     * @return bool
4981
     */
4982 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...
4983
    {
4984
        if ($idname == "") {
4985
            return false;
4986
        } else {
4987
            $result = $this->getTemplateVars(array($idname), $fields, $docid, $published, "", ""); //remove sorting for speed
4988
            return ($result != false) ? $result[0] : false;
4989
        }
4990
    }
4991
4992
    /**
4993
     * getTemplateVars
4994
     * @version 1.0.1 (2014-02-19)
4995
     *
4996
     * @desc Returns an array of site_content field fields and/or TV records from the db.
4997
     * Elements representing a site content field consist of an associative array of 'name' and 'value'.
4998
     * Elements representing a TV consist of an array representing a db row including the fields specified in $fields.
4999
     *
5000
     * @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
5001
     * @param $fields {comma separated string; '*'} - Fields names in the TV table of MODx database. Default: '*'
5002
     * @param $docid {integer; ''} - Id of a document to get. Default: an empty string which indicates the current document.
5003
     * @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.
5004
     * @param $sort {comma separated string} - Fields of the TV table to sort by. Default: 'rank'.
5005
     * @param $dir {'ASC'; 'DESC'} - How to sort the result array (direction). Default: 'ASC'.
5006
     *
5007
     * @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...
5008
     */
5009
    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...
5010
    {
5011
        $cacheKey = md5(print_r(func_get_args(), true));
5012
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
5013
            return $this->tmpCache[__FUNCTION__][$cacheKey];
5014
        }
5015
5016
        if (($idnames != '*' && !is_array($idnames)) || empty($idnames) ) {
5017
            return false;
5018
        } else {
5019
5020
            // get document record
5021
            if ($docid == '') {
5022
                $docid = $this->documentIdentifier;
5023
                $docRow = $this->documentObject;
5024
            } else {
5025
                $docRow = $this->getDocument($docid, '*', $published);
5026
5027
                if (!$docRow) {
5028
                    $this->tmpCache[__FUNCTION__][$cacheKey] = false;
5029
                    return false;
5030
                }
5031
            }
5032
5033
            // get user defined template variables
5034
            $fields = ($fields == '') ? 'tv.*' : 'tv.' . implode(',tv.', array_filter(array_map('trim', explode(',', $fields))));
5035
            $sort = ($sort == '') ? '' : 'tv.' . implode(',tv.', array_filter(array_map('trim', explode(',', $sort))));
5036
5037 View Code Duplication
            if ($idnames == '*') {
5038
                $query = 'tv.id<>0';
5039
            } else {
5040
                $query = (is_numeric($idnames[0]) ? 'tv.id' : 'tv.name') . " IN ('" . implode("','", $idnames) . "')";
5041
            }
5042
5043
            $rs = $this->getDatabase()->select("{$fields}, IF(tvc.value != '', tvc.value, tv.default_text) as value", $this->getDatabase()->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...
5044
                    INNER JOIN " . $this->getDatabase()->getFullTableName('site_tmplvar_templates') . " tvtpl ON tvtpl.tmplvarid = tv.id
5045
                    LEFT JOIN " . $this->getDatabase()->getFullTableName('site_tmplvar_contentvalues') . " tvc ON tvc.tmplvarid=tv.id AND tvc.contentid = '{$docid}'", "{$query} AND tvtpl.templateid = '{$docRow['template']}'", ($sort ? "{$sort} {$dir}" : ""));
5046
5047
            $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 5043 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...
5048
5049
            // get default/built-in template variables
5050
            if(is_array($docRow)){
5051
                ksort($docRow);
5052
5053
                foreach ($docRow as $key => $value) {
5054
                    if ($idnames == '*' || in_array($key, $idnames)) {
5055
                        array_push($result, array(
5056
                            'name' => $key,
5057
                            'value' => $value
5058
                        ));
5059
                    }
5060
                }
5061
            }
5062
5063
            $this->tmpCache[__FUNCTION__][$cacheKey] = $result;
5064
5065
            return $result;
5066
        }
5067
    }
5068
5069
    /**
5070
     * getTemplateVarOutput
5071
     * @version 1.0.1 (2014-02-19)
5072
     *
5073
     * @desc Returns an associative array containing TV rendered output values.
5074
     *
5075
     * @param array $idnames {array; '*'}
5076
     * - 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
5077
     * @param string $docid {integer; ''}
5078
     * - Id of a document to get. Default: an empty string which indicates the current document.
5079
     * @param int $published {0; 1; 'all'}
5080
     * - 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.
5081
     * @param string $sep {string}
5082
     * - Separator that is used while concatenating in getTVDisplayFormat(). Default: ''.
5083
     * @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...
5084
     * - Result array, or false.
5085
     */
5086
    public function getTemplateVarOutput($idnames = array(), $docid = '', $published = 1, $sep = '')
5087
    {
5088
        if (is_array($idnames) && empty($idnames) ) {
5089
            return false;
5090
        } else {
5091
            $output = array();
5092
            $vars = ($idnames == '*' || is_array($idnames)) ? $idnames : array($idnames);
5093
5094
            $docid = (int)$docid > 0 ? (int)$docid : $this->documentIdentifier;
5095
            // remove sort for speed
5096
            $result = $this->getTemplateVars($vars, '*', $docid, $published, '', '');
5097
5098
            if ($result == false) {
5099
                return false;
5100
            } else {
5101
                for ($i = 0; $i < count($result); $i++) {
5102
                    $row = $result[$i];
5103
5104
                    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...
5105
                        $output[$row['name']] = $row['value'];
5106
                    } else {
5107
                        $output[$row['name']] = getTVDisplayFormat($row['name'], $row['value'], $row['display'], $row['display_params'], $row['type'], $docid, $sep);
5108
                    }
5109
                }
5110
5111
                return $output;
5112
            }
5113
        }
5114
    }
5115
5116
    /**
5117
     * Returns the full table name based on db settings
5118
     *
5119
     * @param string $tbl Table name
5120
     * @return string Table name with prefix
5121
     * @deprecated use ->getDatabase()->getFullTableName()
5122
     */
5123
    public function getFullTableName($tbl)
5124
    {
5125
        return $this->getDatabase()->getFullTableName($tbl);
5126
    }
5127
5128
    /**
5129
     * Returns the placeholder value
5130
     *
5131
     * @param string $name Placeholder name
5132
     * @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...
5133
     */
5134
    public function getPlaceholder($name)
5135
    {
5136
        return isset($this->placeholders[$name]) ? $this->placeholders[$name] : null;
5137
    }
5138
5139
    /**
5140
     * Sets a value for a placeholder
5141
     *
5142
     * @param string $name The name of the placeholder
5143
     * @param string $value The value of the placeholder
5144
     */
5145
    public function setPlaceholder($name, $value)
5146
    {
5147
        $this->placeholders[$name] = $value;
5148
    }
5149
5150
    /**
5151
     * Set placeholders en masse via an array or object.
5152
     *
5153
     * @param object|array $subject
5154
     * @param string $prefix
5155
     */
5156
    public function toPlaceholders($subject, $prefix = '')
5157
    {
5158
        if (is_object($subject)) {
5159
            $subject = get_object_vars($subject);
5160
        }
5161
        if (is_array($subject)) {
5162
            foreach ($subject as $key => $value) {
5163
                $this->toPlaceholder($key, $value, $prefix);
5164
            }
5165
        }
5166
    }
5167
5168
    /**
5169
     * For use by toPlaceholders(); For setting an array or object element as placeholder.
5170
     *
5171
     * @param string $key
5172
     * @param object|array $value
5173
     * @param string $prefix
5174
     */
5175
    public function toPlaceholder($key, $value, $prefix = '')
5176
    {
5177
        if (is_array($value) || is_object($value)) {
5178
            $this->toPlaceholders($value, "{$prefix}{$key}.");
5179
        } else {
5180
            $this->setPlaceholder("{$prefix}{$key}", $value);
5181
        }
5182
    }
5183
5184
    /**
5185
     * Returns the manager relative URL/path with respect to the site root.
5186
     *
5187
     * @global string $base_url
5188
     * @return string The complete URL to the manager folder
5189
     */
5190
    public function getManagerPath()
5191
    {
5192
        return MODX_MANAGER_URL;
5193
    }
5194
5195
    /**
5196
     * Returns the cache relative URL/path with respect to the site root.
5197
     *
5198
     * @global string $base_url
5199
     * @return string The complete URL to the cache folder
5200
     */
5201
    public function getCachePath()
5202
    {
5203
        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...
5204
        $pth = $base_url . $this->getCacheFolder();
5205
        return $pth;
5206
    }
5207
5208
    /**
5209
     * Sends a message to a user's message box.
5210
     *
5211
     * @param string $type Type of the message
5212
     * @param string $to The recipient of the message
5213
     * @param string $from The sender of the message
5214
     * @param string $subject The subject of the message
5215
     * @param string $msg The message body
5216
     * @param int $private Whether it is a private message, or not
5217
     *                     Default : 0
5218
     */
5219
    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...
5220
    {
5221
        $private = ($private) ? 1 : 0;
5222 View Code Duplication
        if (!is_numeric($to)) {
5223
            // Query for the To ID
5224
            $rs = $this->getDatabase()->select('id', $this->getDatabase()->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...
5225
            $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 5224 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...
5226
        }
5227 View Code Duplication
        if (!is_numeric($from)) {
5228
            // Query for the From ID
5229
            $rs = $this->getDatabase()->select('id', $this->getDatabase()->getFullTableName("manager_users"), "username='{$from}'");
5230
            $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 5229 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...
5231
        }
5232
        // insert a new message into user_messages
5233
        $this->getDatabase()->insert(array(
5234
            'type' => $type,
5235
            'subject' => $subject,
5236
            'message' => $msg,
5237
            'sender' => $from,
5238
            'recipient' => $to,
5239
            'private' => $private,
5240
            'postdate' => $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'],
5241
            'messageread' => 0,
5242
        ), $this->getDatabase()->getFullTableName('user_messages'));
5243
    }
5244
5245
    /**
5246
     * Returns current user id.
5247
     *
5248
     * @param string $context . Default is an empty string which indicates the method should automatically pick 'web (frontend) or 'mgr' (backend)
5249
     * @return string
5250
     */
5251 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...
5252
    {
5253
        $out = false;
5254
5255
        if (!empty($context)) {
5256
            if (is_scalar($context) && isset($_SESSION[$context . 'Validated'])) {
5257
                $out = $_SESSION[$context . 'InternalKey'];
5258
            }
5259
        } else {
5260
            switch (true) {
5261
                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...
5262
                    $out = $_SESSION['webInternalKey'];
5263
                    break;
5264
                }
5265
                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...
5266
                    $out = $_SESSION['mgrInternalKey'];
5267
                    break;
5268
                }
5269
            }
5270
        }
5271
        return $out;
5272
    }
5273
5274
    /**
5275
     * Returns current user name
5276
     *
5277
     * @param string $context . Default is an empty string which indicates the method should automatically pick 'web (frontend) or 'mgr' (backend)
5278
     * @return string
5279
     */
5280 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...
5281
    {
5282
        $out = false;
5283
5284
        if (!empty($context)) {
5285
            if (is_scalar($context) && isset($_SESSION[$context . 'Validated'])) {
5286
                $out = $_SESSION[$context . 'Shortname'];
5287
            }
5288
        } else {
5289
            switch (true) {
5290
                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...
5291
                    $out = $_SESSION['webShortname'];
5292
                    break;
5293
                }
5294
                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...
5295
                    $out = $_SESSION['mgrShortname'];
5296
                    break;
5297
                }
5298
            }
5299
        }
5300
        return $out;
5301
    }
5302
5303
    /**
5304
     * Returns current login user type - web or manager
5305
     *
5306
     * @return string
5307
     */
5308
    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...
5309
    {
5310
        if ($this->isFrontend() && isset ($_SESSION['webValidated'])) {
5311
            return 'web';
5312
        } elseif ($this->isBackend() && isset ($_SESSION['mgrValidated'])) {
5313
            return 'manager';
5314
        } else {
5315
            return '';
5316
        }
5317
    }
5318
5319
    /**
5320
     * Returns a user info record for the given manager user
5321
     *
5322
     * @param int $uid
5323
     * @return boolean|string
5324
     */
5325
    public function getUserInfo($uid)
5326
    {
5327
        if (isset($this->tmpCache[__FUNCTION__][$uid])) {
5328
            return $this->tmpCache[__FUNCTION__][$uid];
5329
        }
5330
5331
        $from = $this->getDatabase()->getFullTableName('manager_users') . ' mu INNER JOIN ' .
5332
            $this->getDatabase()->getFullTableName('user_attributes'). ' mua ON mua.internalkey=mu.id';
5333
        $where = sprintf("mu.id='%s'", $this->getDatabase()->escape($uid));
5334
        $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...
5335
5336
        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 5334 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...
5337
            return $this->tmpCache[__FUNCTION__][$uid] = false;
5338
        }
5339
5340
        $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 5334 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...
5341 View Code Duplication
        if (!isset($row['usertype']) || !$row['usertype']) {
5342
            $row['usertype'] = 'manager';
5343
        }
5344
5345
        $this->tmpCache[__FUNCTION__][$uid] = $row;
5346
5347
        return $row;
5348
    }
5349
5350
    /**
5351
     * Returns a record for the web user
5352
     *
5353
     * @param int $uid
5354
     * @return boolean|string
5355
     */
5356
    public function getWebUserInfo($uid)
5357
    {
5358
        $rs = $this->getDatabase()->select('wu.username, wu.password, wua.*', $this->getDatabase()->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...
5359
                INNER JOIN " . $this->getDatabase()->getFullTableName("web_user_attributes") . " wua ON wua.internalkey=wu.id", "wu.id='{$uid}'");
5360
        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 5358 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...
5361 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...
5362
                $row["usertype"] = "web";
5363
            }
5364
            return $row;
5365
        }
5366
    }
5367
5368
    /**
5369
     * Returns an array of document groups that current user is assigned to.
5370
     * This function will first return the web user doc groups when running from
5371
     * frontend otherwise it will return manager user's docgroup.
5372
     *
5373
     * @param boolean $resolveIds Set to true to return the document group names
5374
     *                            Default: false
5375
     * @return string|array
5376
     */
5377
    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...
5378
    {
5379
        if ($this->isFrontend() && isset($_SESSION['webDocgroups']) && isset($_SESSION['webValidated'])) {
5380
            $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...
5381
            $dgn = isset($_SESSION['webDocgrpNames']) ? $_SESSION['webDocgrpNames'] : false;
5382
        } else if ($this->isBackend() && isset($_SESSION['mgrDocgroups']) && isset($_SESSION['mgrValidated'])) {
5383
            $dg = $_SESSION['mgrDocgroups'];
5384
            $dgn = isset($_SESSION['mgrDocgrpNames']) ? $_SESSION['mgrDocgrpNames'] : false;
5385
        } else {
5386
            $dg = '';
5387
        }
5388
        if (!$resolveIds) {
5389
            return $dg;
5390
        } else if (is_array($dgn)) {
5391
            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...
5392
        } else if (is_array($dg)) {
5393
            // resolve ids to names
5394
            $dgn = array();
5395
            $ds = $this->getDatabase()->select('name', $this->getDatabase()->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...
5396
            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 5395 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...
5397
                $dgn[] = $row['name'];
5398
            }
5399
            // cache docgroup names to session
5400
            if ($this->isFrontend()) {
5401
                $_SESSION['webDocgrpNames'] = $dgn;
5402
            } else {
5403
                $_SESSION['mgrDocgrpNames'] = $dgn;
5404
            }
5405
            return $dgn;
5406
        }
5407
    }
5408
5409
    /**
5410
     * Change current web user's password
5411
     *
5412
     * @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...
5413
     * @param string $oldPwd
5414
     * @param string $newPwd
5415
     * @return string|boolean Returns true if successful, oterhwise return error
5416
     *                        message
5417
     */
5418
    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...
5419
    {
5420
        $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...
5421
        if ($_SESSION["webValidated"] == 1) {
5422
            $tbl = $this->getDatabase()->getFullTableName("web_users");
5423
            $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...
5424
            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 5423 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...
5425
                if ($row["password"] == md5($oldPwd)) {
5426
                    if (strlen($newPwd) < 6) {
5427
                        return "Password is too short!";
5428
                    } elseif ($newPwd == "") {
5429
                        return "You didn't specify a password for this user!";
5430
                    } else {
5431
                        $this->getDatabase()->update(array(
5432
                            'password' => $this->getDatabase()->escape($newPwd),
5433
                        ), $tbl, "id='" . $this->getLoginUserID() . "'");
5434
                        // invoke OnWebChangePassword event
5435
                        $this->invokeEvent("OnWebChangePassword", array(
5436
                            "userid" => $row["id"],
5437
                            "username" => $row["username"],
5438
                            "userpassword" => $newPwd
5439
                        ));
5440
                        return true;
5441
                    }
5442
                } else {
5443
                    return "Incorrect password.";
5444
                }
5445
            }
5446
        }
5447
        return $rt;
5448
    }
5449
5450
    /**
5451
     * Returns true if the current web user is a member the specified groups
5452
     *
5453
     * @param array $groupNames
5454
     * @return boolean
5455
     */
5456
    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...
5457
    {
5458
        if (!is_array($groupNames)) {
5459
            return false;
5460
        }
5461
        // check cache
5462
        $grpNames = isset ($_SESSION['webUserGroupNames']) ? $_SESSION['webUserGroupNames'] : false;
5463
        if (!is_array($grpNames)) {
5464
            $rs = $this->getDatabase()->select('wgn.name', $this->getDatabase()->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...
5465
                    INNER JOIN " . $this->getDatabase()->getFullTableName("web_groups") . " wg ON wg.webgroup=wgn.id AND wg.webuser='" . $this->getLoginUserID() . "'");
5466
            $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 5464 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...
5467
            // save to cache
5468
            $_SESSION['webUserGroupNames'] = $grpNames;
5469
        }
5470
        foreach ($groupNames as $k => $v) {
5471
            if (in_array(trim($v), $grpNames)) {
5472
                return true;
5473
            }
5474
        }
5475
        return false;
5476
    }
5477
5478
    /**
5479
     * Registers Client-side CSS scripts - these scripts are loaded at inside
5480
     * the <head> tag
5481
     *
5482
     * @param string $src
5483
     * @param string $media Default: Empty string
5484
     * @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...
5485
     */
5486
    public function regClientCSS($src, $media = '')
5487
    {
5488
        if (empty($src) || isset ($this->loadedjscripts[$src])) {
5489
            return '';
5490
        }
5491
        $nextpos = max(array_merge(array(0), array_keys($this->sjscripts))) + 1;
5492
        $this->loadedjscripts[$src]['startup'] = true;
5493
        $this->loadedjscripts[$src]['version'] = '0';
5494
        $this->loadedjscripts[$src]['pos'] = $nextpos;
5495
        if (strpos(strtolower($src), "<style") !== false || strpos(strtolower($src), "<link") !== false) {
5496
            $this->sjscripts[$nextpos] = $src;
5497
        } else {
5498
            $this->sjscripts[$nextpos] = "\t" . '<link rel="stylesheet" type="text/css" href="' . $src . '" ' . ($media ? 'media="' . $media . '" ' : '') . '/>';
5499
        }
5500
    }
5501
5502
    /**
5503
     * Registers Startup Client-side JavaScript - these scripts are loaded at inside the <head> tag
5504
     *
5505
     * @param string $src
5506
     * @param array $options Default: 'name'=>'', 'version'=>'0', 'plaintext'=>false
5507
     */
5508
    public function regClientStartupScript($src, $options = array('name' => '', 'version' => '0', 'plaintext' => false))
5509
    {
5510
        $this->regClientScript($src, $options, true);
5511
    }
5512
5513
    /**
5514
     * Registers Client-side JavaScript these scripts are loaded at the end of the page unless $startup is true
5515
     *
5516
     * @param string $src
5517
     * @param array $options Default: 'name'=>'', 'version'=>'0', 'plaintext'=>false
5518
     * @param boolean $startup Default: false
5519
     * @return string
5520
     */
5521
    public function regClientScript($src, $options = array('name' => '', 'version' => '0', 'plaintext' => false), $startup = false)
5522
    {
5523
        if (empty($src)) {
5524
            return '';
5525
        } // nothing to register
5526
        if (!is_array($options)) {
5527
            if (is_bool($options))  // backward compatibility with old plaintext parameter
5528
            {
5529
                $options = array('plaintext' => $options);
5530
            } elseif (is_string($options)) // Also allow script name as 2nd param
5531
            {
5532
                $options = array('name' => $options);
5533
            } else {
5534
                $options = array();
5535
            }
5536
        }
5537
        $name = isset($options['name']) ? strtolower($options['name']) : '';
5538
        $version = isset($options['version']) ? $options['version'] : '0';
5539
        $plaintext = isset($options['plaintext']) ? $options['plaintext'] : false;
5540
        $key = !empty($name) ? $name : $src;
5541
        unset($overwritepos); // probably unnecessary--just making sure
5542
5543
        $useThisVer = true;
5544
        if (isset($this->loadedjscripts[$key])) { // a matching script was found
5545
            // if existing script is a startup script, make sure the candidate is also a startup script
5546
            if ($this->loadedjscripts[$key]['startup']) {
5547
                $startup = true;
5548
            }
5549
5550
            if (empty($name)) {
5551
                $useThisVer = false; // if the match was based on identical source code, no need to replace the old one
5552
            } else {
5553
                $useThisVer = version_compare($this->loadedjscripts[$key]['version'], $version, '<');
5554
            }
5555
5556
            if ($useThisVer) {
5557
                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...
5558
                    // remove old script from the bottom of the page (new one will be at the top)
5559
                    unset($this->jscripts[$this->loadedjscripts[$key]['pos']]);
5560
                } else {
5561
                    // overwrite the old script (the position may be important for dependent scripts)
5562
                    $overwritepos = $this->loadedjscripts[$key]['pos'];
5563
                }
5564
            } else { // Use the original version
5565
                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...
5566
                    // need to move the exisiting script to the head
5567
                    $version = $this->loadedjscripts[$key][$version];
5568
                    $src = $this->jscripts[$this->loadedjscripts[$key]['pos']];
5569
                    unset($this->jscripts[$this->loadedjscripts[$key]['pos']]);
5570
                } else {
5571
                    return ''; // the script is already in the right place
5572
                }
5573
            }
5574
        }
5575
5576
        if ($useThisVer && $plaintext != true && (strpos(strtolower($src), "<script") === false)) {
5577
            $src = "\t" . '<script type="text/javascript" src="' . $src . '"></script>';
5578
        }
5579
        if ($startup) {
5580
            $pos = isset($overwritepos) ? $overwritepos : max(array_merge(array(0), array_keys($this->sjscripts))) + 1;
5581
            $this->sjscripts[$pos] = $src;
5582
        } else {
5583
            $pos = isset($overwritepos) ? $overwritepos : max(array_merge(array(0), array_keys($this->jscripts))) + 1;
5584
            $this->jscripts[$pos] = $src;
5585
        }
5586
        $this->loadedjscripts[$key]['version'] = $version;
5587
        $this->loadedjscripts[$key]['startup'] = $startup;
5588
        $this->loadedjscripts[$key]['pos'] = $pos;
5589
        return '';
5590
    }
5591
5592
    /**
5593
     * Returns all registered JavaScripts
5594
     *
5595
     * @return string
5596
     */
5597
    public function regClientStartupHTMLBlock($html)
5598
    {
5599
        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...
5600
    }
5601
5602
    /**
5603
     * Returns all registered startup scripts
5604
     *
5605
     * @return string
5606
     */
5607
    public function regClientHTMLBlock($html)
5608
    {
5609
        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...
5610
    }
5611
5612
    /**
5613
     * Remove unwanted html tags and snippet, settings and tags
5614
     *
5615
     * @param string $html
5616
     * @param string $allowed Default: Empty string
5617
     * @return string
5618
     */
5619
    public function stripTags($html, $allowed = "")
5620
    {
5621
        $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...
5622
        $t = preg_replace('~\[\*(.*?)\*\]~', "", $t); //tv
5623
        $t = preg_replace('~\[\[(.*?)\]\]~', "", $t); //snippet
5624
        $t = preg_replace('~\[\!(.*?)\!\]~', "", $t); //snippet
5625
        $t = preg_replace('~\[\((.*?)\)\]~', "", $t); //settings
5626
        $t = preg_replace('~\[\+(.*?)\+\]~', "", $t); //placeholders
5627
        $t = preg_replace('~{{(.*?)}}~', "", $t); //chunks
5628
        return $t;
5629
    }
5630
5631
    /**
5632
     * Add an event listener to a plugin - only for use within the current execution cycle
5633
     *
5634
     * @param string $evtName
5635
     * @param string $pluginName
5636
     * @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...
5637
     */
5638
    public function addEventListener($evtName, $pluginName)
5639
    {
5640
        if (!$evtName || !$pluginName) {
5641
            return false;
5642
        }
5643
        if (!array_key_exists($evtName, $this->pluginEvent)) {
5644
            $this->pluginEvent[$evtName] = array();
5645
        }
5646
        return array_push($this->pluginEvent[$evtName], $pluginName); // return array count
5647
    }
5648
5649
    /**
5650
     * Remove event listener - only for use within the current execution cycle
5651
     *
5652
     * @param string $evtName
5653
     * @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...
5654
     */
5655
    public function removeEventListener($evtName)
5656
    {
5657
        if (!$evtName) {
5658
            return false;
5659
        }
5660
        unset ($this->pluginEvent[$evtName]);
5661
    }
5662
5663
    /**
5664
     * Remove all event listeners - only for use within the current execution cycle
5665
     */
5666
    public function removeAllEventListener()
5667
    {
5668
        unset ($this->pluginEvent);
5669
        $this->pluginEvent = array();
5670
    }
5671
5672
    /**
5673
     * Invoke an event.
5674
     *
5675
     * @param string $evtName
5676
     * @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.
5677
     * @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...
5678
     */
5679
    public function invokeEvent($evtName, $extParams = array())
5680
    {
5681
        if (!$evtName) {
5682
            return false;
5683
        }
5684
        if (!isset ($this->pluginEvent[$evtName])) {
5685
            return false;
5686
        }
5687
5688
        $results = null;
5689
        foreach ($this->pluginEvent[$evtName] as $pluginName) { // start for loop
5690
            if ($this->dumpPlugins) {
5691
                $eventtime = $this->getMicroTime();
5692
            }
5693
            // reset event object
5694
            $e = &$this->event;
5695
            $e->_resetEventObject();
5696
            $e->name = $evtName;
5697
            $e->activePlugin = $pluginName;
5698
5699
            // get plugin code
5700
            $_ = $this->getPluginCode($pluginName);
5701
            $pluginCode = $_['code'];
5702
            $pluginProperties = $_['props'];
5703
5704
            // load default params/properties
5705
            $parameter = $this->parseProperties($pluginProperties);
5706
            if (!is_array($parameter)) {
5707
                $parameter = array();
5708
            }
5709
            if (!empty($extParams)) {
5710
                $parameter = array_merge($parameter, $extParams);
5711
            }
5712
5713
            // eval plugin
5714
            $this->evalPlugin($pluginCode, $parameter);
5715
5716
            if (class_exists('PHxParser')) {
5717
                $this->config['enable_filter'] = 0;
5718
            }
5719
5720
            if ($this->dumpPlugins) {
5721
                $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...
5722
                $this->pluginsCode .= sprintf('<fieldset><legend><b>%s / %s</b> (%2.2f ms)</legend>', $evtName, $pluginName, $eventtime * 1000);
5723
                foreach ($parameter as $k => $v) {
5724
                    $this->pluginsCode .= "{$k} => " . print_r($v, true) . '<br>';
5725
                }
5726
                $this->pluginsCode .= '</fieldset><br />';
5727
                $this->pluginsTime["{$evtName} / {$pluginName}"] += $eventtime;
5728
            }
5729
            if ($e->getOutput() != '') {
5730
                $results[] = $e->getOutput();
5731
            }
5732
            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...
5733
                break;
5734
            }
5735
        }
5736
5737
        $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...
5738
        return $results;
5739
    }
5740
5741
    /**
5742
     * Returns plugin-code and properties
5743
     *
5744
     * @param string $pluginName
5745
     * @return array Associative array consisting of 'code' and 'props'
5746
     */
5747
    public function getPluginCode($pluginName)
5748
    {
5749
        $plugin = array();
5750
        if (isset ($this->pluginCache[$pluginName])) {
5751
            $pluginCode = $this->pluginCache[$pluginName];
5752
            $pluginProperties = isset($this->pluginCache[$pluginName . "Props"]) ? $this->pluginCache[$pluginName . "Props"] : '';
5753
        } else {
5754
            $pluginName = $this->getDatabase()->escape($pluginName);
5755
            $result = $this->getDatabase()->select('name, plugincode, properties', $this->getDatabase()->getFullTableName("site_plugins"), "name='{$pluginName}' AND disabled=0");
5756
            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 5755 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...
5757
                $pluginCode = $this->pluginCache[$row['name']] = $row['plugincode'];
5758
                $pluginProperties = $this->pluginCache[$row['name'] . "Props"] = $row['properties'];
5759
            } else {
5760
                $pluginCode = $this->pluginCache[$pluginName] = "return false;";
5761
                $pluginProperties = '';
5762
            }
5763
        }
5764
        $plugin['code'] = $pluginCode;
5765
        $plugin['props'] = $pluginProperties;
5766
5767
        return $plugin;
5768
    }
5769
5770
    /**
5771
     * Parses a resource property string and returns the result as an array
5772
     *
5773
     * @param string|array $propertyString
5774
     * @param string|null $elementName
5775
     * @param string|null $elementType
5776
     * @return array Associative array in the form property name => property value
5777
     */
5778
    public function parseProperties($propertyString, $elementName = null, $elementType = null)
5779
    {
5780
        $property = array();
5781
5782
        if(\is_scalar($propertyString)) {
5783
            $propertyString = trim($propertyString);
5784
            $propertyString = str_replace('{}', '', $propertyString);
5785
            $propertyString = str_replace('} {', ',', $propertyString);
5786
            if (!empty($propertyString) && $propertyString != '{}') {
5787
                $jsonFormat = $this->isJson($propertyString, true);
5788
                // old format
5789
                if ($jsonFormat === false) {
5790
                    $props = explode('&', $propertyString);
5791
                    foreach ($props as $prop) {
5792
5793
                        if (empty($prop)) {
5794
                            continue;
5795
                        } elseif (strpos($prop, '=') === false) {
5796
                            $property[trim($prop)] = '';
5797
                            continue;
5798
                        }
5799
5800
                        $_ = explode('=', $prop, 2);
5801
                        $key = trim($_[0]);
5802
                        $p = explode(';', trim($_[1]));
5803
                        switch ($p[1]) {
5804
                            case 'list':
5805
                            case 'list-multi':
5806
                            case 'checkbox':
5807
                            case 'radio':
5808
                                $value = !isset($p[3]) ? '' : $p[3];
5809
                                break;
5810
                            default:
5811
                                $value = !isset($p[2]) ? '' : $p[2];
5812
                        }
5813
                        if (!empty($key)) {
5814
                            $property[$key] = $value;
5815
                        }
5816
                    }
5817
                    // new json-format
5818
                } else if (!empty($jsonFormat)) {
5819
                    foreach ($jsonFormat as $key => $row) {
5820
                        if (!empty($key)) {
5821 View Code Duplication
                            if (is_array($row)) {
5822
                                if (isset($row[0]['value'])) {
5823
                                    $value = $row[0]['value'];
5824
                                }
5825
                            } else {
5826
                                $value = $row;
5827
                            }
5828
                            if (isset($value) && $value !== '') {
5829
                                $property[$key] = $value;
5830
                            }
5831
                        }
5832
                    }
5833
                }
5834
            }
5835
        }
5836
        elseif(\is_array($propertyString)) {
5837
            $property = $propertyString;
5838
        }
5839
        if (!empty($elementName) && !empty($elementType)) {
5840
            $out = $this->invokeEvent('OnParseProperties', array(
5841
                'element' => $elementName,
5842
                'type' => $elementType,
5843
                'args' => $property
5844
            ));
5845
            if (is_array($out)) {
5846
                $out = array_pop($out);
5847
            }
5848
            if (is_array($out)) {
5849
                $property = $out;
5850
            }
5851
        }
5852
5853
        return $property;
5854
    }
5855
5856
    /**
5857
     * Parses docBlock from a file and returns the result as an array
5858
     *
5859
     * @param string $element_dir
5860
     * @param string $filename
5861
     * @param boolean $escapeValues
5862
     * @return array Associative array in the form property name => property value
5863
     */
5864
    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...
5865
    {
5866
        $params = array();
5867
        $fullpath = $element_dir . '/' . $filename;
5868
        if (is_readable($fullpath)) {
5869
            $tpl = @fopen($fullpath, "r");
5870
            if ($tpl) {
5871
                $params['filename'] = $filename;
5872
                $docblock_start_found = false;
5873
                $name_found = false;
5874
                $description_found = false;
5875
                $docblock_end_found = false;
5876
                $arrayParams = array('author', 'documentation', 'reportissues', 'link');
5877
5878
                while (!feof($tpl)) {
5879
                    $line = fgets($tpl);
5880
                    $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...
5881
                    $docblock_start_found = $r['docblock_start_found'];
5882
                    $name_found = $r['name_found'];
5883
                    $description_found = $r['description_found'];
5884
                    $docblock_end_found = $r['docblock_end_found'];
5885
                    $param = $r['param'];
5886
                    $val = $r['val'];
5887
                    if (!$docblock_end_found) {
5888
                        break;
5889
                    }
5890
                    if (!$docblock_start_found || !$name_found || !$description_found || empty($param)) {
5891
                        continue;
5892
                    }
5893 View Code Duplication
                    if (!empty($param)) {
5894
                        if (in_array($param, $arrayParams)) {
5895
                            if (!isset($params[$param])) {
5896
                                $params[$param] = array();
5897
                            }
5898
                            $params[$param][] = $escapeValues ? $this->getDatabase()->escape($val) : $val;
5899
                        } else {
5900
                            $params[$param] = $escapeValues ? $this->getDatabase()->escape($val) : $val;
5901
                        }
5902
                    }
5903
                }
5904
                @fclose($tpl);
5905
            }
5906
        }
5907
        return $params;
5908
    }
5909
5910
    /**
5911
     * Parses docBlock from string and returns the result as an array
5912
     *
5913
     * @param string $string
5914
     * @param boolean $escapeValues
5915
     * @return array Associative array in the form property name => property value
5916
     */
5917
    public function parseDocBlockFromString($string, $escapeValues = false)
5918
    {
5919
        $params = array();
5920
        if (!empty($string)) {
5921
            $string = str_replace('\r\n', '\n', $string);
5922
            $exp = explode('\n', $string);
5923
            $docblock_start_found = false;
5924
            $name_found = false;
5925
            $description_found = false;
5926
            $docblock_end_found = false;
5927
            $arrayParams = array('author', 'documentation', 'reportissues', 'link');
5928
5929
            foreach ($exp as $line) {
5930
                $r = $this->parseDocBlockLine($line, $docblock_start_found, $name_found, $description_found, $docblock_end_found);
5931
                $docblock_start_found = $r['docblock_start_found'];
5932
                $name_found = $r['name_found'];
5933
                $description_found = $r['description_found'];
5934
                $docblock_end_found = $r['docblock_end_found'];
5935
                $param = $r['param'];
5936
                $val = $r['val'];
5937
                if (!$docblock_start_found) {
5938
                    continue;
5939
                }
5940
                if ($docblock_end_found) {
5941
                    break;
5942
                }
5943 View Code Duplication
                if (!empty($param)) {
5944
                    if (in_array($param, $arrayParams)) {
5945
                        if (!isset($params[$param])) {
5946
                            $params[$param] = array();
5947
                        }
5948
                        $params[$param][] = $escapeValues ? $this->getDatabase()->escape($val) : $val;
5949
                    } else {
5950
                        $params[$param] = $escapeValues ? $this->getDatabase()->escape($val) : $val;
5951
                    }
5952
                }
5953
            }
5954
        }
5955
        return $params;
5956
    }
5957
5958
    /**
5959
     * Parses docBlock of a component´s source-code and returns the result as an array
5960
     * (modified parseDocBlock() from modules/stores/setup.info.php by Bumkaka & Dmi3yy)
5961
     *
5962
     * @param string $line
5963
     * @param boolean $docblock_start_found
5964
     * @param boolean $name_found
5965
     * @param boolean $description_found
5966
     * @param boolean $docblock_end_found
5967
     * @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...
5968
     */
5969
    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...
5970
    {
5971
        $param = '';
5972
        $val = '';
5973
        $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...
5974
        if (!$docblock_start_found) {
5975
            // find docblock start
5976
            if (strpos($line, '/**') !== false) {
5977
                $docblock_start_found = true;
5978
            }
5979
        } elseif (!$name_found) {
5980
            // find name
5981
            if (preg_match("/^\s+\*\s+(.+)/", $line, $ma)) {
5982
                $param = 'name';
5983
                $val = trim($ma[1]);
5984
                $name_found = !empty($val);
5985
            }
5986
        } elseif (!$description_found) {
5987
            // find description
5988
            if (preg_match("/^\s+\*\s+(.+)/", $line, $ma)) {
5989
                $param = 'description';
5990
                $val = trim($ma[1]);
5991
                $description_found = !empty($val);
5992
            }
5993
        } else {
5994
            if (preg_match("/^\s+\*\s+\@([^\s]+)\s+(.+)/", $line, $ma)) {
5995
                $param = trim($ma[1]);
5996
                $val = trim($ma[2]);
5997
                if (!empty($param) && !empty($val)) {
5998
                    if ($param == 'internal') {
5999
                        $ma = null;
6000
                        if (preg_match("/\@([^\s]+)\s+(.+)/", $val, $ma)) {
6001
                            $param = trim($ma[1]);
6002
                            $val = trim($ma[2]);
6003
                        }
6004
                    }
6005
                }
6006
            } elseif (preg_match("/^\s*\*\/\s*$/", $line)) {
6007
                $docblock_end_found = true;
6008
            }
6009
        }
6010
        return array(
6011
            'docblock_start_found' => $docblock_start_found,
6012
            'name_found' => $name_found,
6013
            'description_found' => $description_found,
6014
            'docblock_end_found' => $docblock_end_found,
6015
            'param' => $param,
6016
            'val' => $val
6017
        );
6018
    }
6019
6020
    /**
6021
     * Renders docBlock-parameters into human readable list
6022
     *
6023
     * @param array $parsed
6024
     * @return string List in HTML-format
6025
     */
6026
    public function convertDocBlockIntoList($parsed)
6027
    {
6028
        global $_lang;
6029
6030
        // Replace special placeholders & make URLs + Emails clickable
6031
        $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...
6032
        $regexUrl = "/((http|https|ftp|ftps)\:\/\/[^\/]+(\/[^\s]+[^,.?!:;\s])?)/";
6033
        $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';
6034
        $emailSubject = isset($parsed['name']) ? '?subject=' . $parsed['name'] : '';
6035
        $emailSubject .= isset($parsed['version']) ? ' v' . $parsed['version'] : '';
6036
        foreach ($parsed as $key => $val) {
6037
            if (is_array($val)) {
6038
                foreach ($val as $key2 => $val2) {
6039
                    $val2 = $this->parseText($val2, $ph);
6040 View Code Duplication
                    if (preg_match($regexUrl, $val2, $url)) {
6041
                        $val2 = preg_replace($regexUrl, "<a href=\"{$url[0]}\" target=\"_blank\">{$url[0]}</a> ", $val2);
6042
                    }
6043 View Code Duplication
                    if (preg_match($regexEmail, $val2, $url)) {
6044
                        $val2 = preg_replace($regexEmail, '<a href="mailto:\\1' . $emailSubject . '">\\1</a>', $val2);
6045
                    }
6046
                    $parsed[$key][$key2] = $val2;
6047
                }
6048
            } else {
6049
                $val = $this->parseText($val, $ph);
6050 View Code Duplication
                if (preg_match($regexUrl, $val, $url)) {
6051
                    $val = preg_replace($regexUrl, "<a href=\"{$url[0]}\" target=\"_blank\">{$url[0]}</a> ", $val);
6052
                }
6053 View Code Duplication
                if (preg_match($regexEmail, $val, $url)) {
6054
                    $val = preg_replace($regexEmail, '<a href="mailto:\\1' . $emailSubject . '">\\1</a>', $val);
6055
                }
6056
                $parsed[$key] = $val;
6057
            }
6058
        }
6059
6060
        $arrayParams = array(
6061
            'documentation' => $_lang['documentation'],
6062
            'reportissues' => $_lang['report_issues'],
6063
            'link' => $_lang['further_info'],
6064
            'author' => $_lang['author_infos']
6065
        );
6066
6067
        $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...
6068
        $list = isset($parsed['logo']) ? '<img src="' . $this->config['base_url'] . ltrim($parsed['logo'], "/") . '" style="float:right;max-width:100px;height:auto;" />' . $nl : '';
6069
        $list .= '<p>' . $nl;
6070
        $list .= isset($parsed['name']) ? '<strong>' . $parsed['name'] . '</strong><br/>' . $nl : '';
6071
        $list .= isset($parsed['description']) ? $parsed['description'] . $nl : '';
6072
        $list .= '</p><br/>' . $nl;
6073
        $list .= isset($parsed['version']) ? '<p><strong>' . $_lang['version'] . ':</strong> ' . $parsed['version'] . '</p>' . $nl : '';
6074
        $list .= isset($parsed['license']) ? '<p><strong>' . $_lang['license'] . ':</strong> ' . $parsed['license'] . '</p>' . $nl : '';
6075
        $list .= isset($parsed['lastupdate']) ? '<p><strong>' . $_lang['last_update'] . ':</strong> ' . $parsed['lastupdate'] . '</p>' . $nl : '';
6076
        $list .= '<br/>' . $nl;
6077
        $first = true;
6078
        foreach ($arrayParams as $param => $label) {
6079
            if (isset($parsed[$param])) {
6080
                if ($first) {
6081
                    $list .= '<p><strong>' . $_lang['references'] . '</strong></p>' . $nl;
6082
                    $list .= '<ul class="docBlockList">' . $nl;
6083
                    $first = false;
6084
                }
6085
                $list .= '    <li><strong>' . $label . '</strong>' . $nl;
6086
                $list .= '        <ul>' . $nl;
6087
                foreach ($parsed[$param] as $val) {
6088
                    $list .= '            <li>' . $val . '</li>' . $nl;
6089
                }
6090
                $list .= '        </ul></li>' . $nl;
6091
            }
6092
        }
6093
        $list .= !$first ? '</ul>' . $nl : '';
6094
6095
        return $list;
6096
    }
6097
6098
    /**
6099
     * @param string $string
6100
     * @return string
6101
     */
6102
    public function removeSanitizeSeed($string = '')
6103
    {
6104
        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...
6105
6106
        if (!$string || strpos($string, $sanitize_seed) === false) {
6107
            return $string;
6108
        }
6109
6110
        return str_replace($sanitize_seed, '', $string);
6111
    }
6112
6113
    /**
6114
     * @param string $content
6115
     * @return string
6116
     */
6117
    public function cleanUpMODXTags($content = '')
6118
    {
6119
        if ($this->minParserPasses < 1) {
6120
            return $content;
6121
        }
6122
6123
        $enable_filter = $this->config['enable_filter'];
6124
        $this->config['enable_filter'] = 1;
6125
        $_ = 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...
6126
        foreach ($_ as $brackets) {
6127
            list($left, $right) = explode(' ', $brackets);
6128
            if (strpos($content, $left) !== false) {
6129
                if ($left === '[*') {
6130
                    $content = $this->mergeDocumentContent($content);
6131
                } elseif ($left === '[(') {
6132
                    $content = $this->mergeSettingsContent($content);
6133
                } elseif ($left === '{{') {
6134
                    $content = $this->mergeChunkContent($content);
6135
                } elseif ($left === '[[') {
6136
                    $content = $this->evalSnippets($content);
6137
                }
6138
            }
6139
        }
6140
        foreach ($_ as $brackets) {
6141
            list($left, $right) = explode(' ', $brackets);
6142
            if (strpos($content, $left) !== false) {
6143
                $matches = $this->getTagsFromContent($content, $left, $right);
6144
                $content = str_replace($matches[0], '', $content);
6145
            }
6146
        }
6147
        $this->config['enable_filter'] = $enable_filter;
6148
        return $content;
6149
    }
6150
6151
    /**
6152
     * @param string $str
6153
     * @param string $allowable_tags
6154
     * @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...
6155
     */
6156
    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...
6157
    {
6158
        $str = strip_tags($str, $allowable_tags);
6159
        modx_sanitize_gpc($str);
6160
        return $str;
6161
    }
6162
6163
    /**
6164
     * {@inheritdoc}
6165
     */
6166
    public function addSnippet($name, $phpCode, $namespace = '#', array $defaultParams = array())
6167
    {
6168
        $this->snippetCache[$namespace . $name] = $phpCode;
6169
        $this->snippetCache[$namespace . $name . 'Props'] = $defaultParams;
6170
    }
6171
6172
    /**
6173
     * {@inheritdoc}
6174
     */
6175
    public function addChunk($name, $text, $namespace = '#')
6176
    {
6177
        $this->chunkCache[$namespace . $name] = $text;
6178
    }
6179
6180
    /**
6181
     * {@inheritdoc}
6182
     */
6183
    public function findElements($type, $scanPath, array $ext)
6184
    {
6185
        $out = array();
6186
6187
        if (! is_dir($scanPath) || empty($ext)) {
6188
            return $out;
6189
        }
6190
        $iterator = new \RecursiveIteratorIterator(
6191
            new \RecursiveDirectoryIterator($scanPath, \RecursiveDirectoryIterator::SKIP_DOTS),
6192
            \RecursiveIteratorIterator::SELF_FIRST
6193
        );
6194
        foreach ($iterator as $item) {
6195
            /**
6196
             * @var \SplFileInfo $item
6197
             */
6198
            if ($item->isFile() && $item->isReadable() && \in_array($item->getExtension(), $ext)) {
6199
                $name = $item->getBasename('.' . $item->getExtension());
6200
                $path = ltrim(str_replace(
6201
                    array(rtrim($scanPath, '//'), '/'),
6202
                    array('', '\\'),
6203
                    $item->getPath() . '/'
6204
                ), '\\');
6205
6206
                if (!empty($path)) {
6207
                    $name = $path . $name;
6208
                }
6209
                switch ($type) {
6210
                    case 'chunk':
6211
                        $out[$name] = file_get_contents($item->getRealPath());
6212
                        break;
6213
                    case 'snippet':
6214
                        $out[$name] = "return require '" . $item->getRealPath() . "';";
6215
                        break;
6216
                    default:
6217
                        throw new \Exception;
6218
                }
6219
            }
6220
        }
6221
6222
        return $out;
6223
    }
6224
6225
    /**
6226
     * @param string $phpcode
6227
     * @param string $evalmode
6228
     * @param string $safe_functions
6229
     * @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...
6230
     */
6231
    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...
6232
    {
6233
        if ($evalmode == '') {
6234
            $evalmode = $this->config['allow_eval'];
6235
        }
6236
        if ($safe_functions == '') {
6237
            $safe_functions = $this->config['safe_functions_at_eval'];
6238
        }
6239
6240
        modx_sanitize_gpc($phpcode);
6241
6242
        switch ($evalmode) {
6243
            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...
6244
                $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...
6245
                break;
6246
            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...
6247
                $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...
6248
                break;
6249
            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...
6250
                $isSafe = true;
6251
                break; // Should debug only
6252
            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...
6253
            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...
6254
                return $phpcode;
6255
        }
6256
6257
        if (!$isSafe) {
6258
            $msg = $phpcode . "\n" . $this->currentSnippet . "\n" . print_r($_SERVER, true);
6259
            $title = sprintf('Unknown eval was executed (%s)', $this->getPhpCompat()->htmlspecialchars(substr(trim($phpcode), 0, 50)));
6260
            $this->messageQuit($title, '', true, '', '', 'Parser', $msg);
6261
            return;
6262
        }
6263
6264
        ob_start();
6265
        $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...
6266
        $echo = ob_get_clean();
6267
6268
        if (is_array($return)) {
6269
            return 'array()';
6270
        }
6271
6272
        $output = $echo . $return;
6273
        modx_sanitize_gpc($output);
6274
        return $this->getPhpCompat()->htmlspecialchars($output); // Maybe, all html tags are dangerous
6275
    }
6276
6277
    /**
6278
     * @param string $phpcode
6279
     * @param string $safe_functions
6280
     * @return bool
6281
     */
6282
    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...
6283
    { // 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...
6284
        if ($safe_functions == '') {
6285
            return false;
6286
        }
6287
6288
        $safe = explode(',', $safe_functions);
6289
6290
        $phpcode = rtrim($phpcode, ';') . ';';
6291
        $tokens = token_get_all('<?php ' . $phpcode);
6292
        foreach ($tokens as $i => $token) {
6293
            if (!is_array($token)) {
6294
                continue;
6295
            }
6296
            $tokens[$i]['token_name'] = token_name($token[0]);
6297
        }
6298
        foreach ($tokens as $token) {
6299
            if (!is_array($token)) {
6300
                continue;
6301
            }
6302
            switch ($token['token_name']) {
6303
                case 'T_STRING':
6304
                    if (!in_array($token[1], $safe)) {
6305
                        return false;
6306
                    }
6307
                    break;
6308
                case 'T_VARIABLE':
6309
                    if ($token[1] == '$GLOBALS') {
6310
                        return false;
6311
                    }
6312
                    break;
6313
                case 'T_EVAL':
6314
                    return false;
6315
            }
6316
        }
6317
        return true;
6318
    }
6319
6320
    /**
6321
     * @param string $str
6322
     * @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...
6323
     */
6324
    public function atBindFileContent($str = '')
6325
    {
6326
6327
        $search_path = array('assets/tvs/', 'assets/chunks/', 'assets/templates/', $this->config['rb_base_url'] . 'files/', '');
6328
6329
        if (stripos($str, '@FILE') !== 0) {
6330
            return $str;
6331
        }
6332 View Code Duplication
        if (strpos($str, "\n") !== false) {
6333
            $str = substr($str, 0, strpos("\n", $str));
6334
        }
6335
6336
        if ($this->getExtFromFilename($str) === '.php') {
6337
            return 'Could not retrieve PHP file.';
6338
        }
6339
6340
        $str = substr($str, 6);
6341
        $str = trim($str);
6342
        if (strpos($str, '\\') !== false) {
6343
            $str = str_replace('\\', '/', $str);
6344
        }
6345
        $str = ltrim($str, '/');
6346
6347
        $errorMsg = sprintf("Could not retrieve string '%s'.", $str);
6348
6349
        foreach ($search_path as $path) {
6350
            $file_path = MODX_BASE_PATH . $path . $str;
6351
            if (strpos($file_path, MODX_MANAGER_PATH) === 0) {
6352
                return $errorMsg;
6353
            } elseif (is_file($file_path)) {
6354
                break;
6355
            } else {
6356
                $file_path = false;
6357
            }
6358
        }
6359
6360
        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...
6361
            return $errorMsg;
6362
        }
6363
6364
        $content = (string)file_get_contents($file_path);
6365
        if ($content === false) {
6366
            return $errorMsg;
6367
        }
6368
6369
        return $content;
6370
    }
6371
6372
    /**
6373
     * @param $str
6374
     * @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...
6375
     */
6376
    public function getExtFromFilename($str)
6377
    {
6378
        $str = strtolower(trim($str));
6379
        $pos = strrpos($str, '.');
6380
        if ($pos === false) {
6381
            return false;
6382
        } else {
6383
            return substr($str, $pos);
6384
        }
6385
    }
6386
    /***************************************************************************************/
6387
    /* End of API functions                                       */
6388
    /***************************************************************************************/
6389
6390
    /**
6391
     * PHP error handler set by http://www.php.net/manual/en/function.set-error-handler.php
6392
     *
6393
     * Checks the PHP error and calls messageQuit() unless:
6394
     *  - error_reporting() returns 0, or
6395
     *  - the PHP error level is 0, or
6396
     *  - the PHP error level is 8 (E_NOTICE) and stopOnNotice is false
6397
     *
6398
     * @param int $nr The PHP error level as per http://www.php.net/manual/en/errorfunc.constants.php
6399
     * @param string $text Error message
6400
     * @param string $file File where the error was detected
6401
     * @param string $line Line number within $file
6402
     * @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...
6403
     */
6404
    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...
6405
    {
6406
        if (error_reporting() == 0 || $nr == 0) {
6407
            return true;
6408
        }
6409
        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...
6410
            switch ($nr) {
6411
                case E_NOTICE:
6412
                    if ($this->error_reporting <= 2) {
6413
                        return true;
6414
                    }
6415
                    $isError = false;
6416
                    $msg = 'PHP Minor Problem (this message show logged in only)';
6417
                    break;
6418
                case E_STRICT:
6419 View Code Duplication
                case E_DEPRECATED:
6420
                    if ($this->error_reporting <= 1) {
6421
                        return true;
6422
                    }
6423
                    $isError = true;
6424
                    $msg = 'PHP Strict Standards Problem';
6425
                    break;
6426 View Code Duplication
                default:
6427
                    if ($this->error_reporting === 0) {
6428
                        return true;
6429
                    }
6430
                    $isError = true;
6431
                    $msg = 'PHP Parse Error';
6432
            }
6433
        }
6434
        if (is_readable($file)) {
6435
            $source = file($file);
6436
            $source = $this->getPhpCompat()->htmlspecialchars($source[$line - 1]);
6437
        } else {
6438
            $source = "";
6439
        } //Error $nr in $file at $line: <div><code>$source</code></div>
6440
6441
        $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...
6442
    }
6443
6444
    /**
6445
     * @param string $msg
6446
     * @param string $query
6447
     * @param bool $is_error
6448
     * @param string $nr
6449
     * @param string $file
6450
     * @param string $source
6451
     * @param string $text
6452
     * @param string $line
6453
     * @param string $output
6454
     * @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...
6455
     */
6456
    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...
6457
    {
6458
6459
        if (0 < $this->messageQuitCount) {
6460
            return;
6461
        }
6462
        $this->messageQuitCount++;
6463
        $MakeTable = new Support\MakeTable();
6464
        $MakeTable->setTableClass('grid');
6465
        $MakeTable->setRowRegularClass('gridItem');
6466
        $MakeTable->setRowAlternateClass('gridAltItem');
6467
        $MakeTable->setColumnWidths(array('100px'));
6468
6469
        $table = array();
6470
6471
        $version = isset ($GLOBALS['modx_version']) ? $GLOBALS['modx_version'] : '';
6472
        $release_date = isset ($GLOBALS['release_date']) ? $GLOBALS['release_date'] : '';
6473
        $request_uri = "http://" . $_SERVER['HTTP_HOST'] . ($_SERVER["SERVER_PORT"] == 80 ? "" : (":" . $_SERVER["SERVER_PORT"])) . $_SERVER['REQUEST_URI'];
6474
        $request_uri = $this->getPhpCompat()->htmlspecialchars($request_uri, ENT_QUOTES, $this->config['modx_charset']);
6475
        $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...
6476
        $referer = $this->getPhpCompat()->htmlspecialchars($_SERVER['HTTP_REFERER'], ENT_QUOTES, $this->config['modx_charset']);
6477
        if ($is_error) {
6478
            $str = '<h2 style="color:red">&laquo; Evo Parse Error &raquo;</h2>';
6479
            if ($msg != 'PHP Parse Error') {
6480
                $str .= '<h3 style="color:red">' . $msg . '</h3>';
6481
            }
6482
        } else {
6483
            $str = '<h2 style="color:#003399">&laquo; Evo Debug/ stop message &raquo;</h2>';
6484
            $str .= '<h3 style="color:#003399">' . $msg . '</h3>';
6485
        }
6486
6487
        if (!empty ($query)) {
6488
            $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>';
6489
        }
6490
6491
        $errortype = array(
6492
            E_ERROR => "ERROR",
6493
            E_WARNING => "WARNING",
6494
            E_PARSE => "PARSING ERROR",
6495
            E_NOTICE => "NOTICE",
6496
            E_CORE_ERROR => "CORE ERROR",
6497
            E_CORE_WARNING => "CORE WARNING",
6498
            E_COMPILE_ERROR => "COMPILE ERROR",
6499
            E_COMPILE_WARNING => "COMPILE WARNING",
6500
            E_USER_ERROR => "USER ERROR",
6501
            E_USER_WARNING => "USER WARNING",
6502
            E_USER_NOTICE => "USER NOTICE",
6503
            E_STRICT => "STRICT NOTICE",
6504
            E_RECOVERABLE_ERROR => "RECOVERABLE ERROR",
6505
            E_DEPRECATED => "DEPRECATED",
6506
            E_USER_DEPRECATED => "USER DEPRECATED"
6507
        );
6508
6509
        if (!empty($nr) || !empty($file)) {
6510
            if ($text != '') {
6511
                $str .= '<div style="font-weight:bold;border:1px solid #ccc;padding:8px;color:#333;background-color:#ffffcd;margin-bottom:15px;">Error : ' . $text . '</div>';
6512
            }
6513
            if ($output != '') {
6514
                $str .= '<div style="font-weight:bold;border:1px solid #ccc;padding:8px;color:#333;background-color:#ffffcd;margin-bottom:15px;">' . $output . '</div>';
6515
            }
6516
            if ($nr !== '') {
6517
                $table[] = array('ErrorType[num]', $errortype [$nr] . "[" . $nr . "]");
6518
            }
6519
            if ($file) {
6520
                $table[] = array('File', $file);
6521
            }
6522
            if ($line) {
6523
                $table[] = array('Line', $line);
6524
            }
6525
6526
        }
6527
6528
        if ($source != '') {
6529
            $table[] = array("Source", $source);
6530
        }
6531
6532
        if (!empty($this->currentSnippet)) {
6533
            $table[] = array('Current Snippet', $this->currentSnippet);
6534
        }
6535
6536
        if (!empty($this->event->activePlugin)) {
6537
            $table[] = array('Current Plugin', $this->event->activePlugin . '(' . $this->event->name . ')');
6538
        }
6539
6540
        $str .= $MakeTable->create($table, array('Error information', ''));
6541
        $str .= "<br />";
6542
6543
        $table = array();
6544
        $table[] = array('REQUEST_URI', $request_uri);
6545
6546
        if ($this->getManagerApi()->action) {
6547
            include_once(MODX_MANAGER_PATH . 'includes/actionlist.inc.php');
6548
            global $action_list;
6549
            $actionName = (isset($action_list[$this->getManagerApi()->action])) ? " - {$action_list[$this->getManagerApi()->action]}" : '';
6550
6551
            $table[] = array('Manager action', $this->getManagerApi()->action . $actionName);
6552
        }
6553
6554
        if (preg_match('@^[0-9]+@', $this->documentIdentifier)) {
6555
            $resource = $this->getDocumentObject('id', $this->documentIdentifier);
6556
            $url = $this->makeUrl($this->documentIdentifier, '', '', 'full');
6557
            $table[] = array('Resource', '[' . $this->documentIdentifier . '] <a href="' . $url . '" target="_blank">' . $resource['pagetitle'] . '</a>');
6558
        }
6559
        $table[] = array('Referer', $referer);
6560
        $table[] = array('User Agent', $ua);
6561
        $table[] = array('IP', $_SERVER['REMOTE_ADDR']);
6562
        $table[] = array('Current time', date("Y-m-d H:i:s", $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time']));
6563
        $str .= $MakeTable->create($table, array('Basic info', ''));
6564
        $str .= "<br />";
6565
6566
        $table = array();
6567
        $table[] = array('MySQL', '[^qt^] ([^q^] Requests)');
6568
        $table[] = array('PHP', '[^p^]');
6569
        $table[] = array('Total', '[^t^]');
6570
        $table[] = array('Memory', '[^m^]');
6571
        $str .= $MakeTable->create($table, array('Benchmarks', ''));
6572
        $str .= "<br />";
6573
6574
        $totalTime = ($this->getMicroTime() - $this->tstart);
6575
6576
        $mem = memory_get_peak_usage(true);
6577
        $total_mem = $mem - $this->mstart;
6578
        $total_mem = ($total_mem / 1024 / 1024) . ' mb';
6579
6580
        $queryTime = $this->queryTime;
6581
        $phpTime = $totalTime - $queryTime;
6582
        $queries = isset ($this->executedQueries) ? $this->executedQueries : 0;
6583
        $queryTime = sprintf("%2.4f s", $queryTime);
6584
        $totalTime = sprintf("%2.4f s", $totalTime);
6585
        $phpTime = sprintf("%2.4f s", $phpTime);
6586
6587
        $str = str_replace('[^q^]', $queries, $str);
6588
        $str = str_replace('[^qt^]', $queryTime, $str);
6589
        $str = str_replace('[^p^]', $phpTime, $str);
6590
        $str = str_replace('[^t^]', $totalTime, $str);
6591
        $str = str_replace('[^m^]', $total_mem, $str);
6592
6593
        if (isset($php_errormsg) && !empty($php_errormsg)) {
6594
            $str = "<b>{$php_errormsg}</b><br />\n{$str}";
6595
        }
6596
        $str .= $this->get_backtrace(debug_backtrace());
6597
        // Log error
6598
        if (!empty($this->currentSnippet)) {
6599
            $source = 'Snippet - ' . $this->currentSnippet;
6600
        } elseif (!empty($this->event->activePlugin)) {
6601
            $source = 'Plugin - ' . $this->event->activePlugin;
6602
        } elseif ($source !== '') {
6603
            $source = 'Parser - ' . $source;
6604
        } elseif ($query !== '') {
6605
            $source = 'SQL Query';
6606
        } else {
6607
            $source = 'Parser';
6608
        }
6609
        if ($msg) {
6610
            $source .= ' / ' . $msg;
6611
        }
6612
        if (isset($actionName) && !empty($actionName)) {
6613
            $source .= $actionName;
6614
        }
6615 View Code Duplication
        switch ($nr) {
6616
            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...
6617
            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...
6618
            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...
6619
            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...
6620
            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...
6621
                $error_level = 2;
6622
                break;
6623
            default:
6624
                $error_level = 3;
6625
        }
6626
        $this->logEvent(0, $error_level, $str, $source);
6627
6628
        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...
6629
            return true;
6630
        }
6631
        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...
6632
            return true;
6633
        }
6634
6635
        // Set 500 response header
6636
        if ($error_level !== 2) {
6637
            header('HTTP/1.1 500 Internal Server Error');
6638
        }
6639
6640
        // Display error
6641
        if (isset($_SESSION['mgrValidated'])) {
6642
            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>
6643
                 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
6644
                 <link rel="stylesheet" type="text/css" href="' . $this->config['site_manager_url'] . 'media/style/' . $this->config['manager_theme'] . '/style.css" />
6645
                 <style type="text/css">body { padding:10px; } td {font:inherit;}</style>
6646
                 </head><body>
6647
                 ' . $str . '</body></html>';
6648
6649
        } else {
6650
            echo 'Error';
6651
        }
6652
        ob_end_flush();
6653
        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...
6654
    }
6655
6656
    /**
6657
     * @param $backtrace
6658
     * @return string
6659
     */
6660
    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...
6661
    {
6662
        $MakeTable = new Support\MakeTable();
6663
        $MakeTable->setTableClass('grid');
6664
        $MakeTable->setRowRegularClass('gridItem');
6665
        $MakeTable->setRowAlternateClass('gridAltItem');
6666
        $table = array();
6667
        $backtrace = array_reverse($backtrace);
6668
        foreach ($backtrace as $key => $val) {
6669
            $key++;
6670
            if (substr($val['function'], 0, 11) === 'messageQuit') {
6671
                break;
6672
            } elseif (substr($val['function'], 0, 8) === 'phpError') {
6673
                break;
6674
            }
6675
            $path = str_replace('\\', '/', $val['file']);
6676
            if (strpos($path, MODX_BASE_PATH) === 0) {
6677
                $path = substr($path, strlen(MODX_BASE_PATH));
6678
            }
6679
            switch ($val['type']) {
6680
                case '->':
6681
                case '::':
6682
                    $functionName = $val['function'] = $val['class'] . $val['type'] . $val['function'];
6683
                    break;
6684
                default:
6685
                    $functionName = $val['function'];
6686
            }
6687
            $tmp = 1;
6688
            $_ = (!empty($val['args'])) ? count($val['args']) : 0;
6689
            $args = array_pad(array(), $_, '$var');
6690
            $args = implode(", ", $args);
6691
            $modx = &$this;
6692
            $args = preg_replace_callback('/\$var/', function () use ($modx, &$tmp, $val) {
6693
                $arg = $val['args'][$tmp - 1];
6694
                switch (true) {
6695
                    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...
6696
                        $out = 'NULL';
6697
                        break;
6698
                    }
6699
                    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...
6700
                        $out = $arg;
6701
                        break;
6702
                    }
6703
                    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...
6704
                        $out = strlen($arg) > 20 ? 'string $var' . $tmp : ("'" . $this->getPhpCompat()->htmlspecialchars(str_replace("'", "\\'", $arg)) . "'");
6705
                        break;
6706
                    }
6707
                    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...
6708
                        $out = $arg ? 'TRUE' : 'FALSE';
6709
                        break;
6710
                    }
6711
                    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...
6712
                        $out = 'array $var' . $tmp;
6713
                        break;
6714
                    }
6715
                    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...
6716
                        $out = get_class($arg) . ' $var' . $tmp;
6717
                        break;
6718
                    }
6719
                    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...
6720
                        $out = '$var' . $tmp;
6721
                    }
6722
                }
6723
                $tmp++;
6724
                return $out;
6725
            }, $args);
6726
            $line = array(
6727
                "<strong>" . $functionName . "</strong>(" . $args . ")",
6728
                $path . " on line " . $val['line']
6729
            );
6730
            $table[] = array(implode("<br />", $line));
6731
        }
6732
        return $MakeTable->create($table, array('Backtrace'));
6733
    }
6734
6735
    /**
6736
     * @return string
6737
     */
6738
    public function getRegisteredClientScripts()
6739
    {
6740
        return implode("\n", $this->jscripts);
6741
    }
6742
6743
    /**
6744
     * @return string
6745
     */
6746
    public function getRegisteredClientStartupScripts()
6747
    {
6748
        return implode("\n", $this->sjscripts);
6749
    }
6750
6751
    /**
6752
     * Format alias to be URL-safe. Strip invalid characters.
6753
     *
6754
     * @param string $alias Alias to be formatted
6755
     * @return string Safe alias
6756
     */
6757
    public function stripAlias($alias)
6758
    {
6759
        // let add-ons overwrite the default behavior
6760
        $results = $this->invokeEvent('OnStripAlias', array('alias' => $alias));
6761
        if (!empty($results)) {
6762
            // if multiple plugins are registered, only the last one is used
6763
            return end($results);
6764
        } else {
6765
            // default behavior: strip invalid characters and replace spaces with dashes.
6766
            $alias = strip_tags($alias); // strip HTML
6767
            $alias = preg_replace('/[^\.A-Za-z0-9 _-]/', '', $alias); // strip non-alphanumeric characters
6768
            $alias = preg_replace('/\s+/', '-', $alias); // convert white-space to dash
6769
            $alias = preg_replace('/-+/', '-', $alias);  // convert multiple dashes to one
6770
            $alias = trim($alias, '-'); // trim excess
6771
            return $alias;
6772
        }
6773
    }
6774
6775
    /**
6776
     * @param $size
6777
     * @return string
6778
     */
6779
    public function nicesize($size)
6780
    {
6781
        $sizes = array('Tb' => 1099511627776, 'Gb' => 1073741824, 'Mb' => 1048576, 'Kb' => 1024, 'b' => 1);
6782
        $precisions = count($sizes) - 1;
6783
        foreach ($sizes as $unit => $bytes) {
6784
            if ($size >= $bytes) {
6785
                return number_format($size / $bytes, $precisions) . ' ' . $unit;
6786
            }
6787
            $precisions--;
6788
        }
6789
        return '0 b';
6790
    }
6791
6792
    /**
6793
     * @param $parentid
6794
     * @param $alias
6795
     * @return bool
6796
     */
6797
    public function getHiddenIdFromAlias($parentid, $alias)
6798
    {
6799
        $table = $this->getDatabase()->getFullTableName('site_content');
6800
        $query = $this->getDatabase()->query("SELECT sc.id, children.id AS child_id, children.alias, COUNT(children2.id) AS children_count
6801
            FROM {$table} sc
6802
            JOIN {$table} children ON children.parent = sc.id
6803
            LEFT JOIN {$table} children2 ON children2.parent = children.id
6804
            WHERE sc.parent = {$parentid} AND sc.alias_visible = '0' GROUP BY children.id;");
6805
6806
        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 6800 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...
6807
            if ($child['alias'] == $alias || $child['child_id'] == $alias) {
6808
                return $child['child_id'];
6809
            }
6810
6811
            if ($child['children_count'] > 0) {
6812
                $id = $this->getHiddenIdFromAlias($child['id'], $alias);
6813
                if ($id) {
6814
                    return $id;
6815
                }
6816
            }
6817
        }
6818
6819
        return false;
6820
    }
6821
6822
    /**
6823
     * @param $alias
6824
     * @return bool|int
6825
     */
6826
    public function getIdFromAlias($alias)
6827
    {
6828
        if (isset($this->documentListing[$alias])) {
6829
            return $this->documentListing[$alias];
6830
        }
6831
6832
        $tbl_site_content = $this->getDatabase()->getFullTableName('site_content');
6833
        if ($this->config['use_alias_path'] == 1) {
6834
            if ($alias == '.') {
6835
                return 0;
6836
            }
6837
6838
            if (strpos($alias, '/') !== false) {
6839
                $_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...
6840
            } else {
6841
                $_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...
6842
            }
6843
            $id = 0;
6844
6845
            foreach ($_a as $alias) {
6846
                if ($id === false) {
6847
                    break;
6848
                }
6849
                $alias = $this->getDatabase()->escape($alias);
6850
                $rs = $this->getDatabase()->select('id', $tbl_site_content, "deleted=0 and parent='{$id}' and alias='{$alias}'");
6851
                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 6850 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...
6852
                    $rs = $this->getDatabase()->select('id', $tbl_site_content, "deleted=0 and parent='{$id}' and id='{$alias}'");
6853
                }
6854
                $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...
6855
                $id = !$next ? $this->getHiddenIdFromAlias($id, $alias) : $next;
6856
            }
6857
        } else {
6858
            $rs = $this->getDatabase()->select('id', $tbl_site_content, "deleted=0 and alias='{$alias}'", 'parent, menuindex');
6859
            $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 6858 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...
6860
            if (!$id) {
6861
                $id = false;
6862
            }
6863
        }
6864
        return $id;
6865
    }
6866
6867
    /**
6868
     * @param string $str
6869
     * @return bool|mixed|string
6870
     */
6871
    public function atBindInclude($str = '')
6872
    {
6873
        if (strpos($str, '@INCLUDE') !== 0) {
6874
            return $str;
6875
        }
6876 View Code Duplication
        if (strpos($str, "\n") !== false) {
6877
            $str = substr($str, 0, strpos("\n", $str));
6878
        }
6879
6880
        $str = substr($str, 9);
6881
        $str = trim($str);
6882
        $str = str_replace('\\', '/', $str);
6883
        $str = ltrim($str, '/');
6884
6885
        $tpl_dir = 'assets/templates/';
6886
6887
        if (strpos($str, MODX_MANAGER_PATH) === 0) {
6888
            return false;
6889
        } elseif (is_file(MODX_BASE_PATH . $str)) {
6890
            $file_path = MODX_BASE_PATH . $str;
6891
        } elseif (is_file(MODX_BASE_PATH . "{$tpl_dir}{$str}")) {
6892
            $file_path = MODX_BASE_PATH . $tpl_dir . $str;
6893
        } else {
6894
            return false;
6895
        }
6896
6897
        if (!$file_path || !is_file($file_path)) {
6898
            return false;
6899
        }
6900
6901
        ob_start();
6902
        $modx = &$this;
6903
        $result = include($file_path);
6904
        if ($result === 1) {
6905
            $result = '';
6906
        }
6907
        $content = ob_get_clean();
6908
        if (!$content && $result) {
6909
            $content = $result;
6910
        }
6911
        return $content;
6912
    }
6913
6914
    // php compat
6915
6916
    /**
6917
     * @param $str
6918
     * @param int $flags
6919
     * @param string $encode
6920
     * @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...
6921
     */
6922
    public function htmlspecialchars($str, $flags = ENT_COMPAT, $encode = '')
6923
    {
6924
        return $this->getPhpCompat()->htmlspecialchars($str, $flags, $encode);
6925
    }
6926
6927
    /**
6928
     * @param $string
6929
     * @param bool $returnData
6930
     * @return bool|mixed
6931
     */
6932
    public function isJson($string, $returnData = false)
6933
    {
6934
        $data = json_decode($string, true);
6935
        return (json_last_error() == JSON_ERROR_NONE) ? ($returnData ? $data : true) : false;
6936
    }
6937
6938
    /**
6939
     * @param $key
6940
     * @return array
6941
     */
6942
    public function splitKeyAndFilter($key)
6943
    {
6944
        if ($this->config['enable_filter'] == 1 && strpos($key, ':') !== false && stripos($key, '@FILE') !== 0) {
6945
            list($key, $modifiers) = explode(':', $key, 2);
6946
        } else {
6947
            $modifiers = false;
6948
        }
6949
6950
        $key = trim($key);
6951
        if ($modifiers !== false) {
6952
            $modifiers = trim($modifiers);
6953
        }
6954
6955
        return array($key, $modifiers);
6956
    }
6957
6958
    /**
6959
     * @param string $value
6960
     * @param bool $modifiers
6961
     * @param string $key
6962
     * @return string
6963
     */
6964
    public function applyFilter($value = '', $modifiers = false, $key = '')
6965
    {
6966
        if ($modifiers === false || $modifiers == 'raw') {
6967
            return $value;
6968
        }
6969
        if ($modifiers !== false) {
6970
            $modifiers = trim($modifiers);
6971
        }
6972
6973
        return $this->getModifiers()->phxFilter($key, $value, $modifiers);
6974
    }
6975
6976
    // End of class.
6977
6978
6979
    /**
6980
     * Get Clean Query String
6981
     *
6982
     * Fixes the issue where passing an array into the q get variable causes errors
6983
     *
6984
     */
6985
    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...
6986
    {
6987
        $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...
6988
6989
        //Return null if the query doesn't exist
6990
        if (empty($q)) {
6991
            return null;
6992
        }
6993
6994
        //If we have a string, return it
6995
        if (is_string($q)) {
6996
            return $q;
6997
        }
6998
6999
        //If we have an array, return the first element
7000
        if (is_array($q)) {
7001
            return $q[0];
7002
        }
7003
    }
7004
7005
    /**
7006
     * @param string $title
7007
     * @param string $msg
7008
     * @param int $type
7009
     */
7010
    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...
7011
    {
7012
        if ($title === '') {
7013
            $title = 'no title';
7014
        }
7015
        if (is_array($msg)) {
7016
            $msg = '<pre>' . print_r($msg, true) . '</pre>';
7017
        } elseif ($msg === '') {
7018
            $msg = $_SERVER['REQUEST_URI'];
7019
        }
7020
        $this->logEvent(0, $type, $msg, $title);
7021
    }
7022
7023
}
7024