Completed
Push — develop ( b9c105...b4ee31 )
by Serg
11:53 queued 05:32
created

Core::evalSnippet()   C

Complexity

Conditions 11
Paths 72

Size

Total Lines 43
Code Lines 26

Duplication

Lines 10
Ratio 23.26 %

Importance

Changes 0
Metric Value
cc 11
eloc 26
nc 72
nop 2
dl 10
loc 43
rs 5.2653
c 0
b 0
f 0

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php namespace EvolutionCMS;
2
3
class Core implements Interfaces\CoreInterface
0 ignored issues
show
Coding Style introduced by
The property $table_prefix is not named in camelCase.

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

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

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

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

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

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

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

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

Loading history...
4
{
5
    /**
6
     * This is New evolution
7
     * @var string
8
     */
9
    public $apiVersion = '1.0.0';
10
11
    /**
12
     * db object
13
     * @var \DBAPI
14
     * @see /manager/includes/extenders/ex_dbapi.inc.php
15
     * @example $this->loadExtension('DBAPI')
16
     */
17
    public $db;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $db. Configured minimum length is 3.

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

Loading history...
18
19
    /**
20
     * @var \MODxMailer
21
     * @see /manager/includes/extenders/ex_modxmailer.inc.php
22
     * @example $this->loadExtension('MODxMailer');
23
     */
24
    public $mail;
25
26
    /**
27
     * @var \PHPCOMPAT
28
     * @see /manager/includes/extenders/ex_phpcompat.inc.php
29
     * @example $this->loadExtension('PHPCOMPAT');
30
     */
31
    public $phpcompat;
32
33
    /**
34
     * @var \MODIFIERS
35
     * @see /manager/includes/extenders/ex_modifiers.inc.php
36
     * @example $this->loadExtension('MODIFIERS');
37
     */
38
    public $filter;
39
40
    /**
41
     * @var \EXPORT_SITE
42
     * @see /manager/includes/extenders/ex_export_site.inc.php
43
     * @example $this->loadExtension('EXPORT_SITE');
44
     */
45
    public $export;
46
47
    /**
48
     * @var \MakeTable
49
     * @see /manager/includes/extenders/ex_maketable.inc.php
50
     * @example $this->loadExtension('makeTable');
51
     */
52
    public $table;
53
54
    /**
55
     * @var \ManagerAPI
56
     * @see /manager/includes/extenders/ex_managerapi.inc.php
57
     * @example $this->loadExtension('ManagerAPI');
58
     */
59
    public $manager;
60
61
    /**
62
     * @var \PasswordHash
63
     * @see manager/includes/extenders/ex_phpass.inc.php
64
     * @example $this->loadExtension('phpass');
65
     */
66
    public $phpass;
67
68
    /**
69
     * event object
70
     * @var Event
71
     */
72
73
    public $event;
74
    /**
75
     * event object
76
     * @var Event
77
     * @deprecated
78
     */
79
    public $Event;
80
81
    /**
82
     * @var array
83
     */
84
    public $pluginEvent = array();
85
86
    /**
87
     * @var array
88
     */
89
    public $config = array();
90
    /**
91
     * @var array
92
     */
93
    public $dbConfig = array();
94
    public $configGlobal = null; // contains backup of settings overwritten by user-settings
95
    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...
96
    public $result;
97
    public $sql;
98
    public $table_prefix;
99
    public $debug = false;
100
    public $documentIdentifier;
101
    public $documentMethod;
102
    public $documentGenerated;
103
    public $documentContent;
104
    public $documentOutput;
105
    public $tstart;
106
    public $mstart;
107
    public $minParserPasses;
108
    public $maxParserPasses;
109
    public $documentObject;
110
    public $templateObject;
111
    public $snippetObjects;
112
    public $stopOnNotice = false;
113
    public $executedQueries;
114
    public $queryTime;
115
    public $currentSnippet;
116
    public $documentName;
117
    public $aliases;
118
    public $visitor;
119
    public $entrypage;
120
    public $documentListing;
121
    /**
122
     * feed the parser the execution start time
123
     * @var bool
124
     */
125
    public $dumpSnippets = false;
126
    public $snippetsCode;
127
    public $snippetsTime = array();
128
    public $chunkCache;
129
    public $snippetCache;
130
    public $contentTypes;
131
    public $dumpSQL = false;
132
    public $queryCode;
133
    public $virtualDir;
134
    public $placeholders;
135
    public $sjscripts = array();
136
    public $jscripts = array();
137
    public $loadedjscripts = array();
138
    public $documentMap;
139
    public $forwards = 3;
140
    public $error_reporting = 1;
141
    public $dumpPlugins = false;
142
    public $pluginsCode;
143
    public $pluginsTime = array();
144
    public $pluginCache = array();
145
    public $aliasListing;
146
    public $lockedElements = null;
147
    public $tmpCache = array();
148
    private $version = array();
149
    public $extensions = array();
150
    public $cacheKey = null;
151
    public $recentUpdate = 0;
152
    public $useConditional = false;
153
    protected $systemCacheKey = null;
154
    public $snipLapCount = 0;
155
    public $messageQuitCount;
156
    public $time;
157
    public $sid;
158
    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...
159
    public $decoded_request_uri;
160
    /**
161
     * @var \OldFunctions
162
     */
163
    public $old;
164
165
    /**
166
     * Hold the class instance.
167
     * @var self
168
     */
169
    private static $instance = null;
170
171
    /**
172
     * Document constructor
173
     *
174
     * @return self
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
175
     */
176
    public function __construct()
0 ignored issues
show
Coding Style introduced by
__construct 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...
177
    {
178
        if ($this->isLoggedIn()) {
179
            ini_set('display_errors', 1);
180
        }
181
        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...
182
        if (substr(PHP_OS, 0, 3) === 'WIN' && $database_server === 'localhost') {
183
            $database_server = '127.0.0.1';
184
        }
185
        $this->loadExtension('DBAPI') or die('Could not load DBAPI class.'); // load DBAPI class
0 ignored issues
show
Coding Style Compatibility introduced by
The method __construct() 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...
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...
186
        $this->dbConfig = &$this->db->config; // alias for backward compatibility
187
        // events
188
        $this->event = new Event();
189
        $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...
190
        // set track_errors ini variable
191
        @ ini_set("track_errors", "1"); // enable error tracking in $php_errormsg
192
        $this->time = $_SERVER['REQUEST_TIME']; // for having global timestamp
193
194
        $this->q = self::_getCleanQueryString();
195
    }
196
197
    final public function __clone()
198
    {
199
    }
200
    /**
201
     * @return self
202
     */
203
    public static function getInstance()
204
    {
205
        if (self::$instance === null) {
206
            self::$instance = new static();
207
        }
208
        return self::$instance;
209
    }
210
211
    /**
212
     * @param $method_name
213
     * @param $arguments
214
     * @return mixed
215
     */
216
    function __call($method_name, $arguments)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
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...
217
    {
218
        include_once(MODX_MANAGER_PATH . 'includes/extenders/deprecated.functions.inc.php');
219
        if (method_exists($this->old, $method_name)) {
220
            $error_type = 1;
221
        } else {
222
            $error_type = 3;
223
        }
224
225
        if (!isset($this->config['error_reporting']) || 1 < $this->config['error_reporting']) {
226
            if ($error_type == 1) {
227
                $title = 'Call deprecated method';
228
                $msg = $this->htmlspecialchars("\$modx->{$method_name}() is deprecated function");
229
            } else {
230
                $title = 'Call undefined method';
231
                $msg = $this->htmlspecialchars("\$modx->{$method_name}() is undefined function");
232
            }
233
            $info = debug_backtrace();
234
            $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...
235
            if (!empty($this->currentSnippet)) {
236
                $m[] = 'Snippet - ' . $this->currentSnippet;
237
            } elseif (!empty($this->event->activePlugin)) {
238
                $m[] = 'Plugin - ' . $this->event->activePlugin;
239
            }
240
            $m[] = $this->decoded_request_uri;
241
            $m[] = str_replace('\\', '/', $info[0]['file']) . '(line:' . $info[0]['line'] . ')';
242
            $msg = implode('<br />', $m);
243
            $this->logEvent(0, $error_type, $msg, $title);
244
        }
245
        if (method_exists($this->old, $method_name)) {
246
            return call_user_func_array(array($this->old, $method_name), $arguments);
247
        }
248
    }
249
250
    /**
251
     * @param string $connector
252
     * @return bool
253
     */
254
    public function checkSQLconnect($connector = 'db')
255
    {
256
        $flag = false;
257
        if (is_scalar($connector) && !empty($connector) && isset($this->{$connector}) && $this->{$connector} instanceof DBAPI) {
0 ignored issues
show
Bug introduced by
The class EvolutionCMS\DBAPI does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
258
            $flag = (bool)$this->{$connector}->conn;
259
        }
260
        return $flag;
261
    }
262
263
    /**
264
     * Loads an extension from the extenders folder.
265
     * You can load any extension creating a boot file:
266
     * MODX_MANAGER_PATH."includes/extenders/ex_{$extname}.inc.php"
267
     * $extname - extension name in lowercase
268
     *
269
     * @param $extname
270
     * @param bool $reload
271
     * @return bool
272
     */
273
    public function loadExtension($extname, $reload = true)
274
    {
275
        $out = false;
276
        $flag = ($reload || !in_array($extname, $this->extensions));
277
        if ($this->checkSQLconnect('db') && $flag) {
278
            $evtOut = $this->invokeEvent('OnBeforeLoadExtension', array('name' => $extname, 'reload' => $reload));
279
            if (is_array($evtOut) && count($evtOut) > 0) {
280
                $out = array_pop($evtOut);
281
            }
282
        }
283
        if (!$out && $flag) {
284
            $extname = trim(str_replace(array('..', '/', '\\'), '', strtolower($extname)));
285
            $filename = MODX_MANAGER_PATH . "includes/extenders/ex_{$extname}.inc.php";
286
            $out = is_file($filename) ? include $filename : false;
287
        }
288
        if ($out && !in_array($extname, $this->extensions)) {
289
            $this->extensions[] = $extname;
290
        }
291
        return $out;
292
    }
293
294
    /**
295
     * Returns the current micro time
296
     *
297
     * @return float
298
     */
299
    public function getMicroTime()
300
    {
301
        list ($usec, $sec) = explode(' ', microtime());
302
        return ((float)$usec + (float)$sec);
303
    }
304
305
    /**
306
     * Redirect
307
     *
308
     * @param string $url
309
     * @param int $count_attempts
310
     * @param string $type $type
311
     * @param string $responseCode
312
     * @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...
313
     * @global string $base_url
314
     * @global string $site_url
315
     */
316
    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...
317
    {
318
        $header = '';
319
        if (empty ($url)) {
320
            return false;
321
        }
322
        if ($count_attempts == 1) {
323
            // append the redirect count string to the url
324
            $currentNumberOfRedirects = isset ($_REQUEST['err']) ? $_REQUEST['err'] : 0;
325
            if ($currentNumberOfRedirects > 3) {
326
                $this->messageQuit('Redirection attempt failed - please ensure the document you\'re trying to redirect to exists. <p>Redirection URL: <i>' . $url . '</i></p>');
327
            } else {
328
                $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...
329
                if (strpos($url, "?") > 0) {
330
                    $url .= "&err=$currentNumberOfRedirects";
331
                } else {
332
                    $url .= "?err=$currentNumberOfRedirects";
333
                }
334
            }
335
        }
336
        if ($type == 'REDIRECT_REFRESH') {
337
            $header = 'Refresh: 0;URL=' . $url;
338
        } elseif ($type == 'REDIRECT_META') {
339
            $header = '<META HTTP-EQUIV="Refresh" CONTENT="0; URL=' . $url . '" />';
340
            echo $header;
341
            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...
342
        } elseif ($type == 'REDIRECT_HEADER' || empty ($type)) {
343
            // check if url has /$base_url
344
            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...
345
            if (substr($url, 0, strlen($base_url)) == $base_url) {
346
                // append $site_url to make it work with Location:
347
                $url = $site_url . substr($url, strlen($base_url));
348
            }
349
            if (strpos($url, "\n") === false) {
350
                $header = 'Location: ' . $url;
351
            } else {
352
                $this->messageQuit('No newline allowed in redirect url.');
353
            }
354
        }
355
        if ($responseCode && (strpos($responseCode, '30') !== false)) {
356
            header($responseCode);
357
        }
358
359
        if(!empty($header)) {
360
            header($header);
361
        }
362
363
        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...
364
    }
365
366
    /**
367
     * Forward to another page
368
     *
369
     * @param int|string $id
370
     * @param string $responseCode
371
     */
372
    public function sendForward($id, $responseCode = '')
373
    {
374
        if ($this->forwards > 0) {
375
            $this->forwards = $this->forwards - 1;
376
            $this->documentIdentifier = $id;
377
            $this->documentMethod = 'id';
378
            if ($responseCode) {
379
                header($responseCode);
380
            }
381
            $this->prepareResponse();
382
            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...
383
        } else {
384
            $this->messageQuit("Internal Server Error id={$id}");
385
            header('HTTP/1.0 500 Internal Server Error');
386
            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...
387
        }
388
    }
389
390
    /**
391
     * Redirect to the error page, by calling sendForward(). This is called for example when the page was not found.
392
     * @param bool $noEvent
393
     */
394
    public function sendErrorPage($noEvent = false)
395
    {
396
        $this->systemCacheKey = 'notfound';
397
        if (!$noEvent) {
398
            // invoke OnPageNotFound event
399
            $this->invokeEvent('OnPageNotFound');
400
        }
401
        $url = $this->config['error_page'] ? $this->config['error_page'] : $this->config['site_start'];
402
403
        $this->sendForward($url, 'HTTP/1.0 404 Not Found');
404
        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...
405
    }
406
407
    /**
408
     * @param bool $noEvent
409
     */
410
    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...
411
    {
412
        // invoke OnPageUnauthorized event
413
        $_REQUEST['refurl'] = $this->documentIdentifier;
414
        $this->systemCacheKey = 'unauth';
415
        if (!$noEvent) {
416
            $this->invokeEvent('OnPageUnauthorized');
417
        }
418
        if ($this->config['unauthorized_page']) {
419
            $unauthorizedPage = $this->config['unauthorized_page'];
420
        } elseif ($this->config['error_page']) {
421
            $unauthorizedPage = $this->config['error_page'];
422
        } else {
423
            $unauthorizedPage = $this->config['site_start'];
424
        }
425
        $this->sendForward($unauthorizedPage, 'HTTP/1.1 401 Unauthorized');
426
        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...
427
    }
428
429
    /**
430
     * Get MODX settings including, but not limited to, the system_settings table
431
     */
432
    public function getSettings()
433
    {
434
        if (!isset($this->config['site_name'])) {
435
            $this->recoverySiteCache();
436
        }
437
438
        // setup default site id - new installation should generate a unique id for the site.
439
        if (!isset($this->config['site_id'])) {
440
            $this->config['site_id'] = "MzGeQ2faT4Dw06+U49x3";
441
        }
442
443
        // store base_url and base_path inside config array
444
        $this->config['base_url'] = MODX_BASE_URL;
445
        $this->config['base_path'] = MODX_BASE_PATH;
446
        $this->config['site_url'] = MODX_SITE_URL;
447
        $this->config['valid_hostnames'] = MODX_SITE_HOSTNAMES;
448
        $this->config['site_manager_url'] = MODX_MANAGER_URL;
449
        $this->config['site_manager_path'] = MODX_MANAGER_PATH;
450
        $this->error_reporting = $this->config['error_reporting'];
451
        $this->config['filemanager_path'] = str_replace('[(base_path)]', MODX_BASE_PATH, $this->config['filemanager_path']);
452
        $this->config['rb_base_dir'] = str_replace('[(base_path)]', MODX_BASE_PATH, $this->config['rb_base_dir']);
453
454
        if (!isset($this->config['enable_at_syntax'])) {
455
            $this->config['enable_at_syntax'] = 1;
456
        } // @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...
457
458
        // now merge user settings into evo-configuration
459
        $this->getUserSettings();
460
    }
461
462
    private function recoverySiteCache()
463
    {
464
        $site_cache_dir = MODX_BASE_PATH . $this->getCacheFolder();
465
        $site_cache_path = $site_cache_dir . 'siteCache.idx.php';
466
467
        if (is_file($site_cache_path)) {
468
            include($site_cache_path);
469
        }
470
        if (isset($this->config['site_name'])) {
471
            return;
472
        }
473
474
        $cache = new Cache();
475
        $cache->setCachepath($site_cache_dir);
476
        $cache->setReport(false);
477
        $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...
478
479
        clearstatcache();
480
        if (is_file($site_cache_path)) {
481
            include($site_cache_path);
482
        }
483
        if (isset($this->config['site_name'])) {
484
            return;
485
        }
486
487
        $rs = $this->db->select('setting_name, setting_value', '[+prefix+]system_settings');
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rs. Configured minimum length is 3.

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

Loading history...
488
        while ($row = $this->db->getRow($rs)) {
489
            $this->config[$row['setting_name']] = $row['setting_value'];
490
        }
491
492
        if (!$this->config['enable_filter']) {
493
            return;
494
        }
495
496
        $where = "plugincode LIKE '%phx.parser.class.inc.php%OnParseDocument();%' AND disabled != 1";
497
        $rs = $this->db->select('id', '[+prefix+]site_plugins', $where);
498
        if ($this->db->getRecordCount($rs)) {
499
            $this->config['enable_filter'] = '0';
500
        }
501
    }
502
503
    /**
504
     * Get user settings and merge into MODX configuration
505
     * @return array
506
     */
507
    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...
508
    {
509
        $tbl_web_user_settings = $this->getFullTableName('web_user_settings');
510
        $tbl_user_settings = $this->getFullTableName('user_settings');
511
512
        // load user setting if user is logged in
513
        $usrSettings = array();
514
        if ($id = $this->getLoginUserID()) {
515
            $usrType = $this->getLoginUserType();
516
            if (isset ($usrType) && $usrType == 'manager') {
517
                $usrType = 'mgr';
518
            }
519
520
            if ($usrType == 'mgr' && $this->isBackend()) {
521
                // invoke the OnBeforeManagerPageInit event, only if in backend
522
                $this->invokeEvent("OnBeforeManagerPageInit");
523
            }
524
525
            if (isset ($_SESSION[$usrType . 'UsrConfigSet'])) {
526
                $usrSettings = &$_SESSION[$usrType . 'UsrConfigSet'];
527
            } else {
528
                if ($usrType == 'web') {
529
                    $from = $tbl_web_user_settings;
530
                    $where = "webuser='{$id}'";
531
                } else {
532
                    $from = $tbl_user_settings;
533
                    $where = "user='{$id}'";
534
                }
535
536
                $which_browser_default = $this->configGlobal['which_browser'] ? $this->configGlobal['which_browser'] : $this->config['which_browser'];
537
538
                $result = $this->db->select('setting_name, setting_value', $from, $where);
539
                while ($row = $this->db->getRow($result)) {
540 View Code Duplication
                    if ($row['setting_name'] == 'which_browser' && $row['setting_value'] == 'default') {
541
                        $row['setting_value'] = $which_browser_default;
542
                    }
543
                    $usrSettings[$row['setting_name']] = $row['setting_value'];
544
                }
545
                if (isset ($usrType)) {
546
                    $_SESSION[$usrType . 'UsrConfigSet'] = $usrSettings;
547
                } // store user settings in session
548
            }
549
        }
550
        if ($this->isFrontend() && $mgrid = $this->getLoginUserID('mgr')) {
551
            $musrSettings = array();
552
            if (isset ($_SESSION['mgrUsrConfigSet'])) {
553
                $musrSettings = &$_SESSION['mgrUsrConfigSet'];
554
            } else {
555
                if ($result = $this->db->select('setting_name, setting_value', $tbl_user_settings, "user='{$mgrid}'")) {
556
                    while ($row = $this->db->getRow($result)) {
557
                        $musrSettings[$row['setting_name']] = $row['setting_value'];
558
                    }
559
                    $_SESSION['mgrUsrConfigSet'] = $musrSettings; // store user settings in session
560
                }
561
            }
562
            if (!empty ($musrSettings)) {
563
                $usrSettings = array_merge($musrSettings, $usrSettings);
564
            }
565
        }
566
        // save global values before overwriting/merging array
567
        foreach ($usrSettings as $param => $value) {
568
            if (isset($this->config[$param])) {
569
                $this->configGlobal[$param] = $this->config[$param];
570
            }
571
        }
572
573
        $this->config = array_merge($this->config, $usrSettings);
574
        $this->config['filemanager_path'] = str_replace('[(base_path)]', MODX_BASE_PATH, $this->config['filemanager_path']);
575
        $this->config['rb_base_dir'] = str_replace('[(base_path)]', MODX_BASE_PATH, $this->config['rb_base_dir']);
576
577
        return $usrSettings;
578
    }
579
580
    /**
581
     * Returns the document identifier of the current request
582
     *
583
     * @param string $method id and alias are allowed
584
     * @return int
585
     */
586
    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...
587
    {
588
        // function to test the query and find the retrieval method
589
        if ($method === 'alias') {
590
            return $this->db->escape($_REQUEST['q']);
591
        }
592
593
        $id_ = filter_input(INPUT_GET, 'id');
594
        if ($id_) {
595
            if (preg_match('@^[1-9][0-9]*$@', $id_)) {
596
                return $id_;
597
            } else {
598
                $this->sendErrorPage();
599
            }
600
        } elseif (strpos($_SERVER['REQUEST_URI'], 'index.php/') !== false) {
601
            $this->sendErrorPage();
602
        } else {
603
            return $this->config['site_start'];
604
        }
605
    }
606
607
    /**
608
     * Check for manager or webuser login session since v1.2
609
     *
610
     * @param string $context
611
     * @return bool
612
     */
613
    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...
614
    {
615
        if (substr($context, 0, 1) == 'm') {
616
            $_ = '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...
617
        } else {
618
            $_ = 'webValidated';
619
        }
620
621
        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...
622
            return true;
623
        } else {
624
            return false;
625
        }
626
    }
627
628
    /**
629
     * Check for manager login session
630
     *
631
     * @return boolean
632
     */
633
    public function checkSession()
634
    {
635
        return $this->isLoggedin();
636
    }
637
638
    /**
639
     * Checks, if a the result is a preview
640
     *
641
     * @return boolean
642
     */
643
    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...
644
    {
645
        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...
646
            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...
647
                return true;
648
            } else {
649
                return false;
650
            }
651
        } else {
652
            return false;
653
        }
654
    }
655
656
    /**
657
     * check if site is offline
658
     *
659
     * @return boolean
660
     */
661
    public function checkSiteStatus()
662
    {
663
        if ($this->config['site_status']) {
664
            return true;
665
        }  // site online
666
        elseif ($this->isLoggedin()) {
667
            return true;
668
        }  // site offline but launched via the manager
669
        else {
670
            return false;
671
        } // site is offline
672
    }
673
674
    /**
675
     * Create a 'clean' document identifier with path information, friendly URL suffix and prefix.
676
     *
677
     * @param string $qOrig
678
     * @return string
679
     */
680
    public function cleanDocumentIdentifier($qOrig)
681
    {
682
        if (!$qOrig) {
683
            $qOrig = $this->config['site_start'];
684
        }
685
        $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...
686
687
        $pre = $this->config['friendly_url_prefix'];
688
        $suf = $this->config['friendly_url_suffix'];
689
        $pre = preg_quote($pre, '/');
690
        $suf = preg_quote($suf, '/');
691 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...
692
            $q = $_[1];
693
        }
694 View Code Duplication
        if ($suf && preg_match('@(.*)' . $suf . '$@', $q, $_)) {
695
            $q = $_[1];
696
        }
697
698
        /* First remove any / before or after */
699
        $q = trim($q, '/');
700
701
        /* Save path if any */
702
        /* FS#476 and FS#308: only return virtualDir if friendly paths are enabled */
703
        if ($this->config['use_alias_path'] == 1) {
704
            $_ = strrpos($q, '/');
705
            $this->virtualDir = $_ !== false ? substr($q, 0, $_) : '';
706
            if ($_ !== false) {
707
                $q = preg_replace('@.*/@', '', $q);
708
            }
709
        } else {
710
            $this->virtualDir = '';
711
        }
712
713
        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 */
714
            /* FS#476 and FS#308: check that id is valid in terms of virtualDir structure */
715
            if ($this->config['use_alias_path'] == 1) {
716
                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))))) {
717
                    $this->documentMethod = 'id';
718
                    return $q;
719
                } else { /* not a valid id in terms of virtualDir, treat as alias */
720
                    $this->documentMethod = 'alias';
721
                    return $q;
722
                }
723
            } else {
724
                $this->documentMethod = 'id';
725
                return $q;
726
            }
727
        } else { /* we didn't get an ID back, so instead we assume it's an alias */
728
            if ($this->config['friendly_alias_urls'] != 1) {
729
                $q = $qOrig;
730
            }
731
            $this->documentMethod = 'alias';
732
            return $q;
733
        }
734
    }
735
736
    /**
737
     * @return string
738
     */
739
    public function getCacheFolder()
740
    {
741
        return "assets/cache/";
742
    }
743
744
    /**
745
     * @param $key
746
     * @return string
747
     */
748
    public function getHashFile($key)
749
    {
750
        return $this->getCacheFolder() . "docid_" . $key . ".pageCache.php";
751
    }
752
753
    /**
754
     * @param $id
755
     * @return array|mixed|null|string
756
     */
757
    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...
758
        $hash = $id;
759
        $tmp = null;
760
        $params = array();
761
        if(!empty($this->systemCacheKey)){
762
            $hash = $this->systemCacheKey;
763
        }else {
764
            if (!empty($_GET)) {
765
                // Sort GET parameters so that the order of parameters on the HTTP request don't affect the generated cache ID.
766
                $params = $_GET;
767
                ksort($params);
768
                $hash .= '_'.md5(http_build_query($params));
769
            }
770
        }
771
        $evtOut = $this->invokeEvent("OnMakePageCacheKey", array ("hash" => $hash, "id" => $id, 'params' => $params));
772
        if (is_array($evtOut) && count($evtOut) > 0){
773
            $tmp = array_pop($evtOut);
774
        }
775
        return empty($tmp) ? $hash : $tmp;
776
    }
777
778
    /**
779
     * @param $id
780
     * @param bool $loading
781
     * @return string
782
     */
783
    public function checkCache($id, $loading = false)
784
    {
785
        return $this->getDocumentObjectFromCache($id, $loading);
786
    }
787
788
    /**
789
     * Check the cache for a specific document/resource
790
     *
791
     * @param int $id
792
     * @param bool $loading
793
     * @return string
794
     */
795
    public function getDocumentObjectFromCache($id, $loading = false)
796
    {
797
        $key = ($this->config['cache_type'] == 2) ? $this->makePageCacheKey($id) : $id;
798
        if ($loading) {
799
            $this->cacheKey = $key;
800
        }
801
802
        $cache_path = $this->getHashFile($key);
803
804
        if (!is_file($cache_path)) {
805
            $this->documentGenerated = 1;
806
            return '';
807
        }
808
        $content = file_get_contents($cache_path, false);
809
        if (substr($content, 0, 5) === '<?php') {
810
            $content = substr($content, strpos($content, '?>') + 2);
811
        } // remove php header
812
        $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...
813
        if (count($a) == 1) {
814
            $result = $a[0];
815
        } // return only document content
816
        else {
817
            $docObj = unserialize($a[0]); // rebuild document object
818
            // check page security
819
            if ($docObj['privateweb'] && isset ($docObj['__MODxDocGroups__'])) {
820
                $pass = false;
821
                $usrGrps = $this->getUserDocGroups();
822
                $docGrps = explode(',', $docObj['__MODxDocGroups__']);
823
                // check is user has access to doc groups
824
                if (is_array($usrGrps)) {
825
                    foreach ($usrGrps as $k => $v) {
826
                        if (!in_array($v, $docGrps)) {
827
                            continue;
828
                        }
829
                        $pass = true;
830
                        break;
831
                    }
832
                }
833
                // diplay error pages if user has no access to cached doc
834
                if (!$pass) {
835
                    if ($this->config['unauthorized_page']) {
836
                        // check if file is not public
837
                        $rs = $this->db->select('count(id)', '[+prefix+]document_groups', "document='{$id}'", '', '1');
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rs. Configured minimum length is 3.

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

Loading history...
838
                        $total = $this->db->getValue($rs);
839
                    } else {
840
                        $total = 0;
841
                    }
842
843
                    if ($total > 0) {
844
                        $this->sendUnauthorizedPage();
845
                    } else {
846
                        $this->sendErrorPage();
847
                    }
848
849
                    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...
850
                }
851
            }
852
            // Grab the Scripts
853
            if (isset($docObj['__MODxSJScripts__'])) {
854
                $this->sjscripts = $docObj['__MODxSJScripts__'];
855
            }
856
            if (isset($docObj['__MODxJScripts__'])) {
857
                $this->jscripts = $docObj['__MODxJScripts__'];
858
            }
859
860
            // Remove intermediate variables
861
            unset($docObj['__MODxDocGroups__'], $docObj['__MODxSJScripts__'], $docObj['__MODxJScripts__']);
862
863
            $this->documentObject = $docObj;
864
865
            $result = $a[1]; // return document content
866
        }
867
868
        $this->documentGenerated = 0;
869
        // invoke OnLoadWebPageCache  event
870
        $this->documentContent = $result;
871
        $this->invokeEvent('OnLoadWebPageCache');
872
        return $result;
873
    }
874
875
    /**
876
     * Final processing and output of the document/resource.
877
     *
878
     * - runs uncached snippets
879
     * - add javascript to <head>
880
     * - removes unused placeholders
881
     * - converts URL tags [~...~] to URLs
882
     *
883
     * @param boolean $noEvent Default: false
884
     */
885
    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...
886
    {
887
        $this->documentOutput = $this->documentContent;
888
889
        if ($this->documentGenerated == 1 && $this->documentObject['cacheable'] == 1 && $this->documentObject['type'] == 'document' && $this->documentObject['published'] == 1) {
890
            if (!empty($this->sjscripts)) {
891
                $this->documentObject['__MODxSJScripts__'] = $this->sjscripts;
892
            }
893
            if (!empty($this->jscripts)) {
894
                $this->documentObject['__MODxJScripts__'] = $this->jscripts;
895
            }
896
        }
897
898
        // check for non-cached snippet output
899
        if (strpos($this->documentOutput, '[!') > -1) {
900
            $this->recentUpdate = $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'];
901
902
            $this->documentOutput = str_replace('[!', '[[', $this->documentOutput);
903
            $this->documentOutput = str_replace('!]', ']]', $this->documentOutput);
904
905
            // Parse document source
906
            $this->documentOutput = $this->parseDocumentSource($this->documentOutput);
907
        }
908
909
        // Moved from prepareResponse() by sirlancelot
910
        // Insert Startup jscripts & CSS scripts into template - template must have a <head> tag
911
        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...
912
            // change to just before closing </head>
913
            // $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...
914
            $this->documentOutput = preg_replace("/(<\/head>)/i", $js . "\n\\1", $this->documentOutput);
915
        }
916
917
        // Insert jscripts & html block into template - template must have a </body> tag
918
        if ($js = $this->getRegisteredClientScripts()) {
919
            $this->documentOutput = preg_replace("/(<\/body>)/i", $js . "\n\\1", $this->documentOutput);
920
        }
921
        // End fix by sirlancelot
922
923
        $this->documentOutput = $this->cleanUpMODXTags($this->documentOutput);
