Completed
Push — develop ( 6a553b...48041c )
by Agel_Nash
06:37
created

Core::__get()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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