924
925
        $this->documentOutput = $this->rewriteUrls($this->documentOutput);
926
927
        // send out content-type and content-disposition headers
928
        if (IN_PARSER_MODE == "true") {
929
            $type = !empty ($this->contentTypes[$this->documentIdentifier]) ? $this->contentTypes[$this->documentIdentifier] : "text/html";
930
            header('Content-Type: ' . $type . '; charset=' . $this->config['modx_charset']);
931
            //            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...
932
            //                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...
933
            if (!$this->checkPreview() && $this->documentObject['content_dispo'] == 1) {
934
                if ($this->documentObject['alias']) {
935
                    $name = $this->documentObject['alias'];
936
                } else {
937
                    // strip title of special characters
938
                    $name = $this->documentObject['pagetitle'];
939
                    $name = strip_tags($name);
940
                    $name = $this->cleanUpMODXTags($name);
941
                    $name = strtolower($name);
942
                    $name = preg_replace('/&.+?;/', '', $name); // kill entities
943
                    $name = preg_replace('/[^\.%a-z0-9 _-]/', '', $name);
944
                    $name = preg_replace('/\s+/', '-', $name);
945
                    $name = preg_replace('|-+|', '-', $name);
946
                    $name = trim($name, '-');
947
                }
948
                $header = 'Content-Disposition: attachment; filename=' . $name;
949
                header($header);
950
            }
951
        }
952
        $this->setConditional();
953
954
        $stats = $this->getTimerStats($this->tstart);
955
956
        $out =& $this->documentOutput;
957
        $out = str_replace("[^q^]", $stats['queries'], $out);
958
        $out = str_replace("[^qt^]", $stats['queryTime'], $out);
959
        $out = str_replace("[^p^]", $stats['phpTime'], $out);
960
        $out = str_replace("[^t^]", $stats['totalTime'], $out);
961
        $out = str_replace("[^s^]", $stats['source'], $out);
962
        $out = str_replace("[^m^]", $stats['phpMemory'], $out);
963
        //$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...
964
965
        // invoke OnWebPagePrerender event
966
        if (!$noEvent) {
967
            $evtOut = $this->invokeEvent('OnWebPagePrerender', array('documentOutput' => $this->documentOutput));
968
            if (is_array($evtOut) && count($evtOut) > 0) {
969
                $this->documentOutput = $evtOut['0'];
970
            }
971
        }
972
973
        $this->documentOutput = $this->removeSanitizeSeed($this->documentOutput);
974
975
        if (strpos($this->documentOutput, '\{') !== false) {
976
            $this->documentOutput = $this->RecoveryEscapedTags($this->documentOutput);
977
        } elseif (strpos($this->documentOutput, '\[') !== false) {
978
            $this->documentOutput = $this->RecoveryEscapedTags($this->documentOutput);
979
        }
980
981
        echo $this->documentOutput;
982
983
        if ($this->dumpSQL) {
984
            echo $this->queryCode;
985
        }
986
        if ($this->dumpSnippets) {
987
            $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...
988
            $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...
989
            foreach ($this->snippetsTime as $s => $v) {
990
                $t = $v['time'];
991
                $sname = $v['sname'];
992
                $sc .= sprintf("%s. %s (%s)<br>", $s, $sname, sprintf("%2.2f ms", $t)); // currentSnippet
993
                $tt += $t;
994
            }
995
            echo "<fieldset><legend><b>Snippets</b> (" . count($this->snippetsTime) . " / " . sprintf("%2.2f ms", $tt) . ")</legend>{$sc}</fieldset><br />";
996
            echo $this->snippetsCode;
997
        }
998
        if ($this->dumpPlugins) {
999
            $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...
1000
            $tt = 0;
1001
            foreach ($this->pluginsTime as $s => $t) {
1002
                $ps .= "$s (" . sprintf("%2.2f ms", $t * 1000) . ")<br>";
1003
                $tt += $t;
1004
            }
1005
            echo "<fieldset><legend><b>Plugins</b> (" . count($this->pluginsTime) . " / " . sprintf("%2.2f ms", $tt * 1000) . ")</legend>{$ps}</fieldset><br />";
1006
            echo $this->pluginsCode;
1007
        }
1008
1009
        ob_end_flush();
1010
    }
1011
1012
    /**
1013
     * @param $contents
1014
     * @return mixed
1015
     */
1016
    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...
1017
    {
1018
        list($sTags, $rTags) = $this->getTagsForEscape();
1019
        return str_replace($rTags, $sTags, $contents);
1020
    }
1021
1022
    /**
1023
     * @param string $tags
1024
     * @return array[]
1025
     */
1026
    public function getTagsForEscape($tags = '{{,}},[[,]],[!,!],[*,*],[(,)],[+,+],[~,~],[^,^]')
1027
    {
1028
        $srcTags = explode(',', $tags);
1029
        $repTags = array();
1030
        foreach ($srcTags as $tag) {
1031
            $repTags[] = '\\' . $tag[0] . '\\' . $tag[1];
1032
        }
1033
        return array($srcTags, $repTags);
1034
    }
1035
1036
    /**
1037
     * @param $tstart
1038
     * @return array
1039
     */
1040
    public function getTimerStats($tstart)
1041
    {
1042
        $stats = array();
1043
1044
        $stats['totalTime'] = ($this->getMicroTime() - $tstart);
1045
        $stats['queryTime'] = $this->queryTime;
1046
        $stats['phpTime'] = $stats['totalTime'] - $stats['queryTime'];
1047
1048
        $stats['queryTime'] = sprintf("%2.4f s", $stats['queryTime']);
1049
        $stats['totalTime'] = sprintf("%2.4f s", $stats['totalTime']);
1050
        $stats['phpTime'] = sprintf("%2.4f s", $stats['phpTime']);
1051
        $stats['source'] = $this->documentGenerated == 1 ? "database" : "cache";
1052
        $stats['queries'] = isset ($this->executedQueries) ? $this->executedQueries : 0;
1053
        $stats['phpMemory'] = (memory_get_peak_usage(true) / 1024 / 1024) . " mb";
1054
1055
        return $stats;
1056
    }
1057
1058
    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...
1059
    {
1060
        if (!empty($_POST) || (defined('MODX_API_MODE') && MODX_API_MODE) || $this->getLoginUserID('mgr') || !$this->useConditional || empty($this->recentUpdate)) {
1061
            return;
1062
        }
1063
        $last_modified = gmdate('D, d M Y H:i:s T', $this->recentUpdate);
1064
        $etag = md5($last_modified);
1065
        $HTTP_IF_MODIFIED_SINCE = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? $_SERVER['HTTP_IF_MODIFIED_SINCE'] : false;
1066
        $HTTP_IF_NONE_MATCH = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? $_SERVER['HTTP_IF_NONE_MATCH'] : false;
1067
        header('Pragma: no-cache');
1068
1069
        if ($HTTP_IF_MODIFIED_SINCE == $last_modified || strpos($HTTP_IF_NONE_MATCH, $etag) !== false) {
1070
            header('HTTP/1.1 304 Not Modified');
1071
            header('Content-Length: 0');
1072
            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...
1073
        } else {
1074
            header("Last-Modified: {$last_modified}");
1075
            header("ETag: '{$etag}'");
1076
        }
1077
    }
1078
1079
    /**
1080
     * Checks the publish state of page
1081
     */
1082
    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...
1083
    {
1084
        $cacheRefreshTime = 0;
1085
        $recent_update = 0;
1086
        @include(MODX_BASE_PATH . $this->getCacheFolder() . 'sitePublishing.idx.php');
1087
        $this->recentUpdate = $recent_update;
1088
1089
        $timeNow = $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'];
1090
        if ($timeNow < $cacheRefreshTime || $cacheRefreshTime == 0) {
1091
            return;
1092
        }
1093
1094
        // now, check for documents that need publishing
1095
        $field = array('published' => 1, 'publishedon' => $timeNow);
1096
        $where = "pub_date <= {$timeNow} AND pub_date!=0 AND published=0";
1097
        $result_pub = $this->db->select( 'id', '[+prefix+]site_content',  $where);
1098
        $this->db->update($field, '[+prefix+]site_content', $where);
1099 View Code Duplication
        if ($this->db->getRecordCount($result_pub) >= 1) { //Event unPublished doc
1100
            while ($row_pub = $this->db->getRow($result_pub)) {
1101
                $this->invokeEvent("OnDocUnPublished", array(
1102
                    "docid" => $row_pub['id']
1103
                ));
1104
            }
1105
        }
1106
1107
        // now, check for documents that need un-publishing
1108
        $field = array('published' => 0, 'publishedon' => 0);
1109
        $where = "unpub_date <= {$timeNow} AND unpub_date!=0 AND published=1";
1110
        $result_unpub = $this->db->select( 'id', '[+prefix+]site_content',  $where);
1111
        $this->db->update($field, '[+prefix+]site_content', $where);
1112 View Code Duplication
        if ($this->db->getRecordCount($result_unpub) >= 1) { //Event unPublished doc
1113
            while ($row_unpub = $this->db->getRow($result_unpub)) {
1114
                $this->invokeEvent("OnDocUnPublished", array(
1115
                    "docid" => $row_unpub['id']
1116
                ));
1117
            }
1118
        }
1119
1120
        $this->recentUpdate = $timeNow;
1121
1122
        // clear the cache
1123
        $this->clearCache('full');
1124
    }
1125
1126
    public function checkPublishStatus()
1127
    {
1128
        $this->updatePubStatus();
1129
    }
1130
1131
    /**
1132
     * Final jobs.
1133
     *
1134
     * - cache page
1135
     */
1136
    public function postProcess()
1137
    {
1138
        // if the current document was generated, cache it!
1139
        $cacheable = ($this->config['enable_cache'] && $this->documentObject['cacheable']) ? 1 : 0;
1140
        if ($cacheable && $this->documentGenerated && $this->documentObject['type'] == 'document' && $this->documentObject['published']) {
1141
            // invoke OnBeforeSaveWebPageCache event
1142
            $this->invokeEvent("OnBeforeSaveWebPageCache");
1143
1144
            if (!empty($this->cacheKey) && is_scalar($this->cacheKey)) {
1145
                // get and store document groups inside document object. Document groups will be used to check security on cache pages
1146
                $where = "document='{$this->documentIdentifier}'";
1147
                $rs = $this->db->select('document_group', '[+prefix+]document_groups', $where);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rs. Configured minimum length is 3.

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

Loading history...
1148
                $docGroups = $this->db->getColumn('document_group', $rs);
1149
1150
                // Attach Document Groups and Scripts
1151
                if (is_array($docGroups)) {
1152
                    $this->documentObject['__MODxDocGroups__'] = implode(",", $docGroups);
1153
                }
1154
1155
                $docObjSerial = serialize($this->documentObject);
1156
                $cacheContent = $docObjSerial . "<!--__MODxCacheSpliter__-->" . $this->documentContent;
1157
                $page_cache_path = MODX_BASE_PATH . $this->getHashFile($this->cacheKey);
1158
                file_put_contents($page_cache_path, "<?php die('Unauthorized access.'); ?>$cacheContent");
1159
            }
1160
        }
1161
1162
        // Useful for example to external page counters/stats packages
1163
        $this->invokeEvent('OnWebPageComplete');
1164
1165
        // end post processing
1166
    }
1167
1168
    /**
1169
     * @param $content
1170
     * @param string $left
1171
     * @param string $right
1172
     * @return array
1173
     */
1174
    public function getTagsFromContent($content, $left = '[+', $right = '+]')
1175
    {
1176
        $_ = $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...
1177
        if (empty($_)) {
1178
            return array();
1179
        }
1180
        foreach ($_ as $v) {
1181
            $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...
1182
            $tags[1][] = $v;
1183
        }
1184
        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...
1185
    }
1186
1187
    /**
1188
     * @param $content
1189
     * @param string $left
1190
     * @param string $right
1191
     * @return array
1192
     */
1193
    public function _getTagsFromContent($content, $left = '[+', $right = '+]')
1194
    {
1195
        if (strpos($content, $left) === false) {
1196
            return array();
1197
        }
1198
        $spacer = md5('<<<EVO>>>');
1199
        if($left==='{{' && strpos($content,';}}')!==false)  $content = str_replace(';}}', sprintf(';}%s}',   $spacer),$content);
1200
        if($left==='{{' && strpos($content,'{{}}')!==false) $content = str_replace('{{}}',sprintf('{%$1s{}%$1s}',$spacer),$content);
1201
        if($left==='[[' && strpos($content,']]]]')!==false) $content = str_replace(']]]]',sprintf(']]%s]]',  $spacer),$content);
1202
        if($left==='[[' && strpos($content,']]]')!==false)  $content = str_replace(']]]', sprintf(']%s]]',   $spacer),$content);
1203
1204
        $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...
1205
        $pos[']]>'] = strpos($content, ']]>');
1206
1207
        if ($pos['<![CDATA['] !== false && $pos[']]>'] !== false) {
1208
            $content = substr($content, 0, $pos['<![CDATA[']) . substr($content, $pos[']]>'] + 3);
1209
        }
1210
1211
        $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...
1212
        $piece = array();
1213
        foreach ($lp as $lc => $lv) {
1214
            if ($lc !== 0) {
1215
                $piece[] = $left;
1216
            }
1217
            if (strpos($lv, $right) === false) {
1218
                $piece[] = $lv;
1219
            } else {
1220
                $rp = explode($right, $lv);
1221
                foreach ($rp as $rc => $rv) {
1222
                    if ($rc !== 0) {
1223
                        $piece[] = $right;
1224
                    }
1225
                    $piece[] = $rv;
1226
                }
1227
            }
1228
        }
1229
        $lc = 0;
1230
        $rc = 0;
1231
        $fetch = '';
1232
        $tags = array();
1233
        foreach ($piece as $v) {
1234
            if ($v === $left) {
1235
                if (0 < $lc) {
1236
                    $fetch .= $left;
1237
                }
1238
                $lc++;
1239
            } elseif ($v === $right) {
1240
                if ($lc === 0) {
1241
                    continue;
1242
                }
1243
                $rc++;
1244
                if ($lc === $rc) {
1245
                    // #1200 Enable modifiers in Wayfinder - add nested placeholders to $tags like for $fetch = "phx:input=`[+wf.linktext+]`:test"
1246
                    if (strpos($fetch, $left) !== false) {
1247
                        $nested = $this->_getTagsFromContent($fetch, $left, $right);
1248
                        foreach ($nested as $tag) {
1249
                            if (!in_array($tag, $tags)) {
1250
                                $tags[] = $tag;
1251
                            }
1252
                        }
1253
                    }
1254
1255
                    if (!in_array($fetch, $tags)) {  // Avoid double Matches
1256
                        $tags[] = $fetch; // Fetch
1257
                    };
1258
                    $fetch = ''; // and reset
1259
                    $lc = 0;
1260
                    $rc = 0;
1261
                } else {
1262
                    $fetch .= $right;
1263
                }
1264
            } else {
1265
                if (0 < $lc) {
1266
                    $fetch .= $v;
1267
                } else {
1268
                    continue;
1269
                }
1270
            }
1271
        }
1272
        foreach($tags as $i=>$tag) {
1273
            if(strpos($tag,$spacer)!==false) $tags[$i] = str_replace($spacer, '', $tag);
1274
        }
1275
        return $tags;
1276
    }
1277
1278
    /**
1279
     * Merge content fields and TVs
1280
     *
1281
     * @param $content
1282
     * @param bool $ph
1283
     * @return string
1284
     * @internal param string $template
1285
     */
1286
    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...
1287
    {
1288 View Code Duplication
        if ($this->config['enable_at_syntax']) {
1289
            if (stripos($content, '<@LITERAL>') !== false) {
1290
                $content = $this->escapeLiteralTagsContent($content);
1291
            }
1292
        }
1293
        if (strpos($content, '[*') === false) {
1294
            return $content;
1295
        }
1296
        if (!isset($this->documentIdentifier)) {
1297
            return $content;
1298
        }
1299
        if (!isset($this->documentObject) || empty($this->documentObject)) {
1300
            return $content;
1301
        }
1302
1303
        if (!$ph) {
1304
            $ph = $this->documentObject;
1305
        }
1306
1307
        $matches = $this->getTagsFromContent($content, '[*', '*]');
1308
        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...
1309
            return $content;
1310
        }
1311
1312
        foreach ($matches[1] as $i => $key) {
1313
            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...
1314
            if (substr($key, 0, 1) == '#') {
1315
                $key = substr($key, 1);
1316
            } // remove # for QuickEdit format
1317
1318
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1319
            if (strpos($key, '@') !== false) {
1320
                list($key, $context) = explode('@', $key, 2);
1321
            } else {
1322
                $context = false;
1323
            }
1324
1325
            // 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...
1326
            if ($context) {
1327
                $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...
1328
            } else {
1329
                $value = isset($ph[$key]) ? $ph[$key] : '';
1330
            }
1331
1332 View Code Duplication
            if (is_array($value)) {
1333
                $value = getTVDisplayFormat($value[0], $value[1], $value[2], $value[3], $value[4]);
1334
            }
1335
1336
            $s = &$matches[0][$i];
1337
            if ($modifiers !== false) {
1338
                $value = $this->applyFilter($value, $modifiers, $key);
1339
            }
1340
1341 View Code Duplication
            if (strpos($content, $s) !== false) {
1342
                $content = str_replace($s, $value, $content);
1343
            } elseif($this->debug) {
1344
                $this->addLog('mergeDocumentContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1345
            }
1346
        }
1347
1348
        return $content;
1349
    }
1350
1351
    /**
1352
     * @param $key
1353
     * @param bool|int $parent
1354
     * @return bool|mixed|string
1355
     */
1356
    public function _contextValue($key, $parent = false)
1357
    {
1358
        if (preg_match('/@\d+\/u/', $key)) {
1359
            $key = str_replace(array('@', '/u'), array('@u(', ')'), $key);
1360
        }
1361
        list($key, $str) = explode('@', $key, 2);
1362
1363
        if (strpos($str, '(')) {
1364
            list($context, $option) = explode('(', $str, 2);
1365
        } else {
1366
            list($context, $option) = array($str, false);
1367
        }
1368
1369
        if ($option) {
1370
            $option = trim($option, ')(\'"`');
1371
        }
1372
1373
        switch (strtolower($context)) {
1374
            case 'site_start':
1375
                $docid = $this->config['site_start'];
1376
                break;
1377
            case 'parent':
1378
            case 'p':
1379
                $docid = $parent;
1380
                if ($docid == 0) {
1381
                    $docid = $this->config['site_start'];
1382
                }
1383
                break;
1384
            case 'ultimateparent':
1385
            case 'uparent':
1386
            case 'up':
1387
            case 'u':
1388 View Code Duplication
                if (strpos($str, '(') !== false) {
1389
                    $top = substr($str, strpos($str, '('));
1390
                    $top = trim($top, '()"\'');
1391
                } else {
1392
                    $top = 0;
1393
                }
1394
                $docid = $this->getUltimateParentId($this->documentIdentifier, $top);
1395
                break;
1396
            case 'alias':
1397
                $str = substr($str, strpos($str, '('));
1398
                $str = trim($str, '()"\'');
1399
                $docid = $this->getIdFromAlias($str);
1400
                break;
1401 View Code Duplication
            case 'prev':
1402
                if (!$option) {
1403
                    $option = 'menuindex,ASC';
1404
                } elseif (strpos($option, ',') === false) {
1405
                    $option .= ',ASC';
1406
                }
1407
                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...
1408
                $children = $this->getActiveChildren($parent, $by, $dir);
1409
                $find = false;
1410
                $prev = false;
1411
                foreach ($children as $row) {
1412
                    if ($row['id'] == $this->documentIdentifier) {
1413
                        $find = true;
1414
                        break;
1415
                    }
1416
                    $prev = $row;
1417
                }
1418
                if ($find) {
1419
                    if (isset($prev[$key])) {
1420
                        return $prev[$key];
1421
                    } else {
1422
                        $docid = $prev['id'];
1423
                    }
1424
                } else {
1425
                    $docid = '';
1426
                }
1427
                break;
1428 View Code Duplication
            case 'next':
1429
                if (!$option) {
1430
                    $option = 'menuindex,ASC';
1431
                } elseif (strpos($option, ',') === false) {
1432
                    $option .= ',ASC';
1433
                }
1434
                list($by, $dir) = explode(',', $option, 2);
1435
                $children = $this->getActiveChildren($parent, $by, $dir);
1436
                $find = false;
1437
                $next = false;
1438
                foreach ($children as $row) {
1439
                    if ($find) {
1440
                        $next = $row;
1441
                        break;
1442
                    }
1443
                    if ($row['id'] == $this->documentIdentifier) {
1444
                        $find = true;
1445
                    }
1446
                }
1447
                if ($find) {
1448
                    if (isset($next[$key])) {
1449
                        return $next[$key];
1450
                    } else {
1451
                        $docid = $next['id'];
1452
                    }
1453
                } else {
1454
                    $docid = '';
1455
                }
1456
                break;
1457
            default:
1458
                $docid = $str;
1459
        }
1460
        if (preg_match('@^[1-9][0-9]*$@', $docid)) {
1461
            $value = $this->getField($key, $docid);
1462
        } else {
1463
            $value = '';
1464
        }
1465
        return $value;
1466
    }
1467
1468
    /**
1469
     * Merge system settings
1470
     *
1471
     * @param $content
1472
     * @param bool|array $ph
1473
     * @return string
1474
     * @internal param string $template
1475
     */
1476
    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...
1477
    {
1478 View Code Duplication
        if ($this->config['enable_at_syntax']) {
1479
            if (stripos($content, '<@LITERAL>') !== false) {
1480
                $content = $this->escapeLiteralTagsContent($content);
1481
            }
1482
        }
1483
        if (strpos($content, '[(') === false) {
1484
            return $content;
1485
        }
1486
1487
        if (empty($ph)) {
1488
            $ph = $this->config;
1489
        }
1490
1491
        $matches = $this->getTagsFromContent($content, '[(', ')]');
1492
        if (empty($matches)) {
1493
            return $content;
1494
        }
1495
1496
        foreach ($matches[1] as $i => $key) {
1497
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1498
1499
            if (isset($ph[$key])) {
1500
                $value = $ph[$key];
1501
            } else {
1502
                continue;
1503
            }
1504
1505
            if ($modifiers !== false) {
1506
                $value = $this->applyFilter($value, $modifiers, $key);
1507
            }
1508
            $s = &$matches[0][$i];
1509 View Code Duplication
            if (strpos($content, $s) !== false) {
1510
                $content = str_replace($s, $value, $content);
1511
            } elseif($this->debug) {
1512
                $this->addLog('mergeSettingsContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1513
            }
1514
        }
1515
        return $content;
1516
    }
1517
1518
    /**
1519
     * Merge chunks
1520
     *
1521
     * @param string $content
1522
     * @param bool|array $ph
1523
     * @return string
1524
     */
1525
    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...
1526
    {
1527
        if ($this->config['enable_at_syntax']) {
1528
            if (strpos($content, '{{ ') !== false) {
1529
                $content = str_replace(array('{{ ', ' }}'), array('\{\{ ', ' \}\}'), $content);
1530
            }
1531
            if (stripos($content, '<@LITERAL>') !== false) {
1532
                $content = $this->escapeLiteralTagsContent($content);
1533
            }
1534
        }
1535
        if (strpos($content, '{{') === false) {
1536
            return $content;
1537
        }
1538
1539
        if (empty($ph)) {
1540
            $ph = $this->chunkCache;
1541
        }
1542
1543
        $matches = $this->getTagsFromContent($content, '{{', '}}');
1544
        if (empty($matches)) {
1545
            return $content;
1546
        }
1547
1548
        foreach ($matches[1] as $i => $key) {
1549
            $snip_call = $this->_split_snip_call($key);
1550
            $key = $snip_call['name'];
1551
            $params = $this->getParamsFromString($snip_call['params']);
1552
1553
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1554
1555
            if (!isset($ph[$key])) {
1556
                $ph[$key] = $this->getChunk($key);
1557
            }
1558
            $value = $ph[$key];
1559
1560
            if (is_null($value)) {
1561
                continue;
1562
            }
1563
1564
            $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 1551 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...
1565
            $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 1551 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...
1566
            if ($this->config['enable_at_syntax']) {
1567
                $value = $this->mergeConditionalTagsContent($value);
1568
            }
1569
            $value = $this->mergeDocumentContent($value);
1570
            $value = $this->mergeSettingsContent($value);
1571
            $value = $this->mergeChunkContent($value);
1572
1573
            if ($modifiers !== false) {
1574
                $value = $this->applyFilter($value, $modifiers, $key);
1575
            }
1576
1577
            $s = &$matches[0][$i];
1578 View Code Duplication
            if (strpos($content, $s) !== false) {
1579
                $content = str_replace($s, $value, $content);
1580
            } elseif($this->debug) {
1581
                $this->addLog('mergeChunkContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1582
            }
1583
        }
1584
        return $content;
1585
    }
1586
1587
    /**
1588
     * Merge placeholder values
1589
     *
1590
     * @param string $content
1591
     * @param bool|array $ph
1592
     * @return string
1593
     */
1594
    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...
1595
    {
1596
1597 View Code Duplication
        if ($this->config['enable_at_syntax']) {
1598
            if (stripos($content, '<@LITERAL>') !== false) {
1599
                $content = $this->escapeLiteralTagsContent($content);
1600
            }
1601
        }
1602
        if (strpos($content, '[+') === false) {
1603
            return $content;
1604
        }
1605
1606
        if (empty($ph)) {
1607
            $ph = $this->placeholders;
1608
        }
1609
1610
        if ($this->config['enable_at_syntax']) {
1611
            $content = $this->mergeConditionalTagsContent($content);
1612
        }
1613
1614
        $content = $this->mergeDocumentContent($content);
1615
        $content = $this->mergeSettingsContent($content);
1616
        $matches = $this->getTagsFromContent($content, '[+', '+]');
1617
        if (empty($matches)) {
1618
            return $content;
1619
        }
1620
        foreach ($matches[1] as $i => $key) {
1621
1622
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1623
1624
            if (isset($ph[$key])) {
1625
                $value = $ph[$key];
1626
            } elseif ($key === 'phx') {
1627
                $value = '';
1628
            } else {
1629
                continue;
1630
            }
1631
1632
            if ($modifiers !== false) {
1633
                $modifiers = $this->mergePlaceholderContent($modifiers);
1634
                $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...
1635
            }
1636
            $s = &$matches[0][$i];
1637 View Code Duplication
            if (strpos($content, $s) !== false) {
1638
                $content = str_replace($s, $value, $content);
1639
            } elseif($this->debug) {
1640
                $this->addLog('mergePlaceholderContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1641
            }
1642
        }
1643
        return $content;
1644
    }
1645
1646
    /**
1647
     * @param $content
1648
     * @param string $iftag
1649
     * @param string $elseiftag
1650
     * @param string $elsetag
1651
     * @param string $endiftag
1652
     * @return mixed|string
1653
     */
1654
    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...
1655
    {
1656
        if (strpos($content, '@IF') !== false) {
1657
            $content = $this->_prepareCTag($content, $iftag, $elseiftag, $elsetag, $endiftag);
1658
        }
1659
1660
        if (strpos($content, $iftag) === false) {
1661
            return $content;
1662
        }
1663
1664
        $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...
1665
        $content = str_replace(array('<?php', '<?=', '<?', '?>'), array("{$sp}b", "{$sp}p", "{$sp}s", "{$sp}e"), $content);
1666
1667
        $pieces = explode('<@IF:', $content);
1668 View Code Duplication
        foreach ($pieces as $i => $split) {
1669
            if ($i === 0) {
1670
                $content = $split;
1671
                continue;
1672
            }
1673
            list($cmd, $text) = explode('>', $split, 2);
1674
            $cmd = str_replace("'", "\'", $cmd);
1675
            $content .= "<?php if(\$this->_parseCTagCMD('" . $cmd . "')): ?>";
1676
            $content .= $text;
1677
        }
1678
        $pieces = explode('<@ELSEIF:', $content);
1679 View Code Duplication
        foreach ($pieces as $i => $split) {
1680
            if ($i === 0) {
1681
                $content = $split;
1682
                continue;
1683
            }
1684
            list($cmd, $text) = explode('>', $split, 2);
1685
            $cmd = str_replace("'", "\'", $cmd);
1686
            $content .= "<?php elseif(\$this->_parseCTagCMD('" . $cmd . "')): ?>";
1687
            $content .= $text;
1688
        }
1689
1690
        $content = str_replace(array('<@ELSE>', '<@ENDIF>'), array('<?php else:?>', '<?php endif;?>'), $content);
1691
        ob_start();
1692
        $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...
1693
        $content = ob_get_clean();
1694
        $content = str_replace(array("{$sp}b", "{$sp}p", "{$sp}s", "{$sp}e"), array('<?php', '<?=', '<?', '?>'), $content);
1695
1696
        return $content;
1697
    }
1698
1699
    /**
1700
     * @param $content
1701
     * @param string $iftag
1702
     * @param string $elseiftag
1703
     * @param string $elsetag
1704
     * @param string $endiftag
1705
     * @return mixed
1706
     */
1707
    private function _prepareCTag($content, $iftag = '<@IF:', $elseiftag = '<@ELSEIF:', $elsetag = '<@ELSE>', $endiftag = '<@ENDIF>')
1708
    {
1709
        if (strpos($content, '<!--@IF ') !== false) {
1710
            $content = str_replace('<!--@IF ', $iftag, $content);
1711
        } // for jp
1712
        if (strpos($content, '<!--@IF:') !== false) {
1713
            $content = str_replace('<!--@IF:', $iftag, $content);
1714
        }
1715
        if (strpos($content, $iftag) === false) {
1716
            return $content;
1717
        }
1718
        if (strpos($content, '<!--@ELSEIF:') !== false) {
1719
            $content = str_replace('<!--@ELSEIF:', $elseiftag, $content);
1720
        } // for jp
1721
        if (strpos($content, '<!--@ELSE-->') !== false) {
1722
            $content = str_replace('<!--@ELSE-->', $elsetag, $content);
1723
        }  // for jp
1724
        if (strpos($content, '<!--@ENDIF-->') !== false) {
1725
            $content = str_replace('<!--@ENDIF-->', $endiftag, $content);
1726
        }    // for jp
1727
        if (strpos($content, '<@ENDIF-->') !== false) {
1728
            $content = str_replace('<@ENDIF-->', $endiftag, $content);
1729
        }
1730
        $tags = array($iftag, $elseiftag, $elsetag, $endiftag);
1731
        $content = str_ireplace($tags, $tags, $content); // Change to capital letters
1732
        return $content;
1733
    }
1734
1735
    /**
1736
     * @param $cmd
1737
     * @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...
1738
     */
1739
    private function _parseCTagCMD($cmd)
1740
    {
1741
        $cmd = trim($cmd);
1742
        $reverse = substr($cmd, 0, 1) === '!' ? true : false;
1743
        if ($reverse) {
1744
            $cmd = ltrim($cmd, '!');
1745
        }
1746
        if (strpos($cmd, '[!') !== false) {
1747
            $cmd = str_replace(array('[!', '!]'), array('[[', ']]'), $cmd);
1748
        }
1749
        $safe = 0;
1750
        while ($safe < 20) {
1751
            $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...
1752
            if (strpos($cmd, '[*') !== false) {
1753
                $cmd = $this->mergeDocumentContent($cmd);
1754
            }
1755
            if (strpos($cmd, '[(') !== false) {
1756
                $cmd = $this->mergeSettingsContent($cmd);
1757
            }
1758
            if (strpos($cmd, '{{') !== false) {
1759
                $cmd = $this->mergeChunkContent($cmd);
1760
            }
1761
            if (strpos($cmd, '[[') !== false) {
1762
                $cmd = $this->evalSnippets($cmd);
1763
            }
1764
            if (strpos($cmd, '[+') !== false && strpos($cmd, '[[') === false) {
1765
                $cmd = $this->mergePlaceholderContent($cmd);
1766
            }
1767
            if ($bt === md5($cmd)) {
1768
                break;
1769
            }
1770
            $safe++;
1771
        }
1772
        $cmd = ltrim($cmd);
1773
        $cmd = rtrim($cmd, '-');
1774
        $cmd = str_ireplace(array(' and ', ' or '), array('&&', '||'), $cmd);
1775
1776
        if (!preg_match('@^[0-9]*$@', $cmd) && preg_match('@^[0-9<= \-\+\*/\(\)%!&|]*$@', $cmd)) {
1777
            $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...
1778
        } else {
1779
            $_ = 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...
1780
            foreach ($_ as $left) {
1781
                if (strpos($cmd, $left) !== false) {
1782
                    $cmd = 0;
1783
                    break;
1784
                }
1785
            }
1786
        }
1787
        $cmd = trim($cmd);
1788
        if (!preg_match('@^[0-9]+$@', $cmd)) {
1789
            $cmd = empty($cmd) ? 0 : 1;
1790
        } elseif ($cmd <= 0) {
1791
            $cmd = 0;
1792
        }
1793
1794
        if ($reverse) {
1795
            $cmd = !$cmd;
1796
        }
1797
1798
        return $cmd;
1799
    }
1800
1801
    /**
1802
     * Remove Comment-Tags from output like <!--@- Comment -@-->
1803
     * @param $content
1804
     * @param string $left
1805
     * @param string $right
1806
     * @return mixed
1807
     */
1808
    function ignoreCommentedTagsContent($content, $left = '<!--@-', $right = '-@-->')
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1809
    {
1810
        if (strpos($content, $left) === false) {
1811
            return $content;
1812
        }
1813
1814
        $matches = $this->getTagsFromContent($content, $left, $right);
1815
        if (!empty($matches)) {
1816
            foreach ($matches[0] as $i => $v) {
1817
                $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...
1818
            }
1819
            $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...
1820
            if (strpos($content, $left) !== false) {
1821
                $content = str_replace($matches[0], '', $content);
1822
            }
1823
        }
1824
        return $content;
1825
    }
1826
1827
    /**
1828
     * @param $content
1829
     * @param string $left
1830
     * @param string $right
1831
     * @return mixed
1832
     */
1833
    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...
1834
    {
1835
        if (stripos($content, $left) === false) {
1836
            return $content;
1837
        }
1838
1839
        $matches = $this->getTagsFromContent($content, $left, $right);
1840
        if (empty($matches)) {
1841
            return $content;
1842
        }
1843
1844
        list($sTags, $rTags) = $this->getTagsForEscape();
1845
        foreach ($matches[1] as $i => $v) {
1846
            $v = str_ireplace($sTags, $rTags, $v);
1847
            $s = &$matches[0][$i];
1848 View Code Duplication
            if (strpos($content, $s) !== false) {
1849
                $content = str_replace($s, $v, $content);
1850
            } elseif($this->debug) {
1851
                $this->addLog('ignoreCommentedTagsContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1852
            }
1853
        }
1854
        return $content;
1855
    }
1856
1857
    /**
1858
     * Detect PHP error according to MODX error level
1859
     *
1860
     * @param integer $error PHP error level
1861
     * @return boolean Error detected
1862
     */
1863
1864
    public function detectError($error)
1865
    {
1866
        $detected = false;
1867
        if ($this->config['error_reporting'] == 99 && $error) {
1868
            $detected = true;
1869
        } elseif ($this->config['error_reporting'] == 2 && ($error & ~E_NOTICE)) {
1870
            $detected = true;
1871
        } elseif ($this->config['error_reporting'] == 1 && ($error & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT)) {
1872
            $detected = true;
1873
        }
1874
        return $detected;
1875
    }
1876
1877
    /**
1878
     * Run a plugin
1879
     *
1880
     * @param string $pluginCode Code to run
1881
     * @param array $params
1882
     */
1883
    public function evalPlugin($pluginCode, $params)
1884
    {
1885
        $modx = &$this;
1886
        $modx->event->params = &$params; // store params inside event object
1887
        if (is_array($params)) {
1888
            extract($params, EXTR_SKIP);
1889
        }
1890
        /* 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...
1891
        // 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.
1892
        // Related to https://github.com/modxcms/evolution/issues/1130
1893
        $lock_file_path = MODX_BASE_PATH . 'assets/cache/lock_' . str_replace(' ','-',strtolower($this->event->activePlugin)) . '.pageCache.php';
1894
        if($this->isBackend()) {
1895
            if(is_file($lock_file_path)) {
1896
                $msg = sprintf("Plugin parse error, Temporarily disabled '%s'.", $this->event->activePlugin);
1897
                $this->logEvent(0, 3, $msg, $msg);
1898
                return;
1899
            }
1900
            elseif(stripos($this->event->activePlugin,'ElementsInTree')===false) touch($lock_file_path);
1901
        }*/
1902
        ob_start();
1903
        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...
1904
        $msg = ob_get_contents();
1905
        ob_end_clean();
1906
        // When reached here, no fatal error occured so the lock should be removed.
1907
        /*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...
1908
1909 View Code Duplication
        if ((0 < $this->config['error_reporting']) && $msg && isset($php_errormsg)) {
1910
            $error_info = error_get_last();
1911
            if ($this->detectError($error_info['type'])) {
1912
                $msg = ($msg === false) ? 'ob_get_contents() error' : $msg;
1913
                $this->messageQuit('PHP Parse Error', '', true, $error_info['type'], $error_info['file'], 'Plugin', $error_info['message'], $error_info['line'], $msg);
1914
                if ($this->isBackend()) {
1915
                    $this->event->alert('An error occurred while loading. Please see the event log for more information.<p>' . $msg . '</p>');
1916
                }
1917
            }
1918
        } else {
1919
            echo $msg;
1920
        }
1921
        unset($modx->event->params);
1922
    }
1923
1924
    /**
1925
     * Run a snippet
1926
     *
1927
     * @param $phpcode
1928
     * @param array $params
1929
     * @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...
1930
     * @internal param string $snippet Code to run
1931
     */
1932
    public function evalSnippet($phpcode, $params)
1933
    {
1934
        $modx = &$this;
1935
        /*
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...
1936
        if(isset($params) && is_array($params)) {
1937
            foreach($params as $k=>$v) {
1938
                $v = strtolower($v);
1939
                if($v==='false')    $params[$k] = false;
1940
                elseif($v==='true') $params[$k] = true;
1941
            }
1942
        }*/
1943
        $modx->event->params = &$params; // store params inside event object
1944
        if (is_array($params)) {
1945
            extract($params, EXTR_SKIP);
1946
        }
1947
        ob_start();
1948
        if (strpos($phpcode, ';') !== false) {
1949
            if (substr($phpcode, 0, 5) === '<?php') {
1950
                $phpcode = substr($phpcode, 5);
1951
            }
1952
            $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...
1953
        } else {
1954
            $return = call_user_func_array($phpcode, array($params));
1955
        }
1956
        $echo = ob_get_contents();
1957
        ob_end_clean();
1958 View Code Duplication
        if ((0 < $this->config['error_reporting']) && isset($php_errormsg)) {
1959
            $error_info = error_get_last();
1960
            if ($this->detectError($error_info['type'])) {
1961
                $echo = ($echo === false) ? 'ob_get_contents() error' : $echo;
1962
                $this->messageQuit('PHP Parse Error', '', true, $error_info['type'], $error_info['file'], 'Snippet', $error_info['message'], $error_info['line'], $echo);
1963
                if ($this->isBackend()) {
1964
                    $this->event->alert('An error occurred while loading. Please see the event log for more information<p>' . $echo . $return . '</p>');
1965
                }
1966
            }
1967
        }
1968
        unset($modx->event->params);
1969
        if (is_array($return) || is_object($return)) {
1970
            return $return;
1971
        } else {
1972
            return $echo . $return;
1973
        }
1974
    }
1975
1976
    /**
1977
     * Run snippets as per the tags in $documentSource and replace the tags with the returned values.
1978
     *
1979
     * @param $content
1980
     * @return string
1981
     * @internal param string $documentSource
1982
     */
1983
    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...
1984
    {
1985
        if (strpos($content, '[[') === false) {
1986
            return $content;
1987
        }
1988
1989
        $matches = $this->getTagsFromContent($content, '[[', ']]');
1990
1991
        if (empty($matches)) {
1992
            return $content;
1993
        }
1994
1995
        $this->snipLapCount++;
1996
        if ($this->dumpSnippets) {
1997
            $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);
1998
        }
1999
2000
        foreach ($matches[1] as $i => $call) {
2001
            $s = &$matches[0][$i];
2002
            if (substr($call, 0, 2) === '$_') {
2003
                if (strpos($content, '_PHX_INTERNAL_') === false) {
2004
                    $value = $this->_getSGVar($call);
2005
                } else {
2006
                    $value = $s;
2007
                }
2008 View Code Duplication
                if (strpos($content, $s) !== false) {
2009
                    $content = str_replace($s, $value, $content);
2010
                } elseif($this->debug) {
2011
                    $this->addLog('evalSnippetsSGVar parse error', $_SERVER['REQUEST_URI'] . $s, 2);
2012
                }
2013
                continue;
2014
            }
2015
            $value = $this->_get_snip_result($call);
2016
            if (is_null($value)) {
2017
                continue;
2018
            }
2019
2020 View Code Duplication
            if (strpos($content, $s) !== false) {
2021
                $content = str_replace($s, $value, $content);
2022
            } elseif($this->debug) {
2023
                $this->addLog('evalSnippets parse error', $_SERVER['REQUEST_URI'] . $s, 2);
2024
            }
2025
        }
2026
2027
        if ($this->dumpSnippets) {
2028
            $this->snippetsCode .= '</fieldset><br />';
2029
        }
2030
2031
        return $content;
2032
    }
2033
2034
    /**
2035
     * @param $value
2036
     * @return mixed|string
2037
     */
2038
    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...
2039
    { // Get super globals
2040
        $key = $value;
2041
        $_ = $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...
2042
        $this->config['enable_filter'] = 1;
2043
        list($key, $modifiers) = $this->splitKeyAndFilter($key);
2044
        $this->config['enable_filter'] = $_;
2045
        $key = str_replace(array('(', ')'), array("['", "']"), $key);
2046
        $key = rtrim($key, ';');
2047
        if (strpos($key, '$_SESSION') !== false) {
2048
            $_ = $_SESSION;
2049
            $key = str_replace('$_SESSION', '$_', $key);
2050
            if (isset($_['mgrFormValues'])) {
2051
                unset($_['mgrFormValues']);
2052
            }
2053
            if (isset($_['token'])) {
2054
                unset($_['token']);
2055
            }
2056
        }
2057
        if (strpos($key, '[') !== false) {
2058
            $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...
2059
        } 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...
2060
            $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...
2061
        } else {
2062
            $value = '';
2063
        }
2064
        if ($modifiers !== false) {
2065
            $value = $this->applyFilter($value, $modifiers, $key);
2066
        }
2067
        return $value;
2068
    }
2069
2070
    /**
2071
     * @param $piece
2072
     * @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...
2073
     */
2074
    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...
2075
    {
2076
        if (ltrim($piece) !== $piece) {
2077
            return '';
2078
        }
2079
2080
        $eventtime = $this->dumpSnippets ? $this->getMicroTime() : 0;
2081
2082
        $snip_call = $this->_split_snip_call($piece);
2083
        $key = $snip_call['name'];
2084
2085
        list($key, $modifiers) = $this->splitKeyAndFilter($key);
2086
        $snip_call['name'] = $key;
2087
        $snippetObject = $this->_getSnippetObject($key);
2088
        if (is_null($snippetObject['content'])) {
2089
            return null;
2090
        }
2091
2092
        $this->currentSnippet = $snippetObject['name'];
2093
2094
        // current params
2095
        $params = $this->getParamsFromString($snip_call['params']);
2096
2097
        if (!isset($snippetObject['properties'])) {
2098
            $snippetObject['properties'] = array();
2099
        }
2100
        $default_params = $this->parseProperties($snippetObject['properties'], $this->currentSnippet, 'snippet');
2101
        $params = array_merge($default_params, $params);
2102
2103
        $value = $this->evalSnippet($snippetObject['content'], $params);
2104
        $this->currentSnippet = '';
2105
        if ($modifiers !== false) {
2106
            $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...
2107
        }
2108
2109
        if ($this->dumpSnippets) {
2110
            $eventtime = $this->getMicroTime() - $eventtime;
2111
            $eventtime = sprintf('%2.2f ms', $eventtime * 1000);
2112
            $code = str_replace("\t", '  ', $this->htmlspecialchars($value));
2113
            $piece = str_replace("\t", '  ', $this->htmlspecialchars($piece));
2114
            $print_r_params = str_replace("\t", '  ', $this->htmlspecialchars('$modx->event->params = ' . print_r($params, true)));
2115
            $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);
2116
            $this->snippetsTime[] = array('sname' => $key, 'time' => $eventtime);
2117
        }
2118
        return $value;
2119
    }
2120
2121
    /**
2122
     * @param string $string
2123
     * @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...
2124
     */
2125
    public function getParamsFromString($string = '')
2126
    {
2127
        if (empty($string)) {
2128
            return array();
2129
        }
2130
2131
        if (strpos($string, '&_PHX_INTERNAL_') !== false) {
2132
            $string = str_replace(array('&_PHX_INTERNAL_091_&', '&_PHX_INTERNAL_093_&'), array('[', ']'), $string);
2133
        }
2134
2135
        $_ = $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...
2136
        $this->documentOutput = $string;
2137
        $this->invokeEvent('OnBeforeParseParams');
2138
        $string = $this->documentOutput;
2139
        $this->documentOutput = $_;
2140
2141
        $_tmp = $string;
2142
        $_tmp = ltrim($_tmp, '?&');
2143
        $temp_params = array();
2144
        $key = '';
2145
        $value = null;
2146
        while ($_tmp !== '') {
2147
            $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...
2148
            $char = substr($_tmp, 0, 1);
2149
            $_tmp = substr($_tmp, 1);
2150
2151
            if ($char === '=') {
2152
                $_tmp = trim($_tmp);
2153
                $delim = substr($_tmp, 0, 1);
2154
                if (in_array($delim, array('"', "'", '`'))) {
2155
                    $null = null;
2156
                    //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...
2157
                    list($null, $value, $_tmp) = explode($delim, $_tmp, 3);
2158
                    unset($null);
2159
2160
                    if (substr(trim($_tmp), 0, 2) === '//') {
2161
                        $_tmp = strstr(trim($_tmp), "\n");
2162
                    }
2163
                    $i = 0;
2164
                    while ($delim === '`' && substr(trim($_tmp), 0, 1) !== '&' && 1 < substr_count($_tmp, '`')) {
2165
                        list($inner, $outer, $_tmp) = explode('`', $_tmp, 3);
2166
                        $value .= "`{$inner}`{$outer}";
2167
                        $i++;
2168
                        if (100 < $i) {
2169
                            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...
2170
                        }
2171
                    }
2172
                    if ($i && $delim === '`') {
2173
                        $value = rtrim($value, '`');
2174
                    }
2175
                } elseif (strpos($_tmp, '&') !== false) {
2176
                    list($value, $_tmp) = explode('&', $_tmp, 2);
2177
                    $value = trim($value);
2178
                } else {
2179
                    $value = $_tmp;
2180
                    $_tmp = '';
2181
                }
2182
            } elseif ($char === '&') {
2183
                if (trim($key) !== '') {
2184
                    $value = '1';
2185
                } else {
2186
                    continue;
2187
                }
2188
            } elseif ($_tmp === '') {
2189
                $key .= $char;
2190
                $value = '1';
2191
            } elseif ($key !== '' || trim($char) !== '') {
2192
                $key .= $char;
2193
            }
2194
2195
            if (isset($value) && !is_null($value)) {
2196
                if (strpos($key, 'amp;') !== false) {
2197
                    $key = str_replace('amp;', '', $key);
2198
                }
2199
                $key = trim($key);
2200 View Code Duplication
                if (strpos($value, '[!') !== false) {
2201
                    $value = str_replace(array('[!', '!]'), array('[[', ']]'), $value);
2202
                }
2203
                $value = $this->mergeDocumentContent($value);
2204
                $value = $this->mergeSettingsContent($value);
2205
                $value = $this->mergeChunkContent($value);
2206
                $value = $this->evalSnippets($value);
2207
                if (substr($value, 0, 6) !== '@CODE:') {
2208
                    $value = $this->mergePlaceholderContent($value);
2209
                }
2210
2211
                $temp_params[][$key] = $value;
2212
2213
                $key = '';
2214
                $value = null;
2215
2216
                $_tmp = ltrim($_tmp, " ,\t");
2217
                if (substr($_tmp, 0, 2) === '//') {
2218
                    $_tmp = strstr($_tmp, "\n");
2219
                }
2220
            }
2221
2222
            if ($_tmp === $bt) {
2223
                $key = trim($key);
2224
                if ($key !== '') {
2225
                    $temp_params[][$key] = '';
2226
                }
2227
                break;
2228
            }
2229
        }
2230
2231
        foreach ($temp_params as $p) {
2232
            $k = key($p);
2233
            if (substr($k, -2) === '[]') {
2234
                $k = substr($k, 0, -2);
2235
                $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...
2236
            } elseif (strpos($k, '[') !== false && substr($k, -1) === ']') {
2237
                list($k, $subk) = explode('[', $k, 2);
2238
                $subk = substr($subk, 0, -1);
2239
                $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...
2240
            } else {
2241
                $params[$k] = current($p);
2242
            }
2243
        }
2244
        return $params;
2245
    }
2246
2247
    /**
2248
     * @param $str
2249
     * @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...
2250
     */
2251
    public function _getSplitPosition($str)
2252
    {
2253
        $closeOpt = false;
2254
        $maybePos = false;
2255
        $inFilter = false;
2256
        $pos = false;
2257
        $total = strlen($str);
2258
        for ($i = 0; $i < $total; $i++) {
2259
            $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...
2260
            $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...
2261
            if (!$inFilter) {
2262
                if ($c === ':') {
2263
                    $inFilter = true;
2264
                } elseif ($c === '?') {
2265
                    $pos = $i;
2266
                } elseif ($c === ' ') {
2267
                    $maybePos = $i;
2268
                } 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...
2269
                    $pos = $maybePos;
2270
                } elseif ($c === "\n") {
2271
                    $pos = $i;
2272
                } else {
2273
                    $pos = false;
2274
                }
2275
            } else {
2276
                if ($cc == $closeOpt) {
2277
                    $closeOpt = false;
2278
                } elseif ($c == $closeOpt) {
2279
                    $closeOpt = false;
2280
                } 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...
2281
                    continue;
2282
                } elseif ($cc === "('") {
2283
                    $closeOpt = "')";
2284
                } elseif ($cc === '("') {
2285
                    $closeOpt = '")';
2286
                } elseif ($cc === '(`') {
2287
                    $closeOpt = '`)';
2288
                } elseif ($c === '(') {
2289
                    $closeOpt = ')';
2290
                } elseif ($c === '?') {
2291
                    $pos = $i;
2292
                } elseif ($c === ' ' && strpos($str, '?') === false) {
2293
                    $pos = $i;
2294
                } else {
2295
                    $pos = false;
2296
                }
2297
            }
2298
            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...
2299
                break;
2300
            }
2301
        }
2302
        return $pos;
2303
    }
2304
2305
    /**
2306
     * @param $call
2307
     * @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...
2308
     */
2309
    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...
2310
    {
2311
        $spacer = md5('dummy');
2312 View Code Duplication
        if (strpos($call, ']]>') !== false) {
2313
            $call = str_replace(']]>', "]{$spacer}]>", $call);
2314
        }
2315
2316
        $splitPosition = $this->_getSplitPosition($call);
2317
2318
        if ($splitPosition !== false) {
2319
            $name = substr($call, 0, $splitPosition);
2320
            $params = substr($call, $splitPosition + 1);
2321
        } else {
2322
            $name = $call;
2323
            $params = '';
2324
        }
2325
2326
        $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...
2327 View Code Duplication
        if (strpos($params, $spacer) !== false) {
2328
            $params = str_replace("]{$spacer}]>", ']]>', $params);
2329
        }
2330
        $snip['params'] = ltrim($params, "?& \t\n");
2331
2332
        return $snip;
2333
    }
2334
2335
    /**
2336
     * @param $snip_name
2337
     * @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...
2338
     */
2339
    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...
2340
    {
2341
        if (isset($this->snippetCache[$snip_name])) {
2342
            $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...
2343
            $snippetObject['content'] = $this->snippetCache[$snip_name];
2344
            if (isset($this->snippetCache["{$snip_name}Props"])) {
2345
                if (!isset($this->snippetCache["{$snip_name}Props"])) {
2346
                    $this->snippetCache["{$snip_name}Props"] = '';
2347
                }
2348
                $snippetObject['properties'] = $this->snippetCache["{$snip_name}Props"];
2349
            }
2350
        } elseif (substr($snip_name, 0, 1) === '@' && isset($this->pluginEvent[trim($snip_name, '@')])) {
2351
            $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...
2352
            $snippetObject['content'] = sprintf('$rs=$this->invokeEvent("%s",$params);echo trim(implode("",$rs));', trim($snip_name, '@'));
2353
            $snippetObject['properties'] = '';
2354
        } else {
2355
            $where = sprintf("name='%s' AND disabled=0", $this->db->escape($snip_name));
2356
            $rs = $this->db->select('name,snippet,properties', '[+prefix+]site_snippets', $where);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rs. Configured minimum length is 3.

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

Loading history...
2357
            $count = $this->db->getRecordCount($rs);
2358
            if (1 < $count) {
2359
                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...
2360
            }
2361
            if ($count) {
2362
                $row = $this->db->getRow($rs);
2363
                $snip_content = $row['snippet'];
2364
                $snip_prop = $row['properties'];
2365
            } else {
2366
                $snip_content = null;
2367
                $snip_prop = '';
2368
            }
2369
            $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...
2370
            $snippetObject['content'] = $snip_content;
2371
            $snippetObject['properties'] = $snip_prop;
2372
            $this->snippetCache[$snip_name] = $snip_content;
2373
            $this->snippetCache["{$snip_name}Props"] = $snip_prop;
2374
        }
2375
        return $snippetObject;
2376
    }
2377
2378
    /**
2379
     * @param $text
2380
     * @return mixed
2381
     */
2382
    public function toAlias($text)
2383
    {
2384
        $suff = $this->config['friendly_url_suffix'];
2385
        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);
2386
    }
2387
2388
    /**
2389
     * makeFriendlyURL
2390
     *
2391
     * @desc Create an URL.
2392
     *
2393
     * @param $pre {string} - Friendly URL Prefix. @required
2394
     * @param $suff {string} - Friendly URL Suffix. @required
2395
     * @param $alias {string} - Full document path. @required
2396
     * @param int $isfolder {0; 1}
2397
     * - Is it a folder? Default: 0.
2398
     * @param int $id {integer}
2399
     * - Document id. Default: 0.
2400
     * @return mixed|string {string} - Result URL.
2401
     * - Result URL.
2402
     */
2403
    public function makeFriendlyURL($pre, $suff, $alias, $isfolder = 0, $id = 0)
2404
    {
2405
        if ($id == $this->config['site_start'] && $this->config['seostrict'] === '1') {
2406
            $url = $this->config['base_url'];
2407
        } else {
2408
            $Alias = explode('/', $alias);
2409
            $alias = array_pop($Alias);
2410
            $dir = implode('/', $Alias);
2411
            unset($Alias);
2412
2413
            if ($this->config['make_folders'] === '1' && $isfolder == 1) {
2414
                $suff = '/';
2415
            }
2416
2417
            $url = ($dir != '' ? $dir . '/' : '') . $pre . $alias . $suff;
2418
        }
2419
2420
        $evtOut = $this->invokeEvent('OnMakeDocUrl', array(
2421
            'id' => $id,
2422
            'url' => $url
2423
        ));
2424
2425
        if (is_array($evtOut) && count($evtOut) > 0) {
2426
            $url = array_pop($evtOut);
2427
        }
2428
2429
        return $url;
2430
    }
2431
2432
    /**
2433
     * Convert URL tags [~...~] to URLs
2434
     *
2435
     * @param string $documentSource
2436
     * @return string
2437
     */
2438
    public function rewriteUrls($documentSource)
2439
    {
2440
        // rewrite the urls
2441
        if ($this->config['friendly_urls'] == 1) {
2442
            $aliases = array();
2443
            if (is_array($this->documentListing)) {
2444
                foreach ($this->documentListing as $path => $docid) { // This is big Loop on large site!
2445
                    $aliases[$docid] = $path;
2446
                    $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...
2447
                }
2448
            }
2449
2450
            if ($this->config['aliaslistingfolder'] == 1) {
2451
                preg_match_all('!\[\~([0-9]+)\~\]!ise', $documentSource, $match);
2452
                $ids = implode(',', array_unique($match['1']));
2453
                if ($ids) {
2454
                    $res = $this->db->select("id,alias,isfolder,parent,alias_visible", $this->getFullTableName('site_content'), "id IN (" . $ids . ") AND isfolder = '0'");
2455
                    while ($row = $this->db->getRow($res)) {
2456
                        if ($this->config['use_alias_path'] == '1' && $row['parent'] != 0) {
2457
                            $parent = $row['parent'];
2458
                            $path = $aliases[$parent];
2459
2460
                            while (isset($this->aliasListing[$parent]) && $this->aliasListing[$parent]['alias_visible'] == 0) {
2461
                                $path = $this->aliasListing[$parent]['path'];
2462
                                $parent = $this->aliasListing[$parent]['parent'];
2463
                            }
2464
2465
                            $aliases[$row['id']] = $path . '/' . $row['alias'];
2466
                        } else {
2467
                            $aliases[$row['id']] = $row['alias'];
2468
                        }
2469
                        $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...
2470
                    }
2471
                }
2472
            }
2473
            $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...
2474
            $isfriendly = ($this->config['friendly_alias_urls'] == 1 ? 1 : 0);
2475
            $pref = $this->config['friendly_url_prefix'];
2476
            $suff = $this->config['friendly_url_suffix'];
2477
            $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...
2478
                global $modx;
2479
                $thealias = $aliases[$m[1]];
2480
                $thefolder = $isfolder[$m[1]];
2481
                if ($isfriendly && isset($thealias)) {
2482
                    //found friendly url
2483
                    $out = ($modx->config['seostrict'] == '1' ? $modx->toAlias($modx->makeFriendlyURL($pref, $suff, $thealias, $thefolder, $m[1])) : $modx->makeFriendlyURL($pref, $suff, $thealias, $thefolder, $m[1]));
2484
                } else {
2485
                    //not found friendly url
2486
                    $out = $modx->makeFriendlyURL($pref, $suff, $m[1]);
2487
                }
2488
                return $out;
2489
            }, $documentSource);
2490
2491
        } else {
2492
            $in = '!\[\~([0-9]+)\~\]!is';
2493
            $out = "index.php?id=" . '\1';
2494
            $documentSource = preg_replace($in, $out, $documentSource);
2495
        }
2496
2497
        return $documentSource;
2498
    }
2499
2500
    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...
2501
    {
2502
        $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...
2503
        // FIX URLs
2504
        if (empty($this->documentIdentifier) || $this->config['seostrict'] == '0' || $this->config['friendly_urls'] == '0') {
2505
            return;
2506
        }
2507
        if ($this->config['site_status'] == 0) {
2508
            return;
2509
        }
2510
2511
        $scheme = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https' : 'http';
2512
        $len_base_url = strlen($this->config['base_url']);
2513
2514
        $url_path = $q;//LANG
2515
2516 View Code Duplication
        if (substr($url_path, 0, $len_base_url) === $this->config['base_url']) {
2517
            $url_path = substr($url_path, $len_base_url);
2518
        }
2519
2520
        $strictURL = $this->toAlias($this->makeUrl($this->documentIdentifier));
2521
2522 View Code Duplication
        if (substr($strictURL, 0, $len_base_url) === $this->config['base_url']) {
2523
            $strictURL = substr($strictURL, $len_base_url);
2524
        }
2525
        $http_host = $_SERVER['HTTP_HOST'];
2526
        $requestedURL = "{$scheme}://{$http_host}" . '/' . $q; //LANG
2527
2528
        $site_url = $this->config['site_url'];
2529
        $url_query_string = explode('?', $_SERVER['REQUEST_URI']);
2530
        // Strip conflicting id/q from query string
2531
        $qstring = !empty($url_query_string[1]) ? preg_replace("#(^|&)(q|id)=[^&]+#", '', $url_query_string[1]) : '';
2532
2533
        if ($this->documentIdentifier == $this->config['site_start']) {
2534
            if ($requestedURL != $this->config['site_url']) {
2535
                // Force redirect of site start
2536
                // $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...
2537
                if ($qstring) {
2538
                    $url = "{$site_url}?{$qstring}";
2539
                } else {
2540
                    $url = $site_url;
2541
                }
2542
                if ($this->config['base_url'] != $_SERVER['REQUEST_URI']) {
2543
                    if (empty($_POST)) {
2544
                        if (($this->config['base_url'] . '?' . $qstring) != $_SERVER['REQUEST_URI']) {
2545
                            $this->sendRedirect($url, 0, 'REDIRECT_HEADER', 'HTTP/1.0 301 Moved Permanently');
2546
                            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...
2547
                        }
2548
                    }
2549
                }
2550
            }
2551
        } elseif ($url_path != $strictURL && $this->documentIdentifier != $this->config['error_page']) {
2552
            // Force page redirect
2553
            //$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...
2554
            if (!empty($qstring)) {
2555
                $url = "{$site_url}{$strictURL}?{$qstring}";
2556
            } else {
2557
                $url = "{$site_url}{$strictURL}";
2558
            }
2559
            $this->sendRedirect($url, 0, 'REDIRECT_HEADER', 'HTTP/1.0 301 Moved Permanently');
2560
            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...
2561
        }
2562
        return;
2563
    }
2564
2565
    /**
2566
     * Get all db fields and TVs for a document/resource
2567
     *
2568
     * @param string $method
2569
     * @param mixed $identifier
2570
     * @param bool $isPrepareResponse
2571
     * @return array
2572
     */
2573
    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...
2574
    {
2575
2576
        $cacheKey = md5(print_r(func_get_args(), true));
2577
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
2578
            return $this->tmpCache[__FUNCTION__][$cacheKey];
2579
        }
2580
2581
        $tblsc = $this->getFullTableName("site_content");
2582
        $tbldg = $this->getFullTableName("document_groups");
2583
2584
        // allow alias to be full path
2585
        if ($method == 'alias') {
2586
            $identifier = $this->cleanDocumentIdentifier($identifier);
2587
            $method = $this->documentMethod;
2588
        }
2589
        if ($method == 'alias' && $this->config['use_alias_path'] && array_key_exists($identifier, $this->documentListing)) {
2590
            $method = 'id';
2591
            $identifier = $this->documentListing[$identifier];
2592
        }
2593
2594
        $out = $this->invokeEvent('OnBeforeLoadDocumentObject', compact('method', 'identifier'));
2595
        if (is_array($out) && is_array($out[0])) {
2596
            $documentObject = $out[0];
2597
        } else {
2598
            // get document groups for current user
2599
            if ($docgrp = $this->getUserDocGroups()) {
2600
                $docgrp = implode(",", $docgrp);
2601
            }
2602
            // get document
2603
            $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
2604
            $rs = $this->db->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...
2605
                LEFT JOIN {$tbldg} dg ON dg.document = sc.id", "sc.{$method} = '{$identifier}' AND ({$access})", "", 1);
2606
            if ($this->db->getRecordCount($rs) < 1) {
2607
                $seclimit = 0;
2608
                if ($this->config['unauthorized_page']) {
2609
                    // method may still be alias, while identifier is not full path alias, e.g. id not found above
2610
                    if ($method === 'alias') {
2611
                        $secrs = $this->db->select('count(dg.id)', "{$tbldg} as dg, {$tblsc} as sc", "dg.document = sc.id AND sc.alias = '{$identifier}'", '', 1);
2612
                    } else {
2613
                        $secrs = $this->db->select('count(id)', $tbldg, "document = '{$identifier}'", '', 1);
2614
                    }
2615
                    // check if file is not public
2616
                    $seclimit = $this->db->getValue($secrs);
2617
                }
2618
                if ($seclimit > 0) {
2619
                    // match found but not publicly accessible, send the visitor to the unauthorized_page
2620
                    $this->sendUnauthorizedPage();
2621
                    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...
2622
                } else {
2623
                    $this->sendErrorPage();
2624
                    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...
2625
                }
2626
            }
2627
            # 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...
2628
            $documentObject = $this->db->getRow($rs);
2629
2630
            if ($isPrepareResponse === 'prepareResponse') {
2631
                $this->documentObject = &$documentObject;
2632
            }
2633
            $out = $this->invokeEvent('OnLoadDocumentObject', compact('method', 'identifier', 'documentObject'));
2634
            if (is_array($out) && is_array($out[0])) {
2635
                $documentObject = $out[0];
2636
            }
2637
            if ($documentObject['template']) {
2638
                // load TVs and merge with document - Orig by Apodigm - Docvars
2639
                $rs = $this->db->select("tv.*, IF(tvc.value!='',tvc.value,tv.default_text) as value", $this->getFullTableName("site_tmplvars") . " tv
2640
                INNER JOIN " . $this->getFullTableName("site_tmplvar_templates") . " tvtpl ON tvtpl.tmplvarid = tv.id
2641
                LEFT JOIN " . $this->getFullTableName("site_tmplvar_contentvalues") . " tvc ON tvc.tmplvarid=tv.id AND tvc.contentid = '{$documentObject['id']}'", "tvtpl.templateid = '{$documentObject['template']}'");
2642
                $tmplvars = array();
2643
                while ($row = $this->db->getRow($rs)) {
2644
                    $tmplvars[$row['name']] = array(
2645
                        $row['name'],
2646
                        $row['value'],
2647
                        $row['display'],
2648
                        $row['display_params'],
2649
                        $row['type']
2650
                    );
2651
                }
2652
                $documentObject = array_merge($documentObject, $tmplvars);
2653
            }
2654
            $out = $this->invokeEvent('OnAfterLoadDocumentObject', compact('method', 'identifier', 'documentObject'));
2655
            if (is_array($out) && array_key_exists(0, $out) !== false && is_array($out[0])) {
2656
                $documentObject = $out[0];
2657
            }
2658
        }
2659
2660
        $this->tmpCache[__FUNCTION__][$cacheKey] = $documentObject;
2661
2662
        return $documentObject;
2663
    }
2664
2665
    /**
2666
     * Parse a source string.
2667
     *
2668
     * Handles most MODX tags. Exceptions include:
2669
     *   - uncached snippet tags [!...!]
2670
     *   - URL tags [~...~]
2671
     *
2672
     * @param string $source
2673
     * @return string
2674
     */
2675
    public function parseDocumentSource($source)
2676
    {
2677
        // set the number of times we are to parse the document source
2678
        $this->minParserPasses = empty ($this->minParserPasses) ? 2 : $this->minParserPasses;
2679
        $this->maxParserPasses = empty ($this->maxParserPasses) ? 10 : $this->maxParserPasses;
2680
        $passes = $this->minParserPasses;
2681
        for ($i = 0; $i < $passes; $i++) {
2682
            // get source length if this is the final pass
2683
            if ($i == ($passes - 1)) {
2684
                $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...
2685
            }
2686
            if ($this->dumpSnippets == 1) {
2687
                $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>";
2688
            }
2689
2690
            // invoke OnParseDocument event
2691
            $this->documentOutput = $source; // store source code so plugins can
2692
            $this->invokeEvent("OnParseDocument"); // work on it via $modx->documentOutput
2693
            $source = $this->documentOutput;
2694
2695
            if ($this->config['enable_at_syntax']) {
2696
                $source = $this->ignoreCommentedTagsContent($source);
2697
                $source = $this->mergeConditionalTagsContent($source);
2698
            }
2699
2700
            $source = $this->mergeSettingsContent($source);
2701
            $source = $this->mergeDocumentContent($source);
2702
            $source = $this->mergeChunkContent($source);
2703
            $source = $this->evalSnippets($source);
2704
            $source = $this->mergePlaceholderContent($source);
2705
2706
            if ($this->dumpSnippets == 1) {
2707
                $this->snippetsCode .= "</fieldset><br />";
2708
            }
2709
            if ($i == ($passes - 1) && $i < ($this->maxParserPasses - 1)) {
2710
                // check if source content was changed
2711
                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...
2712
                    $passes++;
2713
                } // if content change then increase passes because
2714
            } // we have not yet reached maxParserPasses
2715
        }
2716
        return $source;
2717
    }
2718
2719
    /**
2720
     * Starts the parsing operations.
2721
     *
2722
     * - connects to the db
2723
     * - gets the settings (including system_settings)
2724
     * - gets the document/resource identifier as in the query string
2725
     * - finally calls prepareResponse()
2726
     */
2727
    public function executeParser()
2728
    {
2729
        if(MODX_CLI) {
2730
            throw new \RuntimeException('Call DocumentParser::executeParser on CLI mode');
2731
        }
2732
2733
        //error_reporting(0);
2734
        set_error_handler(array(
2735
            & $this,
2736
            "phpError"
2737
        ), E_ALL);
2738
        $this->db->connect();
2739
2740
        // get the settings
2741
        if (empty ($this->config)) {
2742
            $this->getSettings();
2743
        }
2744
2745
        $this->_IIS_furl_fix(); // IIS friendly url fix
2746
2747
        // check site settings
2748
        if ($this->checkSiteStatus()) {
2749
            // make sure the cache doesn't need updating
2750
            $this->updatePubStatus();
2751
2752
            // find out which document we need to display
2753
            $this->documentMethod = filter_input(INPUT_GET, 'q') ? 'alias' : 'id';
2754
            $this->documentIdentifier = $this->getDocumentIdentifier($this->documentMethod);
2755
        } else {
2756
            header('HTTP/1.0 503 Service Unavailable');
2757
            $this->systemCacheKey = 'unavailable';
2758
            if (!$this->config['site_unavailable_page']) {
2759
                // display offline message
2760
                $this->documentContent = $this->config['site_unavailable_message'];
2761
                $this->outputContent();
2762
                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...
2763
            } else {
2764
                // setup offline page document settings
2765
                $this->documentMethod = 'id';
2766
                $this->documentIdentifier = $this->config['site_unavailable_page'];
2767
            }
2768
        }
2769
2770
        if ($this->documentMethod == "alias") {
2771
            $this->documentIdentifier = $this->cleanDocumentIdentifier($this->documentIdentifier);
2772
2773
            // Check use_alias_path and check if $this->virtualDir is set to anything, then parse the path
2774
            if ($this->config['use_alias_path'] == 1) {
2775
                $alias = (strlen($this->virtualDir) > 0 ? $this->virtualDir . '/' : '') . $this->documentIdentifier;
2776
                if (isset($this->documentListing[$alias])) {
2777
                    $this->documentIdentifier = $this->documentListing[$alias];
2778
                } else {
2779
                    //@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...
2780
                    if ($this->config['aliaslistingfolder'] == 1) {
2781
                        $tbl_site_content = $this->getFullTableName('site_content');
2782
2783
                        $parentId = $this->getIdFromAlias($this->virtualDir);
2784
                        $parentId = ($parentId > 0) ? $parentId : '0';
2785
2786
                        $docAlias = $this->db->escape($this->documentIdentifier);
2787
2788
                        $rs = $this->db->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...
2789
                        if ($this->db->getRecordCount($rs) == 0) {
2790
                            $this->sendErrorPage();
2791
                        }
2792
                        $docId = $this->db->getValue($rs);
2793
2794
                        if (!$docId) {
2795
                            $alias = $this->q;
2796
                            if (!empty($this->config['friendly_url_suffix'])) {
2797
                                $pos = strrpos($alias, $this->config['friendly_url_suffix']);
2798
2799
                                if ($pos !== false) {
2800
                                    $alias = substr($alias, 0, $pos);
2801
                                }
2802
                            }
2803
                            $docId = $this->getIdFromAlias($alias);
2804
                        }
2805
2806
                        if ($docId > 0) {
2807
                            $this->documentIdentifier = $docId;
2808
                        } else {
2809
                            /*
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...
2810
                            $rs  = $this->db->select('id', $tbl_site_content, "deleted=0 and alias='{$docAlias}'");
2811
                            if($this->db->getRecordCount($rs)==0)
2812
                            {
2813
                                $rs  = $this->db->select('id', $tbl_site_content, "deleted=0 and id='{$docAlias}'");
2814
                            }
2815
                            $docId = $this->db->getValue($rs);
2816
2817
                            if ($docId > 0)
2818
                            {
2819
                                $this->documentIdentifier = $docId;
2820
2821
                            }else{
2822
                            */
2823
                            $this->sendErrorPage();
2824
                            //}
2825
                        }
2826
                    } else {
2827
                        $this->sendErrorPage();
2828
                    }
2829
                }
2830
            } else {
2831
                if (isset($this->documentListing[$this->documentIdentifier])) {
2832
                    $this->documentIdentifier = $this->documentListing[$this->documentIdentifier];
2833
                } else {
2834
                    $docAlias = $this->db->escape($this->documentIdentifier);
2835
                    $rs = $this->db->select('id', $this->getFullTableName('site_content'), "deleted=0 and alias='{$docAlias}'");
2836
                    $this->documentIdentifier = (int)$this->db->getValue($rs);
2837
                }
2838
            }
2839
            $this->documentMethod = 'id';
2840
        }
2841
2842
        //$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...
2843
        // invoke OnWebPageInit event
2844
        $this->invokeEvent("OnWebPageInit");
2845
        // invoke OnLogPageView event
2846
        if ($this->config['track_visitors'] == 1) {
2847
            $this->invokeEvent("OnLogPageHit");
2848
        }
2849
        if ($this->config['seostrict'] === '1') {
2850
            $this->sendStrictURI();
2851
        }
2852
        $this->prepareResponse();
2853
    }
2854
2855
    /**
2856
     * @param $path
2857
     * @param null $suffix
2858
     * @return mixed
2859
     */
2860
    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...
2861
    {
2862
        $exp = explode('/', $path);
2863
        return str_replace($suffix, '', end($exp));
2864
    }
2865
2866
    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...
2867
    {
2868
        if ($this->config['friendly_urls'] != 1) {
2869
            return;
2870
        }
2871
2872
        if (strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') === false) {
2873
            return;
2874
        }
2875
2876
        $url = $_SERVER['QUERY_STRING'];
2877
        $err = substr($url, 0, 3);
2878
        if ($err !== '404' && $err !== '405') {
2879
            return;
2880
        }
2881
2882
        $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...
2883
        unset ($_GET[$k[0]]);
2884
        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...
2885
        $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...
2886
        $_SERVER['QUERY_STRING'] = $qp['query'];
2887
        if (!empty ($qp['query'])) {
2888
            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...
2889
            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...
2890
                $_REQUEST[$n] = $_GET[$n] = $v;
2891
            }
2892
        }
2893
        $_SERVER['PHP_SELF'] = $this->config['base_url'] . $qp['path'];
2894
        $this->q = $qp['path'];
2895
        return $qp['path'];
2896
    }
2897
2898
    /**
2899
     * The next step called at the end of executeParser()
2900
     *
2901
     * - checks cache
2902
     * - checks if document/resource is deleted/unpublished
2903
     * - checks if resource is a weblink and redirects if so
2904
     * - gets template and parses it
2905
     * - ensures that postProcess is called when PHP is finished
2906
     */
2907
    public function prepareResponse()
2908
    {
2909
        // we now know the method and identifier, let's check the cache
2910
2911
        if ($this->config['enable_cache'] == 2 && $this->isLoggedIn()) {
2912
            $this->config['enable_cache'] = 0;
2913
        }
2914
2915
        if ($this->config['enable_cache']) {
2916
            $this->documentContent = $this->getDocumentObjectFromCache($this->documentIdentifier, true);
2917
        } else {
2918
            $this->documentContent = '';
2919
        }
2920
2921
        if ($this->documentContent == '') {
2922
            // get document object from DB
2923
            $this->documentObject = $this->getDocumentObject($this->documentMethod, $this->documentIdentifier, 'prepareResponse');
2924
2925
            // write the documentName to the object
2926
            $this->documentName = &$this->documentObject['pagetitle'];
2927
2928
            // check if we should not hit this document
2929
            if ($this->documentObject['donthit'] == 1) {
2930
                $this->config['track_visitors'] = 0;
2931
            }
2932
2933
            if ($this->documentObject['deleted'] == 1) {
2934
                $this->sendErrorPage();
2935
            } // validation routines
2936
            elseif ($this->documentObject['published'] == 0) {
2937
                $this->_sendErrorForUnpubPage();
2938
            } elseif ($this->documentObject['type'] == 'reference') {
2939
                $this->_sendRedirectForRefPage($this->documentObject['content']);
2940
            }
2941
2942
            // get the template and start parsing!
2943
            if (!$this->documentObject['template']) {
2944
                $templateCode = '[*content*]';
2945
            } // use blank template
2946
            else {
2947
                $templateCode = $this->_getTemplateCodeFromDB($this->documentObject['template']);
2948
            }
2949
2950
            if (substr($templateCode, 0, 8) === '@INCLUDE') {
2951
                $templateCode = $this->atBindInclude($templateCode);
2952
            }
2953
2954
2955
            $this->documentContent = &$templateCode;
2956
2957
            // invoke OnLoadWebDocument event
2958
            $this->invokeEvent('OnLoadWebDocument');
2959
2960
            // Parse document source
2961
            $this->documentContent = $this->parseDocumentSource($templateCode);
2962
2963
            $this->documentGenerated = 1;
2964
        } else {
2965
            $this->documentGenerated = 0;
2966
        }
2967
2968
        if ($this->config['error_page'] == $this->documentIdentifier && $this->config['error_page'] != $this->config['site_start']) {
2969
            header('HTTP/1.0 404 Not Found');
2970
        }
2971
2972
        register_shutdown_function(array(
2973
            &$this,
2974
            'postProcess'
2975
        )); // tell PHP to call postProcess when it shuts down
2976
        $this->outputContent();
2977
        //$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...
2978
    }
2979
2980
    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...
2981
    {
2982
        // Can't view unpublished pages !$this->checkPreview()
2983
        if (!$this->hasPermission('view_unpublished')) {
2984
            $this->sendErrorPage();
2985
        } else {
2986
            $udperms = new Legacy\Permissions();
2987
            $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...
2988
            $udperms->document = $this->documentIdentifier;
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->documentIdentifier can also be of type string. 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...
2989
            $udperms->role = $_SESSION['mgrRole'];
2990
            // Doesn't have access to this document
2991
            if (!$udperms->checkPermissions()) {
2992
                $this->sendErrorPage();
2993
            }
2994
        }
2995
    }
2996
2997
    /**
2998
     * @param $url
2999
     */
3000
    public function _sendRedirectForRefPage($url)
3001
    {
3002
        // check whether it's a reference
3003
        if (preg_match('@^[1-9][0-9]*$@', $url)) {
3004
            $url = $this->makeUrl($url); // if it's a bare document id
3005
        } elseif (strpos($url, '[~') !== false) {
3006
            $url = $this->rewriteUrls($url); // if it's an internal docid tag, process it
3007
        }
3008
        $this->sendRedirect($url, 0, '', 'HTTP/1.0 301 Moved Permanently');
3009
        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...
3010
    }
3011
3012
    /**
3013
     * @param $templateID
3014
     * @return mixed
3015
     */
3016
    public function _getTemplateCodeFromDB($templateID)
3017
    {
3018
        $rs = $this->db->select('content', '[+prefix+]site_templates', "id = '{$templateID}'");
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rs. Configured minimum length is 3.

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

Loading history...
3019
        if ($this->db->getRecordCount($rs) == 1) {
3020
            return $this->db->getValue($rs);
3021
        } else {
3022
            $this->messageQuit('Incorrect number of templates returned from database');
3023
        }
3024
    }
3025
3026
    /**
3027
     * Returns an array of all parent record IDs for the id passed.
3028
     *
3029
     * @param int $id Docid to get parents for.
3030
     * @param int $height The maximum number of levels to go up, default 10.
3031
     * @return array
3032
     */
3033
    public function getParentIds($id, $height = 10)
3034
    {
3035
        $parents = array();
3036
        while ($id && $height--) {
3037
            $thisid = $id;
3038
            if ($this->config['aliaslistingfolder'] == 1) {
3039
                $id = isset($this->aliasListing[$id]['parent']) ? $this->aliasListing[$id]['parent'] : $this->db->getValue("SELECT `parent` FROM " . $this->getFullTableName("site_content") . " WHERE `id` = '{$id}' LIMIT 0,1");
3040
                if (!$id || $id == '0') {
3041
                    break;
3042
                }
3043
            } else {
3044
                $id = $this->aliasListing[$id]['parent'];
3045
                if (!$id) {
3046
                    break;
3047
                }
3048
            }
3049
            $parents[$thisid] = $id;
3050
        }
3051
        return $parents;
3052
    }
3053
3054
    /**
3055
     * @param $id
3056
     * @param int $top
3057
     * @return mixed
3058
     */
3059
    public function getUltimateParentId($id, $top = 0)
3060
    {
3061
        $i = 0;
3062
        while ($id && $i < 20) {
3063
            if ($top == $this->aliasListing[$id]['parent']) {
3064
                break;
3065
            }
3066
            $id = $this->aliasListing[$id]['parent'];
3067
            $i++;
3068
        }
3069
        return $id;
3070
    }
3071
3072
    /**
3073
     * Returns an array of child IDs belonging to the specified parent.
3074
     *
3075
     * @param int $id The parent resource/document to start from
3076
     * @param int $depth How many levels deep to search for children, default: 10
3077
     * @param array $children Optional array of docids to merge with the result.
3078
     * @return array Contains the document Listing (tree) like the sitemap
3079
     */
3080
    public function getChildIds($id, $depth = 10, $children = array())
3081
    {
3082
3083
        $cacheKey = md5(print_r(func_get_args(), true));
3084
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3085
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3086
        }
3087
3088
        if ($this->config['aliaslistingfolder'] == 1) {
3089
3090
            $res = $this->db->select("id,alias,isfolder,parent", $this->getFullTableName('site_content'), "parent IN (" . $id . ") AND deleted = '0'");
3091
            $idx = array();
3092
            while ($row = $this->db->getRow($res)) {
3093
                $pAlias = '';
3094
                if (isset($this->aliasListing[$row['parent']])) {
3095
                    $pAlias .= !empty($this->aliasListing[$row['parent']]['path']) ? $this->aliasListing[$row['parent']]['path'] . '/' : '';
3096
                    $pAlias .= !empty($this->aliasListing[$row['parent']]['alias']) ? $this->aliasListing[$row['parent']]['alias'] . '/' : '';
3097
                };
3098
                $children[$pAlias . $row['alias']] = $row['id'];
3099
                if ($row['isfolder'] == 1) {
3100
                    $idx[] = $row['id'];
3101
                }
3102
            }
3103
            $depth--;
3104
            $idx = implode(',', $idx);
3105
            if (!empty($idx)) {
3106
                if ($depth) {
3107
                    $children = $this->getChildIds($idx, $depth, $children);
3108
                }
3109
            }
3110
            $this->tmpCache[__FUNCTION__][$cacheKey] = $children;
3111
            return $children;
3112
3113
        } else {
3114
3115
            // Initialise a static array to index parents->children
3116
            static $documentMap_cache = array();
3117
            if (!count($documentMap_cache)) {
3118
                foreach ($this->documentMap as $document) {
3119
                    foreach ($document as $p => $c) {
3120
                        $documentMap_cache[$p][] = $c;
3121
                    }
3122
                }
3123
            }
3124
3125
            // Get all the children for this parent node
3126
            if (isset($documentMap_cache[$id])) {
3127
                $depth--;
3128
3129
                foreach ($documentMap_cache[$id] as $childId) {
3130
                    $pkey = (strlen($this->aliasListing[$childId]['path']) ? "{$this->aliasListing[$childId]['path']}/" : '') . $this->aliasListing[$childId]['alias'];
3131
                    if (!strlen($pkey)) {
3132
                        $pkey = "{$childId}";
3133
                    }
3134
                    $children[$pkey] = $childId;
3135
3136
                    if ($depth && isset($documentMap_cache[$childId])) {
3137
                        $children += $this->getChildIds($childId, $depth);
3138
                    }
3139
                }
3140
            }
3141
            $this->tmpCache[__FUNCTION__][$cacheKey] = $children;
3142
            return $children;
3143
3144
        }
3145
    }
3146
3147
    /**
3148
     * Displays a javascript alert message in the web browser and quit
3149
     *
3150
     * @param string $msg Message to show
3151
     * @param string $url URL to redirect to
3152
     */
3153
    public function webAlertAndQuit($msg, $url = '')
3154
    {
3155
        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...
3156
        switch (true) {
3157
            case (0 === stripos($url, 'javascript:')):
3158
                $fnc = substr($url, 11);
3159
                break;
3160
            case $url === '#':
3161
                $fnc = '';
3162
                break;
3163
            case empty($url):
3164
                $fnc = 'history.back(-1);';
3165
                break;
3166
            default:
3167
                $fnc = "window.location.href='" . addslashes($url) . "';";
3168
        }
3169
3170
        echo "<html><head>
3171
            <title>MODX :: Alert</title>
3172
            <meta http-equiv=\"Content-Type\" content=\"text/html; charset={$modx_manager_charset};\">
3173
            <script>
3174
                function __alertQuit() {
3175
                    var el = document.querySelector('p');
3176
                    alert(el.innerHTML);
3177
                    el.remove();
3178
                    {$fnc}
3179
                }
3180
                window.setTimeout('__alertQuit();',100);
3181
            </script>
3182
            </head><body>
3183
            <p>{$msg}</p>
3184
            </body></html>";
3185
        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...
3186
    }
3187
3188
    /**
3189
     * Returns 1 if user has the currect permission
3190
     *
3191
     * @param string $pm Permission name
3192
     * @return int Why not bool?
3193
     */
3194
    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...
3195
    {
3196
        $state = 0;
3197
        $pms = $_SESSION['mgrPermissions'];
3198
        if ($pms) {
3199
            $state = ((bool)$pms[$pm] === true);
3200
        }
3201
        return (int)$state;
3202
    }
3203
3204
    /**
3205
     * Returns true if element is locked
3206
     *
3207
     * @param int $type Types: 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3208
     * @param int $id Element- / Resource-id
3209
     * @param bool $includeThisUser true = Return also info about actual user
3210
     * @return array lock-details or null
3211
     */
3212
    public function elementIsLocked($type, $id, $includeThisUser = false)
3213
    {
3214
        $id = (int)$id;
3215
        $type = (int)$type;
3216
        if (!$type || !$id) {
3217
            return null;
3218
        }
3219
3220
        // Build lockedElements-Cache at first call
3221
        $this->buildLockedElementsCache();
3222
3223
        if (!$includeThisUser && $this->lockedElements[$type][$id]['sid'] == $this->sid) {
3224
            return null;
3225
        }
3226
3227
        if (isset($this->lockedElements[$type][$id])) {
3228
            return $this->lockedElements[$type][$id];
3229
        } else {
3230
            return null;
3231
        }
3232
    }
3233
3234
    /**
3235
     * Returns Locked Elements as Array
3236
     *
3237
     * @param int $type Types: 0=all, 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3238
     * @param bool $minimumDetails true =
3239
     * @return array|mixed|null
3240
     */
3241
    public function getLockedElements($type = 0, $minimumDetails = false)
3242
    {
3243
        $this->buildLockedElementsCache();
3244
3245
        if (!$minimumDetails) {
3246
            $lockedElements = $this->lockedElements;
3247
        } else {
3248
            // Minimum details for HTML / Ajax-requests
3249
            $lockedElements = array();
3250
            foreach ($this->lockedElements as $elType => $elements) {
3251
                foreach ($elements as $elId => $el) {
3252
                    $lockedElements[$elType][$elId] = array(
3253
                        'username' => $el['username'],
3254
                        'lasthit_df' => $el['lasthit_df'],
3255
                        'state' => $this->determineLockState($el['internalKey'])
3256
                    );
3257
                }
3258
            }
3259
        }
3260
3261
        if ($type == 0) {
3262
            return $lockedElements;
3263
        }
3264
3265
        $type = (int)$type;
3266
        if (isset($lockedElements[$type])) {
3267
            return $lockedElements[$type];
3268
        } else {
3269
            return array();
3270
        }
3271
    }
3272
3273
    /**
3274
     * Builds the Locked Elements Cache once
3275
     */
3276
    public function buildLockedElementsCache()
3277
    {
3278
        if (is_null($this->lockedElements)) {
3279
            $this->lockedElements = array();
3280
            $this->cleanupExpiredLocks();
3281
3282
            $rs = $this->db->select('sid,internalKey,elementType,elementId,lasthit,username', $this->getFullTableName('active_user_locks') . " ul
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rs. Configured minimum length is 3.

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

Loading history...
3283
                LEFT JOIN {$this->getFullTableName('manager_users')} mu on ul.internalKey = mu.id");
3284
            while ($row = $this->db->getRow($rs)) {
3285
                $this->lockedElements[$row['elementType']][$row['elementId']] = array(
3286
                    'sid' => $row['sid'],
3287
                    'internalKey' => $row['internalKey'],
3288
                    'username' => $row['username'],
3289
                    'elementType' => $row['elementType'],
3290
                    'elementId' => $row['elementId'],
3291
                    'lasthit' => $row['lasthit'],
3292
                    'lasthit_df' => $this->toDateFormat($row['lasthit']),
3293
                    'state' => $this->determineLockState($row['sid'])
3294
                );
3295
            }
3296
        }
3297
    }
3298
3299
    /**
3300
     * Cleans up the active user locks table
3301
     */
3302
    public function cleanupExpiredLocks()
3303
    {
3304
        // Clean-up active_user_sessions first
3305
        $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
3306
        $validSessionTimeLimit = $this->time - $timeout;
3307
        $this->db->delete($this->getFullTableName('active_user_sessions'), "lasthit < {$validSessionTimeLimit}");
3308
3309
        // Clean-up active_user_locks
3310
        $rs = $this->db->select('sid,internalKey', $this->getFullTableName('active_user_sessions'));
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rs. Configured minimum length is 3.

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

Loading history...
3311
        $count = $this->db->getRecordCount($rs);
3312
        if ($count) {
3313
            $rs = $this->db->makeArray($rs);
3314
            $userSids = array();
3315
            foreach ($rs as $row) {
0 ignored issues
show
Bug introduced by
The expression $rs of type false|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...
3316
                $userSids[] = $row['sid'];
3317
            }
3318
            $userSids = "'" . implode("','", $userSids) . "'";
3319
            $this->db->delete($this->getFullTableName('active_user_locks'), "sid NOT IN({$userSids})");
3320
        } else {
3321
            $this->db->delete($this->getFullTableName('active_user_locks'));
3322
        }
3323
3324
    }
3325
3326
    /**
3327
     * Cleans up the active users table
3328
     */
3329
    public function cleanupMultipleActiveUsers()
3330
    {
3331
        $timeout = 20 * 60; // Delete multiple user-sessions after 20min
3332
        $validSessionTimeLimit = $this->time - $timeout;
3333
3334
        $activeUserSids = array();
3335
        $rs = $this->db->select('sid', $this->getFullTableName('active_user_sessions'));
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rs. Configured minimum length is 3.

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

Loading history...
3336
        $count = $this->db->getRecordCount($rs);
3337
        if ($count) {
3338
            $rs = $this->db->makeArray($rs);
3339
            foreach ($rs as $row) {
0 ignored issues
show
Bug introduced by
The expression $rs of type false|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...
3340
                $activeUserSids[] = $row['sid'];
3341
            }
3342
        }
3343
3344
        $rs = $this->db->select("sid,internalKey,lasthit", "{$this->getFullTableName('active_users')}", "", "lasthit DESC");
3345
        if ($this->db->getRecordCount($rs)) {
3346
            $rs = $this->db->makeArray($rs);
3347
            $internalKeyCount = array();
3348
            $deleteSids = '';
3349
            foreach ($rs as $row) {
0 ignored issues
show
Bug introduced by
The expression $rs of type false|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...
3350
                if (!isset($internalKeyCount[$row['internalKey']])) {
3351
                    $internalKeyCount[$row['internalKey']] = 0;
3352
                }
3353
                $internalKeyCount[$row['internalKey']]++;
3354
3355
                if ($internalKeyCount[$row['internalKey']] > 1 && !in_array($row['sid'], $activeUserSids) && $row['lasthit'] < $validSessionTimeLimit) {
3356
                    $deleteSids .= $deleteSids == '' ? '' : ' OR ';
3357
                    $deleteSids .= "sid='{$row['sid']}'";
3358
                };
3359
3360
            }
3361
            if ($deleteSids) {
3362
                $this->db->delete($this->getFullTableName('active_users'), $deleteSids);
3363
            }
3364
        }
3365
3366
    }
3367
3368
    /**
3369
     * Determines state of a locked element acc. to user-permissions
3370
     *
3371
     * @param $sid
3372
     * @return int $state States: 0=No display, 1=viewing this element, 2=locked, 3=show unlock-button
3373
     * @internal param int $internalKey : ID of User who locked actual element
3374
     */
3375
    public function determineLockState($sid)
3376
    {
3377
        $state = 0;
3378
        if ($this->hasPermission('display_locks')) {
3379
            if ($sid == $this->sid) {
3380
                $state = 1;
3381
            } else {
3382
                if ($this->hasPermission('remove_locks')) {
3383
                    $state = 3;
3384
                } else {
3385
                    $state = 2;
3386
                }
3387
            }
3388
        }
3389
        return $state;
3390
    }
3391
3392
    /**
3393
     * Locks an element
3394
     *
3395
     * @param int $type Types: 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3396
     * @param int $id Element- / Resource-id
3397
     * @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...
3398
     */
3399
    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...
3400
    {
3401
        $userId = $this->isBackend() && $_SESSION['mgrInternalKey'] ? $_SESSION['mgrInternalKey'] : 0;
3402
        $type = (int)$type;
3403
        $id = (int)$id;
3404
        if (!$type || !$id || !$userId) {
3405
            return false;
3406
        }
3407
3408
        $sql = sprintf('REPLACE INTO %s (internalKey, elementType, elementId, lasthit, sid)
3409
                VALUES (%d, %d, %d, %d, \'%s\')', $this->getFullTableName('active_user_locks'), $userId, $type, $id, $this->time, $this->sid);
3410
        $this->db->query($sql);
3411
    }
3412
3413
    /**
3414
     * Unlocks an element
3415
     *
3416
     * @param int $type Types: 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3417
     * @param int $id Element- / Resource-id
3418
     * @param bool $includeAllUsers true = Deletes not only own user-locks
3419
     * @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...
3420
     */
3421
    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...
3422
    {
3423
        $userId = $this->isBackend() && $_SESSION['mgrInternalKey'] ? $_SESSION['mgrInternalKey'] : 0;
3424
        $type = (int)$type;
3425
        $id = (int)$id;
3426
        if (!$type || !$id) {
3427
            return false;
3428
        }
3429
3430
        if (!$includeAllUsers) {
3431
            $sql = sprintf('DELETE FROM %s WHERE internalKey = %d AND elementType = %d AND elementId = %d;', $this->getFullTableName('active_user_locks'), $userId, $type, $id);
3432
        } else {
3433
            $sql = sprintf('DELETE FROM %s WHERE elementType = %d AND elementId = %d;', $this->getFullTableName('active_user_locks'), $type, $id);
3434
        }
3435
        $this->db->query($sql);
3436
    }
3437
3438
    /**
3439
     * Updates table "active_user_sessions" with userid, lasthit, IP
3440
     */
3441
    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...
3442
    {
3443
        if (!$this->sid) {
3444
            return;
3445
        }
3446
3447
        // web users are stored with negative keys
3448
        $userId = $this->getLoginUserType() == 'manager' ? $this->getLoginUserID() : -$this->getLoginUserID();
3449
3450
        // Get user IP
3451 View Code Duplication
        if ($cip = getenv("HTTP_CLIENT_IP")) {
3452
            $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...
3453
        } elseif ($cip = getenv("HTTP_X_FORWARDED_FOR")) {
3454
            $ip = $cip;
3455
        } elseif ($cip = getenv("REMOTE_ADDR")) {
3456
            $ip = $cip;
3457
        } else {
3458
            $ip = "UNKNOWN";
3459
        }
3460
        $_SESSION['ip'] = $ip;
3461
3462
        $sql = sprintf('REPLACE INTO %s (internalKey, lasthit, ip, sid)
3463
            VALUES (%d, %d, \'%s\', \'%s\')', $this->getFullTableName('active_user_sessions'), $userId, $this->time, $ip, $this->sid);
3464
        $this->db->query($sql);
3465
    }
3466
3467
    /**
3468
     * Add an a alert message to the system event log
3469
     *
3470
     * @param int $evtid Event ID
3471
     * @param int $type Types: 1 = information, 2 = warning, 3 = error
3472
     * @param string $msg Message to be logged
3473
     * @param string $source source of the event (module, snippet name, etc.)
3474
     *                       Default: Parser
3475
     */
3476
    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...
3477
    {
3478
        $msg = $this->db->escape($msg);
3479
        if (strpos($GLOBALS['database_connection_charset'], 'utf8') === 0 && extension_loaded('mbstring')) {
3480
            $esc_source = mb_substr($source, 0, 50, "UTF-8");
3481
        } else {
3482
            $esc_source = substr($source, 0, 50);
3483
        }
3484
        $esc_source = $this->db->escape($esc_source);
3485
3486
        $LoginUserID = $this->getLoginUserID();
3487
        if ($LoginUserID == '') {
3488
            $LoginUserID = 0;
3489
        }
3490
3491
        $usertype = $this->isFrontend() ? 1 : 0;
3492
        $evtid = (int)$evtid;
3493
        $type = (int)$type;
3494
3495
        // Types: 1 = information, 2 = warning, 3 = error
3496
        if ($type < 1) {
3497
            $type = 1;
3498
        } elseif ($type > 3) {
3499
            $type = 3;
3500
        }
3501
3502
        $this->db->insert(array(
3503
            'eventid' => $evtid,
3504
            'type' => $type,
3505
            'createdon' => $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'],
3506
            'source' => $esc_source,
3507
            'description' => $msg,
3508
            'user' => $LoginUserID,
3509
            'usertype' => $usertype
3510
        ), $this->getFullTableName("event_log"));
3511
3512
        if (isset($this->config['send_errormail']) && $this->config['send_errormail'] !== '0') {
3513
            if ($this->config['send_errormail'] <= $type) {
3514
                $this->sendmail(array(
3515
                    'subject' => 'MODX System Error on ' . $this->config['site_name'],
3516
                    'body' => 'Source: ' . $source . ' - The details of the error could be seen in the MODX system events log.',
3517
                    'type' => 'text'
3518
                ));
3519
            }
3520
        }
3521
    }
3522
3523
    /**
3524
     * @param string|array $params
3525
     * @param string $msg
3526
     * @param array $files
3527
     * @return bool
3528
     * @throws \PHPMailer\PHPMailer\Exception
3529
     */
3530
    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...
3531
    {
3532
        if (\is_scalar($params)) {
3533
            if (strpos($params, '=') === false) {
3534
                if (strpos($params, '@') !== false) {
3535
                    $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...
3536
                } else {
3537
                    $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...
3538
                }
3539
            } else {
3540
                $params_array = explode(',', $params);
3541
                foreach ($params_array as $k => $v) {
3542
                    $k = trim($k);
3543
                    $v = trim($v);
3544
                    $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...
3545
                }
3546
            }
3547
        } else {
3548
            $p = $params;
3549
            unset($params);
3550
        }
3551
        if (isset($p['sendto'])) {
3552
            $p['to'] = $p['sendto'];
3553
        }
3554
3555
        if (isset($p['to']) && preg_match('@^[0-9]+$@', $p['to'])) {
3556
            $userinfo = $this->getUserInfo($p['to']);
3557
            $p['to'] = $userinfo['email'];
3558
        }
3559
        if (isset($p['from']) && preg_match('@^[0-9]+$@', $p['from'])) {
3560
            $userinfo = $this->getUserInfo($p['from']);
3561
            $p['from'] = $userinfo['email'];
3562
            $p['fromname'] = $userinfo['username'];
3563
        }
3564
        if ($msg === '' && !isset($p['body'])) {
3565
            $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...
3566
        } elseif (is_string($msg) && 0 < strlen($msg)) {
3567
            $p['body'] = $msg;
3568
        }
3569
3570
        $this->loadExtension('MODxMailer');
3571
        $sendto = (!isset($p['to'])) ? $this->config['emailsender'] : $p['to'];
3572
        $sendto = explode(',', $sendto);
3573
        foreach ($sendto as $address) {
3574
            list($name, $address) = $this->mail->address_split($address);
3575
            $this->mail->AddAddress($address, $name);
3576
        }
3577 View Code Duplication
        if (isset($p['cc'])) {
3578
            $p['cc'] = explode(',', $p['cc']);
3579
            foreach ($p['cc'] as $address) {
3580
                list($name, $address) = $this->mail->address_split($address);
3581
                $this->mail->AddCC($address, $name);
3582
            }
3583
        }
3584 View Code Duplication
        if (isset($p['bcc'])) {
3585
            $p['bcc'] = explode(',', $p['bcc']);
3586
            foreach ($p['bcc'] as $address) {
3587
                list($name, $address) = $this->mail->address_split($address);
3588
                $this->mail->AddBCC($address, $name);
3589
            }
3590
        }
3591
        if (isset($p['from']) && strpos($p['from'], '<') !== false && substr($p['from'], -1) === '>') {
3592
            list($p['fromname'], $p['from']) = $this->mail->address_split($p['from']);
3593
        }
3594
        $this->mail->setFrom(
3595
            isset($p['from']) ? $p['from'] : $this->config['emailsender'],
3596
            isset($p['fromname']) ? $p['fromname'] : $this->config['site_name']
3597
        );
3598
        $this->mail->Subject = (!isset($p['subject'])) ? $this->config['emailsubject'] : $p['subject'];
3599
        $this->mail->Body = $p['body'];
3600
        if (isset($p['type']) && $p['type'] === 'text') {
3601
            $this->mail->IsHTML(false);
3602
        }
3603
        if (!is_array($files)) {
3604
            $files = array();
3605
        }
3606
        foreach ($files as $f) {
3607
            if (file_exists(MODX_BASE_PATH . $f) && is_file(MODX_BASE_PATH . $f) && is_readable(MODX_BASE_PATH . $f)) {
3608
                $this->mail->AddAttachment(MODX_BASE_PATH . $f);
3609
            }
3610
        }
3611
        return $this->mail->send();
3612
    }
3613
3614
    /**
3615
     * @param string $target
3616
     * @param int $limit
3617
     * @param int $trim
3618
     */
3619
    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...
3620
    {
3621
        if ($limit < $trim) {
3622
            $trim = $limit;
3623
        }
3624
3625
        $table_name = $this->getFullTableName($target);
3626
        $count = $this->db->getValue($this->db->select('COUNT(id)', $table_name));
3627
        $over = $count - $limit;
3628
        if (0 < $over) {
3629
            $trim = ($over + $trim);
3630
            $this->db->delete($table_name, '', '', $trim);
3631
        }
3632
        $this->db->optimize($table_name);
3633
    }
3634
3635
    /**
3636
     * Returns true if we are currently in the manager backend
3637
     *
3638
     * @return boolean
3639
     */
3640
    public function isBackend()
3641
    {
3642
        return (defined('IN_MANAGER_MODE') && IN_MANAGER_MODE === true);
3643
    }
3644
3645
    /**
3646
     * Returns true if we are currently in the frontend
3647
     *
3648
     * @return boolean
3649
     */
3650
    public function isFrontend()
3651
    {
3652
        return ! $this->isBackend();
3653
    }
3654
3655
    /**
3656
     * Gets all child documents of the specified document, including those which are unpublished or deleted.
3657
     *
3658
     * @param int $id The Document identifier to start with
3659
     * @param string $sort Sort field
3660
     *                     Default: menuindex
3661
     * @param string $dir Sort direction, ASC and DESC is possible
3662
     *                    Default: ASC
3663
     * @param string $fields Default: id, pagetitle, description, parent, alias, menutitle
3664
     * @return array
3665
     */
3666 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...
3667
    {
3668
3669
        $cacheKey = md5(print_r(func_get_args(), true));
3670
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3671
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3672
        }
3673
3674
        $tblsc = $this->getFullTableName("site_content");
3675
        $tbldg = $this->getFullTableName("document_groups");
3676
        // modify field names to use sc. table reference
3677
        $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3678
        $sort = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
3679
        // get document groups for current user
3680
        if ($docgrp = $this->getUserDocGroups()) {
3681
            $docgrp = implode(",", $docgrp);
3682
        }
3683
        // build query
3684
        $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
3685
        $result = $this->db->select("DISTINCT {$fields}", "{$tblsc} sc
3686
                LEFT JOIN {$tbldg} dg on dg.document = sc.id", "sc.parent = '{$id}' AND ({$access}) GROUP BY sc.id", "{$sort} {$dir}");
3687
        $resourceArray = $this->db->makeArray($result);
3688
        $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
3689
        return $resourceArray;
3690
    }
3691
3692
    /**
3693
     * Gets all active child documents of the specified document, i.e. those which published and not deleted.
3694
     *
3695
     * @param int $id The Document identifier to start with
3696
     * @param string $sort Sort field
3697
     *                     Default: menuindex
3698
     * @param string $dir Sort direction, ASC and DESC is possible
3699
     *                    Default: ASC
3700
     * @param string $fields Default: id, pagetitle, description, parent, alias, menutitle
3701
     * @return array
3702
     */
3703 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...
3704
    {
3705
        $cacheKey = md5(print_r(func_get_args(), true));
3706
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3707
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3708
        }
3709
3710
        $tblsc = $this->getFullTableName("site_content");
3711
        $tbldg = $this->getFullTableName("document_groups");
3712
3713
        // modify field names to use sc. table reference
3714
        $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3715
        $sort = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
3716
        // get document groups for current user
3717
        if ($docgrp = $this->getUserDocGroups()) {
3718
            $docgrp = implode(",", $docgrp);
3719
        }
3720
        // build query
3721
        $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
3722
        $result = $this->db->select("DISTINCT {$fields}", "{$tblsc} sc
3723
                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}");
3724
        $resourceArray = $this->db->makeArray($result);
3725
3726
        $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
3727
3728
        return $resourceArray;
3729
    }
3730
3731
    /**
3732
     * getDocumentChildren
3733
     * @version 1.1.1 (2014-02-19)
3734
     *
3735
     * @desc Returns the children of the selected document/folder as an associative array.
3736
     *
3737
     * @param $parentid {integer} - The parent document identifier. Default: 0 (site root).
3738
     * @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.
3739
     * @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.
3740
     * @param $fields {comma separated string; '*'} - Comma separated list of document fields to get. Default: '*' (all fields).
3741
     * @param $where {string} - Where condition in SQL style. Should include a leading 'AND '. Default: ''.
3742
     * @param $sort {comma separated string} - Should be a comma-separated list of field names on which to sort. Default: 'menuindex'.
3743
     * @param $dir {'ASC'; 'DESC'} - Sort direction, ASC and DESC is possible. Default: 'ASC'.
3744
     * @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).
3745
     *
3746
     * @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...
3747
     */
3748
    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...
3749
    {
3750
3751
        $cacheKey = md5(print_r(func_get_args(), true));
3752
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3753
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3754
        }
3755
3756
        $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...
3757
        $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...
3758
3759
        if ($where != '') {
3760
            $where = 'AND ' . $where;
3761
        }
3762
3763
        // modify field names to use sc. table reference
3764
        $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3765
        $sort = ($sort == '') ? '' : 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
3766
3767
        // get document groups for current user
3768
        if ($docgrp = $this->getUserDocGroups()) {
3769
            $docgrp = implode(',', $docgrp);
3770
        }
3771
3772
        // build query
3773
        $access = ($this->isFrontend() ? 'sc.privateweb=0' : '1="' . $_SESSION['mgrRole'] . '" OR sc.privatemgr=0') . (!$docgrp ? '' : ' OR dg.document_group IN (' . $docgrp . ')');
3774
3775
        $tblsc = $this->getFullTableName('site_content');
3776
        $tbldg = $this->getFullTableName('document_groups');
3777
3778
        $result = $this->db->select("DISTINCT {$fields}", "{$tblsc} sc
3779
                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);
3780
3781
        $resourceArray = $this->db->makeArray($result);
3782
3783
        $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
3784
3785
        return $resourceArray;
3786
    }
3787
3788
    /**
3789
     * getDocuments
3790
     * @version 1.1.1 (2013-02-19)
3791
     *
3792
     * @desc Returns required documents (their fields).
3793
     *
3794
     * @param $ids {array; comma separated string} - Documents Ids to get. @required
3795
     * @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.
3796
     * @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.
3797
     * @param $fields {comma separated string; '*'} - Documents fields to get. Default: '*'.
3798
     * @param $where {string} - SQL WHERE clause. Default: ''.
3799
     * @param $sort {comma separated string} - A comma-separated list of field names to sort by. Default: 'menuindex'.
3800
     * @param $dir {'ASC'; 'DESC'} - Sorting direction. Default: 'ASC'.
3801
     * @param $limit {string} - SQL LIMIT (without 'LIMIT '). An empty string means no limit. Default: ''.
3802
     *
3803
     * @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...
3804
     */
3805
    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...
3806
    {
3807
3808
        $cacheKey = md5(print_r(func_get_args(), true));
3809
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3810
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3811
        }
3812
3813
        if (is_string($ids)) {
3814
            if (strpos($ids, ',') !== false) {
3815
                $ids = array_filter(array_map('intval', explode(',', $ids)));
3816
            } else {
3817
                $ids = array($ids);
3818
            }
3819
        }
3820
        if (count($ids) == 0) {
3821
            $this->tmpCache[__FUNCTION__][$cacheKey] = false;
3822
            return false;
3823
        } else {
3824
            // modify field names to use sc. table reference
3825
            $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3826
            $sort = ($sort == '') ? '' : 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
3827
            if ($where != '') {
3828
                $where = 'AND ' . $where;
3829
            }
3830
3831
            $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...
3832
            $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...
3833
3834
            // get document groups for current user
3835
            if ($docgrp = $this->getUserDocGroups()) {
3836
                $docgrp = implode(',', $docgrp);
3837
            }
3838
3839
            $access = ($this->isFrontend() ? 'sc.privateweb=0' : '1="' . $_SESSION['mgrRole'] . '" OR sc.privatemgr=0') . (!$docgrp ? '' : ' OR dg.document_group IN (' . $docgrp . ')');
3840
3841
            $tblsc = $this->getFullTableName('site_content');
3842
            $tbldg = $this->getFullTableName('document_groups');
3843
3844
            $result = $this->db->select("DISTINCT {$fields}", "{$tblsc} sc
3845
                    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);
3846
3847
            $resourceArray = $this->db->makeArray($result);
3848
3849
            $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
3850
3851
            return $resourceArray;
3852
        }
3853
    }
3854
3855
    /**
3856
     * getDocument
3857
     * @version 1.0.1 (2014-02-19)
3858
     *
3859
     * @desc Returns required fields of a document.
3860
     *
3861
     * @param int $id {integer}
3862
     * - Id of a document which data has to be gained. @required
3863
     * @param string $fields {comma separated string; '*'}
3864
     * - Comma separated list of document fields to get. Default: '*'.
3865
     * @param int $published {0; 1; 'all'}
3866
     * - 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.
3867
     * @param int $deleted {0; 1; 'all'}
3868
     * - 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.
3869
     * @return bool {array; false} - Result array with fields or false.
3870
     * - Result array with fields or false.
3871
     */
3872 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...
3873
    {
3874
        if ($id == 0) {
3875
            return false;
3876
        } else {
3877
            $docs = $this->getDocuments(array($id), $published, $deleted, $fields, '', '', '', 1);
3878
3879
            if ($docs != false) {
3880
                return $docs[0];
3881
            } else {
3882
                return false;
3883
            }
3884
        }
3885
    }
3886
3887
    /**
3888
     * @param string $field
3889
     * @param string $docid
3890
     * @return bool|mixed
3891
     */
3892
    public function getField($field = 'content', $docid = '')
3893
    {
3894
        if (empty($docid) && isset($this->documentIdentifier)) {
3895
            $docid = $this->documentIdentifier;
3896
        } elseif (!preg_match('@^[0-9]+$@', $docid)) {
3897
            $docid = $this->getIdFromAlias($docid);
3898
        }
3899
3900
        if (empty($docid)) {
3901
            return false;
3902
        }
3903
3904
        $cacheKey = md5(print_r(func_get_args(), true));
3905
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3906
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3907
        }
3908
3909
        $doc = $this->getDocumentObject('id', $docid);
3910
        if (is_array($doc[$field])) {
3911
            $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...
3912
            $content = $tvs[$field];
3913
        } else {
3914
            $content = $doc[$field];
3915
        }
3916
3917
        $this->tmpCache[__FUNCTION__][$cacheKey] = $content;
3918
3919
        return $content;
3920
    }
3921
3922
    /**
3923
     * Returns the page information as database row, the type of result is
3924
     * defined with the parameter $rowMode
3925
     *
3926
     * @param int $pageid The parent document identifier
3927
     *                    Default: -1 (no result)
3928
     * @param int $active Should we fetch only published and undeleted documents/resources?
3929
     *                     1 = yes, 0 = no
3930
     *                     Default: 1
3931
     * @param string $fields List of fields
3932
     *                       Default: id, pagetitle, description, alias
3933
     * @return boolean|array
3934
     */
3935
    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...
3936
    {
3937
3938
        $cacheKey = md5(print_r(func_get_args(), true));
3939
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3940
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3941
        }
3942
3943
        if ($pageid == 0) {
3944
            return false;
3945
        } else {
3946
            $tblsc = $this->getFullTableName("site_content");
3947
            $tbldg = $this->getFullTableName("document_groups");
3948
            $activeSql = $active == 1 ? "AND sc.published=1 AND sc.deleted=0" : "";
3949
            // modify field names to use sc. table reference
3950
            $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3951
            // get document groups for current user
3952
            if ($docgrp = $this->getUserDocGroups()) {
3953
                $docgrp = implode(",", $docgrp);
3954
            }
3955
            $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
3956
            $result = $this->db->select($fields, "{$tblsc} sc LEFT JOIN {$tbldg} dg on dg.document = sc.id", "(sc.id='{$pageid}' {$activeSql}) AND ({$access})", "", 1);
3957
            $pageInfo = $this->db->getRow($result);
3958
3959
            $this->tmpCache[__FUNCTION__][$cacheKey] = $pageInfo;
3960
3961
            return $pageInfo;
3962
        }
3963
    }
3964
3965
    /**
3966
     * Returns the parent document/resource of the given docid
3967
     *
3968
     * @param int $pid The parent docid. If -1, then fetch the current document/resource's parent
3969
     *                 Default: -1
3970
     * @param int $active Should we fetch only published and undeleted documents/resources?
3971
     *                     1 = yes, 0 = no
3972
     *                     Default: 1
3973
     * @param string $fields List of fields
3974
     *                       Default: id, pagetitle, description, alias
3975
     * @return boolean|array
3976
     */
3977
    public function getParent($pid = -1, $active = 1, $fields = 'id, pagetitle, description, alias, parent')
3978
    {
3979
        if ($pid == -1) {
3980
            $pid = $this->documentObject['parent'];
3981
            return ($pid == 0) ? false : $this->getPageInfo($pid, $active, $fields);
3982
        } else if ($pid == 0) {
3983
            return false;
3984
        } else {
3985
            // first get the child document
3986
            $child = $this->getPageInfo($pid, $active, "parent");
3987
            // now return the child's parent
3988
            $pid = ($child['parent']) ? $child['parent'] : 0;
3989
            return ($pid == 0) ? false : $this->getPageInfo($pid, $active, $fields);
3990
        }
3991
    }
3992
3993
    /**
3994
     * Returns the id of the current snippet.
3995
     *
3996
     * @return int
3997
     */
3998
    public function getSnippetId()
3999
    {
4000
        if ($this->currentSnippet) {
4001
            $tbl = $this->getFullTableName("site_snippets");
4002
            $rs = $this->db->select('id', $tbl, "name='" . $this->db->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...
4003
            if ($snippetId = $this->db->getValue($rs)) {
4004
                return $snippetId;
4005
            }
4006
        }
4007
        return 0;
4008
    }
4009
4010
    /**
4011
     * Returns the name of the current snippet.
4012
     *
4013
     * @return string
4014
     */
4015
    public function getSnippetName()
4016
    {
4017
        return $this->currentSnippet;
4018
    }
4019
4020
    /**
4021
     * Clear the cache of MODX.
4022
     *
4023
     * @param string $type
4024
     * @param bool $report
4025
     * @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...
4026
     */
4027
    public function clearCache($type = '', $report = false)
4028
    {
4029
        $cache_dir = MODX_BASE_PATH . $this->getCacheFolder();
4030
        if (is_array($type)) {
4031
            foreach ($type as $_) {
4032
                $this->clearCache($_, $report);
4033
            }
4034
        } elseif ($type == 'full') {
4035
            $sync = new Cache();
4036
            $sync->setCachepath($cache_dir);
4037
            $sync->setReport($report);
4038
            $sync->emptyCache();
4039
        } elseif (preg_match('@^[1-9][0-9]*$@', $type)) {
4040
            $key = ($this->config['cache_type'] == 2) ? $this->makePageCacheKey($type) : $type;
4041
            $file_name = "docid_" . $key . "_*.pageCache.php";
4042
            $cache_path = $cache_dir . $file_name;
4043
            $files = glob($cache_path);
4044
            $files[] = $cache_dir . "docid_" . $key . ".pageCache.php";
4045
            foreach ($files as $file) {
4046
                if (!is_file($file)) {
4047
                    continue;
4048
                }
4049
                unlink($file);
4050
            }
4051
        } else {
4052
            $files = glob($cache_dir . '*');
4053
            foreach ($files as $file) {
4054
                $name = basename($file);
4055
                if (strpos($name, '.pageCache.php') === false) {
4056
                    continue;
4057
                }
4058
                if (!is_file($file)) {
4059
                    continue;
4060
                }
4061
                unlink($file);
4062
            }
4063
        }
4064
    }
4065
4066
    /**
4067
     * makeUrl
4068
     *
4069
     * @desc Create an URL for the given document identifier. The url prefix and postfix are used, when “friendly_url” is active.
4070
     *
4071
     * @param $id {integer} - The document identifier. @required
4072
     * @param string $alias {string}
4073
     * - The alias name for the document. Default: ''.
4074
     * @param string $args {string}
4075
     * - The paramaters to add to the URL. Default: ''.
4076
     * @param string $scheme {string}
4077
     * - With full as valus, the site url configuration is used. Default: ''.
4078
     * @return mixed|string {string} - Result URL.
4079
     * - Result URL.
4080
     */
4081
    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...
4082
    {
4083
        $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...
4084
        $virtualDir = isset($this->config['virtual_dir']) ? $this->config['virtual_dir'] : '';
4085
        $f_url_prefix = $this->config['friendly_url_prefix'];
4086
        $f_url_suffix = $this->config['friendly_url_suffix'];
4087
4088
        if (!is_numeric($id)) {
4089
            $this->messageQuit("`{$id}` is not numeric and may not be passed to makeUrl()");
4090
        }
4091
4092
        if ($args !== '') {
4093
            // add ? or & to $args if missing
4094
            $args = ltrim($args, '?&');
4095
            $_ = 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...
4096
4097
            if ($_ === false && $this->config['friendly_urls'] == 1) {
4098
                $args = "?{$args}";
4099
            } else {
4100
                $args = "&{$args}";
4101
            }
4102
        }
4103
4104
        if ($id != $this->config['site_start']) {
4105
            if ($this->config['friendly_urls'] == 1 && $alias == '') {
4106
                $alias = $id;
4107
                $alPath = '';
4108
4109
                if ($this->config['friendly_alias_urls'] == 1) {
4110
4111
                    if ($this->config['aliaslistingfolder'] == 1) {
4112
                        $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...
4113
                    } else {
4114
                        $al = $this->aliasListing[$id];
4115
                    }
4116
4117
                    if ($al['isfolder'] === 1 && $this->config['make_folders'] === '1') {
4118
                        $f_url_suffix = '/';
4119
                    }
4120
4121
                    $alPath = !empty ($al['path']) ? $al['path'] . '/' : '';
4122
4123
                    if ($al && $al['alias']) {
4124
                        $alias = $al['alias'];
4125
                    }
4126
4127
                }
4128
4129
                $alias = $alPath . $f_url_prefix . $alias . $f_url_suffix;
4130
                $url = "{$alias}{$args}";
4131
            } else {
4132
                $url = "index.php?id={$id}{$args}";
4133
            }
4134
        } else {
4135
            $url = $args;
4136
        }
4137
4138
        $host = $this->config['base_url'];
4139
4140
        // check if scheme argument has been set
4141
        if ($scheme != '') {
4142
            // for backward compatibility - check if the desired scheme is different than the current scheme
4143
            if (is_numeric($scheme) && $scheme != $_SERVER['HTTPS']) {
4144
                $scheme = ($_SERVER['HTTPS'] ? 'http' : 'https');
4145
            }
4146
4147
            //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...
4148
            $host = $scheme == 'full' ? $this->config['site_url'] : $scheme . '://' . $_SERVER['HTTP_HOST'] . $host;
4149
        }
4150
4151
        //fix strictUrl by Bumkaka
4152
        if ($this->config['seostrict'] == '1') {
4153
            $url = $this->toAlias($url);
4154
        }
4155
4156
        if ($this->config['xhtml_urls']) {
4157
            $url = preg_replace("/&(?!amp;)/", "&amp;", $host . $virtualDir . $url);
4158
        } else {
4159
            $url = $host . $virtualDir . $url;
4160
        }
4161
4162
        $evtOut = $this->invokeEvent('OnMakeDocUrl', array(
4163
            'id' => $id,
4164
            'url' => $url
4165
        ));
4166
4167
        if (is_array($evtOut) && count($evtOut) > 0) {
4168
            $url = array_pop($evtOut);
4169
        }
4170
4171
        return $url;
4172
    }
4173
4174
    /**
4175
     * @param $id
4176
     * @return mixed
4177
     */
4178
    public function getAliasListing($id)
4179
    {
4180
        if (isset($this->aliasListing[$id])) {
4181
            $out = $this->aliasListing[$id];
4182
        } else {
4183
            $q = $this->db->query("SELECT id,alias,isfolder,parent FROM " . $this->getFullTableName("site_content") . " WHERE id=" . (int)$id);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $q. Configured minimum length is 3.

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

Loading history...
4184
            if ($this->db->getRecordCount($q) == '1') {
4185
                $q = $this->db->getRow($q);
4186
                $this->aliasListing[$id] = array(
4187
                    'id' => (int)$q['id'],
4188
                    'alias' => $q['alias'] == '' ? $q['id'] : $q['alias'],
4189
                    'parent' => (int)$q['parent'],
4190
                    'isfolder' => (int)$q['isfolder'],
4191
                );
4192
                if ($this->aliasListing[$id]['parent'] > 0) {
4193
                    //fix alias_path_usage
4194
                    if ($this->config['use_alias_path'] == '1') {
4195
                        //&& $tmp['path'] != '' - fix error slash with epty path
4196
                        $tmp = $this->getAliasListing($this->aliasListing[$id]['parent']);
4197
                        $this->aliasListing[$id]['path'] = $tmp['path'] . ($tmp['alias_visible'] ? (($tmp['parent'] > 0 && $tmp['path'] != '') ? '/' : '') . $tmp['alias'] : '');
4198
                    } else {
4199
                        $this->aliasListing[$id]['path'] = '';
4200
                    }
4201
                }
4202
4203
                $out = $this->aliasListing[$id];
4204
            }
4205
        }
4206
        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...
4207
    }
4208
4209
    /**
4210
     * Returns an entry from the config
4211
     *
4212
     * Note: most code accesses the config array directly and we will continue to support this.
4213
     *
4214
     * @param string $name
4215
     * @return bool|string
4216
     */
4217
    public function getConfig($name = '')
4218
    {
4219
        if (!empty ($this->config[$name])) {
4220
            return $this->config[$name];
4221
        } else {
4222
            return false;
4223
        }
4224
    }
4225
4226
    /**
4227
     * Returns the MODX version information as version, branch, release date and full application name.
4228
     *
4229
     * @param null $data
4230
     * @return array
4231
     */
4232
4233
    public function getVersionData($data = null)
4234
    {
4235
        $out = array();
4236
        if (empty($this->version) || !is_array($this->version)) {
4237
            //include for compatibility modx version < 1.0.10
4238
            include MODX_MANAGER_PATH . "includes/version.inc.php";
4239
            $this->version = array();
4240
            $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...
4241
            $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...
4242
            $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...
4243
            $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...
4244
            $this->version['new_version'] = isset($this->config['newversiontext']) ? $this->config['newversiontext'] : '';
4245
        }
4246
        return (!is_null($data) && is_array($this->version) && isset($this->version[$data])) ? $this->version[$data] : $this->version;
4247
    }
4248
4249
    /**
4250
     * Executes a snippet.
4251
     *
4252
     * @param string $snippetName
4253
     * @param array $params Default: Empty array
4254
     * @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...
4255
     */
4256
    public function runSnippet($snippetName, $params = array())
4257
    {
4258
        if (isset ($this->snippetCache[$snippetName])) {
4259
            $snippet = $this->snippetCache[$snippetName];
4260
            $properties = !empty($this->snippetCache[$snippetName . "Props"]) ? $this->snippetCache[$snippetName . "Props"] : '';
4261
        } else { // not in cache so let's check the db
4262
            $sql = "SELECT ss.`name`, ss.`snippet`, ss.`properties`, sm.properties as `sharedproperties` FROM " . $this->getFullTableName("site_snippets") . " as ss LEFT JOIN " . $this->getFullTableName('site_modules') . " as sm on sm.guid=ss.moduleguid WHERE ss.`name`='" . $this->db->escape($snippetName) . "'  AND ss.disabled=0;";
4263
            $result = $this->db->query($sql);
4264
            if ($this->db->getRecordCount($result) == 1) {
4265
                $row = $this->db->getRow($result);
4266
                $snippet = $this->snippetCache[$snippetName] = $row['snippet'];
4267
                $mergedProperties = array_merge($this->parseProperties($row['properties']), $this->parseProperties($row['sharedproperties']));
4268
                $properties = $this->snippetCache[$snippetName . "Props"] = json_encode($mergedProperties);
4269
            } else {
4270
                $snippet = $this->snippetCache[$snippetName] = "return false;";
4271
                $properties = $this->snippetCache[$snippetName . "Props"] = '';
4272
            }
4273
        }
4274
        // load default params/properties
4275
        $parameters = $this->parseProperties($properties, $snippetName, 'snippet');
4276
        $parameters = array_merge($parameters, $params);
4277
4278
        // run snippet
4279
        return $this->evalSnippet($snippet, $parameters);
4280
    }
4281
4282
    /**
4283
     * Returns the chunk content for the given chunk name
4284
     *
4285
     * @param string $chunkName
4286
     * @return boolean|string
4287
     */
4288
    public function getChunk($chunkName)
4289
    {
4290
        $out = null;
4291
        if (empty($chunkName)) {
4292
            return $out;
4293
        }
4294
        if (isset ($this->chunkCache[$chunkName])) {
4295
            $out = $this->chunkCache[$chunkName];
4296
        } else if (stripos($chunkName, '@FILE') === 0) {
4297
            $out = $this->chunkCache[$chunkName] = $this->atBindFileContent($chunkName);
4298
        } else {
4299
            $where = sprintf("`name`='%s' AND disabled=0", $this->db->escape($chunkName));
4300
            $rs = $this->db->select('snippet', '[+prefix+]site_htmlsnippets', $where);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rs. Configured minimum length is 3.

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

Loading history...
4301
            if ($this->db->getRecordCount($rs) == 1) {
4302
                $row = $this->db->getRow($rs);
4303
                $out = $this->chunkCache[$chunkName] = $row['snippet'];
4304
            } else {
4305
                $out = $this->chunkCache[$chunkName] = null;
4306
            }
4307
        }
4308
        return $out;
4309
    }
4310
4311
    /**
4312
     * parseText
4313
     * @version 1.0 (2013-10-17)
4314
     *
4315
     * @desc Replaces placeholders in text with required values.
4316
     *
4317
     * @param string $tpl
4318
     * @param array $ph
4319
     * @param string $left
4320
     * @param string $right
4321
     * @param bool $execModifier
4322
     * @return string {string} - Parsed text.
4323
     * - Parsed text.
4324
     * @internal param $chunk {string} - String to parse. - String to parse. @required
4325
     * @internal param $chunkArr {array} - Array of values. Key — placeholder name, value — value. - Array of values. Key — placeholder name, value — value. @required
4326
     * @internal param $prefix {string} - Placeholders prefix. Default: '[+'. - Placeholders prefix. Default: '[+'.
4327
     * @internal param $suffix {string} - Placeholders suffix. Default: '+]'. - Placeholders suffix. Default: '+]'.
4328
     *
4329
     */
4330
    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...
4331
    {
4332
        if (empty($ph) || empty($tpl)) {
4333
            return $tpl;
4334
        }
4335
4336 View Code Duplication
        if ($this->config['enable_at_syntax']) {
4337
            if (stripos($tpl, '<@LITERAL>') !== false) {
4338
                $tpl = $this->escapeLiteralTagsContent($tpl);
4339
            }
4340
        }
4341
4342
        $matches = $this->getTagsFromContent($tpl, $left, $right);
4343
        if (empty($matches)) {
4344
            return $tpl;
4345
        }
4346
4347
        foreach ($matches[1] as $i => $key) {
4348
4349
            if (strpos($key, ':') !== false && $execModifier) {
4350
                list($key, $modifiers) = $this->splitKeyAndFilter($key);
4351
            } else {
4352
                $modifiers = false;
4353
            }
4354
4355
            //          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...
4356
            if (!array_key_exists($key, $ph)) {
4357
                continue;
4358
            } //NULL values must be saved in placeholders, if we got them from database string
4359
4360
            $value = $ph[$key];
4361
4362
            $s = &$matches[0][$i];
4363
            if ($modifiers !== false) {
4364
                if (strpos($modifiers, $left) !== false) {
4365
                    $modifiers = $this->parseText($modifiers, $ph, $left, $right);
4366
                }
4367
                $value = $this->applyFilter($value, $modifiers, $key);
4368
            }
4369 View Code Duplication
            if (strpos($tpl, $s) !== false) {
4370
                $tpl = str_replace($s, $value, $tpl);
4371
            } elseif($this->debug) {
4372
                $this->addLog('parseText parse error', $_SERVER['REQUEST_URI'] . $s, 2);
4373
            }
4374
        }
4375
4376
        return $tpl;
4377
    }
4378
4379
    /**
4380
     * parseChunk
4381
     * @version 1.1 (2013-10-17)
4382
     *
4383
     * @desc Replaces placeholders in a chunk with required values.
4384
     *
4385
     * @param $chunkName {string} - Name of chunk to parse. @required
4386
     * @param $chunkArr {array} - Array of values. Key — placeholder name, value — value. @required
4387
     * @param string $prefix {string}
4388
     * - Placeholders prefix. Default: '{'.
4389
     * @param string $suffix {string}
4390
     * - Placeholders suffix. Default: '}'.
4391
     * @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...
4392
     * - Parsed chunk or false if $chunkArr is not array.
4393
     */
4394
    public function parseChunk($chunkName, $chunkArr, $prefix = '{', $suffix = '}')
4395
    {
4396
        //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...
4397
        if (!is_array($chunkArr)) {
4398
            return false;
4399
        }
4400
4401
        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...
4402
    }
4403
4404
    /**
4405
     * getTpl
4406
     * get template for snippets
4407
     * @param $tpl {string}
4408
     * @return bool|string {string}
4409
     */
4410
    public function getTpl($tpl)
4411
    {
4412
        $template = $tpl;
4413
        if (preg_match("/^@([^:\s]+)[:\s]+(.+)$/s", trim($tpl), $match)) {
4414
            $command = strtoupper($match[1]);
4415
            $template = $match[2];
4416
        }
4417
        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...
4418
            case 'CODE':
4419
                break;
4420
            case 'FILE':
4421
                $template = file_get_contents(MODX_BASE_PATH . $template);
4422
                break;
4423
            case 'CHUNK':
4424
                $template = $this->getChunk($template);
4425
                break;
4426
            case 'DOCUMENT':
4427
                $doc = $this->getDocument($template, 'content', 'all');
4428
                $template = $doc['content'];
4429
                break;
4430
            case 'SELECT':
4431
                $this->db->getValue($this->db->query("SELECT {$template}"));
4432
                break;
4433
            default:
4434
                if (!($template = $this->getChunk($tpl))) {
4435
                    $template = $tpl;
4436
                }
4437
        }
4438
        return $template;
4439
    }
4440
4441
    /**
4442
     * Returns the timestamp in the date format defined in $this->config['datetime_format']
4443
     *
4444
     * @param int $timestamp Default: 0
4445
     * @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.
4446
     * @return string
4447
     */
4448
    public function toDateFormat($timestamp = 0, $mode = '')
4449
    {
4450
        $timestamp = trim($timestamp);
4451
        if ($mode !== 'formatOnly' && empty($timestamp)) {
4452
            return '-';
4453
        }
4454
        $timestamp = (int)$timestamp;
4455
4456
        switch ($this->config['datetime_format']) {
4457
            case 'YYYY/mm/dd':
4458
                $dateFormat = '%Y/%m/%d';
4459
                break;
4460
            case 'dd-mm-YYYY':
4461
                $dateFormat = '%d-%m-%Y';
4462
                break;
4463
            case 'mm/dd/YYYY':
4464
                $dateFormat = '%m/%d/%Y';
4465
                break;
4466
            /*
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...
4467
            case 'dd-mmm-YYYY':
4468
                $dateFormat = '%e-%b-%Y';
4469
                break;
4470
            */
4471
        }
4472
4473
        if (empty($mode)) {
4474
            $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...
4475
        } elseif ($mode == 'dateOnly') {
4476
            $strTime = strftime($dateFormat, $timestamp);
4477
        } elseif ($mode == 'formatOnly') {
4478
            $strTime = $dateFormat;
4479
        }
4480
        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...
4481
    }
4482
4483
    /**
4484
     * Make a timestamp from a string corresponding to the format in $this->config['datetime_format']
4485
     *
4486
     * @param string $str
4487
     * @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...
4488
     */
4489
    public function toTimeStamp($str)
4490
    {
4491
        $str = trim($str);
4492
        if (empty($str)) {
4493
            return '';
4494
        }
4495
4496
        switch ($this->config['datetime_format']) {
4497 View Code Duplication
            case 'YYYY/mm/dd':
4498
                if (!preg_match('/^[0-9]{4}\/[0-9]{2}\/[0-9]{2}[0-9 :]*$/', $str)) {
4499
                    return '';
4500
                }
4501
                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...
4502
                break;
4503 View Code Duplication
            case 'dd-mm-YYYY':
4504
                if (!preg_match('/^[0-9]{2}-[0-9]{2}-[0-9]{4}[0-9 :]*$/', $str)) {
4505
                    return '';
4506
                }
4507
                list ($d, $m, $Y, $H, $M, $S) = sscanf($str, '%2d-%2d-%4d %2d:%2d:%2d');
4508
                break;
4509 View Code Duplication
            case 'mm/dd/YYYY':
4510
                if (!preg_match('/^[0-9]{2}\/[0-9]{2}\/[0-9]{4}[0-9 :]*$/', $str)) {
4511
                    return '';
4512
                }
4513
                list ($m, $d, $Y, $H, $M, $S) = sscanf($str, '%2d/%2d/%4d %2d:%2d:%2d');
4514
                break;
4515
            /*
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...
4516
            case 'dd-mmm-YYYY':
4517
                if (!preg_match('/^[0-9]{2}-[0-9a-z]+-[0-9]{4}[0-9 :]*$/i', $str)) {return '';}
4518
                list ($m, $d, $Y, $H, $M, $S) = sscanf($str, '%2d-%3s-%4d %2d:%2d:%2d');
4519
                break;
4520
            */
4521
        }
4522
        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...
4523
            $H = 0;
4524
            $M = 0;
4525
            $S = 0;
4526
        }
4527
        $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...
4528
        $timeStamp = (int)$timeStamp;
4529
        return $timeStamp;
4530
    }
4531
4532
    /**
4533
     * Get the TVs of a document's children. Returns an array where each element represents one child doc.
4534
     *
4535
     * Ignores deleted children. Gets all children - there is no where clause available.
4536
     *
4537
     * @param int $parentid The parent docid
4538
     *                 Default: 0 (site root)
4539
     * @param array $tvidnames . Which TVs to fetch - Can relate to the TV ids in the db (array elements should be numeric only)
4540
     *                                               or the TV names (array elements should be names only)
4541
     *                      Default: Empty array
4542
     * @param int $published Whether published or unpublished documents are in the result
4543
     *                      Default: 1
4544
     * @param string $docsort How to sort the result array (field)
4545
     *                      Default: menuindex
4546
     * @param ASC|string $docsortdir How to sort the result array (direction)
4547
     *                      Default: ASC
4548
     * @param string $tvfields Fields to fetch from site_tmplvars, default '*'
4549
     *                      Default: *
4550
     * @param string $tvsort How to sort each element of the result array i.e. how to sort the TVs (field)
4551
     *                      Default: rank
4552
     * @param string $tvsortdir How to sort each element of the result array i.e. how to sort the TVs (direction)
4553
     *                      Default: ASC
4554
     * @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...
4555
     */
4556
    public function getDocumentChildrenTVars($parentid = 0, $tvidnames = array(), $published = 1, $docsort = "menuindex", $docsortdir = "ASC", $tvfields = "*", $tvsort = "rank", $tvsortdir = "ASC")
4557
    {
4558
        $docs = $this->getDocumentChildren($parentid, $published, 0, '*', '', $docsort, $docsortdir);
4559
        if (!$docs) {
4560
            return false;
4561
        } else {
4562
            $result = array();
4563
            // get user defined template variables
4564
            if ($tvfields) {
4565
                $_ = 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...
4566
                foreach ($_ as $i => $v) {
4567
                    if ($v === 'value') {
4568
                        unset($_[$i]);
4569
                    } else {
4570
                        $_[$i] = 'tv.' . $v;
4571
                    }
4572
                }
4573
                $fields = implode(',', $_);
4574
            } else {
4575
                $fields = "tv.*";
4576
            }
4577
4578
            if ($tvsort != '') {
4579
                $tvsort = 'tv.' . implode(',tv.', array_filter(array_map('trim', explode(',', $tvsort))));
4580
            }
4581 View Code Duplication
            if ($tvidnames == "*") {
4582
                $query = "tv.id<>0";
4583
            } else {
4584
                $query = (is_numeric($tvidnames[0]) ? "tv.id" : "tv.name") . " IN ('" . implode("','", $tvidnames) . "')";
4585
            }
4586
4587
            $this->getUserDocGroups();
4588
4589
            foreach ($docs as $doc) {
4590
4591
                $docid = $doc['id'];
4592
4593
                $rs = $this->db->select("{$fields}, IF(tvc.value!='',tvc.value,tv.default_text) as value ", "[+prefix+]site_tmplvars tv
4594
                        INNER JOIN [+prefix+]site_tmplvar_templates tvtpl ON tvtpl.tmplvarid = tv.id
4595
                        LEFT JOIN [+prefix+]site_tmplvar_contentvalues tvc ON tvc.tmplvarid=tv.id AND tvc.contentid='{$docid}'", "{$query} AND tvtpl.templateid = '{$doc['template']}'", ($tvsort ? "{$tvsort} {$tvsortdir}" : ""));
4596
                $tvs = $this->db->makeArray($rs);
4597
4598
                // get default/built-in template variables
4599
                ksort($doc);
4600
                foreach ($doc as $key => $value) {
4601
                    if ($tvidnames == '*' || in_array($key, $tvidnames)) {
4602
                        $tvs[] = array('name' => $key, 'value' => $value);
4603
                    }
4604
                }
4605
                if (is_array($tvs) && count($tvs)) {
4606
                    $result[] = $tvs;
4607
                }
4608
            }
4609
            return $result;
4610
        }
4611
    }
4612
4613
    /**
4614
     * getDocumentChildrenTVarOutput
4615
     * @version 1.1 (2014-02-19)
4616
     *
4617
     * @desc Returns an array where each element represents one child doc and contains the result from getTemplateVarOutput().
4618
     *
4619
     * @param int $parentid {integer}
4620
     * - Id of parent document. Default: 0 (site root).
4621
     * @param array $tvidnames {array; '*'}
4622
     * - Which TVs to fetch. In the form expected by getTemplateVarOutput(). Default: array().
4623
     * @param int $published {0; 1; 'all'}
4624
     * - 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.
4625
     * @param string $sortBy {string}
4626
     * - How to sort the result array (field). Default: 'menuindex'.
4627
     * @param string $sortDir {'ASC'; 'DESC'}
4628
     * - How to sort the result array (direction). Default: 'ASC'.
4629
     * @param string $where {string}
4630
     * - SQL WHERE condition (use only document fields, not TV). Default: ''.
4631
     * @param string $resultKey {string; false}
4632
     * - Field, which values are keys into result array. Use the “false”, that result array keys just will be numbered. Default: 'id'.
4633
     * @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...
4634
     * - Result array, or false.
4635
     */
4636
    public function getDocumentChildrenTVarOutput($parentid = 0, $tvidnames = array(), $published = 1, $sortBy = 'menuindex', $sortDir = 'ASC', $where = '', $resultKey = 'id')
4637
    {
4638
        $docs = $this->getDocumentChildren($parentid, $published, 0, 'id', $where, $sortBy, $sortDir);
4639
4640
        if (!$docs) {
4641
            return false;
4642
        } else {
4643
            $result = array();
4644
4645
            $unsetResultKey = false;
4646
4647
            if ($resultKey !== false) {
4648
                if (is_array($tvidnames)) {
4649
                    if (count($tvidnames) != 0 && !in_array($resultKey, $tvidnames)) {
4650
                        $tvidnames[] = $resultKey;
4651
                        $unsetResultKey = true;
4652
                    }
4653
                } else if ($tvidnames != '*' && $tvidnames != $resultKey) {
4654
                    $tvidnames = array($tvidnames, $resultKey);
4655
                    $unsetResultKey = true;
4656
                }
4657
            }
4658
4659
            for ($i = 0; $i < count($docs); $i++) {
4660
                $tvs = $this->getTemplateVarOutput($tvidnames, $docs[$i]['id'], $published);
4661
4662
                if ($tvs) {
4663
                    if ($resultKey !== false && array_key_exists($resultKey, $tvs)) {
4664
                        $result[$tvs[$resultKey]] = $tvs;
4665
4666
                        if ($unsetResultKey) {
4667
                            unset($result[$tvs[$resultKey]][$resultKey]);
4668
                        }
4669
                    } else {
4670
                        $result[] = $tvs;
4671
                    }
4672
                }
4673
            }
4674
4675
            return $result;
4676
        }
4677
    }
4678
4679
    /**
4680
     * Modified by Raymond for TV - Orig Modified by Apodigm - DocVars
4681
     * Returns a single site_content field or TV record from the db.
4682
     *
4683
     * If a site content field the result is an associative array of 'name' and 'value'.
4684
     *
4685
     * If a TV the result is an array representing a db row including the fields specified in $fields.
4686
     *
4687
     * @param string $idname Can be a TV id or name
4688
     * @param string $fields Fields to fetch from site_tmplvars. Default: *
4689
     * @param string|type $docid Docid. Defaults to empty string which indicates the current document.
4690
     * @param int $published Whether published or unpublished documents are in the result
4691
     *                        Default: 1
4692
     * @return bool
4693
     */
4694 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...
4695
    {
4696
        if ($idname == "") {
4697
            return false;
4698
        } else {
4699
            $result = $this->getTemplateVars(array($idname), $fields, $docid, $published, "", ""); //remove sorting for speed
4700
            return ($result != false) ? $result[0] : false;
4701
        }
4702
    }
4703
4704
    /**
4705
     * getTemplateVars
4706
     * @version 1.0.1 (2014-02-19)
4707
     *
4708
     * @desc Returns an array of site_content field fields and/or TV records from the db.
4709
     * Elements representing a site content field consist of an associative array of 'name' and 'value'.
4710
     * Elements representing a TV consist of an array representing a db row including the fields specified in $fields.
4711
     *
4712
     * @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
4713
     * @param $fields {comma separated string; '*'} - Fields names in the TV table of MODx database. Default: '*'
4714
     * @param $docid {integer; ''} - Id of a document to get. Default: an empty string which indicates the current document.
4715
     * @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.
4716
     * @param $sort {comma separated string} - Fields of the TV table to sort by. Default: 'rank'.
4717
     * @param $dir {'ASC'; 'DESC'} - How to sort the result array (direction). Default: 'ASC'.
4718
     *
4719
     * @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...
4720
     */
4721
    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...
4722
    {
4723
        $cacheKey = md5(print_r(func_get_args(), true));
4724
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
4725
            return $this->tmpCache[__FUNCTION__][$cacheKey];
4726
        }
4727
4728
        if (($idnames != '*' && !is_array($idnames)) || empty($idnames) ) {
4729
            return false;
4730
        } else {
4731
4732
            // get document record
4733
            if ($docid == '') {
4734
                $docid = $this->documentIdentifier;
4735
                $docRow = $this->documentObject;
4736
            } else {
4737
                $docRow = $this->getDocument($docid, '*', $published);
4738
4739
                if (!$docRow) {
4740
                    $this->tmpCache[__FUNCTION__][$cacheKey] = false;
4741
                    return false;
4742
                }
4743
            }
4744
4745
            // get user defined template variables
4746
            $fields = ($fields == '') ? 'tv.*' : 'tv.' . implode(',tv.', array_filter(array_map('trim', explode(',', $fields))));
4747
            $sort = ($sort == '') ? '' : 'tv.' . implode(',tv.', array_filter(array_map('trim', explode(',', $sort))));
4748
4749 View Code Duplication
            if ($idnames == '*') {
4750
                $query = 'tv.id<>0';
4751
            } else {
4752
                $query = (is_numeric($idnames[0]) ? 'tv.id' : 'tv.name') . " IN ('" . implode("','", $idnames) . "')";
4753
            }
4754
4755
            $rs = $this->db->select("{$fields}, IF(tvc.value != '', tvc.value, tv.default_text) as value", $this->getFullTableName('site_tmplvars') . " tv
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rs. Configured minimum length is 3.

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

Loading history...
4756
                    INNER JOIN " . $this->getFullTableName('site_tmplvar_templates') . " tvtpl ON tvtpl.tmplvarid = tv.id
4757
                    LEFT JOIN " . $this->getFullTableName('site_tmplvar_contentvalues') . " tvc ON tvc.tmplvarid=tv.id AND tvc.contentid = '{$docid}'", "{$query} AND tvtpl.templateid = '{$docRow['template']}'", ($sort ? "{$sort} {$dir}" : ""));
4758
4759
            $result = $this->db->makeArray($rs);
4760
4761
            // get default/built-in template variables
4762
            if(is_array($docRow)){
4763
                ksort($docRow);
4764
4765
                foreach ($docRow as $key => $value) {
4766
                    if ($idnames == '*' || in_array($key, $idnames)) {
4767
                        array_push($result, array(
4768
                            'name' => $key,
4769
                            'value' => $value
4770
                        ));
4771
                    }
4772
                }
4773
            }
4774
4775
            $this->tmpCache[__FUNCTION__][$cacheKey] = $result;
4776
4777
            return $result;
4778
        }
4779
    }
4780
4781
    /**
4782
     * getTemplateVarOutput
4783
     * @version 1.0.1 (2014-02-19)
4784
     *
4785
     * @desc Returns an associative array containing TV rendered output values.
4786
     *
4787
     * @param array $idnames {array; '*'}
4788
     * - 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
4789
     * @param string $docid {integer; ''}
4790
     * - Id of a document to get. Default: an empty string which indicates the current document.
4791
     * @param int $published {0; 1; 'all'}
4792
     * - 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.
4793
     * @param string $sep {string}
4794
     * - Separator that is used while concatenating in getTVDisplayFormat(). Default: ''.
4795
     * @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...
4796
     * - Result array, or false.
4797
     */
4798
    public function getTemplateVarOutput($idnames = array(), $docid = '', $published = 1, $sep = '')
4799
    {
4800
        if (is_array($idnames) && empty($idnames) ) {
4801
            return false;
4802
        } else {
4803
            $output = array();
4804
            $vars = ($idnames == '*' || is_array($idnames)) ? $idnames : array($idnames);
4805
4806
            $docid = (int)$docid > 0 ? (int)$docid : $this->documentIdentifier;
4807
            // remove sort for speed
4808
            $result = $this->getTemplateVars($vars, '*', $docid, $published, '', '');
4809
4810
            if ($result == false) {
4811
                return false;
4812
            } else {
4813
                for ($i = 0; $i < count($result); $i++) {
4814
                    $row = $result[$i];
4815
4816
                    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...
4817
                        $output[$row['name']] = $row['value'];
4818
                    } else {
4819
                        $output[$row['name']] = getTVDisplayFormat($row['name'], $row['value'], $row['display'], $row['display_params'], $row['type'], $docid, $sep);
4820
                    }
4821
                }
4822
4823
                return $output;
4824
            }
4825
        }
4826
    }
4827
4828
    /**
4829
     * Returns the full table name based on db settings
4830
     *
4831
     * @param string $tbl Table name
4832
     * @return string Table name with prefix
4833
     */
4834
    public function getFullTableName($tbl)
4835
    {
4836
        return $this->db->config['dbase'] . ".`" . $this->db->config['table_prefix'] . $tbl . "`";
4837
    }
4838
4839
    /**
4840
     * Returns the placeholder value
4841
     *
4842
     * @param string $name Placeholder name
4843
     * @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...
4844
     */
4845
    public function getPlaceholder($name)
4846
    {
4847
        return isset($this->placeholders[$name]) ? $this->placeholders[$name] : null;
4848
    }
4849
4850
    /**
4851
     * Sets a value for a placeholder
4852
     *
4853
     * @param string $name The name of the placeholder
4854
     * @param string $value The value of the placeholder
4855
     */
4856
    public function setPlaceholder($name, $value)
4857
    {
4858
        $this->placeholders[$name] = $value;
4859
    }
4860
4861
    /**
4862
     * Set placeholders en masse via an array or object.
4863
     *
4864
     * @param object|array $subject
4865
     * @param string $prefix
4866
     */
4867
    public function toPlaceholders($subject, $prefix = '')
4868
    {
4869
        if (is_object($subject)) {
4870
            $subject = get_object_vars($subject);
4871
        }
4872
        if (is_array($subject)) {
4873
            foreach ($subject as $key => $value) {
4874
                $this->toPlaceholder($key, $value, $prefix);
4875
            }
4876
        }
4877
    }
4878
4879
    /**
4880
     * For use by toPlaceholders(); For setting an array or object element as placeholder.
4881
     *
4882
     * @param string $key
4883
     * @param object|array $value
4884
     * @param string $prefix
4885
     */
4886
    public function toPlaceholder($key, $value, $prefix = '')
4887
    {
4888
        if (is_array($value) || is_object($value)) {
4889
            $this->toPlaceholders($value, "{$prefix}{$key}.");
4890
        } else {
4891
            $this->setPlaceholder("{$prefix}{$key}", $value);
4892
        }
4893
    }
4894
4895
    /**
4896
     * Returns the manager relative URL/path with respect to the site root.
4897
     *
4898
     * @global string $base_url
4899
     * @return string The complete URL to the manager folder
4900
     */
4901
    public function getManagerPath()
4902
    {
4903
        return MODX_MANAGER_URL;
4904
    }
4905
4906
    /**
4907
     * Returns the cache relative URL/path with respect to the site root.
4908
     *
4909
     * @global string $base_url
4910
     * @return string The complete URL to the cache folder
4911
     */
4912
    public function getCachePath()
4913
    {
4914
        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...
4915
        $pth = $base_url . $this->getCacheFolder();
4916
        return $pth;
4917
    }
4918
4919
    /**
4920
     * Sends a message to a user's message box.
4921
     *
4922
     * @param string $type Type of the message
4923
     * @param string $to The recipient of the message
4924
     * @param string $from The sender of the message
4925
     * @param string $subject The subject of the message
4926
     * @param string $msg The message body
4927
     * @param int $private Whether it is a private message, or not
4928
     *                     Default : 0
4929
     */
4930
    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...
4931
    {
4932
        $private = ($private) ? 1 : 0;
4933 View Code Duplication
        if (!is_numeric($to)) {
4934
            // Query for the To ID
4935
            $rs = $this->db->select('id', $this->getFullTableName("manager_users"), "username='{$to}'");
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rs. Configured minimum length is 3.

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

Loading history...
4936
            $to = $this->db->getValue($rs);
4937
        }
4938 View Code Duplication
        if (!is_numeric($from)) {
4939
            // Query for the From ID
4940
            $rs = $this->db->select('id', $this->getFullTableName("manager_users"), "username='{$from}'");
4941
            $from = $this->db->getValue($rs);
4942
        }
4943
        // insert a new message into user_messages
4944
        $this->db->insert(array(
4945
            'type' => $type,
4946
            'subject' => $subject,
4947
            'message' => $msg,
4948
            'sender' => $from,
4949
            'recipient' => $to,
4950
            'private' => $private,
4951
            'postdate' => $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'],
4952
            'messageread' => 0,
4953
        ), $this->getFullTableName('user_messages'));
4954
    }
4955
4956
    /**
4957
     * Returns current user id.
4958
     *
4959
     * @param string $context . Default is an empty string which indicates the method should automatically pick 'web (frontend) or 'mgr' (backend)
4960
     * @return string
4961
     */
4962 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...
4963
    {
4964
        $out = false;
4965
4966
        if (!empty($context)) {
4967
            if (is_scalar($context) && isset($_SESSION[$context . 'Validated'])) {
4968
                $out = $_SESSION[$context . 'InternalKey'];
4969
            }
4970
        } else {
4971
            switch (true) {
4972
                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...
4973
                    $out = $_SESSION['webInternalKey'];
4974
                    break;
4975
                }
4976
                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...
4977
                    $out = $_SESSION['mgrInternalKey'];
4978
                    break;
4979
                }
4980
            }
4981
        }
4982
        return $out;
4983
    }
4984
4985
    /**
4986
     * Returns current user name
4987
     *
4988
     * @param string $context . Default is an empty string which indicates the method should automatically pick 'web (frontend) or 'mgr' (backend)
4989
     * @return string
4990
     */
4991 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...
4992
    {
4993
        $out = false;
4994
4995
        if (!empty($context)) {
4996
            if (is_scalar($context) && isset($_SESSION[$context . 'Validated'])) {
4997
                $out = $_SESSION[$context . 'Shortname'];
4998
            }
4999
        } else {
5000
            switch (true) {
5001
                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...
5002
                    $out = $_SESSION['webShortname'];
5003
                    break;
5004
                }
5005
                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...
5006
                    $out = $_SESSION['mgrShortname'];
5007
                    break;
5008
                }
5009
            }
5010
        }
5011
        return $out;
5012
    }
5013
5014
    /**
5015
     * Returns current login user type - web or manager
5016
     *
5017
     * @return string
5018
     */
5019
    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...
5020
    {
5021
        if ($this->isFrontend() && isset ($_SESSION['webValidated'])) {
5022
            return 'web';
5023
        } elseif ($this->isBackend() && isset ($_SESSION['mgrValidated'])) {
5024
            return 'manager';
5025
        } else {
5026
            return '';
5027
        }
5028
    }
5029
5030
    /**
5031
     * Returns a user info record for the given manager user
5032
     *
5033
     * @param int $uid
5034
     * @return boolean|string
5035
     */
5036
    public function getUserInfo($uid)
5037
    {
5038
        if (isset($this->tmpCache[__FUNCTION__][$uid])) {
5039
            return $this->tmpCache[__FUNCTION__][$uid];
5040
        }
5041
5042
        $from = '[+prefix+]manager_users mu INNER JOIN [+prefix+]user_attributes mua ON mua.internalkey=mu.id';
5043
        $where = sprintf("mu.id='%s'", $this->db->escape($uid));
5044
        $rs = $this->db->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...
5045
5046
        if (!$this->db->getRecordCount($rs)) {
5047
            return $this->tmpCache[__FUNCTION__][$uid] = false;
5048
        }
5049
5050
        $row = $this->db->getRow($rs);
5051 View Code Duplication
        if (!isset($row['usertype']) || !$row['usertype']) {
5052
            $row['usertype'] = 'manager';
5053
        }
5054
5055
        $this->tmpCache[__FUNCTION__][$uid] = $row;
5056
5057
        return $row;
5058
    }
5059
5060
    /**
5061
     * Returns a record for the web user
5062
     *
5063
     * @param int $uid
5064
     * @return boolean|string
5065
     */
5066
    public function getWebUserInfo($uid)
5067
    {
5068
        $rs = $this->db->select('wu.username, wu.password, wua.*', $this->getFullTableName("web_users") . " wu
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rs. Configured minimum length is 3.

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

Loading history...
5069
                INNER JOIN " . $this->getFullTableName("web_user_attributes") . " wua ON wua.internalkey=wu.id", "wu.id='{$uid}'");
5070
        if ($row = $this->db->getRow($rs)) {
5071 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...
5072
                $row["usertype"] = "web";
5073
            }
5074
            return $row;
5075
        }
5076
    }
5077
5078
    /**
5079
     * Returns an array of document groups that current user is assigned to.
5080
     * This function will first return the web user doc groups when running from
5081
     * frontend otherwise it will return manager user's docgroup.
5082
     *
5083
     * @param boolean $resolveIds Set to true to return the document group names
5084
     *                            Default: false
5085
     * @return string|array
5086
     */
5087
    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...
5088
    {
5089
        if ($this->isFrontend() && isset($_SESSION['webDocgroups']) && isset($_SESSION['webValidated'])) {
5090
            $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...
5091
            $dgn = isset($_SESSION['webDocgrpNames']) ? $_SESSION['webDocgrpNames'] : false;
5092
        } else if ($this->isBackend() && isset($_SESSION['mgrDocgroups']) && isset($_SESSION['mgrValidated'])) {
5093
            $dg = $_SESSION['mgrDocgroups'];
5094
            $dgn = isset($_SESSION['mgrDocgrpNames']) ? $_SESSION['mgrDocgrpNames'] : false;
5095
        } else {
5096
            $dg = '';
5097
        }
5098
        if (!$resolveIds) {
5099
            return $dg;
5100
        } else if (is_array($dgn)) {
5101
            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...
5102
        } else if (is_array($dg)) {
5103
            // resolve ids to names
5104
            $dgn = array();
5105
            $ds = $this->db->select('name', $this->getFullTableName("documentgroup_names"), "id IN (" . implode(",", $dg) . ")");
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ds. Configured minimum length is 3.

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

Loading history...
5106
            while ($row = $this->db->getRow($ds)) {
5107
                $dgn[] = $row['name'];
5108
            }
5109
            // cache docgroup names to session
5110
            if ($this->isFrontend()) {
5111
                $_SESSION['webDocgrpNames'] = $dgn;
5112
            } else {
5113
                $_SESSION['mgrDocgrpNames'] = $dgn;
5114
            }
5115
            return $dgn;
5116
        }
5117
    }
5118
5119
    /**
5120
     * Change current web user's password
5121
     *
5122
     * @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...
5123
     * @param string $oldPwd
5124
     * @param string $newPwd
5125
     * @return string|boolean Returns true if successful, oterhwise return error
5126
     *                        message
5127
     */
5128
    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...
5129
    {
5130
        $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...
5131
        if ($_SESSION["webValidated"] == 1) {
5132
            $tbl = $this->getFullTableName("web_users");
5133
            $ds = $this->db->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...
5134
            if ($row = $this->db->getRow($ds)) {
5135
                if ($row["password"] == md5($oldPwd)) {
5136
                    if (strlen($newPwd) < 6) {
5137
                        return "Password is too short!";
5138
                    } elseif ($newPwd == "") {
5139
                        return "You didn't specify a password for this user!";
5140
                    } else {
5141
                        $this->db->update(array(
5142
                            'password' => $this->db->escape($newPwd),
5143
                        ), $tbl, "id='" . $this->getLoginUserID() . "'");
5144
                        // invoke OnWebChangePassword event
5145
                        $this->invokeEvent("OnWebChangePassword", array(
5146
                            "userid" => $row["id"],
5147
                            "username" => $row["username"],
5148
                            "userpassword" => $newPwd
5149
                        ));
5150
                        return true;
5151
                    }
5152
                } else {
5153
                    return "Incorrect password.";
5154
                }
5155
            }
5156
        }
5157
        return $rt;
5158
    }
5159
5160
    /**
5161
     * Returns true if the current web user is a member the specified groups
5162
     *
5163
     * @param array $groupNames
5164
     * @return boolean
5165
     */
5166
    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...
5167
    {
5168
        if (!is_array($groupNames)) {
5169
            return false;
5170
        }
5171
        // check cache
5172
        $grpNames = isset ($_SESSION['webUserGroupNames']) ? $_SESSION['webUserGroupNames'] : false;
5173
        if (!is_array($grpNames)) {
5174
            $rs = $this->db->select('wgn.name', $this->getFullTableName("webgroup_names") . " wgn
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rs. Configured minimum length is 3.

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

Loading history...
5175
                    INNER JOIN " . $this->getFullTableName("web_groups") . " wg ON wg.webgroup=wgn.id AND wg.webuser='" . $this->getLoginUserID() . "'");
5176
            $grpNames = $this->db->getColumn("name", $rs);
5177
            // save to cache
5178
            $_SESSION['webUserGroupNames'] = $grpNames;
5179
        }
5180
        foreach ($groupNames as $k => $v) {
5181
            if (in_array(trim($v), $grpNames)) {
5182
                return true;
5183
            }
5184
        }
5185
        return false;
5186
    }
5187
5188
    /**
5189
     * Registers Client-side CSS scripts - these scripts are loaded at inside
5190
     * the <head> tag
5191
     *
5192
     * @param string $src
5193
     * @param string $media Default: Empty string
5194
     * @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...
5195
     */
5196
    public function regClientCSS($src, $media = '')
5197
    {
5198
        if (empty($src) || isset ($this->loadedjscripts[$src])) {
5199
            return '';
5200
        }
5201
        $nextpos = max(array_merge(array(0), array_keys($this->sjscripts))) + 1;
5202
        $this->loadedjscripts[$src]['startup'] = true;
5203
        $this->loadedjscripts[$src]['version'] = '0';
5204
        $this->loadedjscripts[$src]['pos'] = $nextpos;
5205
        if (strpos(strtolower($src), "<style") !== false || strpos(strtolower($src), "<link") !== false) {
5206
            $this->sjscripts[$nextpos] = $src;
5207
        } else {
5208
            $this->sjscripts[$nextpos] = "\t" . '<link rel="stylesheet" type="text/css" href="' . $src . '" ' . ($media ? 'media="' . $media . '" ' : '') . '/>';
5209
        }
5210
    }
5211
5212
    /**
5213
     * Registers Startup Client-side JavaScript - these scripts are loaded at inside the <head> tag
5214
     *
5215
     * @param string $src
5216
     * @param array $options Default: 'name'=>'', 'version'=>'0', 'plaintext'=>false
5217
     */
5218
    public function regClientStartupScript($src, $options = array('name' => '', 'version' => '0', 'plaintext' => false))
5219
    {
5220
        $this->regClientScript($src, $options, true);
5221
    }
5222
5223
    /**
5224
     * Registers Client-side JavaScript these scripts are loaded at the end of the page unless $startup is true
5225
     *
5226
     * @param string $src
5227
     * @param array $options Default: 'name'=>'', 'version'=>'0', 'plaintext'=>false
5228
     * @param boolean $startup Default: false
5229
     * @return string
5230
     */
5231
    public function regClientScript($src, $options = array('name' => '', 'version' => '0', 'plaintext' => false), $startup = false)
5232
    {
5233
        if (empty($src)) {
5234
            return '';
5235
        } // nothing to register
5236
        if (!is_array($options)) {
5237
            if (is_bool($options))  // backward compatibility with old plaintext parameter
5238
            {
5239
                $options = array('plaintext' => $options);
5240
            } elseif (is_string($options)) // Also allow script name as 2nd param
5241
            {
5242
                $options = array('name' => $options);
5243
            } else {
5244
                $options = array();
5245
            }
5246
        }
5247
        $name = isset($options['name']) ? strtolower($options['name']) : '';
5248
        $version = isset($options['version']) ? $options['version'] : '0';
5249
        $plaintext = isset($options['plaintext']) ? $options['plaintext'] : false;
5250
        $key = !empty($name) ? $name : $src;
5251
        unset($overwritepos); // probably unnecessary--just making sure
5252
5253
        $useThisVer = true;
5254
        if (isset($this->loadedjscripts[$key])) { // a matching script was found
5255
            // if existing script is a startup script, make sure the candidate is also a startup script
5256
            if ($this->loadedjscripts[$key]['startup']) {
5257
                $startup = true;
5258
            }
5259
5260
            if (empty($name)) {
5261
                $useThisVer = false; // if the match was based on identical source code, no need to replace the old one
5262
            } else {
5263
                $useThisVer = version_compare($this->loadedjscripts[$key]['version'], $version, '<');
5264
            }
5265
5266
            if ($useThisVer) {
5267
                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...
5268
                    // remove old script from the bottom of the page (new one will be at the top)
5269
                    unset($this->jscripts[$this->loadedjscripts[$key]['pos']]);
5270
                } else {
5271
                    // overwrite the old script (the position may be important for dependent scripts)
5272
                    $overwritepos = $this->loadedjscripts[$key]['pos'];
5273
                }
5274
            } else { // Use the original version
5275
                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...
5276
                    // need to move the exisiting script to the head
5277
                    $version = $this->loadedjscripts[$key][$version];
5278
                    $src = $this->jscripts[$this->loadedjscripts[$key]['pos']];
5279
                    unset($this->jscripts[$this->loadedjscripts[$key]['pos']]);
5280
                } else {
5281
                    return ''; // the script is already in the right place
5282
                }
5283
            }
5284
        }
5285
5286
        if ($useThisVer && $plaintext != true && (strpos(strtolower($src), "<script") === false)) {
5287
            $src = "\t" . '<script type="text/javascript" src="' . $src . '"></script>';
5288
        }
5289
        if ($startup) {
5290
            $pos = isset($overwritepos) ? $overwritepos : max(array_merge(array(0), array_keys($this->sjscripts))) + 1;
5291
            $this->sjscripts[$pos] = $src;
5292
        } else {
5293
            $pos = isset($overwritepos) ? $overwritepos : max(array_merge(array(0), array_keys($this->jscripts))) + 1;
5294
            $this->jscripts[$pos] = $src;
5295
        }
5296
        $this->loadedjscripts[$key]['version'] = $version;
5297
        $this->loadedjscripts[$key]['startup'] = $startup;
5298
        $this->loadedjscripts[$key]['pos'] = $pos;
5299
        return '';
5300
    }
5301
5302
    /**
5303
     * Returns all registered JavaScripts
5304
     *
5305
     * @return string
5306
     */
5307
    public function regClientStartupHTMLBlock($html)
5308
    {
5309
        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...
5310
    }
5311
5312
    /**
5313
     * Returns all registered startup scripts
5314
     *
5315
     * @return string
5316
     */
5317
    public function regClientHTMLBlock($html)
5318
    {
5319
        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...
5320
    }
5321
5322
    /**
5323
     * Remove unwanted html tags and snippet, settings and tags
5324
     *
5325
     * @param string $html
5326
     * @param string $allowed Default: Empty string
5327
     * @return string
5328
     */
5329
    public function stripTags($html, $allowed = "")
5330
    {
5331
        $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...
5332
        $t = preg_replace('~\[\*(.*?)\*\]~', "", $t); //tv
5333
        $t = preg_replace('~\[\[(.*?)\]\]~', "", $t); //snippet
5334
        $t = preg_replace('~\[\!(.*?)\!\]~', "", $t); //snippet
5335
        $t = preg_replace('~\[\((.*?)\)\]~', "", $t); //settings
5336
        $t = preg_replace('~\[\+(.*?)\+\]~', "", $t); //placeholders
5337
        $t = preg_replace('~{{(.*?)}}~', "", $t); //chunks
5338
        return $t;
5339
    }
5340
5341
    /**
5342
     * Add an event listener to a plugin - only for use within the current execution cycle
5343
     *
5344
     * @param string $evtName
5345
     * @param string $pluginName
5346
     * @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...
5347
     */
5348
    public function addEventListener($evtName, $pluginName)
5349
    {
5350
        if (!$evtName || !$pluginName) {
5351
            return false;
5352
        }
5353
        if (!array_key_exists($evtName, $this->pluginEvent)) {
5354
            $this->pluginEvent[$evtName] = array();
5355
        }
5356
        return array_push($this->pluginEvent[$evtName], $pluginName); // return array count
5357
    }
5358
5359
    /**
5360
     * Remove event listener - only for use within the current execution cycle
5361
     *
5362
     * @param string $evtName
5363
     * @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...
5364
     */
5365
    public function removeEventListener($evtName)
5366
    {
5367
        if (!$evtName) {
5368
            return false;
5369
        }
5370
        unset ($this->pluginEvent[$evtName]);
5371
    }
5372
5373
    /**
5374
     * Remove all event listeners - only for use within the current execution cycle
5375
     */
5376
    public function removeAllEventListener()
5377
    {
5378
        unset ($this->pluginEvent);
5379
        $this->pluginEvent = array();
5380
    }
5381
5382
    /**
5383
     * Invoke an event.
5384
     *
5385
     * @param string $evtName
5386
     * @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.
5387
     * @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...
5388
     */
5389
    public function invokeEvent($evtName, $extParams = array())
5390
    {
5391
        if (!$evtName) {
5392
            return false;
5393
        }
5394
        if (!isset ($this->pluginEvent[$evtName])) {
5395
            return false;
5396
        }
5397
5398
        $results = null;
5399
        foreach ($this->pluginEvent[$evtName] as $pluginName) { // start for loop
5400
            if ($this->dumpPlugins) {
5401
                $eventtime = $this->getMicroTime();
5402
            }
5403
            // reset event object
5404
            $e = &$this->event;
5405
            $e->_resetEventObject();
5406
            $e->name = $evtName;
5407
            $e->activePlugin = $pluginName;
5408
5409
            // get plugin code
5410
            $_ = $this->getPluginCode($pluginName);
5411
            $pluginCode = $_['code'];
5412
            $pluginProperties = $_['props'];
5413
5414
            // load default params/properties
5415
            $parameter = $this->parseProperties($pluginProperties);
5416
            if (!is_array($parameter)) {
5417
                $parameter = array();
5418
            }
5419
            if (!empty($extParams)) {
5420
                $parameter = array_merge($parameter, $extParams);
5421
            }
5422
5423
            // eval plugin
5424
            $this->evalPlugin($pluginCode, $parameter);
5425
5426
            if (class_exists('PHxParser')) {
5427
                $this->config['enable_filter'] = 0;
5428
            }
5429
5430
            if ($this->dumpPlugins) {
5431
                $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...
5432
                $this->pluginsCode .= sprintf('<fieldset><legend><b>%s / %s</b> (%2.2f ms)</legend>', $evtName, $pluginName, $eventtime * 1000);
5433
                foreach ($parameter as $k => $v) {
5434
                    $this->pluginsCode .= "{$k} => " . print_r($v, true) . '<br>';
5435
                }
5436
                $this->pluginsCode .= '</fieldset><br />';
5437
                $this->pluginsTime["{$evtName} / {$pluginName}"] += $eventtime;
5438
            }
5439
            if ($e->getOutput() != '') {
5440
                $results[] = $e->getOutput();
5441
            }
5442
            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...
5443
                break;
5444
            }
5445
        }
5446
5447
        $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...
5448
        return $results;
5449
    }
5450
5451
    /**
5452
     * Returns plugin-code and properties
5453
     *
5454
     * @param string $pluginName
5455
     * @return array Associative array consisting of 'code' and 'props'
5456
     */
5457
    public function getPluginCode($pluginName)
5458
    {
5459
        $plugin = array();
5460
        if (isset ($this->pluginCache[$pluginName])) {
5461
            $pluginCode = $this->pluginCache[$pluginName];
5462
            $pluginProperties = isset($this->pluginCache[$pluginName . "Props"]) ? $this->pluginCache[$pluginName . "Props"] : '';
5463
        } else {
5464
            $pluginName = $this->db->escape($pluginName);
5465
            $result = $this->db->select('name, plugincode, properties', $this->getFullTableName("site_plugins"), "name='{$pluginName}' AND disabled=0");
5466
            if ($row = $this->db->getRow($result)) {
5467
                $pluginCode = $this->pluginCache[$row['name']] = $row['plugincode'];
5468
                $pluginProperties = $this->pluginCache[$row['name'] . "Props"] = $row['properties'];
5469
            } else {
5470
                $pluginCode = $this->pluginCache[$pluginName] = "return false;";
5471
                $pluginProperties = '';
5472
            }
5473
        }
5474
        $plugin['code'] = $pluginCode;
5475
        $plugin['props'] = $pluginProperties;
5476
5477
        return $plugin;
5478
    }
5479
5480
    /**
5481
     * Parses a resource property string and returns the result as an array
5482
     *
5483
     * @param string|array $propertyString
5484
     * @param string|null $elementName
5485
     * @param string|null $elementType
5486
     * @return array Associative array in the form property name => property value
5487
     */
5488
    public function parseProperties($propertyString, $elementName = null, $elementType = null)
5489
    {
5490
        $property = array();
5491
5492
        if(\is_scalar($propertyString)) {
5493
            $propertyString = trim($propertyString);
5494
            $propertyString = str_replace('{}', '', $propertyString);
5495
            $propertyString = str_replace('} {', ',', $propertyString);
5496
            if (!empty($propertyString) && $propertyString != '{}') {
5497
                $jsonFormat = $this->isJson($propertyString, true);
5498
                // old format
5499
                if ($jsonFormat === false) {
5500
                    $props = explode('&', $propertyString);
5501
                    foreach ($props as $prop) {
5502
5503
                        if (empty($prop)) {
5504
                            continue;
5505
                        } elseif (strpos($prop, '=') === false) {
5506
                            $property[trim($prop)] = '';
5507
                            continue;
5508
                        }
5509
5510
                        $_ = explode('=', $prop, 2);
5511
                        $key = trim($_[0]);
5512
                        $p = explode(';', trim($_[1]));
5513
                        switch ($p[1]) {
5514
                            case 'list':
5515
                            case 'list-multi':
5516
                            case 'checkbox':
5517
                            case 'radio':
5518
                                $value = !isset($p[3]) ? '' : $p[3];
5519
                                break;
5520
                            default:
5521
                                $value = !isset($p[2]) ? '' : $p[2];
5522
                        }
5523
                        if (!empty($key)) {
5524
                            $property[$key] = $value;
5525
                        }
5526
                    }
5527
                    // new json-format
5528
                } else if (!empty($jsonFormat)) {
5529
                    foreach ($jsonFormat as $key => $row) {
5530
                        if (!empty($key)) {
5531 View Code Duplication
                            if (is_array($row)) {
5532
                                if (isset($row[0]['value'])) {
5533
                                    $value = $row[0]['value'];
5534
                                }
5535
                            } else {
5536
                                $value = $row;
5537
                            }
5538
                            if (isset($value) && $value !== '') {
5539
                                $property[$key] = $value;
5540
                            }
5541
                        }
5542
                    }
5543
                }
5544
            }
5545
        }
5546
        elseif(\is_array($propertyString)) {
5547
            $property = $propertyString;
5548
        }
5549
        if (!empty($elementName) && !empty($elementType)) {
5550
            $out = $this->invokeEvent('OnParseProperties', array(
5551
                'element' => $elementName,
5552
                'type' => $elementType,
5553
                'args' => $property
5554
            ));
5555
            if (is_array($out)) {
5556
                $out = array_pop($out);
5557
            }
5558
            if (is_array($out)) {
5559
                $property = $out;
5560
            }
5561
        }
5562
5563
        return $property;
5564
    }
5565
5566
    /**
5567
     * Parses docBlock from a file and returns the result as an array
5568
     *
5569
     * @param string $element_dir
5570
     * @param string $filename
5571
     * @param boolean $escapeValues
5572
     * @return array Associative array in the form property name => property value
5573
     */
5574
    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...
5575
    {
5576
        $params = array();
5577
        $fullpath = $element_dir . '/' . $filename;
5578
        if (is_readable($fullpath)) {
5579
            $tpl = @fopen($fullpath, "r");
5580
            if ($tpl) {
5581
                $params['filename'] = $filename;
5582
                $docblock_start_found = false;
5583
                $name_found = false;
5584
                $description_found = false;
5585
                $docblock_end_found = false;
5586
                $arrayParams = array('author', 'documentation', 'reportissues', 'link');
5587
5588
                while (!feof($tpl)) {
5589
                    $line = fgets($tpl);
5590
                    $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...
5591
                    $docblock_start_found = $r['docblock_start_found'];
5592
                    $name_found = $r['name_found'];
5593
                    $description_found = $r['description_found'];
5594
                    $docblock_end_found = $r['docblock_end_found'];
5595
                    $param = $r['param'];
5596
                    $val = $r['val'];
5597
                    if (!$docblock_end_found) {
5598
                        break;
5599
                    }
5600
                    if (!$docblock_start_found || !$name_found || !$description_found || empty($param)) {
5601
                        continue;
5602
                    }
5603 View Code Duplication
                    if (!empty($param)) {
5604
                        if (in_array($param, $arrayParams)) {
5605
                            if (!isset($params[$param])) {
5606
                                $params[$param] = array();
5607
                            }
5608
                            $params[$param][] = $escapeValues ? $this->db->escape($val) : $val;
5609
                        } else {
5610
                            $params[$param] = $escapeValues ? $this->db->escape($val) : $val;
5611
                        }
5612
                    }
5613
                }
5614
                @fclose($tpl);
5615
            }
5616
        }
5617
        return $params;
5618
    }
5619
5620
    /**
5621
     * Parses docBlock from string and returns the result as an array
5622
     *
5623
     * @param string $string
5624
     * @param boolean $escapeValues
5625
     * @return array Associative array in the form property name => property value
5626
     */
5627
    public function parseDocBlockFromString($string, $escapeValues = false)
5628
    {
5629
        $params = array();
5630
        if (!empty($string)) {
5631
            $string = str_replace('\r\n', '\n', $string);
5632
            $exp = explode('\n', $string);
5633
            $docblock_start_found = false;
5634
            $name_found = false;
5635
            $description_found = false;
5636
            $docblock_end_found = false;
5637
            $arrayParams = array('author', 'documentation', 'reportissues', 'link');
5638
5639
            foreach ($exp as $line) {
5640
                $r = $this->parseDocBlockLine($line, $docblock_start_found, $name_found, $description_found, $docblock_end_found);
5641
                $docblock_start_found = $r['docblock_start_found'];
5642
                $name_found = $r['name_found'];
5643
                $description_found = $r['description_found'];
5644
                $docblock_end_found = $r['docblock_end_found'];
5645
                $param = $r['param'];
5646
                $val = $r['val'];
5647
                if (!$docblock_start_found) {
5648
                    continue;
5649
                }
5650
                if ($docblock_end_found) {
5651
                    break;
5652
                }
5653 View Code Duplication
                if (!empty($param)) {
5654
                    if (in_array($param, $arrayParams)) {
5655
                        if (!isset($params[$param])) {
5656
                            $params[$param] = array();
5657
                        }
5658
                        $params[$param][] = $escapeValues ? $this->db->escape($val) : $val;
5659
                    } else {
5660
                        $params[$param] = $escapeValues ? $this->db->escape($val) : $val;
5661
                    }
5662
                }
5663
            }
5664
        }
5665
        return $params;
5666
    }
5667
5668
    /**
5669
     * Parses docBlock of a component´s source-code and returns the result as an array
5670
     * (modified parseDocBlock() from modules/stores/setup.info.php by Bumkaka & Dmi3yy)
5671
     *
5672
     * @param string $line
5673
     * @param boolean $docblock_start_found
5674
     * @param boolean $name_found
5675
     * @param boolean $description_found
5676
     * @param boolean $docblock_end_found
5677
     * @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...
5678
     */
5679
    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...
5680
    {
5681
        $param = '';
5682
        $val = '';
5683
        $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...
5684
        if (!$docblock_start_found) {
5685
            // find docblock start
5686
            if (strpos($line, '/**') !== false) {
5687
                $docblock_start_found = true;
5688
            }
5689
        } elseif (!$name_found) {
5690
            // find name
5691
            if (preg_match("/^\s+\*\s+(.+)/", $line, $ma)) {
5692
                $param = 'name';
5693
                $val = trim($ma[1]);
5694
                $name_found = !empty($val);
5695
            }
5696
        } elseif (!$description_found) {
5697
            // find description
5698
            if (preg_match("/^\s+\*\s+(.+)/", $line, $ma)) {
5699
                $param = 'description';
5700
                $val = trim($ma[1]);
5701
                $description_found = !empty($val);
5702
            }
5703
        } else {
5704
            if (preg_match("/^\s+\*\s+\@([^\s]+)\s+(.+)/", $line, $ma)) {
5705
                $param = trim($ma[1]);
5706
                $val = trim($ma[2]);
5707
                if (!empty($param) && !empty($val)) {
5708
                    if ($param == 'internal') {
5709
                        $ma = null;
5710
                        if (preg_match("/\@([^\s]+)\s+(.+)/", $val, $ma)) {
5711
                            $param = trim($ma[1]);
5712
                            $val = trim($ma[2]);
5713
                        }
5714
                    }
5715
                }
5716
            } elseif (preg_match("/^\s*\*\/\s*$/", $line)) {
5717
                $docblock_end_found = true;
5718
            }
5719
        }
5720
        return array(
5721
            'docblock_start_found' => $docblock_start_found,
5722
            'name_found' => $name_found,
5723
            'description_found' => $description_found,
5724
            'docblock_end_found' => $docblock_end_found,
5725
            'param' => $param,
5726
            'val' => $val
5727
        );
5728
    }
5729
5730
    /**
5731
     * Renders docBlock-parameters into human readable list
5732
     *
5733
     * @param array $parsed
5734
     * @return string List in HTML-format
5735
     */
5736
    public function convertDocBlockIntoList($parsed)
5737
    {
5738
        global $_lang;
5739
5740
        // Replace special placeholders & make URLs + Emails clickable
5741
        $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...
5742
        $regexUrl = "/((http|https|ftp|ftps)\:\/\/[^\/]+(\/[^\s]+[^,.?!:;\s])?)/";
5743
        $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';
5744
        $emailSubject = isset($parsed['name']) ? '?subject=' . $parsed['name'] : '';
5745
        $emailSubject .= isset($parsed['version']) ? ' v' . $parsed['version'] : '';
5746
        foreach ($parsed as $key => $val) {
5747
            if (is_array($val)) {
5748
                foreach ($val as $key2 => $val2) {
5749
                    $val2 = $this->parseText($val2, $ph);
5750 View Code Duplication
                    if (preg_match($regexUrl, $val2, $url)) {
5751
                        $val2 = preg_replace($regexUrl, "<a href=\"{$url[0]}\" target=\"_blank\">{$url[0]}</a> ", $val2);
5752
                    }
5753 View Code Duplication
                    if (preg_match($regexEmail, $val2, $url)) {
5754
                        $val2 = preg_replace($regexEmail, '<a href="mailto:\\1' . $emailSubject . '">\\1</a>', $val2);
5755
                    }
5756
                    $parsed[$key][$key2] = $val2;
5757
                }
5758
            } else {
5759
                $val = $this->parseText($val, $ph);
5760 View Code Duplication
                if (preg_match($regexUrl, $val, $url)) {
5761
                    $val = preg_replace($regexUrl, "<a href=\"{$url[0]}\" target=\"_blank\">{$url[0]}</a> ", $val);
5762
                }
5763 View Code Duplication
                if (preg_match($regexEmail, $val, $url)) {
5764
                    $val = preg_replace($regexEmail, '<a href="mailto:\\1' . $emailSubject . '">\\1</a>', $val);
5765
                }
5766
                $parsed[$key] = $val;
5767
            }
5768
        }
5769
5770
        $arrayParams = array(
5771
            'documentation' => $_lang['documentation'],
5772
            'reportissues' => $_lang['report_issues'],
5773
            'link' => $_lang['further_info'],
5774
            'author' => $_lang['author_infos']
5775
        );
5776
5777
        $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...
5778
        $list = isset($parsed['logo']) ? '<img src="' . $this->config['base_url'] . ltrim($parsed['logo'], "/") . '" style="float:right;max-width:100px;height:auto;" />' . $nl : '';
5779
        $list .= '<p>' . $nl;
5780
        $list .= isset($parsed['name']) ? '<strong>' . $parsed['name'] . '</strong><br/>' . $nl : '';
5781
        $list .= isset($parsed['description']) ? $parsed['description'] . $nl : '';
5782
        $list .= '</p><br/>' . $nl;
5783
        $list .= isset($parsed['version']) ? '<p><strong>' . $_lang['version'] . ':</strong> ' . $parsed['version'] . '</p>' . $nl : '';
5784
        $list .= isset($parsed['license']) ? '<p><strong>' . $_lang['license'] . ':</strong> ' . $parsed['license'] . '</p>' . $nl : '';
5785
        $list .= isset($parsed['lastupdate']) ? '<p><strong>' . $_lang['last_update'] . ':</strong> ' . $parsed['lastupdate'] . '</p>' . $nl : '';
5786
        $list .= '<br/>' . $nl;
5787
        $first = true;
5788
        foreach ($arrayParams as $param => $label) {
5789
            if (isset($parsed[$param])) {
5790
                if ($first) {
5791
                    $list .= '<p><strong>' . $_lang['references'] . '</strong></p>' . $nl;
5792
                    $list .= '<ul class="docBlockList">' . $nl;
5793
                    $first = false;
5794
                }
5795
                $list .= '    <li><strong>' . $label . '</strong>' . $nl;
5796
                $list .= '        <ul>' . $nl;
5797
                foreach ($parsed[$param] as $val) {
5798
                    $list .= '            <li>' . $val . '</li>' . $nl;
5799
                }
5800
                $list .= '        </ul></li>' . $nl;
5801
            }
5802
        }
5803
        $list .= !$first ? '</ul>' . $nl : '';
5804
5805
        return $list;
5806
    }
5807
5808
    /**
5809
     * @param string $string
5810
     * @return string
5811
     */
5812
    public function removeSanitizeSeed($string = '')
5813
    {
5814
        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...
5815
5816
        if (!$string || strpos($string, $sanitize_seed) === false) {
5817
            return $string;
5818
        }
5819
5820
        return str_replace($sanitize_seed, '', $string);
5821
    }
5822
5823
    /**
5824
     * @param string $content
5825
     * @return string
5826
     */
5827
    public function cleanUpMODXTags($content = '')
5828
    {
5829
        if ($this->minParserPasses < 1) {
5830
            return $content;
5831
        }
5832
5833
        $enable_filter = $this->config['enable_filter'];
5834
        $this->config['enable_filter'] = 1;
5835
        $_ = 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...
5836
        foreach ($_ as $brackets) {
5837
            list($left, $right) = explode(' ', $brackets);
5838
            if (strpos($content, $left) !== false) {
5839
                if ($left === '[*') {
5840
                    $content = $this->mergeDocumentContent($content);
5841
                } elseif ($left === '[(') {
5842
                    $content = $this->mergeSettingsContent($content);
5843
                } elseif ($left === '{{') {
5844
                    $content = $this->mergeChunkContent($content);
5845
                } elseif ($left === '[[') {
5846
                    $content = $this->evalSnippets($content);
5847
                }
5848
            }
5849
        }
5850
        foreach ($_ as $brackets) {
5851
            list($left, $right) = explode(' ', $brackets);
5852
            if (strpos($content, $left) !== false) {
5853
                $matches = $this->getTagsFromContent($content, $left, $right);
5854
                $content = str_replace($matches[0], '', $content);
5855
            }
5856
        }
5857
        $this->config['enable_filter'] = $enable_filter;
5858
        return $content;
5859
    }
5860
5861
    /**
5862
     * @param string $str
5863
     * @param string $allowable_tags
5864
     * @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...
5865
     */
5866
    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...
5867
    {
5868
        $str = strip_tags($str, $allowable_tags);
5869
        modx_sanitize_gpc($str);
5870
        return $str;
5871
    }
5872
5873
    /**
5874
     * {@inheritdoc}
5875
     */
5876
    public function addSnippet($name, $phpCode, $namespace = '#', array $defaultParams = array())
5877
    {
5878
        $this->snippetCache[$namespace . $name] = $phpCode;
5879
        $this->snippetCache[$namespace . $name . 'Props'] = $defaultParams;
5880
    }
5881
5882
    /**
5883
     * {@inheritdoc}
5884
     */
5885
    public function addChunk($name, $text, $namespace = '#')
5886
    {
5887
        $this->chunkCache[$namespace . $name] = $text;
5888
    }
5889
5890
    /**
5891
     * {@inheritdoc}
5892
     */
5893
    public function findElements($type, $scanPath, array $ext)
5894
    {
5895
        $out = array();
5896
5897
        if (! is_dir($scanPath) || empty($ext)) {
5898
            return $out;
5899
        }
5900
        $iterator = new \RecursiveIteratorIterator(
5901
            new \RecursiveDirectoryIterator($scanPath, \RecursiveDirectoryIterator::SKIP_DOTS),
5902
            \RecursiveIteratorIterator::SELF_FIRST
5903
        );
5904
        foreach ($iterator as $item) {
5905
            /**
5906
             * @var \SplFileInfo $item
5907
             */
5908
            if ($item->isFile() && $item->isReadable() && \in_array($item->getExtension(), $ext)) {
5909
                $name = $item->getBasename('.' . $item->getExtension());
5910
                $path = ltrim(str_replace(
5911
                    array(rtrim($scanPath, '//'), '/'),
5912
                    array('', '\\'),
5913
                    $item->getPath() . '/'
5914
                ), '\\');
5915
5916
                if (!empty($path)) {
5917
                    $name = $path . $name;
5918
                }
5919
                switch ($type) {
5920
                    case 'chunk':
5921
                        $out[$name] = file_get_contents($item->getRealPath());
5922
                        break;
5923
                    case 'snippet':
5924
                        $out[$name] = "return require '" . $item->getRealPath() . "';";
5925
                        break;
5926
                    default:
5927
                        throw new \Exception;
5928
                }
5929
            }
5930
        }
5931
5932
        return $out;
5933
    }
5934
5935
    /**
5936
     * @param string $phpcode
5937
     * @param string $evalmode
5938
     * @param string $safe_functions
5939
     * @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...
5940
     */
5941
    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...
5942
    {
5943
        if ($evalmode == '') {
5944
            $evalmode = $this->config['allow_eval'];
5945
        }
5946
        if ($safe_functions == '') {
5947
            $safe_functions = $this->config['safe_functions_at_eval'];
5948
        }
5949
5950
        modx_sanitize_gpc($phpcode);
5951
5952
        switch ($evalmode) {
5953
            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...
5954
                $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...
5955
                break;
5956
            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...
5957
                $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...
5958
                break;
5959
            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...
5960
                $isSafe = true;
5961
                break; // Should debug only
5962
            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...
5963
            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...
5964
                return $phpcode;
5965
        }
5966
5967
        if (!$isSafe) {
5968
            $msg = $phpcode . "\n" . $this->currentSnippet . "\n" . print_r($_SERVER, true);
5969
            $title = sprintf('Unknown eval was executed (%s)', $this->htmlspecialchars(substr(trim($phpcode), 0, 50)));
5970
            $this->messageQuit($title, '', true, '', '', 'Parser', $msg);
5971
            return;
5972
        }
5973
5974
        ob_start();
5975
        $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...
5976
        $echo = ob_get_clean();
5977
5978
        if (is_array($return)) {
5979
            return 'array()';
5980
        }
5981
5982
        $output = $echo . $return;
5983
        modx_sanitize_gpc($output);
5984
        return $this->htmlspecialchars($output); // Maybe, all html tags are dangerous
5985
    }
5986
5987
    /**
5988
     * @param string $phpcode
5989
     * @param string $safe_functions
5990
     * @return bool
5991
     */
5992
    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...
5993
    { // 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...
5994
        if ($safe_functions == '') {
5995
            return false;
5996
        }
5997
5998
        $safe = explode(',', $safe_functions);
5999
6000
        $phpcode = rtrim($phpcode, ';') . ';';
6001
        $tokens = token_get_all('<?php ' . $phpcode);
6002
        foreach ($tokens as $i => $token) {
6003
            if (!is_array($token)) {
6004
                continue;
6005
            }
6006
            $tokens[$i]['token_name'] = token_name($token[0]);
6007
        }
6008
        foreach ($tokens as $token) {
6009
            if (!is_array($token)) {
6010
                continue;
6011
            }
6012
            switch ($token['token_name']) {
6013
                case 'T_STRING':
6014
                    if (!in_array($token[1], $safe)) {
6015
                        return false;
6016
                    }
6017
                    break;
6018
                case 'T_VARIABLE':
6019
                    if ($token[1] == '$GLOBALS') {
6020
                        return false;
6021
                    }
6022
                    break;
6023
                case 'T_EVAL':
6024
                    return false;
6025
            }
6026
        }
6027
        return true;
6028
    }
6029
6030
    /**
6031
     * @param string $str
6032
     * @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...
6033
     */
6034
    public function atBindFileContent($str = '')
6035
    {
6036
6037
        $search_path = array('assets/tvs/', 'assets/chunks/', 'assets/templates/', $this->config['rb_base_url'] . 'files/', '');
6038
6039
        if (stripos($str, '@FILE') !== 0) {
6040
            return $str;
6041
        }
6042 View Code Duplication
        if (strpos($str, "\n") !== false) {
6043
            $str = substr($str, 0, strpos("\n", $str));
6044
        }
6045
6046
        if ($this->getExtFromFilename($str) === '.php') {
6047
            return 'Could not retrieve PHP file.';
6048
        }
6049
6050
        $str = substr($str, 6);
6051
        $str = trim($str);
6052
        if (strpos($str, '\\') !== false) {
6053
            $str = str_replace('\\', '/', $str);
6054
        }
6055
        $str = ltrim($str, '/');
6056
6057
        $errorMsg = sprintf("Could not retrieve string '%s'.", $str);
6058
6059
        foreach ($search_path as $path) {
6060
            $file_path = MODX_BASE_PATH . $path . $str;
6061
            if (strpos($file_path, MODX_MANAGER_PATH) === 0) {
6062
                return $errorMsg;
6063
            } elseif (is_file($file_path)) {
6064
                break;
6065
            } else {
6066
                $file_path = false;
6067
            }
6068
        }
6069
6070
        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...
6071
            return $errorMsg;
6072
        }
6073
6074
        $content = (string)file_get_contents($file_path);
6075
        if ($content === false) {
6076
            return $errorMsg;
6077
        }
6078
6079
        return $content;
6080
    }
6081
6082
    /**
6083
     * @param $str
6084
     * @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...
6085
     */
6086
    public function getExtFromFilename($str)
6087
    {
6088
        $str = strtolower(trim($str));
6089
        $pos = strrpos($str, '.');
6090
        if ($pos === false) {
6091
            return false;
6092
        } else {
6093
            return substr($str, $pos);
6094
        }
6095
    }
6096
    /***************************************************************************************/
6097
    /* End of API functions                                       */
6098
    /***************************************************************************************/
6099
6100
    /**
6101
     * PHP error handler set by http://www.php.net/manual/en/function.set-error-handler.php
6102
     *
6103
     * Checks the PHP error and calls messageQuit() unless:
6104
     *  - error_reporting() returns 0, or
6105
     *  - the PHP error level is 0, or
6106
     *  - the PHP error level is 8 (E_NOTICE) and stopOnNotice is false
6107
     *
6108
     * @param int $nr The PHP error level as per http://www.php.net/manual/en/errorfunc.constants.php
6109
     * @param string $text Error message
6110
     * @param string $file File where the error was detected
6111
     * @param string $line Line number within $file
6112
     * @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...
6113
     */
6114
    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...
6115
    {
6116
        if (error_reporting() == 0 || $nr == 0) {
6117
            return true;
6118
        }
6119
        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...
6120
            switch ($nr) {
6121
                case E_NOTICE:
6122
                    if ($this->error_reporting <= 2) {
6123
                        return true;
6124
                    }
6125
                    $isError = false;
6126
                    $msg = 'PHP Minor Problem (this message show logged in only)';
6127
                    break;
6128
                case E_STRICT:
6129 View Code Duplication
                case E_DEPRECATED:
6130
                    if ($this->error_reporting <= 1) {
6131
                        return true;
6132
                    }
6133
                    $isError = true;
6134
                    $msg = 'PHP Strict Standards Problem';
6135
                    break;
6136 View Code Duplication
                default:
6137
                    if ($this->error_reporting === 0) {
6138
                        return true;
6139
                    }
6140
                    $isError = true;
6141
                    $msg = 'PHP Parse Error';
6142
            }
6143
        }
6144
        if (is_readable($file)) {
6145
            $source = file($file);
6146
            $source = $this->htmlspecialchars($source[$line - 1]);
6147
        } else {
6148
            $source = "";
6149
        } //Error $nr in $file at $line: <div><code>$source</code></div>
6150
6151
        $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...
6152
    }
6153
6154
    /**
6155
     * @param string $msg
6156
     * @param string $query
6157
     * @param bool $is_error
6158
     * @param string $nr
6159
     * @param string $file
6160
     * @param string $source
6161
     * @param string $text
6162
     * @param string $line
6163
     * @param string $output
6164
     * @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...
6165
     */
6166
    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...
6167
    {
6168
6169
        if (0 < $this->messageQuitCount) {
6170
            return;
6171
        }
6172
        $this->messageQuitCount++;
6173
        $MakeTable = new Support\MakeTable();
6174
        $MakeTable->setTableClass('grid');
6175
        $MakeTable->setRowRegularClass('gridItem');
6176
        $MakeTable->setRowAlternateClass('gridAltItem');
6177
        $MakeTable->setColumnWidths(array('100px'));
6178
6179
        $table = array();
6180
6181
        $version = isset ($GLOBALS['modx_version']) ? $GLOBALS['modx_version'] : '';
6182
        $release_date = isset ($GLOBALS['release_date']) ? $GLOBALS['release_date'] : '';
6183
        $request_uri = "http://" . $_SERVER['HTTP_HOST'] . ($_SERVER["SERVER_PORT"] == 80 ? "" : (":" . $_SERVER["SERVER_PORT"])) . $_SERVER['REQUEST_URI'];
6184
        $request_uri = $this->htmlspecialchars($request_uri, ENT_QUOTES, $this->config['modx_charset']);
6185
        $ua = $this->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...
6186
        $referer = $this->htmlspecialchars($_SERVER['HTTP_REFERER'], ENT_QUOTES, $this->config['modx_charset']);
6187
        if ($is_error) {
6188
            $str = '<h2 style="color:red">&laquo; Evo Parse Error &raquo;</h2>';
6189
            if ($msg != 'PHP Parse Error') {
6190
                $str .= '<h3 style="color:red">' . $msg . '</h3>';
6191
            }
6192
        } else {
6193
            $str = '<h2 style="color:#003399">&laquo; Evo Debug/ stop message &raquo;</h2>';
6194
            $str .= '<h3 style="color:#003399">' . $msg . '</h3>';
6195
        }
6196
6197
        if (!empty ($query)) {
6198
            $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>';
6199
        }
6200
6201
        $errortype = array(
6202
            E_ERROR => "ERROR",
6203
            E_WARNING => "WARNING",
6204
            E_PARSE => "PARSING ERROR",
6205
            E_NOTICE => "NOTICE",
6206
            E_CORE_ERROR => "CORE ERROR",
6207
            E_CORE_WARNING => "CORE WARNING",
6208
            E_COMPILE_ERROR => "COMPILE ERROR",
6209
            E_COMPILE_WARNING => "COMPILE WARNING",
6210
            E_USER_ERROR => "USER ERROR",
6211
            E_USER_WARNING => "USER WARNING",
6212
            E_USER_NOTICE => "USER NOTICE",
6213
            E_STRICT => "STRICT NOTICE",
6214
            E_RECOVERABLE_ERROR => "RECOVERABLE ERROR",
6215
            E_DEPRECATED => "DEPRECATED",
6216
            E_USER_DEPRECATED => "USER DEPRECATED"
6217
        );
6218
6219
        if (!empty($nr) || !empty($file)) {
6220
            if ($text != '') {
6221
                $str .= '<div style="font-weight:bold;border:1px solid #ccc;padding:8px;color:#333;background-color:#ffffcd;margin-bottom:15px;">Error : ' . $text . '</div>';
6222
            }
6223
            if ($output != '') {
6224
                $str .= '<div style="font-weight:bold;border:1px solid #ccc;padding:8px;color:#333;background-color:#ffffcd;margin-bottom:15px;">' . $output . '</div>';
6225
            }
6226
            if ($nr !== '') {
6227
                $table[] = array('ErrorType[num]', $errortype [$nr] . "[" . $nr . "]");
6228
            }
6229
            if ($file) {
6230
                $table[] = array('File', $file);
6231
            }
6232
            if ($line) {
6233
                $table[] = array('Line', $line);
6234
            }
6235
6236
        }
6237
6238
        if ($source != '') {
6239
            $table[] = array("Source", $source);
6240
        }
6241
6242
        if (!empty($this->currentSnippet)) {
6243
            $table[] = array('Current Snippet', $this->currentSnippet);
6244
        }
6245
6246
        if (!empty($this->event->activePlugin)) {
6247
            $table[] = array('Current Plugin', $this->event->activePlugin . '(' . $this->event->name . ')');
6248
        }
6249
6250
        $str .= $MakeTable->create($table, array('Error information', ''));
6251
        $str .= "<br />";
6252
6253
        $table = array();
6254
        $table[] = array('REQUEST_URI', $request_uri);
6255
6256
        if ($this->manager->action) {
6257
            include_once(MODX_MANAGER_PATH . 'includes/actionlist.inc.php');
6258
            global $action_list;
6259
            $actionName = (isset($action_list[$this->manager->action])) ? " - {$action_list[$this->manager->action]}" : '';
6260
6261
            $table[] = array('Manager action', $this->manager->action . $actionName);
6262
        }
6263
6264
        if (preg_match('@^[0-9]+@', $this->documentIdentifier)) {
6265
            $resource = $this->getDocumentObject('id', $this->documentIdentifier);
6266
            $url = $this->makeUrl($this->documentIdentifier, '', '', 'full');
6267
            $table[] = array('Resource', '[' . $this->documentIdentifier . '] <a href="' . $url . '" target="_blank">' . $resource['pagetitle'] . '</a>');
6268
        }
6269
        $table[] = array('Referer', $referer);
6270
        $table[] = array('User Agent', $ua);
6271
        $table[] = array('IP', $_SERVER['REMOTE_ADDR']);
6272
        $table[] = array('Current time', date("Y-m-d H:i:s", $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time']));
6273
        $str .= $MakeTable->create($table, array('Basic info', ''));
6274
        $str .= "<br />";
6275
6276
        $table = array();
6277
        $table[] = array('MySQL', '[^qt^] ([^q^] Requests)');
6278
        $table[] = array('PHP', '[^p^]');
6279
        $table[] = array('Total', '[^t^]');
6280
        $table[] = array('Memory', '[^m^]');
6281
        $str .= $MakeTable->create($table, array('Benchmarks', ''));
6282
        $str .= "<br />";
6283
6284
        $totalTime = ($this->getMicroTime() - $this->tstart);
6285
6286
        $mem = memory_get_peak_usage(true);
6287
        $total_mem = $mem - $this->mstart;
6288
        $total_mem = ($total_mem / 1024 / 1024) . ' mb';
6289
6290
        $queryTime = $this->queryTime;
6291
        $phpTime = $totalTime - $queryTime;
6292
        $queries = isset ($this->executedQueries) ? $this->executedQueries : 0;
6293
        $queryTime = sprintf("%2.4f s", $queryTime);
6294
        $totalTime = sprintf("%2.4f s", $totalTime);
6295
        $phpTime = sprintf("%2.4f s", $phpTime);
6296
6297
        $str = str_replace('[^q^]', $queries, $str);
6298
        $str = str_replace('[^qt^]', $queryTime, $str);
6299
        $str = str_replace('[^p^]', $phpTime, $str);
6300
        $str = str_replace('[^t^]', $totalTime, $str);
6301
        $str = str_replace('[^m^]', $total_mem, $str);
6302
6303
        if (isset($php_errormsg) && !empty($php_errormsg)) {
6304
            $str = "<b>{$php_errormsg}</b><br />\n{$str}";
6305
        }
6306
        $str .= $this->get_backtrace(debug_backtrace());
6307
        // Log error
6308
        if (!empty($this->currentSnippet)) {
6309
            $source = 'Snippet - ' . $this->currentSnippet;
6310
        } elseif (!empty($this->event->activePlugin)) {
6311
            $source = 'Plugin - ' . $this->event->activePlugin;
6312
        } elseif ($source !== '') {
6313
            $source = 'Parser - ' . $source;
6314
        } elseif ($query !== '') {
6315
            $source = 'SQL Query';
6316
        } else {
6317
            $source = 'Parser';
6318
        }
6319
        if ($msg) {
6320
            $source .= ' / ' . $msg;
6321
        }
6322
        if (isset($actionName) && !empty($actionName)) {
6323
            $source .= $actionName;
6324
        }
6325 View Code Duplication
        switch ($nr) {
6326
            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...
6327
            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...
6328
            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...
6329
            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...
6330
            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...
6331
                $error_level = 2;
6332
                break;
6333
            default:
6334
                $error_level = 3;
6335
        }
6336
        $this->logEvent(0, $error_level, $str, $source);
6337
6338
        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...
6339
            return true;
6340
        }
6341
        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...
6342
            return true;
6343
        }
6344
6345
        // Set 500 response header
6346
        if ($error_level !== 2) {
6347
            header('HTTP/1.1 500 Internal Server Error');
6348
        }
6349
6350
        // Display error
6351
        if (isset($_SESSION['mgrValidated'])) {
6352
            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>
6353
                 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
6354
                 <link rel="stylesheet" type="text/css" href="' . $this->config['site_manager_url'] . 'media/style/' . $this->config['manager_theme'] . '/style.css" />
6355
                 <style type="text/css">body { padding:10px; } td {font:inherit;}</style>
6356
                 </head><body>
6357
                 ' . $str . '</body></html>';
6358
6359
        } else {
6360
            echo 'Error';
6361
        }
6362
        ob_end_flush();
6363
        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...
6364
    }
6365
6366
    /**
6367
     * @param $backtrace
6368
     * @return string
6369
     */
6370
    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...
6371
    {
6372
        $MakeTable = new Support\MakeTable();
6373
        $MakeTable->setTableClass('grid');
6374
        $MakeTable->setRowRegularClass('gridItem');
6375
        $MakeTable->setRowAlternateClass('gridAltItem');
6376
        $table = array();
6377
        $backtrace = array_reverse($backtrace);
6378
        foreach ($backtrace as $key => $val) {
6379
            $key++;
6380
            if (substr($val['function'], 0, 11) === 'messageQuit') {
6381
                break;
6382
            } elseif (substr($val['function'], 0, 8) === 'phpError') {
6383
                break;
6384
            }
6385
            $path = str_replace('\\', '/', $val['file']);
6386
            if (strpos($path, MODX_BASE_PATH) === 0) {
6387
                $path = substr($path, strlen(MODX_BASE_PATH));
6388
            }
6389
            switch ($val['type']) {
6390
                case '->':
6391
                case '::':
6392
                    $functionName = $val['function'] = $val['class'] . $val['type'] . $val['function'];
6393
                    break;
6394
                default:
6395
                    $functionName = $val['function'];
6396
            }
6397
            $tmp = 1;
6398
            $_ = (!empty($val['args'])) ? count($val['args']) : 0;
6399
            $args = array_pad(array(), $_, '$var');
6400
            $args = implode(", ", $args);
6401
            $modx = &$this;
6402
            $args = preg_replace_callback('/\$var/', function () use ($modx, &$tmp, $val) {
6403
                $arg = $val['args'][$tmp - 1];
6404
                switch (true) {
6405
                    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...
6406
                        $out = 'NULL';
6407
                        break;
6408
                    }
6409
                    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...
6410
                        $out = $arg;
6411
                        break;
6412
                    }
6413
                    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...
6414
                        $out = strlen($arg) > 20 ? 'string $var' . $tmp : ("'" . $this->htmlspecialchars(str_replace("'", "\\'", $arg)) . "'");
6415
                        break;
6416
                    }
6417
                    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...
6418
                        $out = $arg ? 'TRUE' : 'FALSE';
6419
                        break;
6420
                    }
6421
                    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...
6422
                        $out = 'array $var' . $tmp;
6423
                        break;
6424
                    }
6425
                    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...
6426
                        $out = get_class($arg) . ' $var' . $tmp;
6427
                        break;
6428
                    }
6429
                    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...
6430
                        $out = '$var' . $tmp;
6431
                    }
6432
                }
6433
                $tmp++;
6434
                return $out;
6435
            }, $args);
6436
            $line = array(
6437
                "<strong>" . $functionName . "</strong>(" . $args . ")",
6438
                $path . " on line " . $val['line']
6439
            );
6440
            $table[] = array(implode("<br />", $line));
6441
        }
6442
        return $MakeTable->create($table, array('Backtrace'));
6443
    }
6444
6445
    /**
6446
     * @return string
6447
     */
6448
    public function getRegisteredClientScripts()
6449
    {
6450
        return implode("\n", $this->jscripts);
6451
    }
6452
6453
    /**
6454
     * @return string
6455
     */
6456
    public function getRegisteredClientStartupScripts()
6457
    {
6458
        return implode("\n", $this->sjscripts);
6459
    }
6460
6461
    /**
6462
     * Format alias to be URL-safe. Strip invalid characters.
6463
     *
6464
     * @param string $alias Alias to be formatted
6465
     * @return string Safe alias
6466
     */
6467
    public function stripAlias($alias)
6468
    {
6469
        // let add-ons overwrite the default behavior
6470
        $results = $this->invokeEvent('OnStripAlias', array('alias' => $alias));
6471
        if (!empty($results)) {
6472
            // if multiple plugins are registered, only the last one is used
6473
            return end($results);
6474
        } else {
6475
            // default behavior: strip invalid characters and replace spaces with dashes.
6476
            $alias = strip_tags($alias); // strip HTML
6477
            $alias = preg_replace('/[^\.A-Za-z0-9 _-]/', '', $alias); // strip non-alphanumeric characters
6478
            $alias = preg_replace('/\s+/', '-', $alias); // convert white-space to dash
6479
            $alias = preg_replace('/-+/', '-', $alias);  // convert multiple dashes to one
6480
            $alias = trim($alias, '-'); // trim excess
6481
            return $alias;
6482
        }
6483
    }
6484
6485
    /**
6486
     * @param $size
6487
     * @return string
6488
     */
6489
    public function nicesize($size)
6490
    {
6491
        $sizes = array('Tb' => 1099511627776, 'Gb' => 1073741824, 'Mb' => 1048576, 'Kb' => 1024, 'b' => 1);
6492
        $precisions = count($sizes) - 1;
6493
        foreach ($sizes as $unit => $bytes) {
6494
            if ($size >= $bytes) {
6495
                return number_format($size / $bytes, $precisions) . ' ' . $unit;
6496
            }
6497
            $precisions--;
6498
        }
6499
        return '0 b';
6500
    }
6501
6502
    /**
6503
     * @param $parentid
6504
     * @param $alias
6505
     * @return bool
6506
     */
6507
    public function getHiddenIdFromAlias($parentid, $alias)
6508
    {
6509
        $table = $this->getFullTableName('site_content');
6510
        $query = $this->db->query("SELECT sc.id, children.id AS child_id, children.alias, COUNT(children2.id) AS children_count
6511
            FROM {$table} sc
6512
            JOIN {$table} children ON children.parent = sc.id
6513
            LEFT JOIN {$table} children2 ON children2.parent = children.id
6514
            WHERE sc.parent = {$parentid} AND sc.alias_visible = '0' GROUP BY children.id;");
6515
6516
        while ($child = $this->db->getRow($query)) {
6517
            if ($child['alias'] == $alias || $child['child_id'] == $alias) {
6518
                return $child['child_id'];
6519
            }
6520
6521
            if ($child['children_count'] > 0) {
6522
                $id = $this->getHiddenIdFromAlias($child['id'], $alias);
6523
                if ($id) {
6524
                    return $id;
6525
                }
6526
            }
6527
        }
6528
6529
        return false;
6530
    }
6531
6532
    /**
6533
     * @param $alias
6534
     * @return bool|int
6535
     */
6536
    public function getIdFromAlias($alias)
6537
    {
6538
        if (isset($this->documentListing[$alias])) {
6539
            return $this->documentListing[$alias];
6540
        }
6541
6542
        $tbl_site_content = $this->getFullTableName('site_content');
6543
        if ($this->config['use_alias_path'] == 1) {
6544
            if ($alias == '.') {
6545
                return 0;
6546
            }
6547
6548
            if (strpos($alias, '/') !== false) {
6549
                $_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...
6550
            } else {
6551
                $_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...
6552
            }
6553
            $id = 0;
6554
6555
            foreach ($_a as $alias) {
6556
                if ($id === false) {
6557
                    break;
6558
                }
6559
                $alias = $this->db->escape($alias);
6560
                $rs = $this->db->select('id', $tbl_site_content, "deleted=0 and parent='{$id}' and alias='{$alias}'");
6561
                if ($this->db->getRecordCount($rs) == 0) {
6562
                    $rs = $this->db->select('id', $tbl_site_content, "deleted=0 and parent='{$id}' and id='{$alias}'");
6563
                }
6564
                $next = $this->db->getValue($rs);
6565
                $id = !$next ? $this->getHiddenIdFromAlias($id, $alias) : $next;
6566
            }
6567
        } else {
6568
            $rs = $this->db->select('id', $tbl_site_content, "deleted=0 and alias='{$alias}'", 'parent, menuindex');
6569
            $id = $this->db->getValue($rs);
6570
            if (!$id) {
6571
                $id = false;
6572
            }
6573
        }
6574
        return $id;
6575
    }
6576
6577
    /**
6578
     * @param string $str
6579
     * @return bool|mixed|string
6580
     */
6581
    public function atBindInclude($str = '')
6582
    {
6583
        if (strpos($str, '@INCLUDE') !== 0) {
6584
            return $str;
6585
        }
6586 View Code Duplication
        if (strpos($str, "\n") !== false) {
6587
            $str = substr($str, 0, strpos("\n", $str));
6588
        }
6589
6590
        $str = substr($str, 9);
6591
        $str = trim($str);
6592
        $str = str_replace('\\', '/', $str);
6593
        $str = ltrim($str, '/');
6594
6595
        $tpl_dir = 'assets/templates/';
6596
6597
        if (strpos($str, MODX_MANAGER_PATH) === 0) {
6598
            return false;
6599
        } elseif (is_file(MODX_BASE_PATH . $str)) {
6600
            $file_path = MODX_BASE_PATH . $str;
6601
        } elseif (is_file(MODX_BASE_PATH . "{$tpl_dir}{$str}")) {
6602
            $file_path = MODX_BASE_PATH . $tpl_dir . $str;
6603
        } else {
6604
            return false;
6605
        }
6606
6607
        if (!$file_path || !is_file($file_path)) {
6608
            return false;
6609
        }
6610
6611
        ob_start();
6612
        $modx = &$this;
6613
        $result = include($file_path);
6614
        if ($result === 1) {
6615
            $result = '';
6616
        }
6617
        $content = ob_get_clean();
6618
        if (!$content && $result) {
6619
            $content = $result;
6620
        }
6621
        return $content;
6622
    }
6623
6624
    // php compat
6625
6626
    /**
6627
     * @param $str
6628
     * @param int $flags
6629
     * @param string $encode
6630
     * @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...
6631
     */
6632
    public function htmlspecialchars($str, $flags = ENT_COMPAT, $encode = '')
6633
    {
6634
        $this->loadExtension('PHPCOMPAT');
6635
        return $this->phpcompat->htmlspecialchars($str, $flags, $encode);
6636
    }
6637
6638
    /**
6639
     * @param $string
6640
     * @param bool $returnData
6641
     * @return bool|mixed
6642
     */
6643
    public function isJson($string, $returnData = false)
6644
    {
6645
        $data = json_decode($string, true);
6646
        return (json_last_error() == JSON_ERROR_NONE) ? ($returnData ? $data : true) : false;
6647
    }
6648
6649
    /**
6650
     * @param $key
6651
     * @return array
6652
     */
6653
    public function splitKeyAndFilter($key)
6654
    {
6655
        if ($this->config['enable_filter'] == 1 && strpos($key, ':') !== false && stripos($key, '@FILE') !== 0) {
6656
            list($key, $modifiers) = explode(':', $key, 2);
6657
        } else {
6658
            $modifiers = false;
6659
        }
6660
6661
        $key = trim($key);
6662
        if ($modifiers !== false) {
6663
            $modifiers = trim($modifiers);
6664
        }
6665
6666
        return array($key, $modifiers);
6667
    }
6668
6669
    /**
6670
     * @param string $value
6671
     * @param bool $modifiers
6672
     * @param string $key
6673
     * @return string
6674
     */
6675
    public function applyFilter($value = '', $modifiers = false, $key = '')
6676
    {
6677
        if ($modifiers === false || $modifiers == 'raw') {
6678
            return $value;
6679
        }
6680
        if ($modifiers !== false) {
6681
            $modifiers = trim($modifiers);
6682
        }
6683
6684
        $this->loadExtension('MODIFIERS');
6685
        return $this->filter->phxFilter($key, $value, $modifiers);
0 ignored issues
show
Bug introduced by
It seems like $modifiers defined by parameter $modifiers on line 6675 can also be of type false; however, MODIFIERS::phxFilter() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and 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...
6686
    }
6687
6688
    // End of class.
6689
6690
6691
    /**
6692
     * Get Clean Query String
6693
     *
6694
     * Fixes the issue where passing an array into the q get variable causes errors
6695
     *
6696
     */
6697
    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...
6698
    {
6699
        $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...
6700
6701
        //Return null if the query doesn't exist
6702
        if (empty($q)) {
6703
            return null;
6704
        }
6705
6706
        //If we have a string, return it
6707
        if (is_string($q)) {
6708
            return $q;
6709
        }
6710
6711
        //If we have an array, return the first element
6712
        if (is_array($q)) {
6713
            return $q[0];
6714
        }
6715
    }
6716
6717
    /**
6718
     * @param string $title
6719
     * @param string $msg
6720
     * @param int $type
6721
     */
6722
    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...
6723
    {
6724
        if ($title === '') {
6725
            $title = 'no title';
6726
        }
6727
        if (is_array($msg)) {
6728
            $msg = '<pre>' . print_r($msg, true) . '</pre>';
6729
        } elseif ($msg === '') {
6730
            $msg = $_SERVER['REQUEST_URI'];
6731
        }
6732
        $this->logEvent(0, $type, $msg, $title);
6733
    }
6734
6735
}
6736