Completed
Pull Request — develop (#716)
by Agel_Nash
06:45 queued 44s
created

Core::executeParser()   F

Complexity

Conditions 20
Paths 1707

Size

Total Lines 127
Code Lines 68

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 20
eloc 68
nc 1707
nop 0
dl 0
loc 127
rs 2
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

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

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

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

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

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

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

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

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

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

Loading history...
4
{
5
    /**
6
     * This is New evolution
7
     * @var string
8
     */
9
    public $apiVersion = '1.0.0';
10
11
    /**
12
     * 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
            $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...
1950
        } else {
1951
            $return = call_user_func_array($phpcode, array($params));
1952
        }
1953
        $echo = ob_get_contents();
1954
        ob_end_clean();
1955 View Code Duplication
        if ((0 < $this->config['error_reporting']) && isset($php_errormsg)) {
1956
            $error_info = error_get_last();
1957
            if ($this->detectError($error_info['type'])) {
1958
                $echo = ($echo === false) ? 'ob_get_contents() error' : $echo;
1959
                $this->messageQuit('PHP Parse Error', '', true, $error_info['type'], $error_info['file'], 'Snippet', $error_info['message'], $error_info['line'], $echo);
1960
                if ($this->isBackend()) {
1961
                    $this->event->alert('An error occurred while loading. Please see the event log for more information<p>' . $echo . $return . '</p>');
1962
                }
1963
            }
1964
        }
1965
        unset($modx->event->params);
1966
        if (is_array($return) || is_object($return)) {
1967
            return $return;
1968
        } else {
1969
            return $echo . $return;
1970
        }
1971
    }
1972
1973
    /**
1974
     * Run snippets as per the tags in $documentSource and replace the tags with the returned values.
1975
     *
1976
     * @param $content
1977
     * @return string
1978
     * @internal param string $documentSource
1979
     */
1980
    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...
1981
    {
1982
        if (strpos($content, '[[') === false) {
1983
            return $content;
1984
        }
1985
1986
        $matches = $this->getTagsFromContent($content, '[[', ']]');
1987
1988
        if (empty($matches)) {
1989
            return $content;
1990
        }
1991
1992
        $this->snipLapCount++;
1993
        if ($this->dumpSnippets) {
1994
            $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);
1995
        }
1996
1997
        foreach ($matches[1] as $i => $call) {
1998
            $s = &$matches[0][$i];
1999
            if (substr($call, 0, 2) === '$_') {
2000
                if (strpos($content, '_PHX_INTERNAL_') === false) {
2001
                    $value = $this->_getSGVar($call);
2002
                } else {
2003
                    $value = $s;
2004
                }
2005 View Code Duplication
                if (strpos($content, $s) !== false) {
2006
                    $content = str_replace($s, $value, $content);
2007
                } elseif($this->debug) {
2008
                    $this->addLog('evalSnippetsSGVar parse error', $_SERVER['REQUEST_URI'] . $s, 2);
2009
                }
2010
                continue;
2011
            }
2012
            $value = $this->_get_snip_result($call);
2013
            if (is_null($value)) {
2014
                continue;
2015
            }
2016
2017 View Code Duplication
            if (strpos($content, $s) !== false) {
2018
                $content = str_replace($s, $value, $content);
2019
            } elseif($this->debug) {
2020
                $this->addLog('evalSnippets parse error', $_SERVER['REQUEST_URI'] . $s, 2);
2021
            }
2022
        }
2023
2024
        if ($this->dumpSnippets) {
2025
            $this->snippetsCode .= '</fieldset><br />';
2026
        }
2027
2028
        return $content;
2029
    }
2030
2031
    /**
2032
     * @param $value
2033
     * @return mixed|string
2034
     */
2035
    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...
2036
    { // Get super globals
2037
        $key = $value;
2038
        $_ = $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...
2039
        $this->config['enable_filter'] = 1;
2040
        list($key, $modifiers) = $this->splitKeyAndFilter($key);
2041
        $this->config['enable_filter'] = $_;
2042
        $key = str_replace(array('(', ')'), array("['", "']"), $key);
2043
        $key = rtrim($key, ';');
2044
        if (strpos($key, '$_SESSION') !== false) {
2045
            $_ = $_SESSION;
2046
            $key = str_replace('$_SESSION', '$_', $key);
2047
            if (isset($_['mgrFormValues'])) {
2048
                unset($_['mgrFormValues']);
2049
            }
2050
            if (isset($_['token'])) {
2051
                unset($_['token']);
2052
            }
2053
        }
2054
        if (strpos($key, '[') !== false) {
2055
            $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...
2056
        } 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...
2057
            $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...
2058
        } else {
2059
            $value = '';
2060
        }
2061
        if ($modifiers !== false) {
2062
            $value = $this->applyFilter($value, $modifiers, $key);
2063
        }
2064
        return $value;
2065
    }
2066
2067
    /**
2068
     * @param $piece
2069
     * @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...
2070
     */
2071
    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...
2072
    {
2073
        if (ltrim($piece) !== $piece) {
2074
            return '';
2075
        }
2076
2077
        $eventtime = $this->dumpSnippets ? $this->getMicroTime() : 0;
2078
2079
        $snip_call = $this->_split_snip_call($piece);
2080
        $key = $snip_call['name'];
2081
2082
        list($key, $modifiers) = $this->splitKeyAndFilter($key);
2083
        $snip_call['name'] = $key;
2084
        $snippetObject = $this->_getSnippetObject($key);
2085
        if (is_null($snippetObject['content'])) {
2086
            return null;
2087
        }
2088
2089
        $this->currentSnippet = $snippetObject['name'];
2090
2091
        // current params
2092
        $params = $this->getParamsFromString($snip_call['params']);
2093
2094
        if (!isset($snippetObject['properties'])) {
2095
            $snippetObject['properties'] = array();
2096
        }
2097
        $default_params = $this->parseProperties($snippetObject['properties'], $this->currentSnippet, 'snippet');
2098
        $params = array_merge($default_params, $params);
2099
2100
        $value = $this->evalSnippet($snippetObject['content'], $params);
2101
        $this->currentSnippet = '';
2102
        if ($modifiers !== false) {
2103
            $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...
2104
        }
2105
2106
        if ($this->dumpSnippets) {
2107
            $eventtime = $this->getMicroTime() - $eventtime;
2108
            $eventtime = sprintf('%2.2f ms', $eventtime * 1000);
2109
            $code = str_replace("\t", '  ', $this->htmlspecialchars($value));
2110
            $piece = str_replace("\t", '  ', $this->htmlspecialchars($piece));
2111
            $print_r_params = str_replace("\t", '  ', $this->htmlspecialchars('$modx->event->params = ' . print_r($params, true)));
2112
            $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);
2113
            $this->snippetsTime[] = array('sname' => $key, 'time' => $eventtime);
2114
        }
2115
        return $value;
2116
    }
2117
2118
    /**
2119
     * @param string $string
2120
     * @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...
2121
     */
2122
    public function getParamsFromString($string = '')
2123
    {
2124
        if (empty($string)) {
2125
            return array();
2126
        }
2127
2128
        if (strpos($string, '&_PHX_INTERNAL_') !== false) {
2129
            $string = str_replace(array('&_PHX_INTERNAL_091_&', '&_PHX_INTERNAL_093_&'), array('[', ']'), $string);
2130
        }
2131
2132
        $_ = $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...
2133
        $this->documentOutput = $string;
2134
        $this->invokeEvent('OnBeforeParseParams');
2135
        $string = $this->documentOutput;
2136
        $this->documentOutput = $_;
2137
2138
        $_tmp = $string;
2139
        $_tmp = ltrim($_tmp, '?&');
2140
        $temp_params = array();
2141
        $key = '';
2142
        $value = null;
2143
        while ($_tmp !== '') {
2144
            $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...
2145
            $char = substr($_tmp, 0, 1);
2146
            $_tmp = substr($_tmp, 1);
2147
2148
            if ($char === '=') {
2149
                $_tmp = trim($_tmp);
2150
                $delim = substr($_tmp, 0, 1);
2151
                if (in_array($delim, array('"', "'", '`'))) {
2152
                    $null = null;
2153
                    //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...
2154
                    list($null, $value, $_tmp) = explode($delim, $_tmp, 3);
2155
                    unset($null);
2156
2157
                    if (substr(trim($_tmp), 0, 2) === '//') {
2158
                        $_tmp = strstr(trim($_tmp), "\n");
2159
                    }
2160
                    $i = 0;
2161
                    while ($delim === '`' && substr(trim($_tmp), 0, 1) !== '&' && 1 < substr_count($_tmp, '`')) {
2162
                        list($inner, $outer, $_tmp) = explode('`', $_tmp, 3);
2163
                        $value .= "`{$inner}`{$outer}";
2164
                        $i++;
2165
                        if (100 < $i) {
2166
                            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...
2167
                        }
2168
                    }
2169
                    if ($i && $delim === '`') {
2170
                        $value = rtrim($value, '`');
2171
                    }
2172
                } elseif (strpos($_tmp, '&') !== false) {
2173
                    list($value, $_tmp) = explode('&', $_tmp, 2);
2174
                    $value = trim($value);
2175
                } else {
2176
                    $value = $_tmp;
2177
                    $_tmp = '';
2178
                }
2179
            } elseif ($char === '&') {
2180
                if (trim($key) !== '') {
2181
                    $value = '1';
2182
                } else {
2183
                    continue;
2184
                }
2185
            } elseif ($_tmp === '') {
2186
                $key .= $char;
2187
                $value = '1';
2188
            } elseif ($key !== '' || trim($char) !== '') {
2189
                $key .= $char;
2190
            }
2191
2192
            if (isset($value) && !is_null($value)) {
2193
                if (strpos($key, 'amp;') !== false) {
2194
                    $key = str_replace('amp;', '', $key);
2195
                }
2196
                $key = trim($key);
2197 View Code Duplication
                if (strpos($value, '[!') !== false) {
2198
                    $value = str_replace(array('[!', '!]'), array('[[', ']]'), $value);
2199
                }
2200
                $value = $this->mergeDocumentContent($value);
2201
                $value = $this->mergeSettingsContent($value);
2202
                $value = $this->mergeChunkContent($value);
2203
                $value = $this->evalSnippets($value);
2204
                if (substr($value, 0, 6) !== '@CODE:') {
2205
                    $value = $this->mergePlaceholderContent($value);
2206
                }
2207
2208
                $temp_params[][$key] = $value;
2209
2210
                $key = '';
2211
                $value = null;
2212
2213
                $_tmp = ltrim($_tmp, " ,\t");
2214
                if (substr($_tmp, 0, 2) === '//') {
2215
                    $_tmp = strstr($_tmp, "\n");
2216
                }
2217
            }
2218
2219
            if ($_tmp === $bt) {
2220
                $key = trim($key);
2221
                if ($key !== '') {
2222
                    $temp_params[][$key] = '';
2223
                }
2224
                break;
2225
            }
2226
        }
2227
2228
        foreach ($temp_params as $p) {
2229
            $k = key($p);
2230
            if (substr($k, -2) === '[]') {
2231
                $k = substr($k, 0, -2);
2232
                $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...
2233
            } elseif (strpos($k, '[') !== false && substr($k, -1) === ']') {
2234
                list($k, $subk) = explode('[', $k, 2);
2235
                $subk = substr($subk, 0, -1);
2236
                $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...
2237
            } else {
2238
                $params[$k] = current($p);
2239
            }
2240
        }
2241
        return $params;
2242
    }
2243
2244
    /**
2245
     * @param $str
2246
     * @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...
2247
     */
2248
    public function _getSplitPosition($str)
2249
    {
2250
        $closeOpt = false;
2251
        $maybePos = false;
2252
        $inFilter = false;
2253
        $pos = false;
2254
        $total = strlen($str);
2255
        for ($i = 0; $i < $total; $i++) {
2256
            $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...
2257
            $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...
2258
            if (!$inFilter) {
2259
                if ($c === ':') {
2260
                    $inFilter = true;
2261
                } elseif ($c === '?') {
2262
                    $pos = $i;
2263
                } elseif ($c === ' ') {
2264
                    $maybePos = $i;
2265
                } 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...
2266
                    $pos = $maybePos;
2267
                } elseif ($c === "\n") {
2268
                    $pos = $i;
2269
                } else {
2270
                    $pos = false;
2271
                }
2272
            } else {
2273
                if ($cc == $closeOpt) {
2274
                    $closeOpt = false;
2275
                } elseif ($c == $closeOpt) {
2276
                    $closeOpt = false;
2277
                } 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...
2278
                    continue;
2279
                } elseif ($cc === "('") {
2280
                    $closeOpt = "')";
2281
                } elseif ($cc === '("') {
2282
                    $closeOpt = '")';
2283
                } elseif ($cc === '(`') {
2284
                    $closeOpt = '`)';
2285
                } elseif ($c === '(') {
2286
                    $closeOpt = ')';
2287
                } elseif ($c === '?') {
2288
                    $pos = $i;
2289
                } elseif ($c === ' ' && strpos($str, '?') === false) {
2290
                    $pos = $i;
2291
                } else {
2292
                    $pos = false;
2293
                }
2294
            }
2295
            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...
2296
                break;
2297
            }
2298
        }
2299
        return $pos;
2300
    }
2301
2302
    /**
2303
     * @param $call
2304
     * @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...
2305
     */
2306
    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...
2307
    {
2308
        $spacer = md5('dummy');
2309 View Code Duplication
        if (strpos($call, ']]>') !== false) {
2310
            $call = str_replace(']]>', "]{$spacer}]>", $call);
2311
        }
2312
2313
        $splitPosition = $this->_getSplitPosition($call);
2314
2315
        if ($splitPosition !== false) {
2316
            $name = substr($call, 0, $splitPosition);
2317
            $params = substr($call, $splitPosition + 1);
2318
        } else {
2319
            $name = $call;
2320
            $params = '';
2321
        }
2322
2323
        $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...
2324 View Code Duplication
        if (strpos($params, $spacer) !== false) {
2325
            $params = str_replace("]{$spacer}]>", ']]>', $params);
2326
        }
2327
        $snip['params'] = ltrim($params, "?& \t\n");
2328
2329
        return $snip;
2330
    }
2331
2332
    /**
2333
     * @param $snip_name
2334
     * @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...
2335
     */
2336
    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...
2337
    {
2338
        if (isset($this->snippetCache[$snip_name])) {
2339
            $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...
2340
            $snippetObject['content'] = $this->snippetCache[$snip_name];
2341
            if (isset($this->snippetCache["{$snip_name}Props"])) {
2342
                if (!isset($this->snippetCache["{$snip_name}Props"])) {
2343
                    $this->snippetCache["{$snip_name}Props"] = '';
2344
                }
2345
                $snippetObject['properties'] = $this->snippetCache["{$snip_name}Props"];
2346
            }
2347
        } elseif (substr($snip_name, 0, 1) === '@' && isset($this->pluginEvent[trim($snip_name, '@')])) {
2348
            $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...
2349
            $snippetObject['content'] = sprintf('$rs=$this->invokeEvent("%s",$params);echo trim(implode("",$rs));', trim($snip_name, '@'));
2350
            $snippetObject['properties'] = '';
2351
        } else {
2352
            $where = sprintf("name='%s' AND disabled=0", $this->db->escape($snip_name));
2353
            $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...
2354
            $count = $this->db->getRecordCount($rs);
2355
            if (1 < $count) {
2356
                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...
2357
            }
2358
            if ($count) {
2359
                $row = $this->db->getRow($rs);
2360
                $snip_content = $row['snippet'];
2361
                $snip_prop = $row['properties'];
2362
            } else {
2363
                $snip_content = null;
2364
                $snip_prop = '';
2365
            }
2366
            $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...
2367
            $snippetObject['content'] = $snip_content;
2368
            $snippetObject['properties'] = $snip_prop;
2369
            $this->snippetCache[$snip_name] = $snip_content;
2370
            $this->snippetCache["{$snip_name}Props"] = $snip_prop;
2371
        }
2372
        return $snippetObject;
2373
    }
2374
2375
    /**
2376
     * @param $text
2377
     * @return mixed
2378
     */
2379
    public function toAlias($text)
2380
    {
2381
        $suff = $this->config['friendly_url_suffix'];
2382
        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);
2383
    }
2384
2385
    /**
2386
     * makeFriendlyURL
2387
     *
2388
     * @desc Create an URL.
2389
     *
2390
     * @param $pre {string} - Friendly URL Prefix. @required
2391
     * @param $suff {string} - Friendly URL Suffix. @required
2392
     * @param $alias {string} - Full document path. @required
2393
     * @param int $isfolder {0; 1}
2394
     * - Is it a folder? Default: 0.
2395
     * @param int $id {integer}
2396
     * - Document id. Default: 0.
2397
     * @return mixed|string {string} - Result URL.
2398
     * - Result URL.
2399
     */
2400
    public function makeFriendlyURL($pre, $suff, $alias, $isfolder = 0, $id = 0)
2401
    {
2402
        if ($id == $this->config['site_start'] && $this->config['seostrict'] === '1') {
2403
            $url = $this->config['base_url'];
2404
        } else {
2405
            $Alias = explode('/', $alias);
2406
            $alias = array_pop($Alias);
2407
            $dir = implode('/', $Alias);
2408
            unset($Alias);
2409
2410
            if ($this->config['make_folders'] === '1' && $isfolder == 1) {
2411
                $suff = '/';
2412
            }
2413
2414
            $url = ($dir != '' ? $dir . '/' : '') . $pre . $alias . $suff;
2415
        }
2416
2417
        $evtOut = $this->invokeEvent('OnMakeDocUrl', array(
2418
            'id' => $id,
2419
            'url' => $url
2420
        ));
2421
2422
        if (is_array($evtOut) && count($evtOut) > 0) {
2423
            $url = array_pop($evtOut);
2424
        }
2425
2426
        return $url;
2427
    }
2428
2429
    /**
2430
     * Convert URL tags [~...~] to URLs
2431
     *
2432
     * @param string $documentSource
2433
     * @return string
2434
     */
2435
    public function rewriteUrls($documentSource)
2436
    {
2437
        // rewrite the urls
2438
        if ($this->config['friendly_urls'] == 1) {
2439
            $aliases = array();
2440
            if (is_array($this->documentListing)) {
2441
                foreach ($this->documentListing as $path => $docid) { // This is big Loop on large site!
2442
                    $aliases[$docid] = $path;
2443
                    $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...
2444
                }
2445
            }
2446
2447
            if ($this->config['aliaslistingfolder'] == 1) {
2448
                preg_match_all('!\[\~([0-9]+)\~\]!ise', $documentSource, $match);
2449
                $ids = implode(',', array_unique($match['1']));
2450
                if ($ids) {
2451
                    $res = $this->db->select("id,alias,isfolder,parent,alias_visible", $this->getFullTableName('site_content'), "id IN (" . $ids . ") AND isfolder = '0'");
2452
                    while ($row = $this->db->getRow($res)) {
2453
                        if ($this->config['use_alias_path'] == '1' && $row['parent'] != 0) {
2454
                            $parent = $row['parent'];
2455
                            $path = $aliases[$parent];
2456
2457
                            while (isset($this->aliasListing[$parent]) && $this->aliasListing[$parent]['alias_visible'] == 0) {
2458
                                $path = $this->aliasListing[$parent]['path'];
2459
                                $parent = $this->aliasListing[$parent]['parent'];
2460
                            }
2461
2462
                            $aliases[$row['id']] = $path . '/' . $row['alias'];
2463
                        } else {
2464
                            $aliases[$row['id']] = $row['alias'];
2465
                        }
2466
                        $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...
2467
                    }
2468
                }
2469
            }
2470
            $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...
2471
            $isfriendly = ($this->config['friendly_alias_urls'] == 1 ? 1 : 0);
2472
            $pref = $this->config['friendly_url_prefix'];
2473
            $suff = $this->config['friendly_url_suffix'];
2474
            $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...
2475
                global $modx;
2476
                $thealias = $aliases[$m[1]];
2477
                $thefolder = $isfolder[$m[1]];
2478
                if ($isfriendly && isset($thealias)) {
2479
                    //found friendly url
2480
                    $out = ($modx->config['seostrict'] == '1' ? $modx->toAlias($modx->makeFriendlyURL($pref, $suff, $thealias, $thefolder, $m[1])) : $modx->makeFriendlyURL($pref, $suff, $thealias, $thefolder, $m[1]));
2481
                } else {
2482
                    //not found friendly url
2483
                    $out = $modx->makeFriendlyURL($pref, $suff, $m[1]);
2484
                }
2485
                return $out;
2486
            }, $documentSource);
2487
2488
        } else {
2489
            $in = '!\[\~([0-9]+)\~\]!is';
2490
            $out = "index.php?id=" . '\1';
2491
            $documentSource = preg_replace($in, $out, $documentSource);
2492
        }
2493
2494
        return $documentSource;
2495
    }
2496
2497
    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...
2498
    {
2499
        $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...
2500
        // FIX URLs
2501
        if (empty($this->documentIdentifier) || $this->config['seostrict'] == '0' || $this->config['friendly_urls'] == '0') {
2502
            return;
2503
        }
2504
        if ($this->config['site_status'] == 0) {
2505
            return;
2506
        }
2507
2508
        $scheme = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https' : 'http';
2509
        $len_base_url = strlen($this->config['base_url']);
2510
2511
        $url_path = $q;//LANG
2512
2513 View Code Duplication
        if (substr($url_path, 0, $len_base_url) === $this->config['base_url']) {
2514
            $url_path = substr($url_path, $len_base_url);
2515
        }
2516
2517
        $strictURL = $this->toAlias($this->makeUrl($this->documentIdentifier));
2518
2519 View Code Duplication
        if (substr($strictURL, 0, $len_base_url) === $this->config['base_url']) {
2520
            $strictURL = substr($strictURL, $len_base_url);
2521
        }
2522
        $http_host = $_SERVER['HTTP_HOST'];
2523
        $requestedURL = "{$scheme}://{$http_host}" . '/' . $q; //LANG
2524
2525
        $site_url = $this->config['site_url'];
2526
        $url_query_string = explode('?', $_SERVER['REQUEST_URI']);
2527
        // Strip conflicting id/q from query string
2528
        $qstring = !empty($url_query_string[1]) ? preg_replace("#(^|&)(q|id)=[^&]+#", '', $url_query_string[1]) : '';
2529
2530
        if ($this->documentIdentifier == $this->config['site_start']) {
2531
            if ($requestedURL != $this->config['site_url']) {
2532
                // Force redirect of site start
2533
                // $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...
2534
                if ($qstring) {
2535
                    $url = "{$site_url}?{$qstring}";
2536
                } else {
2537
                    $url = $site_url;
2538
                }
2539
                if ($this->config['base_url'] != $_SERVER['REQUEST_URI']) {
2540
                    if (empty($_POST)) {
2541
                        if (($this->config['base_url'] . '?' . $qstring) != $_SERVER['REQUEST_URI']) {
2542
                            $this->sendRedirect($url, 0, 'REDIRECT_HEADER', 'HTTP/1.0 301 Moved Permanently');
2543
                            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...
2544
                        }
2545
                    }
2546
                }
2547
            }
2548
        } elseif ($url_path != $strictURL && $this->documentIdentifier != $this->config['error_page']) {
2549
            // Force page redirect
2550
            //$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...
2551
            if (!empty($qstring)) {
2552
                $url = "{$site_url}{$strictURL}?{$qstring}";
2553
            } else {
2554
                $url = "{$site_url}{$strictURL}";
2555
            }
2556
            $this->sendRedirect($url, 0, 'REDIRECT_HEADER', 'HTTP/1.0 301 Moved Permanently');
2557
            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...
2558
        }
2559
        return;
2560
    }
2561
2562
    /**
2563
     * Get all db fields and TVs for a document/resource
2564
     *
2565
     * @param string $method
2566
     * @param mixed $identifier
2567
     * @param bool $isPrepareResponse
2568
     * @return array
2569
     */
2570
    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...
2571
    {
2572
2573
        $cacheKey = md5(print_r(func_get_args(), true));
2574
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
2575
            return $this->tmpCache[__FUNCTION__][$cacheKey];
2576
        }
2577
2578
        $tblsc = $this->getFullTableName("site_content");
2579
        $tbldg = $this->getFullTableName("document_groups");
2580
2581
        // allow alias to be full path
2582
        if ($method == 'alias') {
2583
            $identifier = $this->cleanDocumentIdentifier($identifier);
2584
            $method = $this->documentMethod;
2585
        }
2586
        if ($method == 'alias' && $this->config['use_alias_path'] && array_key_exists($identifier, $this->documentListing)) {
2587
            $method = 'id';
2588
            $identifier = $this->documentListing[$identifier];
2589
        }
2590
2591
        $out = $this->invokeEvent('OnBeforeLoadDocumentObject', compact('method', 'identifier'));
2592
        if (is_array($out) && is_array($out[0])) {
2593
            $documentObject = $out[0];
2594
        } else {
2595
            // get document groups for current user
2596
            if ($docgrp = $this->getUserDocGroups()) {
2597
                $docgrp = implode(",", $docgrp);
2598
            }
2599
            // get document
2600
            $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
2601
            $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...
2602
                LEFT JOIN {$tbldg} dg ON dg.document = sc.id", "sc.{$method} = '{$identifier}' AND ({$access})", "", 1);
2603
            if ($this->db->getRecordCount($rs) < 1) {
2604
                $seclimit = 0;
2605
                if ($this->config['unauthorized_page']) {
2606
                    // method may still be alias, while identifier is not full path alias, e.g. id not found above
2607
                    if ($method === 'alias') {
2608
                        $secrs = $this->db->select('count(dg.id)', "{$tbldg} as dg, {$tblsc} as sc", "dg.document = sc.id AND sc.alias = '{$identifier}'", '', 1);
2609
                    } else {
2610
                        $secrs = $this->db->select('count(id)', $tbldg, "document = '{$identifier}'", '', 1);
2611
                    }
2612
                    // check if file is not public
2613
                    $seclimit = $this->db->getValue($secrs);
2614
                }
2615
                if ($seclimit > 0) {
2616
                    // match found but not publicly accessible, send the visitor to the unauthorized_page
2617
                    $this->sendUnauthorizedPage();
2618
                    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...
2619
                } else {
2620
                    $this->sendErrorPage();
2621
                    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...
2622
                }
2623
            }
2624
            # 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...
2625
            $documentObject = $this->db->getRow($rs);
2626
2627
            if ($isPrepareResponse === 'prepareResponse') {
2628
                $this->documentObject = &$documentObject;
2629
            }
2630
            $out = $this->invokeEvent('OnLoadDocumentObject', compact('method', 'identifier', 'documentObject'));
2631
            if (is_array($out) && is_array($out[0])) {
2632
                $documentObject = $out[0];
2633
            }
2634
            if ($documentObject['template']) {
2635
                // load TVs and merge with document - Orig by Apodigm - Docvars
2636
                $rs = $this->db->select("tv.*, IF(tvc.value!='',tvc.value,tv.default_text) as value", $this->getFullTableName("site_tmplvars") . " tv
2637
                INNER JOIN " . $this->getFullTableName("site_tmplvar_templates") . " tvtpl ON tvtpl.tmplvarid = tv.id
2638
                LEFT JOIN " . $this->getFullTableName("site_tmplvar_contentvalues") . " tvc ON tvc.tmplvarid=tv.id AND tvc.contentid = '{$documentObject['id']}'", "tvtpl.templateid = '{$documentObject['template']}'");
2639
                $tmplvars = array();
2640
                while ($row = $this->db->getRow($rs)) {
2641
                    $tmplvars[$row['name']] = array(
2642
                        $row['name'],
2643
                        $row['value'],
2644
                        $row['display'],
2645
                        $row['display_params'],
2646
                        $row['type']
2647
                    );
2648
                }
2649
                $documentObject = array_merge($documentObject, $tmplvars);
2650
            }
2651
            $out = $this->invokeEvent('OnAfterLoadDocumentObject', compact('method', 'identifier', 'documentObject'));
2652
            if (is_array($out) && array_key_exists(0, $out) !== false && is_array($out[0])) {
2653
                $documentObject = $out[0];
2654
            }
2655
        }
2656
2657
        $this->tmpCache[__FUNCTION__][$cacheKey] = $documentObject;
2658
2659
        return $documentObject;
2660
    }
2661
2662
    /**
2663
     * Parse a source string.
2664
     *
2665
     * Handles most MODX tags. Exceptions include:
2666
     *   - uncached snippet tags [!...!]
2667
     *   - URL tags [~...~]
2668
     *
2669
     * @param string $source
2670
     * @return string
2671
     */
2672
    public function parseDocumentSource($source)
2673
    {
2674
        // set the number of times we are to parse the document source
2675
        $this->minParserPasses = empty ($this->minParserPasses) ? 2 : $this->minParserPasses;
2676
        $this->maxParserPasses = empty ($this->maxParserPasses) ? 10 : $this->maxParserPasses;
2677
        $passes = $this->minParserPasses;
2678
        for ($i = 0; $i < $passes; $i++) {
2679
            // get source length if this is the final pass
2680
            if ($i == ($passes - 1)) {
2681
                $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...
2682
            }
2683
            if ($this->dumpSnippets == 1) {
2684
                $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>";
2685
            }
2686
2687
            // invoke OnParseDocument event
2688
            $this->documentOutput = $source; // store source code so plugins can
2689
            $this->invokeEvent("OnParseDocument"); // work on it via $modx->documentOutput
2690
            $source = $this->documentOutput;
2691
2692
            if ($this->config['enable_at_syntax']) {
2693
                $source = $this->ignoreCommentedTagsContent($source);
2694
                $source = $this->mergeConditionalTagsContent($source);
2695
            }
2696
2697
            $source = $this->mergeSettingsContent($source);
2698
            $source = $this->mergeDocumentContent($source);
2699
            $source = $this->mergeChunkContent($source);
2700
            $source = $this->evalSnippets($source);
2701
            $source = $this->mergePlaceholderContent($source);
2702
2703
            if ($this->dumpSnippets == 1) {
2704
                $this->snippetsCode .= "</fieldset><br />";
2705
            }
2706
            if ($i == ($passes - 1) && $i < ($this->maxParserPasses - 1)) {
2707
                // check if source content was changed
2708
                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...
2709
                    $passes++;
2710
                } // if content change then increase passes because
2711
            } // we have not yet reached maxParserPasses
2712
        }
2713
        return $source;
2714
    }
2715
2716
    /**
2717
     * Starts the parsing operations.
2718
     *
2719
     * - connects to the db
2720
     * - gets the settings (including system_settings)
2721
     * - gets the document/resource identifier as in the query string
2722
     * - finally calls prepareResponse()
2723
     */
2724
    public function executeParser()
2725
    {
2726
        if(MODX_CLI) {
2727
            throw new \RuntimeException('Call DocumentParser::executeParser on CLI mode');
2728
        }
2729
2730
        //error_reporting(0);
2731
        set_error_handler(array(
2732
            & $this,
2733
            "phpError"
2734
        ), E_ALL);
2735
        $this->db->connect();
2736
2737
        // get the settings
2738
        if (empty ($this->config)) {
2739
            $this->getSettings();
2740
        }
2741
2742
        $this->_IIS_furl_fix(); // IIS friendly url fix
2743
2744
        // check site settings
2745
        if ($this->checkSiteStatus()) {
2746
            // make sure the cache doesn't need updating
2747
            $this->updatePubStatus();
2748
2749
            // find out which document we need to display
2750
            $this->documentMethod = filter_input(INPUT_GET, 'q') ? 'alias' : 'id';
2751
            $this->documentIdentifier = $this->getDocumentIdentifier($this->documentMethod);
2752
        } else {
2753
            header('HTTP/1.0 503 Service Unavailable');
2754
            $this->systemCacheKey = 'unavailable';
2755
            if (!$this->config['site_unavailable_page']) {
2756
                // display offline message
2757
                $this->documentContent = $this->config['site_unavailable_message'];
2758
                $this->outputContent();
2759
                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...
2760
            } else {
2761
                // setup offline page document settings
2762
                $this->documentMethod = 'id';
2763
                $this->documentIdentifier = $this->config['site_unavailable_page'];
2764
            }
2765
        }
2766
2767
        if ($this->documentMethod == "alias") {
2768
            $this->documentIdentifier = $this->cleanDocumentIdentifier($this->documentIdentifier);
2769
2770
            // Check use_alias_path and check if $this->virtualDir is set to anything, then parse the path
2771
            if ($this->config['use_alias_path'] == 1) {
2772
                $alias = (strlen($this->virtualDir) > 0 ? $this->virtualDir . '/' : '') . $this->documentIdentifier;
2773
                if (isset($this->documentListing[$alias])) {
2774
                    $this->documentIdentifier = $this->documentListing[$alias];
2775
                } else {
2776
                    //@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...
2777
                    if ($this->config['aliaslistingfolder'] == 1) {
2778
                        $tbl_site_content = $this->getFullTableName('site_content');
2779
2780
                        $parentId = $this->getIdFromAlias($this->virtualDir);
2781
                        $parentId = ($parentId > 0) ? $parentId : '0';
2782
2783
                        $docAlias = $this->db->escape($this->documentIdentifier);
2784
2785
                        $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...
2786
                        if ($this->db->getRecordCount($rs) == 0) {
2787
                            $this->sendErrorPage();
2788
                        }
2789
                        $docId = $this->db->getValue($rs);
2790
2791
                        if (!$docId) {
2792
                            $alias = $this->q;
2793
                            if (!empty($this->config['friendly_url_suffix'])) {
2794
                                $pos = strrpos($alias, $this->config['friendly_url_suffix']);
2795
2796
                                if ($pos !== false) {
2797
                                    $alias = substr($alias, 0, $pos);
2798
                                }
2799
                            }
2800
                            $docId = $this->getIdFromAlias($alias);
2801
                        }
2802
2803
                        if ($docId > 0) {
2804
                            $this->documentIdentifier = $docId;
2805
                        } else {
2806
                            /*
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...
2807
                            $rs  = $this->db->select('id', $tbl_site_content, "deleted=0 and alias='{$docAlias}'");
2808
                            if($this->db->getRecordCount($rs)==0)
2809
                            {
2810
                                $rs  = $this->db->select('id', $tbl_site_content, "deleted=0 and id='{$docAlias}'");
2811
                            }
2812
                            $docId = $this->db->getValue($rs);
2813
2814
                            if ($docId > 0)
2815
                            {
2816
                                $this->documentIdentifier = $docId;
2817
2818
                            }else{
2819
                            */
2820
                            $this->sendErrorPage();
2821
                            //}
2822
                        }
2823
                    } else {
2824
                        $this->sendErrorPage();
2825
                    }
2826
                }
2827
            } else {
2828
                if (isset($this->documentListing[$this->documentIdentifier])) {
2829
                    $this->documentIdentifier = $this->documentListing[$this->documentIdentifier];
2830
                } else {
2831
                    $docAlias = $this->db->escape($this->documentIdentifier);
2832
                    $rs = $this->db->select('id', $this->getFullTableName('site_content'), "deleted=0 and alias='{$docAlias}'");
2833
                    $this->documentIdentifier = (int)$this->db->getValue($rs);
2834
                }
2835
            }
2836
            $this->documentMethod = 'id';
2837
        }
2838
2839
        //$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...
2840
        // invoke OnWebPageInit event
2841
        $this->invokeEvent("OnWebPageInit");
2842
        // invoke OnLogPageView event
2843
        if ($this->config['track_visitors'] == 1) {
2844
            $this->invokeEvent("OnLogPageHit");
2845
        }
2846
        if ($this->config['seostrict'] === '1') {
2847
            $this->sendStrictURI();
2848
        }
2849
        $this->prepareResponse();
2850
    }
2851
2852
    /**
2853
     * @param $path
2854
     * @param null $suffix
2855
     * @return mixed
2856
     */
2857
    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...
2858
    {
2859
        $exp = explode('/', $path);
2860
        return str_replace($suffix, '', end($exp));
2861
    }
2862
2863
    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...
2864
    {
2865
        if ($this->config['friendly_urls'] != 1) {
2866
            return;
2867
        }
2868
2869
        if (strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') === false) {
2870
            return;
2871
        }
2872
2873
        $url = $_SERVER['QUERY_STRING'];
2874
        $err = substr($url, 0, 3);
2875
        if ($err !== '404' && $err !== '405') {
2876
            return;
2877
        }
2878
2879
        $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...
2880
        unset ($_GET[$k[0]]);
2881
        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...
2882
        $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...
2883
        $_SERVER['QUERY_STRING'] = $qp['query'];
2884
        if (!empty ($qp['query'])) {
2885
            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...
2886
            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...
2887
                $_REQUEST[$n] = $_GET[$n] = $v;
2888
            }
2889
        }
2890
        $_SERVER['PHP_SELF'] = $this->config['base_url'] . $qp['path'];
2891
        $this->q = $qp['path'];
2892
        return $qp['path'];
2893
    }
2894
2895
    /**
2896
     * The next step called at the end of executeParser()
2897
     *
2898
     * - checks cache
2899
     * - checks if document/resource is deleted/unpublished
2900
     * - checks if resource is a weblink and redirects if so
2901
     * - gets template and parses it
2902
     * - ensures that postProcess is called when PHP is finished
2903
     */
2904
    public function prepareResponse()
2905
    {
2906
        // we now know the method and identifier, let's check the cache
2907
2908
        if ($this->config['enable_cache'] == 2 && $this->isLoggedIn()) {
2909
            $this->config['enable_cache'] = 0;
2910
        }
2911
2912
        if ($this->config['enable_cache']) {
2913
            $this->documentContent = $this->getDocumentObjectFromCache($this->documentIdentifier, true);
2914
        } else {
2915
            $this->documentContent = '';
2916
        }
2917
2918
        if ($this->documentContent == '') {
2919
            // get document object from DB
2920
            $this->documentObject = $this->getDocumentObject($this->documentMethod, $this->documentIdentifier, 'prepareResponse');
2921
2922
            // write the documentName to the object
2923
            $this->documentName = &$this->documentObject['pagetitle'];
2924
2925
            // check if we should not hit this document
2926
            if ($this->documentObject['donthit'] == 1) {
2927
                $this->config['track_visitors'] = 0;
2928
            }
2929
2930
            if ($this->documentObject['deleted'] == 1) {
2931
                $this->sendErrorPage();
2932
            } // validation routines
2933
            elseif ($this->documentObject['published'] == 0) {
2934
                $this->_sendErrorForUnpubPage();
2935
            } elseif ($this->documentObject['type'] == 'reference') {
2936
                $this->_sendRedirectForRefPage($this->documentObject['content']);
2937
            }
2938
2939
            // get the template and start parsing!
2940
            if (!$this->documentObject['template']) {
2941
                $templateCode = '[*content*]';
2942
            } // use blank template
2943
            else {
2944
                $templateCode = $this->_getTemplateCodeFromDB($this->documentObject['template']);
2945
            }
2946
2947
            if (substr($templateCode, 0, 8) === '@INCLUDE') {
2948
                $templateCode = $this->atBindInclude($templateCode);
2949
            }
2950
2951
2952
            $this->documentContent = &$templateCode;
2953
2954
            // invoke OnLoadWebDocument event
2955
            $this->invokeEvent('OnLoadWebDocument');
2956
2957
            // Parse document source
2958
            $this->documentContent = $this->parseDocumentSource($templateCode);
2959
2960
            $this->documentGenerated = 1;
2961
        } else {
2962
            $this->documentGenerated = 0;
2963
        }
2964
2965
        if ($this->config['error_page'] == $this->documentIdentifier && $this->config['error_page'] != $this->config['site_start']) {
2966
            header('HTTP/1.0 404 Not Found');
2967
        }
2968
2969
        register_shutdown_function(array(
2970
            &$this,
2971
            'postProcess'
2972
        )); // tell PHP to call postProcess when it shuts down
2973
        $this->outputContent();
2974
        //$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...
2975
    }
2976
2977
    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...
2978
    {
2979
        // Can't view unpublished pages !$this->checkPreview()
2980
        if (!$this->hasPermission('view_unpublished')) {
2981
            $this->sendErrorPage();
2982
        } else {
2983
            $udperms = new Legacy\Permissions();
2984
            $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...
2985
            $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...
2986
            $udperms->role = $_SESSION['mgrRole'];
2987
            // Doesn't have access to this document
2988
            if (!$udperms->checkPermissions()) {
2989
                $this->sendErrorPage();
2990
            }
2991
        }
2992
    }
2993
2994
    /**
2995
     * @param $url
2996
     */
2997
    public function _sendRedirectForRefPage($url)
2998
    {
2999
        // check whether it's a reference
3000
        if (preg_match('@^[1-9][0-9]*$@', $url)) {
3001
            $url = $this->makeUrl($url); // if it's a bare document id
3002
        } elseif (strpos($url, '[~') !== false) {
3003
            $url = $this->rewriteUrls($url); // if it's an internal docid tag, process it
3004
        }
3005
        $this->sendRedirect($url, 0, '', 'HTTP/1.0 301 Moved Permanently');
3006
        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...
3007
    }
3008
3009
    /**
3010
     * @param $templateID
3011
     * @return mixed
3012
     */
3013
    public function _getTemplateCodeFromDB($templateID)
3014
    {
3015
        $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...
3016
        if ($this->db->getRecordCount($rs) == 1) {
3017
            return $this->db->getValue($rs);
3018
        } else {
3019
            $this->messageQuit('Incorrect number of templates returned from database');
3020
        }
3021
    }
3022
3023
    /**
3024
     * Returns an array of all parent record IDs for the id passed.
3025
     *
3026
     * @param int $id Docid to get parents for.
3027
     * @param int $height The maximum number of levels to go up, default 10.
3028
     * @return array
3029
     */
3030
    public function getParentIds($id, $height = 10)
3031
    {
3032
        $parents = array();
3033
        while ($id && $height--) {
3034
            $thisid = $id;
3035
            if ($this->config['aliaslistingfolder'] == 1) {
3036
                $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");
3037
                if (!$id || $id == '0') {
3038
                    break;
3039
                }
3040
            } else {
3041
                $id = $this->aliasListing[$id]['parent'];
3042
                if (!$id) {
3043
                    break;
3044
                }
3045
            }
3046
            $parents[$thisid] = $id;
3047
        }
3048
        return $parents;
3049
    }
3050
3051
    /**
3052
     * @param $id
3053
     * @param int $top
3054
     * @return mixed
3055
     */
3056
    public function getUltimateParentId($id, $top = 0)
3057
    {
3058
        $i = 0;
3059
        while ($id && $i < 20) {
3060
            if ($top == $this->aliasListing[$id]['parent']) {
3061
                break;
3062
            }
3063
            $id = $this->aliasListing[$id]['parent'];
3064
            $i++;
3065
        }
3066
        return $id;
3067
    }
3068
3069
    /**
3070
     * Returns an array of child IDs belonging to the specified parent.
3071
     *
3072
     * @param int $id The parent resource/document to start from
3073
     * @param int $depth How many levels deep to search for children, default: 10
3074
     * @param array $children Optional array of docids to merge with the result.
3075
     * @return array Contains the document Listing (tree) like the sitemap
3076
     */
3077
    public function getChildIds($id, $depth = 10, $children = array())
3078
    {
3079
3080
        $cacheKey = md5(print_r(func_get_args(), true));
3081
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3082
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3083
        }
3084
3085
        if ($this->config['aliaslistingfolder'] == 1) {
3086
3087
            $res = $this->db->select("id,alias,isfolder,parent", $this->getFullTableName('site_content'), "parent IN (" . $id . ") AND deleted = '0'");
3088
            $idx = array();
3089
            while ($row = $this->db->getRow($res)) {
3090
                $pAlias = '';
3091
                if (isset($this->aliasListing[$row['parent']])) {
3092
                    $pAlias .= !empty($this->aliasListing[$row['parent']]['path']) ? $this->aliasListing[$row['parent']]['path'] . '/' : '';
3093
                    $pAlias .= !empty($this->aliasListing[$row['parent']]['alias']) ? $this->aliasListing[$row['parent']]['alias'] . '/' : '';
3094
                };
3095
                $children[$pAlias . $row['alias']] = $row['id'];
3096
                if ($row['isfolder'] == 1) {
3097
                    $idx[] = $row['id'];
3098
                }
3099
            }
3100
            $depth--;
3101
            $idx = implode(',', $idx);
3102
            if (!empty($idx)) {
3103
                if ($depth) {
3104
                    $children = $this->getChildIds($idx, $depth, $children);
3105
                }
3106
            }
3107
            $this->tmpCache[__FUNCTION__][$cacheKey] = $children;
3108
            return $children;
3109
3110
        } else {
3111
3112
            // Initialise a static array to index parents->children
3113
            static $documentMap_cache = array();
3114
            if (!count($documentMap_cache)) {
3115
                foreach ($this->documentMap as $document) {
3116
                    foreach ($document as $p => $c) {
3117
                        $documentMap_cache[$p][] = $c;
3118
                    }
3119
                }
3120
            }
3121
3122
            // Get all the children for this parent node
3123
            if (isset($documentMap_cache[$id])) {
3124
                $depth--;
3125
3126
                foreach ($documentMap_cache[$id] as $childId) {
3127
                    $pkey = (strlen($this->aliasListing[$childId]['path']) ? "{$this->aliasListing[$childId]['path']}/" : '') . $this->aliasListing[$childId]['alias'];
3128
                    if (!strlen($pkey)) {
3129
                        $pkey = "{$childId}";
3130
                    }
3131
                    $children[$pkey] = $childId;
3132
3133
                    if ($depth && isset($documentMap_cache[$childId])) {
3134
                        $children += $this->getChildIds($childId, $depth);
3135
                    }
3136
                }
3137
            }
3138
            $this->tmpCache[__FUNCTION__][$cacheKey] = $children;
3139
            return $children;
3140
3141
        }
3142
    }
3143
3144
    /**
3145
     * Displays a javascript alert message in the web browser and quit
3146
     *
3147
     * @param string $msg Message to show
3148
     * @param string $url URL to redirect to
3149
     */
3150
    public function webAlertAndQuit($msg, $url = '')
3151
    {
3152
        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...
3153
        switch (true) {
3154
            case (0 === stripos($url, 'javascript:')):
3155
                $fnc = substr($url, 11);
3156
                break;
3157
            case $url === '#':
3158
                $fnc = '';
3159
                break;
3160
            case empty($url):
3161
                $fnc = 'history.back(-1);';
3162
                break;
3163
            default:
3164
                $fnc = "window.location.href='" . addslashes($url) . "';";
3165
        }
3166
3167
        echo "<html><head>
3168
            <title>MODX :: Alert</title>
3169
            <meta http-equiv=\"Content-Type\" content=\"text/html; charset={$modx_manager_charset};\">
3170
            <script>
3171
                function __alertQuit() {
3172
                    var el = document.querySelector('p');
3173
                    alert(el.innerHTML);
3174
                    el.remove();
3175
                    {$fnc}
3176
                }
3177
                window.setTimeout('__alertQuit();',100);
3178
            </script>
3179
            </head><body>
3180
            <p>{$msg}</p>
3181
            </body></html>";
3182
        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...
3183
    }
3184
3185
    /**
3186
     * Returns 1 if user has the currect permission
3187
     *
3188
     * @param string $pm Permission name
3189
     * @return int Why not bool?
3190
     */
3191
    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...
3192
    {
3193
        $state = 0;
3194
        $pms = $_SESSION['mgrPermissions'];
3195
        if ($pms) {
3196
            $state = ((bool)$pms[$pm] === true);
3197
        }
3198
        return (int)$state;
3199
    }
3200
3201
    /**
3202
     * Returns true if element is locked
3203
     *
3204
     * @param int $type Types: 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3205
     * @param int $id Element- / Resource-id
3206
     * @param bool $includeThisUser true = Return also info about actual user
3207
     * @return array lock-details or null
3208
     */
3209
    public function elementIsLocked($type, $id, $includeThisUser = false)
3210
    {
3211
        $id = (int)$id;
3212
        $type = (int)$type;
3213
        if (!$type || !$id) {
3214
            return null;
3215
        }
3216
3217
        // Build lockedElements-Cache at first call
3218
        $this->buildLockedElementsCache();
3219
3220
        if (!$includeThisUser && $this->lockedElements[$type][$id]['sid'] == $this->sid) {
3221
            return null;
3222
        }
3223
3224
        if (isset($this->lockedElements[$type][$id])) {
3225
            return $this->lockedElements[$type][$id];
3226
        } else {
3227
            return null;
3228
        }
3229
    }
3230
3231
    /**
3232
     * Returns Locked Elements as Array
3233
     *
3234
     * @param int $type Types: 0=all, 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3235
     * @param bool $minimumDetails true =
3236
     * @return array|mixed|null
3237
     */
3238
    public function getLockedElements($type = 0, $minimumDetails = false)
3239
    {
3240
        $this->buildLockedElementsCache();
3241
3242
        if (!$minimumDetails) {
3243
            $lockedElements = $this->lockedElements;
3244
        } else {
3245
            // Minimum details for HTML / Ajax-requests
3246
            $lockedElements = array();
3247
            foreach ($this->lockedElements as $elType => $elements) {
3248
                foreach ($elements as $elId => $el) {
3249
                    $lockedElements[$elType][$elId] = array(
3250
                        'username' => $el['username'],
3251
                        'lasthit_df' => $el['lasthit_df'],
3252
                        'state' => $this->determineLockState($el['internalKey'])
3253
                    );
3254
                }
3255
            }
3256
        }
3257
3258
        if ($type == 0) {
3259
            return $lockedElements;
3260
        }
3261
3262
        $type = (int)$type;
3263
        if (isset($lockedElements[$type])) {
3264
            return $lockedElements[$type];
3265
        } else {
3266
            return array();
3267
        }
3268
    }
3269
3270
    /**
3271
     * Builds the Locked Elements Cache once
3272
     */
3273
    public function buildLockedElementsCache()
3274
    {
3275
        if (is_null($this->lockedElements)) {
3276
            $this->lockedElements = array();
3277
            $this->cleanupExpiredLocks();
3278
3279
            $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...
3280
                LEFT JOIN {$this->getFullTableName('manager_users')} mu on ul.internalKey = mu.id");
3281
            while ($row = $this->db->getRow($rs)) {
3282
                $this->lockedElements[$row['elementType']][$row['elementId']] = array(
3283
                    'sid' => $row['sid'],
3284
                    'internalKey' => $row['internalKey'],
3285
                    'username' => $row['username'],
3286
                    'elementType' => $row['elementType'],
3287
                    'elementId' => $row['elementId'],
3288
                    'lasthit' => $row['lasthit'],
3289
                    'lasthit_df' => $this->toDateFormat($row['lasthit']),
3290
                    'state' => $this->determineLockState($row['sid'])
3291
                );
3292
            }
3293
        }
3294
    }
3295
3296
    /**
3297
     * Cleans up the active user locks table
3298
     */
3299
    public function cleanupExpiredLocks()
3300
    {
3301
        // Clean-up active_user_sessions first
3302
        $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
3303
        $validSessionTimeLimit = $this->time - $timeout;
3304
        $this->db->delete($this->getFullTableName('active_user_sessions'), "lasthit < {$validSessionTimeLimit}");
3305
3306
        // Clean-up active_user_locks
3307
        $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...
3308
        $count = $this->db->getRecordCount($rs);
3309
        if ($count) {
3310
            $rs = $this->db->makeArray($rs);
3311
            $userSids = array();
3312
            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...
3313
                $userSids[] = $row['sid'];
3314
            }
3315
            $userSids = "'" . implode("','", $userSids) . "'";
3316
            $this->db->delete($this->getFullTableName('active_user_locks'), "sid NOT IN({$userSids})");
3317
        } else {
3318
            $this->db->delete($this->getFullTableName('active_user_locks'));
3319
        }
3320
3321
    }
3322
3323
    /**
3324
     * Cleans up the active users table
3325
     */
3326
    public function cleanupMultipleActiveUsers()
3327
    {
3328
        $timeout = 20 * 60; // Delete multiple user-sessions after 20min
3329
        $validSessionTimeLimit = $this->time - $timeout;
3330
3331
        $activeUserSids = array();
3332
        $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...
3333
        $count = $this->db->getRecordCount($rs);
3334
        if ($count) {
3335
            $rs = $this->db->makeArray($rs);
3336
            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...
3337
                $activeUserSids[] = $row['sid'];
3338
            }
3339
        }
3340
3341
        $rs = $this->db->select("sid,internalKey,lasthit", "{$this->getFullTableName('active_users')}", "", "lasthit DESC");
3342
        if ($this->db->getRecordCount($rs)) {
3343
            $rs = $this->db->makeArray($rs);
3344
            $internalKeyCount = array();
3345
            $deleteSids = '';
3346
            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...
3347
                if (!isset($internalKeyCount[$row['internalKey']])) {
3348
                    $internalKeyCount[$row['internalKey']] = 0;
3349
                }
3350
                $internalKeyCount[$row['internalKey']]++;
3351
3352
                if ($internalKeyCount[$row['internalKey']] > 1 && !in_array($row['sid'], $activeUserSids) && $row['lasthit'] < $validSessionTimeLimit) {
3353
                    $deleteSids .= $deleteSids == '' ? '' : ' OR ';
3354
                    $deleteSids .= "sid='{$row['sid']}'";
3355
                };
3356
3357
            }
3358
            if ($deleteSids) {
3359
                $this->db->delete($this->getFullTableName('active_users'), $deleteSids);
3360
            }
3361
        }
3362
3363
    }
3364
3365
    /**
3366
     * Determines state of a locked element acc. to user-permissions
3367
     *
3368
     * @param $sid
3369
     * @return int $state States: 0=No display, 1=viewing this element, 2=locked, 3=show unlock-button
3370
     * @internal param int $internalKey : ID of User who locked actual element
3371
     */
3372
    public function determineLockState($sid)
3373
    {
3374
        $state = 0;
3375
        if ($this->hasPermission('display_locks')) {
3376
            if ($sid == $this->sid) {
3377
                $state = 1;
3378
            } else {
3379
                if ($this->hasPermission('remove_locks')) {
3380
                    $state = 3;
3381
                } else {
3382
                    $state = 2;
3383
                }
3384
            }
3385
        }
3386
        return $state;
3387
    }
3388
3389
    /**
3390
     * Locks an element
3391
     *
3392
     * @param int $type Types: 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3393
     * @param int $id Element- / Resource-id
3394
     * @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...
3395
     */
3396
    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...
3397
    {
3398
        $userId = $this->isBackend() && $_SESSION['mgrInternalKey'] ? $_SESSION['mgrInternalKey'] : 0;
3399
        $type = (int)$type;
3400
        $id = (int)$id;
3401
        if (!$type || !$id || !$userId) {
3402
            return false;
3403
        }
3404
3405
        $sql = sprintf('REPLACE INTO %s (internalKey, elementType, elementId, lasthit, sid)
3406
                VALUES (%d, %d, %d, %d, \'%s\')', $this->getFullTableName('active_user_locks'), $userId, $type, $id, $this->time, $this->sid);
3407
        $this->db->query($sql);
3408
    }
3409
3410
    /**
3411
     * Unlocks an element
3412
     *
3413
     * @param int $type Types: 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3414
     * @param int $id Element- / Resource-id
3415
     * @param bool $includeAllUsers true = Deletes not only own user-locks
3416
     * @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...
3417
     */
3418
    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...
3419
    {
3420
        $userId = $this->isBackend() && $_SESSION['mgrInternalKey'] ? $_SESSION['mgrInternalKey'] : 0;
3421
        $type = (int)$type;
3422
        $id = (int)$id;
3423
        if (!$type || !$id) {
3424
            return false;
3425
        }
3426
3427
        if (!$includeAllUsers) {
3428
            $sql = sprintf('DELETE FROM %s WHERE internalKey = %d AND elementType = %d AND elementId = %d;', $this->getFullTableName('active_user_locks'), $userId, $type, $id);
3429
        } else {
3430
            $sql = sprintf('DELETE FROM %s WHERE elementType = %d AND elementId = %d;', $this->getFullTableName('active_user_locks'), $type, $id);
3431
        }
3432
        $this->db->query($sql);
3433
    }
3434
3435
    /**
3436
     * Updates table "active_user_sessions" with userid, lasthit, IP
3437
     */
3438
    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...
3439
    {
3440
        if (!$this->sid) {
3441
            return;
3442
        }
3443
3444
        // web users are stored with negative keys
3445
        $userId = $this->getLoginUserType() == 'manager' ? $this->getLoginUserID() : -$this->getLoginUserID();
3446
3447
        // Get user IP
3448 View Code Duplication
        if ($cip = getenv("HTTP_CLIENT_IP")) {
3449
            $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...
3450
        } elseif ($cip = getenv("HTTP_X_FORWARDED_FOR")) {
3451
            $ip = $cip;
3452
        } elseif ($cip = getenv("REMOTE_ADDR")) {
3453
            $ip = $cip;
3454
        } else {
3455
            $ip = "UNKNOWN";
3456
        }
3457
        $_SESSION['ip'] = $ip;
3458
3459
        $sql = sprintf('REPLACE INTO %s (internalKey, lasthit, ip, sid)
3460
            VALUES (%d, %d, \'%s\', \'%s\')', $this->getFullTableName('active_user_sessions'), $userId, $this->time, $ip, $this->sid);
3461
        $this->db->query($sql);
3462
    }
3463
3464
    /**
3465
     * Add an a alert message to the system event log
3466
     *
3467
     * @param int $evtid Event ID
3468
     * @param int $type Types: 1 = information, 2 = warning, 3 = error
3469
     * @param string $msg Message to be logged
3470
     * @param string $source source of the event (module, snippet name, etc.)
3471
     *                       Default: Parser
3472
     */
3473
    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...
3474
    {
3475
        $msg = $this->db->escape($msg);
3476
        if (strpos($GLOBALS['database_connection_charset'], 'utf8') === 0 && extension_loaded('mbstring')) {
3477
            $esc_source = mb_substr($source, 0, 50, "UTF-8");
3478
        } else {
3479
            $esc_source = substr($source, 0, 50);
3480
        }
3481
        $esc_source = $this->db->escape($esc_source);
3482
3483
        $LoginUserID = $this->getLoginUserID();
3484
        if ($LoginUserID == '') {
3485
            $LoginUserID = 0;
3486
        }
3487
3488
        $usertype = $this->isFrontend() ? 1 : 0;
3489
        $evtid = (int)$evtid;
3490
        $type = (int)$type;
3491
3492
        // Types: 1 = information, 2 = warning, 3 = error
3493
        if ($type < 1) {
3494
            $type = 1;
3495
        } elseif ($type > 3) {
3496
            $type = 3;
3497
        }
3498
3499
        $this->db->insert(array(
3500
            'eventid' => $evtid,
3501
            'type' => $type,
3502
            'createdon' => $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'],
3503
            'source' => $esc_source,
3504
            'description' => $msg,
3505
            'user' => $LoginUserID,
3506
            'usertype' => $usertype
3507
        ), $this->getFullTableName("event_log"));
3508
3509
        if (isset($this->config['send_errormail']) && $this->config['send_errormail'] !== '0') {
3510
            if ($this->config['send_errormail'] <= $type) {
3511
                $this->sendmail(array(
3512
                    'subject' => 'MODX System Error on ' . $this->config['site_name'],
3513
                    'body' => 'Source: ' . $source . ' - The details of the error could be seen in the MODX system events log.',
3514
                    'type' => 'text'
3515
                ));
3516
            }
3517
        }
3518
    }
3519
3520
    /**
3521
     * @param string|array $params
3522
     * @param string $msg
3523
     * @param array $files
3524
     * @return bool
3525
     * @throws \PHPMailer\PHPMailer\Exception
3526
     */
3527
    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...
3528
    {
3529
        if (\is_scalar($params)) {
3530
            if (strpos($params, '=') === false) {
3531
                if (strpos($params, '@') !== false) {
3532
                    $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...
3533
                } else {
3534
                    $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...
3535
                }
3536
            } else {
3537
                $params_array = explode(',', $params);
3538
                foreach ($params_array as $k => $v) {
3539
                    $k = trim($k);
3540
                    $v = trim($v);
3541
                    $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...
3542
                }
3543
            }
3544
        } else {
3545
            $p = $params;
3546
            unset($params);
3547
        }
3548
        if (isset($p['sendto'])) {
3549
            $p['to'] = $p['sendto'];
3550
        }
3551
3552
        if (isset($p['to']) && preg_match('@^[0-9]+$@', $p['to'])) {
3553
            $userinfo = $this->getUserInfo($p['to']);
3554
            $p['to'] = $userinfo['email'];
3555
        }
3556
        if (isset($p['from']) && preg_match('@^[0-9]+$@', $p['from'])) {
3557
            $userinfo = $this->getUserInfo($p['from']);
3558
            $p['from'] = $userinfo['email'];
3559
            $p['fromname'] = $userinfo['username'];
3560
        }
3561
        if ($msg === '' && !isset($p['body'])) {
3562
            $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...
3563
        } elseif (is_string($msg) && 0 < strlen($msg)) {
3564
            $p['body'] = $msg;
3565
        }
3566
3567
        $this->loadExtension('MODxMailer');
3568
        $sendto = (!isset($p['to'])) ? $this->config['emailsender'] : $p['to'];
3569
        $sendto = explode(',', $sendto);
3570
        foreach ($sendto as $address) {
3571
            list($name, $address) = $this->mail->address_split($address);
3572
            $this->mail->AddAddress($address, $name);
3573
        }
3574 View Code Duplication
        if (isset($p['cc'])) {
3575
            $p['cc'] = explode(',', $p['cc']);
3576
            foreach ($p['cc'] as $address) {
3577
                list($name, $address) = $this->mail->address_split($address);
3578
                $this->mail->AddCC($address, $name);
3579
            }
3580
        }
3581 View Code Duplication
        if (isset($p['bcc'])) {
3582
            $p['bcc'] = explode(',', $p['bcc']);
3583
            foreach ($p['bcc'] as $address) {
3584
                list($name, $address) = $this->mail->address_split($address);
3585
                $this->mail->AddBCC($address, $name);
3586
            }
3587
        }
3588
        if (isset($p['from']) && strpos($p['from'], '<') !== false && substr($p['from'], -1) === '>') {
3589
            list($p['fromname'], $p['from']) = $this->mail->address_split($p['from']);
3590
        }
3591
        $this->mail->setFrom(
3592
            isset($p['from']) ? $p['from'] : $this->config['emailsender'],
3593
            isset($p['fromname']) ? $p['fromname'] : $this->config['site_name']
3594
        );
3595
        $this->mail->Subject = (!isset($p['subject'])) ? $this->config['emailsubject'] : $p['subject'];
3596
        $this->mail->Body = $p['body'];
3597
        if (isset($p['type']) && $p['type'] === 'text') {
3598
            $this->mail->IsHTML(false);
3599
        }
3600
        if (!is_array($files)) {
3601
            $files = array();
3602
        }
3603
        foreach ($files as $f) {
3604
            if (file_exists(MODX_BASE_PATH . $f) && is_file(MODX_BASE_PATH . $f) && is_readable(MODX_BASE_PATH . $f)) {
3605
                $this->mail->AddAttachment(MODX_BASE_PATH . $f);
3606
            }
3607
        }
3608
        return $this->mail->send();
3609
    }
3610
3611
    /**
3612
     * @param string $target
3613
     * @param int $limit
3614
     * @param int $trim
3615
     */
3616
    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...
3617
    {
3618
        if ($limit < $trim) {
3619
            $trim = $limit;
3620
        }
3621
3622
        $table_name = $this->getFullTableName($target);
3623
        $count = $this->db->getValue($this->db->select('COUNT(id)', $table_name));
3624
        $over = $count - $limit;
3625
        if (0 < $over) {
3626
            $trim = ($over + $trim);
3627
            $this->db->delete($table_name, '', '', $trim);
3628
        }
3629
        $this->db->optimize($table_name);
3630
    }
3631
3632
    /**
3633
     * Returns true if we are currently in the manager backend
3634
     *
3635
     * @return boolean
3636
     */
3637
    public function isBackend()
3638
    {
3639
        return (defined('IN_MANAGER_MODE') && IN_MANAGER_MODE === true);
3640
    }
3641
3642
    /**
3643
     * Returns true if we are currently in the frontend
3644
     *
3645
     * @return boolean
3646
     */
3647
    public function isFrontend()
3648
    {
3649
        return ! $this->isBackend();
3650
    }
3651
3652
    /**
3653
     * Gets all child documents of the specified document, including those which are unpublished or deleted.
3654
     *
3655
     * @param int $id The Document identifier to start with
3656
     * @param string $sort Sort field
3657
     *                     Default: menuindex
3658
     * @param string $dir Sort direction, ASC and DESC is possible
3659
     *                    Default: ASC
3660
     * @param string $fields Default: id, pagetitle, description, parent, alias, menutitle
3661
     * @return array
3662
     */
3663 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...
3664
    {
3665
3666
        $cacheKey = md5(print_r(func_get_args(), true));
3667
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3668
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3669
        }
3670
3671
        $tblsc = $this->getFullTableName("site_content");
3672
        $tbldg = $this->getFullTableName("document_groups");
3673
        // modify field names to use sc. table reference
3674
        $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3675
        $sort = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
3676
        // get document groups for current user
3677
        if ($docgrp = $this->getUserDocGroups()) {
3678
            $docgrp = implode(",", $docgrp);
3679
        }
3680
        // build query
3681
        $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
3682
        $result = $this->db->select("DISTINCT {$fields}", "{$tblsc} sc
3683
                LEFT JOIN {$tbldg} dg on dg.document = sc.id", "sc.parent = '{$id}' AND ({$access}) GROUP BY sc.id", "{$sort} {$dir}");
3684
        $resourceArray = $this->db->makeArray($result);
3685
        $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
3686
        return $resourceArray;
3687
    }
3688
3689
    /**
3690
     * Gets all active child documents of the specified document, i.e. those which published and not deleted.
3691
     *
3692
     * @param int $id The Document identifier to start with
3693
     * @param string $sort Sort field
3694
     *                     Default: menuindex
3695
     * @param string $dir Sort direction, ASC and DESC is possible
3696
     *                    Default: ASC
3697
     * @param string $fields Default: id, pagetitle, description, parent, alias, menutitle
3698
     * @return array
3699
     */
3700 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...
3701
    {
3702
        $cacheKey = md5(print_r(func_get_args(), true));
3703
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3704
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3705
        }
3706
3707
        $tblsc = $this->getFullTableName("site_content");
3708
        $tbldg = $this->getFullTableName("document_groups");
3709
3710
        // modify field names to use sc. table reference
3711
        $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3712
        $sort = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
3713
        // get document groups for current user
3714
        if ($docgrp = $this->getUserDocGroups()) {
3715
            $docgrp = implode(",", $docgrp);
3716
        }
3717
        // build query
3718
        $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
3719
        $result = $this->db->select("DISTINCT {$fields}", "{$tblsc} sc
3720
                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}");
3721
        $resourceArray = $this->db->makeArray($result);
3722
3723
        $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
3724
3725
        return $resourceArray;
3726
    }
3727
3728
    /**
3729
     * getDocumentChildren
3730
     * @version 1.1.1 (2014-02-19)
3731
     *
3732
     * @desc Returns the children of the selected document/folder as an associative array.
3733
     *
3734
     * @param $parentid {integer} - The parent document identifier. Default: 0 (site root).
3735
     * @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.
3736
     * @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.
3737
     * @param $fields {comma separated string; '*'} - Comma separated list of document fields to get. Default: '*' (all fields).
3738
     * @param $where {string} - Where condition in SQL style. Should include a leading 'AND '. Default: ''.
3739
     * @param $sort {comma separated string} - Should be a comma-separated list of field names on which to sort. Default: 'menuindex'.
3740
     * @param $dir {'ASC'; 'DESC'} - Sort direction, ASC and DESC is possible. Default: 'ASC'.
3741
     * @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).
3742
     *
3743
     * @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...
3744
     */
3745
    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...
3746
    {
3747
3748
        $cacheKey = md5(print_r(func_get_args(), true));
3749
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3750
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3751
        }
3752
3753
        $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...
3754
        $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...
3755
3756
        if ($where != '') {
3757
            $where = 'AND ' . $where;
3758
        }
3759
3760
        // modify field names to use sc. table reference
3761
        $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3762
        $sort = ($sort == '') ? '' : 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
3763
3764
        // get document groups for current user
3765
        if ($docgrp = $this->getUserDocGroups()) {
3766
            $docgrp = implode(',', $docgrp);
3767
        }
3768
3769
        // build query
3770
        $access = ($this->isFrontend() ? 'sc.privateweb=0' : '1="' . $_SESSION['mgrRole'] . '" OR sc.privatemgr=0') . (!$docgrp ? '' : ' OR dg.document_group IN (' . $docgrp . ')');
3771
3772
        $tblsc = $this->getFullTableName('site_content');
3773
        $tbldg = $this->getFullTableName('document_groups');
3774
3775
        $result = $this->db->select("DISTINCT {$fields}", "{$tblsc} sc
3776
                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);
3777
3778
        $resourceArray = $this->db->makeArray($result);
3779
3780
        $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
3781
3782
        return $resourceArray;
3783
    }
3784
3785
    /**
3786
     * getDocuments
3787
     * @version 1.1.1 (2013-02-19)
3788
     *
3789
     * @desc Returns required documents (their fields).
3790
     *
3791
     * @param $ids {array; comma separated string} - Documents Ids to get. @required
3792
     * @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.
3793
     * @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.
3794
     * @param $fields {comma separated string; '*'} - Documents fields to get. Default: '*'.
3795
     * @param $where {string} - SQL WHERE clause. Default: ''.
3796
     * @param $sort {comma separated string} - A comma-separated list of field names to sort by. Default: 'menuindex'.
3797
     * @param $dir {'ASC'; 'DESC'} - Sorting direction. Default: 'ASC'.
3798
     * @param $limit {string} - SQL LIMIT (without 'LIMIT '). An empty string means no limit. Default: ''.
3799
     *
3800
     * @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...
3801
     */
3802
    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...
3803
    {
3804
3805
        $cacheKey = md5(print_r(func_get_args(), true));
3806
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3807
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3808
        }
3809
3810
        if (is_string($ids)) {
3811
            if (strpos($ids, ',') !== false) {
3812
                $ids = array_filter(array_map('intval', explode(',', $ids)));
3813
            } else {
3814
                $ids = array($ids);
3815
            }
3816
        }
3817
        if (count($ids) == 0) {
3818
            $this->tmpCache[__FUNCTION__][$cacheKey] = false;
3819
            return false;
3820
        } else {
3821
            // modify field names to use sc. table reference
3822
            $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3823
            $sort = ($sort == '') ? '' : 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
3824
            if ($where != '') {
3825
                $where = 'AND ' . $where;
3826
            }
3827
3828
            $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...
3829
            $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...
3830
3831
            // get document groups for current user
3832
            if ($docgrp = $this->getUserDocGroups()) {
3833
                $docgrp = implode(',', $docgrp);
3834
            }
3835
3836
            $access = ($this->isFrontend() ? 'sc.privateweb=0' : '1="' . $_SESSION['mgrRole'] . '" OR sc.privatemgr=0') . (!$docgrp ? '' : ' OR dg.document_group IN (' . $docgrp . ')');
3837
3838
            $tblsc = $this->getFullTableName('site_content');
3839
            $tbldg = $this->getFullTableName('document_groups');
3840
3841
            $result = $this->db->select("DISTINCT {$fields}", "{$tblsc} sc
3842
                    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);
3843
3844
            $resourceArray = $this->db->makeArray($result);
3845
3846
            $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
3847
3848
            return $resourceArray;
3849
        }
3850
    }
3851
3852
    /**
3853
     * getDocument
3854
     * @version 1.0.1 (2014-02-19)
3855
     *
3856
     * @desc Returns required fields of a document.
3857
     *
3858
     * @param int $id {integer}
3859
     * - Id of a document which data has to be gained. @required
3860
     * @param string $fields {comma separated string; '*'}
3861
     * - Comma separated list of document fields to get. Default: '*'.
3862
     * @param int $published {0; 1; 'all'}
3863
     * - 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.
3864
     * @param int $deleted {0; 1; 'all'}
3865
     * - 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.
3866
     * @return bool {array; false} - Result array with fields or false.
3867
     * - Result array with fields or false.
3868
     */
3869 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...
3870
    {
3871
        if ($id == 0) {
3872
            return false;
3873
        } else {
3874
            $docs = $this->getDocuments(array($id), $published, $deleted, $fields, '', '', '', 1);
3875
3876
            if ($docs != false) {
3877
                return $docs[0];
3878
            } else {
3879
                return false;
3880
            }
3881
        }
3882
    }
3883
3884
    /**
3885
     * @param string $field
3886
     * @param string $docid
3887
     * @return bool|mixed
3888
     */
3889
    public function getField($field = 'content', $docid = '')
3890
    {
3891
        if (empty($docid) && isset($this->documentIdentifier)) {
3892
            $docid = $this->documentIdentifier;
3893
        } elseif (!preg_match('@^[0-9]+$@', $docid)) {
3894
            $docid = $this->getIdFromAlias($docid);
3895
        }
3896
3897
        if (empty($docid)) {
3898
            return false;
3899
        }
3900
3901
        $cacheKey = md5(print_r(func_get_args(), true));
3902
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3903
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3904
        }
3905
3906
        $doc = $this->getDocumentObject('id', $docid);
3907
        if (is_array($doc[$field])) {
3908
            $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...
3909
            $content = $tvs[$field];
3910
        } else {
3911
            $content = $doc[$field];
3912
        }
3913
3914
        $this->tmpCache[__FUNCTION__][$cacheKey] = $content;
3915
3916
        return $content;
3917
    }
3918
3919
    /**
3920
     * Returns the page information as database row, the type of result is
3921
     * defined with the parameter $rowMode
3922
     *
3923
     * @param int $pageid The parent document identifier
3924
     *                    Default: -1 (no result)
3925
     * @param int $active Should we fetch only published and undeleted documents/resources?
3926
     *                     1 = yes, 0 = no
3927
     *                     Default: 1
3928
     * @param string $fields List of fields
3929
     *                       Default: id, pagetitle, description, alias
3930
     * @return boolean|array
3931
     */
3932
    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...
3933
    {
3934
3935
        $cacheKey = md5(print_r(func_get_args(), true));
3936
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3937
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3938
        }
3939
3940
        if ($pageid == 0) {
3941
            return false;
3942
        } else {
3943
            $tblsc = $this->getFullTableName("site_content");
3944
            $tbldg = $this->getFullTableName("document_groups");
3945
            $activeSql = $active == 1 ? "AND sc.published=1 AND sc.deleted=0" : "";
3946
            // modify field names to use sc. table reference
3947
            $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3948
            // get document groups for current user
3949
            if ($docgrp = $this->getUserDocGroups()) {
3950
                $docgrp = implode(",", $docgrp);
3951
            }
3952
            $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
3953
            $result = $this->db->select($fields, "{$tblsc} sc LEFT JOIN {$tbldg} dg on dg.document = sc.id", "(sc.id='{$pageid}' {$activeSql}) AND ({$access})", "", 1);
3954
            $pageInfo = $this->db->getRow($result);
3955
3956
            $this->tmpCache[__FUNCTION__][$cacheKey] = $pageInfo;
3957
3958
            return $pageInfo;
3959
        }
3960
    }
3961
3962
    /**
3963
     * Returns the parent document/resource of the given docid
3964
     *
3965
     * @param int $pid The parent docid. If -1, then fetch the current document/resource's parent
3966
     *                 Default: -1
3967
     * @param int $active Should we fetch only published and undeleted documents/resources?
3968
     *                     1 = yes, 0 = no
3969
     *                     Default: 1
3970
     * @param string $fields List of fields
3971
     *                       Default: id, pagetitle, description, alias
3972
     * @return boolean|array
3973
     */
3974
    public function getParent($pid = -1, $active = 1, $fields = 'id, pagetitle, description, alias, parent')
3975
    {
3976
        if ($pid == -1) {
3977
            $pid = $this->documentObject['parent'];
3978
            return ($pid == 0) ? false : $this->getPageInfo($pid, $active, $fields);
3979
        } else if ($pid == 0) {
3980
            return false;
3981
        } else {
3982
            // first get the child document
3983
            $child = $this->getPageInfo($pid, $active, "parent");
3984
            // now return the child's parent
3985
            $pid = ($child['parent']) ? $child['parent'] : 0;
3986
            return ($pid == 0) ? false : $this->getPageInfo($pid, $active, $fields);
3987
        }
3988
    }
3989
3990
    /**
3991
     * Returns the id of the current snippet.
3992
     *
3993
     * @return int
3994
     */
3995
    public function getSnippetId()
3996
    {
3997
        if ($this->currentSnippet) {
3998
            $tbl = $this->getFullTableName("site_snippets");
3999
            $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...
4000
            if ($snippetId = $this->db->getValue($rs)) {
4001
                return $snippetId;
4002
            }
4003
        }
4004
        return 0;
4005
    }
4006
4007
    /**
4008
     * Returns the name of the current snippet.
4009
     *
4010
     * @return string
4011
     */
4012
    public function getSnippetName()
4013
    {
4014
        return $this->currentSnippet;
4015
    }
4016
4017
    /**
4018
     * Clear the cache of MODX.
4019
     *
4020
     * @param string $type
4021
     * @param bool $report
4022
     * @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...
4023
     */
4024
    public function clearCache($type = '', $report = false)
4025
    {
4026
        $cache_dir = MODX_BASE_PATH . $this->getCacheFolder();
4027
        if (is_array($type)) {
4028
            foreach ($type as $_) {
4029
                $this->clearCache($_, $report);
4030
            }
4031
        } elseif ($type == 'full') {
4032
            $sync = new Cache();
4033
            $sync->setCachepath($cache_dir);
4034
            $sync->setReport($report);
4035
            $sync->emptyCache();
4036
        } elseif (preg_match('@^[1-9][0-9]*$@', $type)) {
4037
            $key = ($this->config['cache_type'] == 2) ? $this->makePageCacheKey($type) : $type;
4038
            $file_name = "docid_" . $key . "_*.pageCache.php";
4039
            $cache_path = $cache_dir . $file_name;
4040
            $files = glob($cache_path);
4041
            $files[] = $cache_dir . "docid_" . $key . ".pageCache.php";
4042
            foreach ($files as $file) {
4043
                if (!is_file($file)) {
4044
                    continue;
4045
                }
4046
                unlink($file);
4047
            }
4048
        } else {
4049
            $files = glob($cache_dir . '*');
4050
            foreach ($files as $file) {
4051
                $name = basename($file);
4052
                if (strpos($name, '.pageCache.php') === false) {
4053
                    continue;
4054
                }
4055
                if (!is_file($file)) {
4056
                    continue;
4057
                }
4058
                unlink($file);
4059
            }
4060
        }
4061
    }
4062
4063
    /**
4064
     * makeUrl
4065
     *
4066
     * @desc Create an URL for the given document identifier. The url prefix and postfix are used, when “friendly_url” is active.
4067
     *
4068
     * @param $id {integer} - The document identifier. @required
4069
     * @param string $alias {string}
4070
     * - The alias name for the document. Default: ''.
4071
     * @param string $args {string}
4072
     * - The paramaters to add to the URL. Default: ''.
4073
     * @param string $scheme {string}
4074
     * - With full as valus, the site url configuration is used. Default: ''.
4075
     * @return mixed|string {string} - Result URL.
4076
     * - Result URL.
4077
     */
4078
    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...
4079
    {
4080
        $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...
4081
        $virtualDir = isset($this->config['virtual_dir']) ? $this->config['virtual_dir'] : '';
4082
        $f_url_prefix = $this->config['friendly_url_prefix'];
4083
        $f_url_suffix = $this->config['friendly_url_suffix'];
4084
4085
        if (!is_numeric($id)) {
4086
            $this->messageQuit("`{$id}` is not numeric and may not be passed to makeUrl()");
4087
        }
4088
4089
        if ($args !== '') {
4090
            // add ? or & to $args if missing
4091
            $args = ltrim($args, '?&');
4092
            $_ = 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...
4093
4094
            if ($_ === false && $this->config['friendly_urls'] == 1) {
4095
                $args = "?{$args}";
4096
            } else {
4097
                $args = "&{$args}";
4098
            }
4099
        }
4100
4101
        if ($id != $this->config['site_start']) {
4102
            if ($this->config['friendly_urls'] == 1 && $alias == '') {
4103
                $alias = $id;
4104
                $alPath = '';
4105
4106
                if ($this->config['friendly_alias_urls'] == 1) {
4107
4108
                    if ($this->config['aliaslistingfolder'] == 1) {
4109
                        $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...
4110
                    } else {
4111
                        $al = $this->aliasListing[$id];
4112
                    }
4113
4114
                    if ($al['isfolder'] === 1 && $this->config['make_folders'] === '1') {
4115
                        $f_url_suffix = '/';
4116
                    }
4117
4118
                    $alPath = !empty ($al['path']) ? $al['path'] . '/' : '';
4119
4120
                    if ($al && $al['alias']) {
4121
                        $alias = $al['alias'];
4122
                    }
4123
4124
                }
4125
4126
                $alias = $alPath . $f_url_prefix . $alias . $f_url_suffix;
4127
                $url = "{$alias}{$args}";
4128
            } else {
4129
                $url = "index.php?id={$id}{$args}";
4130
            }
4131
        } else {
4132
            $url = $args;
4133
        }
4134
4135
        $host = $this->config['base_url'];
4136
4137
        // check if scheme argument has been set
4138
        if ($scheme != '') {
4139
            // for backward compatibility - check if the desired scheme is different than the current scheme
4140
            if (is_numeric($scheme) && $scheme != $_SERVER['HTTPS']) {
4141
                $scheme = ($_SERVER['HTTPS'] ? 'http' : 'https');
4142
            }
4143
4144
            //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...
4145
            $host = $scheme == 'full' ? $this->config['site_url'] : $scheme . '://' . $_SERVER['HTTP_HOST'] . $host;
4146
        }
4147
4148
        //fix strictUrl by Bumkaka
4149
        if ($this->config['seostrict'] == '1') {
4150
            $url = $this->toAlias($url);
4151
        }
4152
4153
        if ($this->config['xhtml_urls']) {
4154
            $url = preg_replace("/&(?!amp;)/", "&amp;", $host . $virtualDir . $url);
4155
        } else {
4156
            $url = $host . $virtualDir . $url;
4157
        }
4158
4159
        $evtOut = $this->invokeEvent('OnMakeDocUrl', array(
4160
            'id' => $id,
4161
            'url' => $url
4162
        ));
4163
4164
        if (is_array($evtOut) && count($evtOut) > 0) {
4165
            $url = array_pop($evtOut);
4166
        }
4167
4168
        return $url;
4169
    }
4170
4171
    /**
4172
     * @param $id
4173
     * @return mixed
4174
     */
4175
    public function getAliasListing($id)
4176
    {
4177
        if (isset($this->aliasListing[$id])) {
4178
            $out = $this->aliasListing[$id];
4179
        } else {
4180
            $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...
4181
            if ($this->db->getRecordCount($q) == '1') {
4182
                $q = $this->db->getRow($q);
4183
                $this->aliasListing[$id] = array(
4184
                    'id' => (int)$q['id'],
4185
                    'alias' => $q['alias'] == '' ? $q['id'] : $q['alias'],
4186
                    'parent' => (int)$q['parent'],
4187
                    'isfolder' => (int)$q['isfolder'],
4188
                );
4189
                if ($this->aliasListing[$id]['parent'] > 0) {
4190
                    //fix alias_path_usage
4191
                    if ($this->config['use_alias_path'] == '1') {
4192
                        //&& $tmp['path'] != '' - fix error slash with epty path
4193
                        $tmp = $this->getAliasListing($this->aliasListing[$id]['parent']);
4194
                        $this->aliasListing[$id]['path'] = $tmp['path'] . ($tmp['alias_visible'] ? (($tmp['parent'] > 0 && $tmp['path'] != '') ? '/' : '') . $tmp['alias'] : '');
4195
                    } else {
4196
                        $this->aliasListing[$id]['path'] = '';
4197
                    }
4198
                }
4199
4200
                $out = $this->aliasListing[$id];
4201
            }
4202
        }
4203
        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...
4204
    }
4205
4206
    /**
4207
     * Returns an entry from the config
4208
     *
4209
     * Note: most code accesses the config array directly and we will continue to support this.
4210
     *
4211
     * @param string $name
4212
     * @return bool|string
4213
     */
4214
    public function getConfig($name = '')
4215
    {
4216
        if (!empty ($this->config[$name])) {
4217
            return $this->config[$name];
4218
        } else {
4219
            return false;
4220
        }
4221
    }
4222
4223
    /**
4224
     * Returns the MODX version information as version, branch, release date and full application name.
4225
     *
4226
     * @param null $data
4227
     * @return array
4228
     */
4229
4230
    public function getVersionData($data = null)
4231
    {
4232
        $out = array();
4233
        if (empty($this->version) || !is_array($this->version)) {
4234
            //include for compatibility modx version < 1.0.10
4235
            include MODX_MANAGER_PATH . "includes/version.inc.php";
4236
            $this->version = array();
4237
            $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...
4238
            $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...
4239
            $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...
4240
            $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...
4241
            $this->version['new_version'] = isset($this->config['newversiontext']) ? $this->config['newversiontext'] : '';
4242
        }
4243
        return (!is_null($data) && is_array($this->version) && isset($this->version[$data])) ? $this->version[$data] : $this->version;
4244
    }
4245
4246
    /**
4247
     * Executes a snippet.
4248
     *
4249
     * @param string $snippetName
4250
     * @param array $params Default: Empty array
4251
     * @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...
4252
     */
4253
    public function runSnippet($snippetName, $params = array())
4254
    {
4255
        if (isset ($this->snippetCache[$snippetName])) {
4256
            $snippet = $this->snippetCache[$snippetName];
4257
            $properties = !empty($this->snippetCache[$snippetName . "Props"]) ? $this->snippetCache[$snippetName . "Props"] : '';
4258
        } else { // not in cache so let's check the db
4259
            $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;";
4260
            $result = $this->db->query($sql);
4261
            if ($this->db->getRecordCount($result) == 1) {
4262
                $row = $this->db->getRow($result);
4263
                $snippet = $this->snippetCache[$snippetName] = $row['snippet'];
4264
                $mergedProperties = array_merge($this->parseProperties($row['properties']), $this->parseProperties($row['sharedproperties']));
4265
                $properties = $this->snippetCache[$snippetName . "Props"] = json_encode($mergedProperties);
4266
            } else {
4267
                $snippet = $this->snippetCache[$snippetName] = "return false;";
4268
                $properties = $this->snippetCache[$snippetName . "Props"] = '';
4269
            }
4270
        }
4271
        // load default params/properties
4272
        $parameters = $this->parseProperties($properties, $snippetName, 'snippet');
4273
        $parameters = array_merge($parameters, $params);
4274
4275
        // run snippet
4276
        return $this->evalSnippet($snippet, $parameters);
4277
    }
4278
4279
    /**
4280
     * Returns the chunk content for the given chunk name
4281
     *
4282
     * @param string $chunkName
4283
     * @return boolean|string
4284
     */
4285
    public function getChunk($chunkName)
4286
    {
4287
        $out = null;
4288
        if (empty($chunkName)) {
4289
            return $out;
4290
        }
4291
        if (isset ($this->chunkCache[$chunkName])) {
4292
            $out = $this->chunkCache[$chunkName];
4293
        } else if (stripos($chunkName, '@FILE') === 0) {
4294
            $out = $this->chunkCache[$chunkName] = $this->atBindFileContent($chunkName);
4295
        } else {
4296
            $where = sprintf("`name`='%s' AND disabled=0", $this->db->escape($chunkName));
4297
            $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...
4298
            if ($this->db->getRecordCount($rs) == 1) {
4299
                $row = $this->db->getRow($rs);
4300
                $out = $this->chunkCache[$chunkName] = $row['snippet'];
4301
            } else {
4302
                $out = $this->chunkCache[$chunkName] = null;
4303
            }
4304
        }
4305
        return $out;
4306
    }
4307
4308
    /**
4309
     * parseText
4310
     * @version 1.0 (2013-10-17)
4311
     *
4312
     * @desc Replaces placeholders in text with required values.
4313
     *
4314
     * @param string $tpl
4315
     * @param array $ph
4316
     * @param string $left
4317
     * @param string $right
4318
     * @param bool $execModifier
4319
     * @return string {string} - Parsed text.
4320
     * - Parsed text.
4321
     * @internal param $chunk {string} - String to parse. - String to parse. @required
4322
     * @internal param $chunkArr {array} - Array of values. Key — placeholder name, value — value. - Array of values. Key — placeholder name, value — value. @required
4323
     * @internal param $prefix {string} - Placeholders prefix. Default: '[+'. - Placeholders prefix. Default: '[+'.
4324
     * @internal param $suffix {string} - Placeholders suffix. Default: '+]'. - Placeholders suffix. Default: '+]'.
4325
     *
4326
     */
4327
    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...
4328
    {
4329
        if (empty($ph) || empty($tpl)) {
4330
            return $tpl;
4331
        }
4332
4333 View Code Duplication
        if ($this->config['enable_at_syntax']) {
4334
            if (stripos($tpl, '<@LITERAL>') !== false) {
4335
                $tpl = $this->escapeLiteralTagsContent($tpl);
4336
            }
4337
        }
4338
4339
        $matches = $this->getTagsFromContent($tpl, $left, $right);
4340
        if (empty($matches)) {
4341
            return $tpl;
4342
        }
4343
4344
        foreach ($matches[1] as $i => $key) {
4345
4346
            if (strpos($key, ':') !== false && $execModifier) {
4347
                list($key, $modifiers) = $this->splitKeyAndFilter($key);
4348
            } else {
4349
                $modifiers = false;
4350
            }
4351
4352
            //          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...
4353
            if (!array_key_exists($key, $ph)) {
4354
                continue;
4355
            } //NULL values must be saved in placeholders, if we got them from database string
4356
4357
            $value = $ph[$key];
4358
4359
            $s = &$matches[0][$i];
4360
            if ($modifiers !== false) {
4361
                if (strpos($modifiers, $left) !== false) {
4362
                    $modifiers = $this->parseText($modifiers, $ph, $left, $right);
4363
                }
4364
                $value = $this->applyFilter($value, $modifiers, $key);
4365
            }
4366 View Code Duplication
            if (strpos($tpl, $s) !== false) {
4367
                $tpl = str_replace($s, $value, $tpl);
4368
            } elseif($this->debug) {
4369
                $this->addLog('parseText parse error', $_SERVER['REQUEST_URI'] . $s, 2);
4370
            }
4371
        }
4372
4373
        return $tpl;
4374
    }
4375
4376
    /**
4377
     * parseChunk
4378
     * @version 1.1 (2013-10-17)
4379
     *
4380
     * @desc Replaces placeholders in a chunk with required values.
4381
     *
4382
     * @param $chunkName {string} - Name of chunk to parse. @required
4383
     * @param $chunkArr {array} - Array of values. Key — placeholder name, value — value. @required
4384
     * @param string $prefix {string}
4385
     * - Placeholders prefix. Default: '{'.
4386
     * @param string $suffix {string}
4387
     * - Placeholders suffix. Default: '}'.
4388
     * @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...
4389
     * - Parsed chunk or false if $chunkArr is not array.
4390
     */
4391
    public function parseChunk($chunkName, $chunkArr, $prefix = '{', $suffix = '}')
4392
    {
4393
        //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...
4394
        if (!is_array($chunkArr)) {
4395
            return false;
4396
        }
4397
4398
        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...
4399
    }
4400
4401
    /**
4402
     * getTpl
4403
     * get template for snippets
4404
     * @param $tpl {string}
4405
     * @return bool|string {string}
4406
     */
4407
    public function getTpl($tpl)
4408
    {
4409
        $template = $tpl;
4410
        if (preg_match("/^@([^:\s]+)[:\s]+(.+)$/s", trim($tpl), $match)) {
4411
            $command = strtoupper($match[1]);
4412
            $template = $match[2];
4413
        }
4414
        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...
4415
            case 'CODE':
4416
                break;
4417
            case 'FILE':
4418
                $template = file_get_contents(MODX_BASE_PATH . $template);
4419
                break;
4420
            case 'CHUNK':
4421
                $template = $this->getChunk($template);
4422
                break;
4423
            case 'DOCUMENT':
4424
                $doc = $this->getDocument($template, 'content', 'all');
4425
                $template = $doc['content'];
4426
                break;
4427
            case 'SELECT':
4428
                $this->db->getValue($this->db->query("SELECT {$template}"));
4429
                break;
4430
            default:
4431
                if (!($template = $this->getChunk($tpl))) {
4432
                    $template = $tpl;
4433
                }
4434
        }
4435
        return $template;
4436
    }
4437
4438
    /**
4439
     * Returns the timestamp in the date format defined in $this->config['datetime_format']
4440
     *
4441
     * @param int $timestamp Default: 0
4442
     * @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.
4443
     * @return string
4444
     */
4445
    public function toDateFormat($timestamp = 0, $mode = '')
4446
    {
4447
        $timestamp = trim($timestamp);
4448
        if ($mode !== 'formatOnly' && empty($timestamp)) {
4449
            return '-';
4450
        }
4451
        $timestamp = (int)$timestamp;
4452
4453
        switch ($this->config['datetime_format']) {
4454
            case 'YYYY/mm/dd':
4455
                $dateFormat = '%Y/%m/%d';
4456
                break;
4457
            case 'dd-mm-YYYY':
4458
                $dateFormat = '%d-%m-%Y';
4459
                break;
4460
            case 'mm/dd/YYYY':
4461
                $dateFormat = '%m/%d/%Y';
4462
                break;
4463
            /*
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...
4464
            case 'dd-mmm-YYYY':
4465
                $dateFormat = '%e-%b-%Y';
4466
                break;
4467
            */
4468
        }
4469
4470
        if (empty($mode)) {
4471
            $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...
4472
        } elseif ($mode == 'dateOnly') {
4473
            $strTime = strftime($dateFormat, $timestamp);
4474
        } elseif ($mode == 'formatOnly') {
4475
            $strTime = $dateFormat;
4476
        }
4477
        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...
4478
    }
4479
4480
    /**
4481
     * Make a timestamp from a string corresponding to the format in $this->config['datetime_format']
4482
     *
4483
     * @param string $str
4484
     * @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...
4485
     */
4486
    public function toTimeStamp($str)
4487
    {
4488
        $str = trim($str);
4489
        if (empty($str)) {
4490
            return '';
4491
        }
4492
4493
        switch ($this->config['datetime_format']) {
4494 View Code Duplication
            case 'YYYY/mm/dd':
4495
                if (!preg_match('/^[0-9]{4}\/[0-9]{2}\/[0-9]{2}[0-9 :]*$/', $str)) {
4496
                    return '';
4497
                }
4498
                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...
4499
                break;
4500 View Code Duplication
            case 'dd-mm-YYYY':
4501
                if (!preg_match('/^[0-9]{2}-[0-9]{2}-[0-9]{4}[0-9 :]*$/', $str)) {
4502
                    return '';
4503
                }
4504
                list ($d, $m, $Y, $H, $M, $S) = sscanf($str, '%2d-%2d-%4d %2d:%2d:%2d');
4505
                break;
4506 View Code Duplication
            case 'mm/dd/YYYY':
4507
                if (!preg_match('/^[0-9]{2}\/[0-9]{2}\/[0-9]{4}[0-9 :]*$/', $str)) {
4508
                    return '';
4509
                }
4510
                list ($m, $d, $Y, $H, $M, $S) = sscanf($str, '%2d/%2d/%4d %2d:%2d:%2d');
4511
                break;
4512
            /*
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...
4513
            case 'dd-mmm-YYYY':
4514
                if (!preg_match('/^[0-9]{2}-[0-9a-z]+-[0-9]{4}[0-9 :]*$/i', $str)) {return '';}
4515
                list ($m, $d, $Y, $H, $M, $S) = sscanf($str, '%2d-%3s-%4d %2d:%2d:%2d');
4516
                break;
4517
            */
4518
        }
4519
        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...
4520
            $H = 0;
4521
            $M = 0;
4522
            $S = 0;
4523
        }
4524
        $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...
4525
        $timeStamp = (int)$timeStamp;
4526
        return $timeStamp;
4527
    }
4528
4529
    /**
4530
     * Get the TVs of a document's children. Returns an array where each element represents one child doc.
4531
     *
4532
     * Ignores deleted children. Gets all children - there is no where clause available.
4533
     *
4534
     * @param int $parentid The parent docid
4535
     *                 Default: 0 (site root)
4536
     * @param array $tvidnames . Which TVs to fetch - Can relate to the TV ids in the db (array elements should be numeric only)
4537
     *                                               or the TV names (array elements should be names only)
4538
     *                      Default: Empty array
4539
     * @param int $published Whether published or unpublished documents are in the result
4540
     *                      Default: 1
4541
     * @param string $docsort How to sort the result array (field)
4542
     *                      Default: menuindex
4543
     * @param ASC|string $docsortdir How to sort the result array (direction)
4544
     *                      Default: ASC
4545
     * @param string $tvfields Fields to fetch from site_tmplvars, default '*'
4546
     *                      Default: *
4547
     * @param string $tvsort How to sort each element of the result array i.e. how to sort the TVs (field)
4548
     *                      Default: rank
4549
     * @param string $tvsortdir How to sort each element of the result array i.e. how to sort the TVs (direction)
4550
     *                      Default: ASC
4551
     * @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...
4552
     */
4553
    public function getDocumentChildrenTVars($parentid = 0, $tvidnames = array(), $published = 1, $docsort = "menuindex", $docsortdir = "ASC", $tvfields = "*", $tvsort = "rank", $tvsortdir = "ASC")
4554
    {
4555
        $docs = $this->getDocumentChildren($parentid, $published, 0, '*', '', $docsort, $docsortdir);
4556
        if (!$docs) {
4557
            return false;
4558
        } else {
4559
            $result = array();
4560
            // get user defined template variables
4561
            if ($tvfields) {
4562
                $_ = 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...
4563
                foreach ($_ as $i => $v) {
4564
                    if ($v === 'value') {
4565
                        unset($_[$i]);
4566
                    } else {
4567
                        $_[$i] = 'tv.' . $v;
4568
                    }
4569
                }
4570
                $fields = implode(',', $_);
4571
            } else {
4572
                $fields = "tv.*";
4573
            }
4574
4575
            if ($tvsort != '') {
4576
                $tvsort = 'tv.' . implode(',tv.', array_filter(array_map('trim', explode(',', $tvsort))));
4577
            }
4578 View Code Duplication
            if ($tvidnames == "*") {
4579
                $query = "tv.id<>0";
4580
            } else {
4581
                $query = (is_numeric($tvidnames[0]) ? "tv.id" : "tv.name") . " IN ('" . implode("','", $tvidnames) . "')";
4582
            }
4583
4584
            $this->getUserDocGroups();
4585
4586
            foreach ($docs as $doc) {
4587
4588
                $docid = $doc['id'];
4589
4590
                $rs = $this->db->select("{$fields}, IF(tvc.value!='',tvc.value,tv.default_text) as value ", "[+prefix+]site_tmplvars tv
4591
                        INNER JOIN [+prefix+]site_tmplvar_templates tvtpl ON tvtpl.tmplvarid = tv.id
4592
                        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}" : ""));
4593
                $tvs = $this->db->makeArray($rs);
4594
4595
                // get default/built-in template variables
4596
                ksort($doc);
4597
                foreach ($doc as $key => $value) {
4598
                    if ($tvidnames == '*' || in_array($key, $tvidnames)) {
4599
                        $tvs[] = array('name' => $key, 'value' => $value);
4600
                    }
4601
                }
4602
                if (is_array($tvs) && count($tvs)) {
4603
                    $result[] = $tvs;
4604
                }
4605
            }
4606
            return $result;
4607
        }
4608
    }
4609
4610
    /**
4611
     * getDocumentChildrenTVarOutput
4612
     * @version 1.1 (2014-02-19)
4613
     *
4614
     * @desc Returns an array where each element represents one child doc and contains the result from getTemplateVarOutput().
4615
     *
4616
     * @param int $parentid {integer}
4617
     * - Id of parent document. Default: 0 (site root).
4618
     * @param array $tvidnames {array; '*'}
4619
     * - Which TVs to fetch. In the form expected by getTemplateVarOutput(). Default: array().
4620
     * @param int $published {0; 1; 'all'}
4621
     * - 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.
4622
     * @param string $sortBy {string}
4623
     * - How to sort the result array (field). Default: 'menuindex'.
4624
     * @param string $sortDir {'ASC'; 'DESC'}
4625
     * - How to sort the result array (direction). Default: 'ASC'.
4626
     * @param string $where {string}
4627
     * - SQL WHERE condition (use only document fields, not TV). Default: ''.
4628
     * @param string $resultKey {string; false}
4629
     * - Field, which values are keys into result array. Use the “false”, that result array keys just will be numbered. Default: 'id'.
4630
     * @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...
4631
     * - Result array, or false.
4632
     */
4633
    public function getDocumentChildrenTVarOutput($parentid = 0, $tvidnames = array(), $published = 1, $sortBy = 'menuindex', $sortDir = 'ASC', $where = '', $resultKey = 'id')
4634
    {
4635
        $docs = $this->getDocumentChildren($parentid, $published, 0, 'id', $where, $sortBy, $sortDir);
4636
4637
        if (!$docs) {
4638
            return false;
4639
        } else {
4640
            $result = array();
4641
4642
            $unsetResultKey = false;
4643
4644
            if ($resultKey !== false) {
4645
                if (is_array($tvidnames)) {
4646
                    if (count($tvidnames) != 0 && !in_array($resultKey, $tvidnames)) {
4647
                        $tvidnames[] = $resultKey;
4648
                        $unsetResultKey = true;
4649
                    }
4650
                } else if ($tvidnames != '*' && $tvidnames != $resultKey) {
4651
                    $tvidnames = array($tvidnames, $resultKey);
4652
                    $unsetResultKey = true;
4653
                }
4654
            }
4655
4656
            for ($i = 0; $i < count($docs); $i++) {
4657
                $tvs = $this->getTemplateVarOutput($tvidnames, $docs[$i]['id'], $published);
4658
4659
                if ($tvs) {
4660
                    if ($resultKey !== false && array_key_exists($resultKey, $tvs)) {
4661
                        $result[$tvs[$resultKey]] = $tvs;
4662
4663
                        if ($unsetResultKey) {
4664
                            unset($result[$tvs[$resultKey]][$resultKey]);
4665
                        }
4666
                    } else {
4667
                        $result[] = $tvs;
4668
                    }
4669
                }
4670
            }
4671
4672
            return $result;
4673
        }
4674
    }
4675
4676
    /**
4677
     * Modified by Raymond for TV - Orig Modified by Apodigm - DocVars
4678
     * Returns a single site_content field or TV record from the db.
4679
     *
4680
     * If a site content field the result is an associative array of 'name' and 'value'.
4681
     *
4682
     * If a TV the result is an array representing a db row including the fields specified in $fields.
4683
     *
4684
     * @param string $idname Can be a TV id or name
4685
     * @param string $fields Fields to fetch from site_tmplvars. Default: *
4686
     * @param string|type $docid Docid. Defaults to empty string which indicates the current document.
4687
     * @param int $published Whether published or unpublished documents are in the result
4688
     *                        Default: 1
4689
     * @return bool
4690
     */
4691 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...
4692
    {
4693
        if ($idname == "") {
4694
            return false;
4695
        } else {
4696
            $result = $this->getTemplateVars(array($idname), $fields, $docid, $published, "", ""); //remove sorting for speed
4697
            return ($result != false) ? $result[0] : false;
4698
        }
4699
    }
4700
4701
    /**
4702
     * getTemplateVars
4703
     * @version 1.0.1 (2014-02-19)
4704
     *
4705
     * @desc Returns an array of site_content field fields and/or TV records from the db.
4706
     * Elements representing a site content field consist of an associative array of 'name' and 'value'.
4707
     * Elements representing a TV consist of an array representing a db row including the fields specified in $fields.
4708
     *
4709
     * @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
4710
     * @param $fields {comma separated string; '*'} - Fields names in the TV table of MODx database. Default: '*'
4711
     * @param $docid {integer; ''} - Id of a document to get. Default: an empty string which indicates the current document.
4712
     * @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.
4713
     * @param $sort {comma separated string} - Fields of the TV table to sort by. Default: 'rank'.
4714
     * @param $dir {'ASC'; 'DESC'} - How to sort the result array (direction). Default: 'ASC'.
4715
     *
4716
     * @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...
4717
     */
4718
    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...
4719
    {
4720
        $cacheKey = md5(print_r(func_get_args(), true));
4721
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
4722
            return $this->tmpCache[__FUNCTION__][$cacheKey];
4723
        }
4724
4725
        if (($idnames != '*' && !is_array($idnames)) || empty($idnames) ) {
4726
            return false;
4727
        } else {
4728
4729
            // get document record
4730
            if ($docid == '') {
4731
                $docid = $this->documentIdentifier;
4732
                $docRow = $this->documentObject;
4733
            } else {
4734
                $docRow = $this->getDocument($docid, '*', $published);
4735
4736
                if (!$docRow) {
4737
                    $this->tmpCache[__FUNCTION__][$cacheKey] = false;
4738
                    return false;
4739
                }
4740
            }
4741
4742
            // get user defined template variables
4743
            $fields = ($fields == '') ? 'tv.*' : 'tv.' . implode(',tv.', array_filter(array_map('trim', explode(',', $fields))));
4744
            $sort = ($sort == '') ? '' : 'tv.' . implode(',tv.', array_filter(array_map('trim', explode(',', $sort))));
4745
4746 View Code Duplication
            if ($idnames == '*') {
4747
                $query = 'tv.id<>0';
4748
            } else {
4749
                $query = (is_numeric($idnames[0]) ? 'tv.id' : 'tv.name') . " IN ('" . implode("','", $idnames) . "')";
4750
            }
4751
4752
            $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...
4753
                    INNER JOIN " . $this->getFullTableName('site_tmplvar_templates') . " tvtpl ON tvtpl.tmplvarid = tv.id
4754
                    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}" : ""));
4755
4756
            $result = $this->db->makeArray($rs);
4757
4758
            // get default/built-in template variables
4759
            if(is_array($docRow)){
4760
                ksort($docRow);
4761
4762
                foreach ($docRow as $key => $value) {
4763
                    if ($idnames == '*' || in_array($key, $idnames)) {
4764
                        array_push($result, array(
4765
                            'name' => $key,
4766
                            'value' => $value
4767
                        ));
4768
                    }
4769
                }
4770
            }
4771
4772
            $this->tmpCache[__FUNCTION__][$cacheKey] = $result;
4773
4774
            return $result;
4775
        }
4776
    }
4777
4778
    /**
4779
     * getTemplateVarOutput
4780
     * @version 1.0.1 (2014-02-19)
4781
     *
4782
     * @desc Returns an associative array containing TV rendered output values.
4783
     *
4784
     * @param array $idnames {array; '*'}
4785
     * - 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
4786
     * @param string $docid {integer; ''}
4787
     * - Id of a document to get. Default: an empty string which indicates the current document.
4788
     * @param int $published {0; 1; 'all'}
4789
     * - 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.
4790
     * @param string $sep {string}
4791
     * - Separator that is used while concatenating in getTVDisplayFormat(). Default: ''.
4792
     * @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...
4793
     * - Result array, or false.
4794
     */
4795
    public function getTemplateVarOutput($idnames = array(), $docid = '', $published = 1, $sep = '')
4796
    {
4797
        if (is_array($idnames) && empty($idnames) ) {
4798
            return false;
4799
        } else {
4800
            $output = array();
4801
            $vars = ($idnames == '*' || is_array($idnames)) ? $idnames : array($idnames);
4802
4803
            $docid = (int)$docid > 0 ? (int)$docid : $this->documentIdentifier;
4804
            // remove sort for speed
4805
            $result = $this->getTemplateVars($vars, '*', $docid, $published, '', '');
4806
4807
            if ($result == false) {
4808
                return false;
4809
            } else {
4810
                for ($i = 0; $i < count($result); $i++) {
4811
                    $row = $result[$i];
4812
4813
                    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...
4814
                        $output[$row['name']] = $row['value'];
4815
                    } else {
4816
                        $output[$row['name']] = getTVDisplayFormat($row['name'], $row['value'], $row['display'], $row['display_params'], $row['type'], $docid, $sep);
4817
                    }
4818
                }
4819
4820
                return $output;
4821
            }
4822
        }
4823
    }
4824
4825
    /**
4826
     * Returns the full table name based on db settings
4827
     *
4828
     * @param string $tbl Table name
4829
     * @return string Table name with prefix
4830
     */
4831
    public function getFullTableName($tbl)
4832
    {
4833
        return $this->db->config['dbase'] . ".`" . $this->db->config['table_prefix'] . $tbl . "`";
4834
    }
4835
4836
    /**
4837
     * Returns the placeholder value
4838
     *
4839
     * @param string $name Placeholder name
4840
     * @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...
4841
     */
4842
    public function getPlaceholder($name)
4843
    {
4844
        return isset($this->placeholders[$name]) ? $this->placeholders[$name] : null;
4845
    }
4846
4847
    /**
4848
     * Sets a value for a placeholder
4849
     *
4850
     * @param string $name The name of the placeholder
4851
     * @param string $value The value of the placeholder
4852
     */
4853
    public function setPlaceholder($name, $value)
4854
    {
4855
        $this->placeholders[$name] = $value;
4856
    }
4857
4858
    /**
4859
     * Set placeholders en masse via an array or object.
4860
     *
4861
     * @param object|array $subject
4862
     * @param string $prefix
4863
     */
4864
    public function toPlaceholders($subject, $prefix = '')
4865
    {
4866
        if (is_object($subject)) {
4867
            $subject = get_object_vars($subject);
4868
        }
4869
        if (is_array($subject)) {
4870
            foreach ($subject as $key => $value) {
4871
                $this->toPlaceholder($key, $value, $prefix);
4872
            }
4873
        }
4874
    }
4875
4876
    /**
4877
     * For use by toPlaceholders(); For setting an array or object element as placeholder.
4878
     *
4879
     * @param string $key
4880
     * @param object|array $value
4881
     * @param string $prefix
4882
     */
4883
    public function toPlaceholder($key, $value, $prefix = '')
4884
    {
4885
        if (is_array($value) || is_object($value)) {
4886
            $this->toPlaceholders($value, "{$prefix}{$key}.");
4887
        } else {
4888
            $this->setPlaceholder("{$prefix}{$key}", $value);
4889
        }
4890
    }
4891
4892
    /**
4893
     * Returns the manager relative URL/path with respect to the site root.
4894
     *
4895
     * @global string $base_url
4896
     * @return string The complete URL to the manager folder
4897
     */
4898
    public function getManagerPath()
4899
    {
4900
        return MODX_MANAGER_URL;
4901
    }
4902
4903
    /**
4904
     * Returns the cache relative URL/path with respect to the site root.
4905
     *
4906
     * @global string $base_url
4907
     * @return string The complete URL to the cache folder
4908
     */
4909
    public function getCachePath()
4910
    {
4911
        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...
4912
        $pth = $base_url . $this->getCacheFolder();
4913
        return $pth;
4914
    }
4915
4916
    /**
4917
     * Sends a message to a user's message box.
4918
     *
4919
     * @param string $type Type of the message
4920
     * @param string $to The recipient of the message
4921
     * @param string $from The sender of the message
4922
     * @param string $subject The subject of the message
4923
     * @param string $msg The message body
4924
     * @param int $private Whether it is a private message, or not
4925
     *                     Default : 0
4926
     */
4927
    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...
4928
    {
4929
        $private = ($private) ? 1 : 0;
4930 View Code Duplication
        if (!is_numeric($to)) {
4931
            // Query for the To ID
4932
            $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...
4933
            $to = $this->db->getValue($rs);
4934
        }
4935 View Code Duplication
        if (!is_numeric($from)) {
4936
            // Query for the From ID
4937
            $rs = $this->db->select('id', $this->getFullTableName("manager_users"), "username='{$from}'");
4938
            $from = $this->db->getValue($rs);
4939
        }
4940
        // insert a new message into user_messages
4941
        $this->db->insert(array(
4942
            'type' => $type,
4943
            'subject' => $subject,
4944
            'message' => $msg,
4945
            'sender' => $from,
4946
            'recipient' => $to,
4947
            'private' => $private,
4948
            'postdate' => $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'],
4949
            'messageread' => 0,
4950
        ), $this->getFullTableName('user_messages'));
4951
    }
4952
4953
    /**
4954
     * Returns current user id.
4955
     *
4956
     * @param string $context . Default is an empty string which indicates the method should automatically pick 'web (frontend) or 'mgr' (backend)
4957
     * @return string
4958
     */
4959 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...
4960
    {
4961
        $out = false;
4962
4963
        if (!empty($context)) {
4964
            if (is_scalar($context) && isset($_SESSION[$context . 'Validated'])) {
4965
                $out = $_SESSION[$context . 'InternalKey'];
4966
            }
4967
        } else {
4968
            switch (true) {
4969
                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...
4970
                    $out = $_SESSION['webInternalKey'];
4971
                    break;
4972
                }
4973
                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...
4974
                    $out = $_SESSION['mgrInternalKey'];
4975
                    break;
4976
                }
4977
            }
4978
        }
4979
        return $out;
4980
    }
4981
4982
    /**
4983
     * Returns current user name
4984
     *
4985
     * @param string $context . Default is an empty string which indicates the method should automatically pick 'web (frontend) or 'mgr' (backend)
4986
     * @return string
4987
     */
4988 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...
4989
    {
4990
        $out = false;
4991
4992
        if (!empty($context)) {
4993
            if (is_scalar($context) && isset($_SESSION[$context . 'Validated'])) {
4994
                $out = $_SESSION[$context . 'Shortname'];
4995
            }
4996
        } else {
4997
            switch (true) {
4998
                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...
4999
                    $out = $_SESSION['webShortname'];
5000
                    break;
5001
                }
5002
                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...
5003
                    $out = $_SESSION['mgrShortname'];
5004
                    break;
5005
                }
5006
            }
5007
        }
5008
        return $out;
5009
    }
5010
5011
    /**
5012
     * Returns current login user type - web or manager
5013
     *
5014
     * @return string
5015
     */
5016
    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...
5017
    {
5018
        if ($this->isFrontend() && isset ($_SESSION['webValidated'])) {
5019
            return 'web';
5020
        } elseif ($this->isBackend() && isset ($_SESSION['mgrValidated'])) {
5021
            return 'manager';
5022
        } else {
5023
            return '';
5024
        }
5025
    }
5026
5027
    /**
5028
     * Returns a user info record for the given manager user
5029
     *
5030
     * @param int $uid
5031
     * @return boolean|string
5032
     */
5033
    public function getUserInfo($uid)
5034
    {
5035
        if (isset($this->tmpCache[__FUNCTION__][$uid])) {
5036
            return $this->tmpCache[__FUNCTION__][$uid];
5037
        }
5038
5039
        $from = '[+prefix+]manager_users mu INNER JOIN [+prefix+]user_attributes mua ON mua.internalkey=mu.id';
5040
        $where = sprintf("mu.id='%s'", $this->db->escape($uid));
5041
        $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...
5042
5043
        if (!$this->db->getRecordCount($rs)) {
5044
            return $this->tmpCache[__FUNCTION__][$uid] = false;
5045
        }
5046
5047
        $row = $this->db->getRow($rs);
5048 View Code Duplication
        if (!isset($row['usertype']) || !$row['usertype']) {
5049
            $row['usertype'] = 'manager';
5050
        }
5051
5052
        $this->tmpCache[__FUNCTION__][$uid] = $row;
5053
5054
        return $row;
5055
    }
5056
5057
    /**
5058
     * Returns a record for the web user
5059
     *
5060
     * @param int $uid
5061
     * @return boolean|string
5062
     */
5063
    public function getWebUserInfo($uid)
5064
    {
5065
        $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...
5066
                INNER JOIN " . $this->getFullTableName("web_user_attributes") . " wua ON wua.internalkey=wu.id", "wu.id='{$uid}'");
5067
        if ($row = $this->db->getRow($rs)) {
5068 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...
5069
                $row["usertype"] = "web";
5070
            }
5071
            return $row;
5072
        }
5073
    }
5074
5075
    /**
5076
     * Returns an array of document groups that current user is assigned to.
5077
     * This function will first return the web user doc groups when running from
5078
     * frontend otherwise it will return manager user's docgroup.
5079
     *
5080
     * @param boolean $resolveIds Set to true to return the document group names
5081
     *                            Default: false
5082
     * @return string|array
5083
     */
5084
    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...
5085
    {
5086
        if ($this->isFrontend() && isset($_SESSION['webDocgroups']) && isset($_SESSION['webValidated'])) {
5087
            $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...
5088
            $dgn = isset($_SESSION['webDocgrpNames']) ? $_SESSION['webDocgrpNames'] : false;
5089
        } else if ($this->isBackend() && isset($_SESSION['mgrDocgroups']) && isset($_SESSION['mgrValidated'])) {
5090
            $dg = $_SESSION['mgrDocgroups'];
5091
            $dgn = isset($_SESSION['mgrDocgrpNames']) ? $_SESSION['mgrDocgrpNames'] : false;
5092
        } else {
5093
            $dg = '';
5094
        }
5095
        if (!$resolveIds) {
5096
            return $dg;
5097
        } else if (is_array($dgn)) {
5098
            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...
5099
        } else if (is_array($dg)) {
5100
            // resolve ids to names
5101
            $dgn = array();
5102
            $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...
5103
            while ($row = $this->db->getRow($ds)) {
5104
                $dgn[] = $row['name'];
5105
            }
5106
            // cache docgroup names to session
5107
            if ($this->isFrontend()) {
5108
                $_SESSION['webDocgrpNames'] = $dgn;
5109
            } else {
5110
                $_SESSION['mgrDocgrpNames'] = $dgn;
5111
            }
5112
            return $dgn;
5113
        }
5114
    }
5115
5116
    /**
5117
     * Change current web user's password
5118
     *
5119
     * @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...
5120
     * @param string $oldPwd
5121
     * @param string $newPwd
5122
     * @return string|boolean Returns true if successful, oterhwise return error
5123
     *                        message
5124
     */
5125
    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...
5126
    {
5127
        $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...
5128
        if ($_SESSION["webValidated"] == 1) {
5129
            $tbl = $this->getFullTableName("web_users");
5130
            $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...
5131
            if ($row = $this->db->getRow($ds)) {
5132
                if ($row["password"] == md5($oldPwd)) {
5133
                    if (strlen($newPwd) < 6) {
5134
                        return "Password is too short!";
5135
                    } elseif ($newPwd == "") {
5136
                        return "You didn't specify a password for this user!";
5137
                    } else {
5138
                        $this->db->update(array(
5139
                            'password' => $this->db->escape($newPwd),
5140
                        ), $tbl, "id='" . $this->getLoginUserID() . "'");
5141
                        // invoke OnWebChangePassword event
5142
                        $this->invokeEvent("OnWebChangePassword", array(
5143
                            "userid" => $row["id"],
5144
                            "username" => $row["username"],
5145
                            "userpassword" => $newPwd
5146
                        ));
5147
                        return true;
5148
                    }
5149
                } else {
5150
                    return "Incorrect password.";
5151
                }
5152
            }
5153
        }
5154
        return $rt;
5155
    }
5156
5157
    /**
5158
     * Returns true if the current web user is a member the specified groups
5159
     *
5160
     * @param array $groupNames
5161
     * @return boolean
5162
     */
5163
    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...
5164
    {
5165
        if (!is_array($groupNames)) {
5166
            return false;
5167
        }
5168
        // check cache
5169
        $grpNames = isset ($_SESSION['webUserGroupNames']) ? $_SESSION['webUserGroupNames'] : false;
5170
        if (!is_array($grpNames)) {
5171
            $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...
5172
                    INNER JOIN " . $this->getFullTableName("web_groups") . " wg ON wg.webgroup=wgn.id AND wg.webuser='" . $this->getLoginUserID() . "'");
5173
            $grpNames = $this->db->getColumn("name", $rs);
5174
            // save to cache
5175
            $_SESSION['webUserGroupNames'] = $grpNames;
5176
        }
5177
        foreach ($groupNames as $k => $v) {
5178
            if (in_array(trim($v), $grpNames)) {
5179
                return true;
5180
            }
5181
        }
5182
        return false;
5183
    }
5184
5185
    /**
5186
     * Registers Client-side CSS scripts - these scripts are loaded at inside
5187
     * the <head> tag
5188
     *
5189
     * @param string $src
5190
     * @param string $media Default: Empty string
5191
     * @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...
5192
     */
5193
    public function regClientCSS($src, $media = '')
5194
    {
5195
        if (empty($src) || isset ($this->loadedjscripts[$src])) {
5196
            return '';
5197
        }
5198
        $nextpos = max(array_merge(array(0), array_keys($this->sjscripts))) + 1;
5199
        $this->loadedjscripts[$src]['startup'] = true;
5200
        $this->loadedjscripts[$src]['version'] = '0';
5201
        $this->loadedjscripts[$src]['pos'] = $nextpos;
5202
        if (strpos(strtolower($src), "<style") !== false || strpos(strtolower($src), "<link") !== false) {
5203
            $this->sjscripts[$nextpos] = $src;
5204
        } else {
5205
            $this->sjscripts[$nextpos] = "\t" . '<link rel="stylesheet" type="text/css" href="' . $src . '" ' . ($media ? 'media="' . $media . '" ' : '') . '/>';
5206
        }
5207
    }
5208
5209
    /**
5210
     * Registers Startup Client-side JavaScript - these scripts are loaded at inside the <head> tag
5211
     *
5212
     * @param string $src
5213
     * @param array $options Default: 'name'=>'', 'version'=>'0', 'plaintext'=>false
5214
     */
5215
    public function regClientStartupScript($src, $options = array('name' => '', 'version' => '0', 'plaintext' => false))
5216
    {
5217
        $this->regClientScript($src, $options, true);
5218
    }
5219
5220
    /**
5221
     * Registers Client-side JavaScript these scripts are loaded at the end of the page unless $startup is true
5222
     *
5223
     * @param string $src
5224
     * @param array $options Default: 'name'=>'', 'version'=>'0', 'plaintext'=>false
5225
     * @param boolean $startup Default: false
5226
     * @return string
5227
     */
5228
    public function regClientScript($src, $options = array('name' => '', 'version' => '0', 'plaintext' => false), $startup = false)
5229
    {
5230
        if (empty($src)) {
5231
            return '';
5232
        } // nothing to register
5233
        if (!is_array($options)) {
5234
            if (is_bool($options))  // backward compatibility with old plaintext parameter
5235
            {
5236
                $options = array('plaintext' => $options);
5237
            } elseif (is_string($options)) // Also allow script name as 2nd param
5238
            {
5239
                $options = array('name' => $options);
5240
            } else {
5241
                $options = array();
5242
            }
5243
        }
5244
        $name = isset($options['name']) ? strtolower($options['name']) : '';
5245
        $version = isset($options['version']) ? $options['version'] : '0';
5246
        $plaintext = isset($options['plaintext']) ? $options['plaintext'] : false;
5247
        $key = !empty($name) ? $name : $src;
5248
        unset($overwritepos); // probably unnecessary--just making sure
5249
5250
        $useThisVer = true;
5251
        if (isset($this->loadedjscripts[$key])) { // a matching script was found
5252
            // if existing script is a startup script, make sure the candidate is also a startup script
5253
            if ($this->loadedjscripts[$key]['startup']) {
5254
                $startup = true;
5255
            }
5256
5257
            if (empty($name)) {
5258
                $useThisVer = false; // if the match was based on identical source code, no need to replace the old one
5259
            } else {
5260
                $useThisVer = version_compare($this->loadedjscripts[$key]['version'], $version, '<');
5261
            }
5262
5263
            if ($useThisVer) {
5264
                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...
5265
                    // remove old script from the bottom of the page (new one will be at the top)
5266
                    unset($this->jscripts[$this->loadedjscripts[$key]['pos']]);
5267
                } else {
5268
                    // overwrite the old script (the position may be important for dependent scripts)
5269
                    $overwritepos = $this->loadedjscripts[$key]['pos'];
5270
                }
5271
            } else { // Use the original version
5272
                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...
5273
                    // need to move the exisiting script to the head
5274
                    $version = $this->loadedjscripts[$key][$version];
5275
                    $src = $this->jscripts[$this->loadedjscripts[$key]['pos']];
5276
                    unset($this->jscripts[$this->loadedjscripts[$key]['pos']]);
5277
                } else {
5278
                    return ''; // the script is already in the right place
5279
                }
5280
            }
5281
        }
5282
5283
        if ($useThisVer && $plaintext != true && (strpos(strtolower($src), "<script") === false)) {
5284
            $src = "\t" . '<script type="text/javascript" src="' . $src . '"></script>';
5285
        }
5286
        if ($startup) {
5287
            $pos = isset($overwritepos) ? $overwritepos : max(array_merge(array(0), array_keys($this->sjscripts))) + 1;
5288
            $this->sjscripts[$pos] = $src;
5289
        } else {
5290
            $pos = isset($overwritepos) ? $overwritepos : max(array_merge(array(0), array_keys($this->jscripts))) + 1;
5291
            $this->jscripts[$pos] = $src;
5292
        }
5293
        $this->loadedjscripts[$key]['version'] = $version;
5294
        $this->loadedjscripts[$key]['startup'] = $startup;
5295
        $this->loadedjscripts[$key]['pos'] = $pos;
5296
        return '';
5297
    }
5298
5299
    /**
5300
     * Returns all registered JavaScripts
5301
     *
5302
     * @return string
5303
     */
5304
    public function regClientStartupHTMLBlock($html)
5305
    {
5306
        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...
5307
    }
5308
5309
    /**
5310
     * Returns all registered startup scripts
5311
     *
5312
     * @return string
5313
     */
5314
    public function regClientHTMLBlock($html)
5315
    {
5316
        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...
5317
    }
5318
5319
    /**
5320
     * Remove unwanted html tags and snippet, settings and tags
5321
     *
5322
     * @param string $html
5323
     * @param string $allowed Default: Empty string
5324
     * @return string
5325
     */
5326
    public function stripTags($html, $allowed = "")
5327
    {
5328
        $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...
5329
        $t = preg_replace('~\[\*(.*?)\*\]~', "", $t); //tv
5330
        $t = preg_replace('~\[\[(.*?)\]\]~', "", $t); //snippet
5331
        $t = preg_replace('~\[\!(.*?)\!\]~', "", $t); //snippet
5332
        $t = preg_replace('~\[\((.*?)\)\]~', "", $t); //settings
5333
        $t = preg_replace('~\[\+(.*?)\+\]~', "", $t); //placeholders
5334
        $t = preg_replace('~{{(.*?)}}~', "", $t); //chunks
5335
        return $t;
5336
    }
5337
5338
    /**
5339
     * Add an event listener to a plugin - only for use within the current execution cycle
5340
     *
5341
     * @param string $evtName
5342
     * @param string $pluginName
5343
     * @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...
5344
     */
5345
    public function addEventListener($evtName, $pluginName)
5346
    {
5347
        if (!$evtName || !$pluginName) {
5348
            return false;
5349
        }
5350
        if (!array_key_exists($evtName, $this->pluginEvent)) {
5351
            $this->pluginEvent[$evtName] = array();
5352
        }
5353
        return array_push($this->pluginEvent[$evtName], $pluginName); // return array count
5354
    }
5355
5356
    /**
5357
     * Remove event listener - only for use within the current execution cycle
5358
     *
5359
     * @param string $evtName
5360
     * @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...
5361
     */
5362
    public function removeEventListener($evtName)
5363
    {
5364
        if (!$evtName) {
5365
            return false;
5366
        }
5367
        unset ($this->pluginEvent[$evtName]);
5368
    }
5369
5370
    /**
5371
     * Remove all event listeners - only for use within the current execution cycle
5372
     */
5373
    public function removeAllEventListener()
5374
    {
5375
        unset ($this->pluginEvent);
5376
        $this->pluginEvent = array();
5377
    }
5378
5379
    /**
5380
     * Invoke an event.
5381
     *
5382
     * @param string $evtName
5383
     * @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.
5384
     * @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...
5385
     */
5386
    public function invokeEvent($evtName, $extParams = array())
5387
    {
5388
        if (!$evtName) {
5389
            return false;
5390
        }
5391
        if (!isset ($this->pluginEvent[$evtName])) {
5392
            return false;
5393
        }
5394
5395
        $results = null;
5396
        foreach ($this->pluginEvent[$evtName] as $pluginName) { // start for loop
5397
            if ($this->dumpPlugins) {
5398
                $eventtime = $this->getMicroTime();
5399
            }
5400
            // reset event object
5401
            $e = &$this->event;
5402
            $e->_resetEventObject();
5403
            $e->name = $evtName;
5404
            $e->activePlugin = $pluginName;
5405
5406
            // get plugin code
5407
            $_ = $this->getPluginCode($pluginName);
5408
            $pluginCode = $_['code'];
5409
            $pluginProperties = $_['props'];
5410
5411
            // load default params/properties
5412
            $parameter = $this->parseProperties($pluginProperties);
5413
            if (!is_array($parameter)) {
5414
                $parameter = array();
5415
            }
5416
            if (!empty($extParams)) {
5417
                $parameter = array_merge($parameter, $extParams);
5418
            }
5419
5420
            // eval plugin
5421
            $this->evalPlugin($pluginCode, $parameter);
5422
5423
            if (class_exists('PHxParser')) {
5424
                $this->config['enable_filter'] = 0;
5425
            }
5426
5427
            if ($this->dumpPlugins) {
5428
                $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...
5429
                $this->pluginsCode .= sprintf('<fieldset><legend><b>%s / %s</b> (%2.2f ms)</legend>', $evtName, $pluginName, $eventtime * 1000);
5430
                foreach ($parameter as $k => $v) {
5431
                    $this->pluginsCode .= "{$k} => " . print_r($v, true) . '<br>';
5432
                }
5433
                $this->pluginsCode .= '</fieldset><br />';
5434
                $this->pluginsTime["{$evtName} / {$pluginName}"] += $eventtime;
5435
            }
5436
            if ($e->getOutput() != '') {
5437
                $results[] = $e->getOutput();
5438
            }
5439
            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...
5440
                break;
5441
            }
5442
        }
5443
5444
        $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...
5445
        return $results;
5446
    }
5447
5448
    /**
5449
     * Returns plugin-code and properties
5450
     *
5451
     * @param string $pluginName
5452
     * @return array Associative array consisting of 'code' and 'props'
5453
     */
5454
    public function getPluginCode($pluginName)
5455
    {
5456
        $plugin = array();
5457
        if (isset ($this->pluginCache[$pluginName])) {
5458
            $pluginCode = $this->pluginCache[$pluginName];
5459
            $pluginProperties = isset($this->pluginCache[$pluginName . "Props"]) ? $this->pluginCache[$pluginName . "Props"] : '';
5460
        } else {
5461
            $pluginName = $this->db->escape($pluginName);
5462
            $result = $this->db->select('name, plugincode, properties', $this->getFullTableName("site_plugins"), "name='{$pluginName}' AND disabled=0");
5463
            if ($row = $this->db->getRow($result)) {
5464
                $pluginCode = $this->pluginCache[$row['name']] = $row['plugincode'];
5465
                $pluginProperties = $this->pluginCache[$row['name'] . "Props"] = $row['properties'];
5466
            } else {
5467
                $pluginCode = $this->pluginCache[$pluginName] = "return false;";
5468
                $pluginProperties = '';
5469
            }
5470
        }
5471
        $plugin['code'] = $pluginCode;
5472
        $plugin['props'] = $pluginProperties;
5473
5474
        return $plugin;
5475
    }
5476
5477
    /**
5478
     * Parses a resource property string and returns the result as an array
5479
     *
5480
     * @param string|array $propertyString
5481
     * @param string|null $elementName
5482
     * @param string|null $elementType
5483
     * @return array Associative array in the form property name => property value
5484
     */
5485
    public function parseProperties($propertyString, $elementName = null, $elementType = null)
5486
    {
5487
        $property = array();
5488
5489
        if(\is_scalar($propertyString)) {
5490
            $propertyString = trim($propertyString);
5491
            $propertyString = str_replace('{}', '', $propertyString);
5492
            $propertyString = str_replace('} {', ',', $propertyString);
5493
            if (!empty($propertyString) && $propertyString != '{}') {
5494
                $jsonFormat = $this->isJson($propertyString, true);
5495
                // old format
5496
                if ($jsonFormat === false) {
5497
                    $props = explode('&', $propertyString);
5498
                    foreach ($props as $prop) {
5499
5500
                        if (empty($prop)) {
5501
                            continue;
5502
                        } elseif (strpos($prop, '=') === false) {
5503
                            $property[trim($prop)] = '';
5504
                            continue;
5505
                        }
5506
5507
                        $_ = explode('=', $prop, 2);
5508
                        $key = trim($_[0]);
5509
                        $p = explode(';', trim($_[1]));
5510
                        switch ($p[1]) {
5511
                            case 'list':
5512
                            case 'list-multi':
5513
                            case 'checkbox':
5514
                            case 'radio':
5515
                                $value = !isset($p[3]) ? '' : $p[3];
5516
                                break;
5517
                            default:
5518
                                $value = !isset($p[2]) ? '' : $p[2];
5519
                        }
5520
                        if (!empty($key)) {
5521
                            $property[$key] = $value;
5522
                        }
5523
                    }
5524
                    // new json-format
5525
                } else if (!empty($jsonFormat)) {
5526
                    foreach ($jsonFormat as $key => $row) {
5527
                        if (!empty($key)) {
5528 View Code Duplication
                            if (is_array($row)) {
5529
                                if (isset($row[0]['value'])) {
5530
                                    $value = $row[0]['value'];
5531
                                }
5532
                            } else {
5533
                                $value = $row;
5534
                            }
5535
                            if (isset($value) && $value !== '') {
5536
                                $property[$key] = $value;
5537
                            }
5538
                        }
5539
                    }
5540
                }
5541
            }
5542
        }
5543
        elseif(\is_array($propertyString)) {
5544
            $property = $propertyString;
5545
        }
5546
        if (!empty($elementName) && !empty($elementType)) {
5547
            $out = $this->invokeEvent('OnParseProperties', array(
5548
                'element' => $elementName,
5549
                'type' => $elementType,
5550
                'args' => $property
5551
            ));
5552
            if (is_array($out)) {
5553
                $out = array_pop($out);
5554
            }
5555
            if (is_array($out)) {
5556
                $property = $out;
5557
            }
5558
        }
5559
5560
        return $property;
5561
    }
5562
5563
    /**
5564
     * Parses docBlock from a file and returns the result as an array
5565
     *
5566
     * @param string $element_dir
5567
     * @param string $filename
5568
     * @param boolean $escapeValues
5569
     * @return array Associative array in the form property name => property value
5570
     */
5571
    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...
5572
    {
5573
        $params = array();
5574
        $fullpath = $element_dir . '/' . $filename;
5575
        if (is_readable($fullpath)) {
5576
            $tpl = @fopen($fullpath, "r");
5577
            if ($tpl) {
5578
                $params['filename'] = $filename;
5579
                $docblock_start_found = false;
5580
                $name_found = false;
5581
                $description_found = false;
5582
                $docblock_end_found = false;
5583
                $arrayParams = array('author', 'documentation', 'reportissues', 'link');
5584
5585
                while (!feof($tpl)) {
5586
                    $line = fgets($tpl);
5587
                    $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...
5588
                    $docblock_start_found = $r['docblock_start_found'];
5589
                    $name_found = $r['name_found'];
5590
                    $description_found = $r['description_found'];
5591
                    $docblock_end_found = $r['docblock_end_found'];
5592
                    $param = $r['param'];
5593
                    $val = $r['val'];
5594
                    if (!$docblock_end_found) {
5595
                        break;
5596
                    }
5597
                    if (!$docblock_start_found || !$name_found || !$description_found || empty($param)) {
5598
                        continue;
5599
                    }
5600 View Code Duplication
                    if (!empty($param)) {
5601
                        if (in_array($param, $arrayParams)) {
5602
                            if (!isset($params[$param])) {
5603
                                $params[$param] = array();
5604
                            }
5605
                            $params[$param][] = $escapeValues ? $this->db->escape($val) : $val;
5606
                        } else {
5607
                            $params[$param] = $escapeValues ? $this->db->escape($val) : $val;
5608
                        }
5609
                    }
5610
                }
5611
                @fclose($tpl);
5612
            }
5613
        }
5614
        return $params;
5615
    }
5616
5617
    /**
5618
     * Parses docBlock from string and returns the result as an array
5619
     *
5620
     * @param string $string
5621
     * @param boolean $escapeValues
5622
     * @return array Associative array in the form property name => property value
5623
     */
5624
    public function parseDocBlockFromString($string, $escapeValues = false)
5625
    {
5626
        $params = array();
5627
        if (!empty($string)) {
5628
            $string = str_replace('\r\n', '\n', $string);
5629
            $exp = explode('\n', $string);
5630
            $docblock_start_found = false;
5631
            $name_found = false;
5632
            $description_found = false;
5633
            $docblock_end_found = false;
5634
            $arrayParams = array('author', 'documentation', 'reportissues', 'link');
5635
5636
            foreach ($exp as $line) {
5637
                $r = $this->parseDocBlockLine($line, $docblock_start_found, $name_found, $description_found, $docblock_end_found);
5638
                $docblock_start_found = $r['docblock_start_found'];
5639
                $name_found = $r['name_found'];
5640
                $description_found = $r['description_found'];
5641
                $docblock_end_found = $r['docblock_end_found'];
5642
                $param = $r['param'];
5643
                $val = $r['val'];
5644
                if (!$docblock_start_found) {
5645
                    continue;
5646
                }
5647
                if ($docblock_end_found) {
5648
                    break;
5649
                }
5650 View Code Duplication
                if (!empty($param)) {
5651
                    if (in_array($param, $arrayParams)) {
5652
                        if (!isset($params[$param])) {
5653
                            $params[$param] = array();
5654
                        }
5655
                        $params[$param][] = $escapeValues ? $this->db->escape($val) : $val;
5656
                    } else {
5657
                        $params[$param] = $escapeValues ? $this->db->escape($val) : $val;
5658
                    }
5659
                }
5660
            }
5661
        }
5662
        return $params;
5663
    }
5664
5665
    /**
5666
     * Parses docBlock of a component´s source-code and returns the result as an array
5667
     * (modified parseDocBlock() from modules/stores/setup.info.php by Bumkaka & Dmi3yy)
5668
     *
5669
     * @param string $line
5670
     * @param boolean $docblock_start_found
5671
     * @param boolean $name_found
5672
     * @param boolean $description_found
5673
     * @param boolean $docblock_end_found
5674
     * @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...
5675
     */
5676
    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...
5677
    {
5678
        $param = '';
5679
        $val = '';
5680
        $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...
5681
        if (!$docblock_start_found) {
5682
            // find docblock start
5683
            if (strpos($line, '/**') !== false) {
5684
                $docblock_start_found = true;
5685
            }
5686
        } elseif (!$name_found) {
5687
            // find name
5688
            if (preg_match("/^\s+\*\s+(.+)/", $line, $ma)) {
5689
                $param = 'name';
5690
                $val = trim($ma[1]);
5691
                $name_found = !empty($val);
5692
            }
5693
        } elseif (!$description_found) {
5694
            // find description
5695
            if (preg_match("/^\s+\*\s+(.+)/", $line, $ma)) {
5696
                $param = 'description';
5697
                $val = trim($ma[1]);
5698
                $description_found = !empty($val);
5699
            }
5700
        } else {
5701
            if (preg_match("/^\s+\*\s+\@([^\s]+)\s+(.+)/", $line, $ma)) {
5702
                $param = trim($ma[1]);
5703
                $val = trim($ma[2]);
5704
                if (!empty($param) && !empty($val)) {
5705
                    if ($param == 'internal') {
5706
                        $ma = null;
5707
                        if (preg_match("/\@([^\s]+)\s+(.+)/", $val, $ma)) {
5708
                            $param = trim($ma[1]);
5709
                            $val = trim($ma[2]);
5710
                        }
5711
                    }
5712
                }
5713
            } elseif (preg_match("/^\s*\*\/\s*$/", $line)) {
5714
                $docblock_end_found = true;
5715
            }
5716
        }
5717
        return array(
5718
            'docblock_start_found' => $docblock_start_found,
5719
            'name_found' => $name_found,
5720
            'description_found' => $description_found,
5721
            'docblock_end_found' => $docblock_end_found,
5722
            'param' => $param,
5723
            'val' => $val
5724
        );
5725
    }
5726
5727
    /**
5728
     * Renders docBlock-parameters into human readable list
5729
     *
5730
     * @param array $parsed
5731
     * @return string List in HTML-format
5732
     */
5733
    public function convertDocBlockIntoList($parsed)
5734
    {
5735
        global $_lang;
5736
5737
        // Replace special placeholders & make URLs + Emails clickable
5738
        $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...
5739
        $regexUrl = "/((http|https|ftp|ftps)\:\/\/[^\/]+(\/[^\s]+[^,.?!:;\s])?)/";
5740
        $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';
5741
        $emailSubject = isset($parsed['name']) ? '?subject=' . $parsed['name'] : '';
5742
        $emailSubject .= isset($parsed['version']) ? ' v' . $parsed['version'] : '';
5743
        foreach ($parsed as $key => $val) {
5744
            if (is_array($val)) {
5745
                foreach ($val as $key2 => $val2) {
5746
                    $val2 = $this->parseText($val2, $ph);
5747 View Code Duplication
                    if (preg_match($regexUrl, $val2, $url)) {
5748
                        $val2 = preg_replace($regexUrl, "<a href=\"{$url[0]}\" target=\"_blank\">{$url[0]}</a> ", $val2);
5749
                    }
5750 View Code Duplication
                    if (preg_match($regexEmail, $val2, $url)) {
5751
                        $val2 = preg_replace($regexEmail, '<a href="mailto:\\1' . $emailSubject . '">\\1</a>', $val2);
5752
                    }
5753
                    $parsed[$key][$key2] = $val2;
5754
                }
5755
            } else {
5756
                $val = $this->parseText($val, $ph);
5757 View Code Duplication
                if (preg_match($regexUrl, $val, $url)) {
5758
                    $val = preg_replace($regexUrl, "<a href=\"{$url[0]}\" target=\"_blank\">{$url[0]}</a> ", $val);
5759
                }
5760 View Code Duplication
                if (preg_match($regexEmail, $val, $url)) {
5761
                    $val = preg_replace($regexEmail, '<a href="mailto:\\1' . $emailSubject . '">\\1</a>', $val);
5762
                }
5763
                $parsed[$key] = $val;
5764
            }
5765
        }
5766
5767
        $arrayParams = array(
5768
            'documentation' => $_lang['documentation'],
5769
            'reportissues' => $_lang['report_issues'],
5770
            'link' => $_lang['further_info'],
5771
            'author' => $_lang['author_infos']
5772
        );
5773
5774
        $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...
5775
        $list = isset($parsed['logo']) ? '<img src="' . $this->config['base_url'] . ltrim($parsed['logo'], "/") . '" style="float:right;max-width:100px;height:auto;" />' . $nl : '';
5776
        $list .= '<p>' . $nl;
5777
        $list .= isset($parsed['name']) ? '<strong>' . $parsed['name'] . '</strong><br/>' . $nl : '';
5778
        $list .= isset($parsed['description']) ? $parsed['description'] . $nl : '';
5779
        $list .= '</p><br/>' . $nl;
5780
        $list .= isset($parsed['version']) ? '<p><strong>' . $_lang['version'] . ':</strong> ' . $parsed['version'] . '</p>' . $nl : '';
5781
        $list .= isset($parsed['license']) ? '<p><strong>' . $_lang['license'] . ':</strong> ' . $parsed['license'] . '</p>' . $nl : '';
5782
        $list .= isset($parsed['lastupdate']) ? '<p><strong>' . $_lang['last_update'] . ':</strong> ' . $parsed['lastupdate'] . '</p>' . $nl : '';
5783
        $list .= '<br/>' . $nl;
5784
        $first = true;
5785
        foreach ($arrayParams as $param => $label) {
5786
            if (isset($parsed[$param])) {
5787
                if ($first) {
5788
                    $list .= '<p><strong>' . $_lang['references'] . '</strong></p>' . $nl;
5789
                    $list .= '<ul class="docBlockList">' . $nl;
5790
                    $first = false;
5791
                }
5792
                $list .= '    <li><strong>' . $label . '</strong>' . $nl;
5793
                $list .= '        <ul>' . $nl;
5794
                foreach ($parsed[$param] as $val) {
5795
                    $list .= '            <li>' . $val . '</li>' . $nl;
5796
                }
5797
                $list .= '        </ul></li>' . $nl;
5798
            }
5799
        }
5800
        $list .= !$first ? '</ul>' . $nl : '';
5801
5802
        return $list;
5803
    }
5804
5805
    /**
5806
     * @param string $string
5807
     * @return string
5808
     */
5809
    public function removeSanitizeSeed($string = '')
5810
    {
5811
        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...
5812
5813
        if (!$string || strpos($string, $sanitize_seed) === false) {
5814
            return $string;
5815
        }
5816
5817
        return str_replace($sanitize_seed, '', $string);
5818
    }
5819
5820
    /**
5821
     * @param string $content
5822
     * @return string
5823
     */
5824
    public function cleanUpMODXTags($content = '')
5825
    {
5826
        if ($this->minParserPasses < 1) {
5827
            return $content;
5828
        }
5829
5830
        $enable_filter = $this->config['enable_filter'];
5831
        $this->config['enable_filter'] = 1;
5832
        $_ = 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...
5833
        foreach ($_ as $brackets) {
5834
            list($left, $right) = explode(' ', $brackets);
5835
            if (strpos($content, $left) !== false) {
5836
                if ($left === '[*') {
5837
                    $content = $this->mergeDocumentContent($content);
5838
                } elseif ($left === '[(') {
5839
                    $content = $this->mergeSettingsContent($content);
5840
                } elseif ($left === '{{') {
5841
                    $content = $this->mergeChunkContent($content);
5842
                } elseif ($left === '[[') {
5843
                    $content = $this->evalSnippets($content);
5844
                }
5845
            }
5846
        }
5847
        foreach ($_ as $brackets) {
5848
            list($left, $right) = explode(' ', $brackets);
5849
            if (strpos($content, $left) !== false) {
5850
                $matches = $this->getTagsFromContent($content, $left, $right);
5851
                $content = str_replace($matches[0], '', $content);
5852
            }
5853
        }
5854
        $this->config['enable_filter'] = $enable_filter;
5855
        return $content;
5856
    }
5857
5858
    /**
5859
     * @param string $str
5860
     * @param string $allowable_tags
5861
     * @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...
5862
     */
5863
    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...
5864
    {
5865
        $str = strip_tags($str, $allowable_tags);
5866
        modx_sanitize_gpc($str);
5867
        return $str;
5868
    }
5869
5870
    /**
5871
     * @param string $name
5872
     * @param string $phpCode
5873
     * @param string $namespace
5874
     * @param array defaultParams
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
     * @param string $name
5884
     * @param string $text
5885
     * @param string $namespace
5886
     */
5887
    public function addChunk($name, $text, $namespace = '#')
5888
    {
5889
        $this->chunkCache[$namespace . $name] = $text;
5890
    }
5891
5892
    /**
5893
     * @param $type
5894
     * @param $scanPath
5895
     * @param array $ext
5896
     * @return array
5897
     * @throws \Exception
5898
     */
5899
    public function findElements($type, $scanPath, array $ext)
5900
    {
5901
        $out = array();
5902
5903
        if (! is_dir($scanPath) || empty($ext)) {
5904
            return $out;
5905
        }
5906
        $iterator = new \RecursiveIteratorIterator(
5907
            new \RecursiveDirectoryIterator($scanPath, \RecursiveDirectoryIterator::SKIP_DOTS),
5908
            \RecursiveIteratorIterator::SELF_FIRST
5909
        );
5910
        foreach ($iterator as $item) {
5911
            /**
5912
             * @var \SplFileInfo $item
5913
             */
5914
            if ($item->isFile() && $item->isReadable() && \in_array($item->getExtension(), $ext)) {
5915
                $name = $item->getBasename('.' . $item->getExtension());
5916
                $path = str_replace($scanPath, '', $item->getPath() . '/');
5917
                if (!empty($path)) {
5918
                    $name = str_replace('/', '\\', $path) . $name;
5919
                }
5920
                switch ($type) {
5921
                    case 'chunk':
5922
                        $out[$name] = file_get_contents($item->getRealPath());
5923
                        break;
5924
                    case 'snippet':
5925
                        $out[$name] = "return require '" . $item->getRealPath() . "';";
5926
                        break;
5927
                    default:
5928
                        throw new \Exception;
5929
                }
5930
            }
5931
        }
5932
5933
        return $out;
5934
    }
5935
5936
    /**
5937
     * @param string $phpcode
5938
     * @param string $evalmode
5939
     * @param string $safe_functions
5940
     * @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...
5941
     */
5942
    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...
5943
    {
5944
        if ($evalmode == '') {
5945
            $evalmode = $this->config['allow_eval'];
5946
        }
5947
        if ($safe_functions == '') {
5948
            $safe_functions = $this->config['safe_functions_at_eval'];
5949
        }
5950
5951
        modx_sanitize_gpc($phpcode);
5952
5953
        switch ($evalmode) {
5954
            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...
5955
                $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...
5956
                break;
5957
            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...
5958
                $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...
5959
                break;
5960
            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...
5961
                $isSafe = true;
5962
                break; // Should debug only
5963
            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...
5964
            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...
5965
                return $phpcode;
5966
        }
5967
5968
        if (!$isSafe) {
5969
            $msg = $phpcode . "\n" . $this->currentSnippet . "\n" . print_r($_SERVER, true);
5970
            $title = sprintf('Unknown eval was executed (%s)', $this->htmlspecialchars(substr(trim($phpcode), 0, 50)));
5971
            $this->messageQuit($title, '', true, '', '', 'Parser', $msg);
5972
            return;
5973
        }
5974
5975
        ob_start();
5976
        $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...
5977
        $echo = ob_get_clean();
5978
5979
        if (is_array($return)) {
5980
            return 'array()';
5981
        }
5982
5983
        $output = $echo . $return;
5984
        modx_sanitize_gpc($output);
5985
        return $this->htmlspecialchars($output); // Maybe, all html tags are dangerous
5986
    }
5987
5988
    /**
5989
     * @param string $phpcode
5990
     * @param string $safe_functions
5991
     * @return bool
5992
     */
5993
    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...
5994
    { // 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...
5995
        if ($safe_functions == '') {
5996
            return false;
5997
        }
5998
5999
        $safe = explode(',', $safe_functions);
6000
6001
        $phpcode = rtrim($phpcode, ';') . ';';
6002
        $tokens = token_get_all('<?php ' . $phpcode);
6003
        foreach ($tokens as $i => $token) {
6004
            if (!is_array($token)) {
6005
                continue;
6006
            }
6007
            $tokens[$i]['token_name'] = token_name($token[0]);
6008
        }
6009
        foreach ($tokens as $token) {
6010
            if (!is_array($token)) {
6011
                continue;
6012
            }
6013
            switch ($token['token_name']) {
6014
                case 'T_STRING':
6015
                    if (!in_array($token[1], $safe)) {
6016
                        return false;
6017
                    }
6018
                    break;
6019
                case 'T_VARIABLE':
6020
                    if ($token[1] == '$GLOBALS') {
6021
                        return false;
6022
                    }
6023
                    break;
6024
                case 'T_EVAL':
6025
                    return false;
6026
            }
6027
        }
6028
        return true;
6029
    }
6030
6031
    /**
6032
     * @param string $str
6033
     * @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...
6034
     */
6035
    public function atBindFileContent($str = '')
6036
    {
6037
6038
        $search_path = array('assets/tvs/', 'assets/chunks/', 'assets/templates/', $this->config['rb_base_url'] . 'files/', '');
6039
6040
        if (stripos($str, '@FILE') !== 0) {
6041
            return $str;
6042
        }
6043 View Code Duplication
        if (strpos($str, "\n") !== false) {
6044
            $str = substr($str, 0, strpos("\n", $str));
6045
        }
6046
6047
        if ($this->getExtFromFilename($str) === '.php') {
6048
            return 'Could not retrieve PHP file.';
6049
        }
6050
6051
        $str = substr($str, 6);
6052
        $str = trim($str);
6053
        if (strpos($str, '\\') !== false) {
6054
            $str = str_replace('\\', '/', $str);
6055
        }
6056
        $str = ltrim($str, '/');
6057
6058
        $errorMsg = sprintf("Could not retrieve string '%s'.", $str);
6059
6060
        foreach ($search_path as $path) {
6061
            $file_path = MODX_BASE_PATH . $path . $str;
6062
            if (strpos($file_path, MODX_MANAGER_PATH) === 0) {
6063
                return $errorMsg;
6064
            } elseif (is_file($file_path)) {
6065
                break;
6066
            } else {
6067
                $file_path = false;
6068
            }
6069
        }
6070
6071
        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...
6072
            return $errorMsg;
6073
        }
6074
6075
        $content = (string)file_get_contents($file_path);
6076
        if ($content === false) {
6077
            return $errorMsg;
6078
        }
6079
6080
        return $content;
6081
    }
6082
6083
    /**
6084
     * @param $str
6085
     * @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...
6086
     */
6087
    public function getExtFromFilename($str)
6088
    {
6089
        $str = strtolower(trim($str));
6090
        $pos = strrpos($str, '.');
6091
        if ($pos === false) {
6092
            return false;
6093
        } else {
6094
            return substr($str, $pos);
6095
        }
6096
    }
6097
    /***************************************************************************************/
6098
    /* End of API functions                                       */
6099
    /***************************************************************************************/
6100
6101
    /**
6102
     * PHP error handler set by http://www.php.net/manual/en/function.set-error-handler.php
6103
     *
6104
     * Checks the PHP error and calls messageQuit() unless:
6105
     *  - error_reporting() returns 0, or
6106
     *  - the PHP error level is 0, or
6107
     *  - the PHP error level is 8 (E_NOTICE) and stopOnNotice is false
6108
     *
6109
     * @param int $nr The PHP error level as per http://www.php.net/manual/en/errorfunc.constants.php
6110
     * @param string $text Error message
6111
     * @param string $file File where the error was detected
6112
     * @param string $line Line number within $file
6113
     * @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...
6114
     */
6115
    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...
6116
    {
6117
        if (error_reporting() == 0 || $nr == 0) {
6118
            return true;
6119
        }
6120
        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...
6121
            switch ($nr) {
6122
                case E_NOTICE:
6123
                    if ($this->error_reporting <= 2) {
6124
                        return true;
6125
                    }
6126
                    $isError = false;
6127
                    $msg = 'PHP Minor Problem (this message show logged in only)';
6128
                    break;
6129
                case E_STRICT:
6130 View Code Duplication
                case E_DEPRECATED:
6131
                    if ($this->error_reporting <= 1) {
6132
                        return true;
6133
                    }
6134
                    $isError = true;
6135
                    $msg = 'PHP Strict Standards Problem';
6136
                    break;
6137 View Code Duplication
                default:
6138
                    if ($this->error_reporting === 0) {
6139
                        return true;
6140
                    }
6141
                    $isError = true;
6142
                    $msg = 'PHP Parse Error';
6143
            }
6144
        }
6145
        if (is_readable($file)) {
6146
            $source = file($file);
6147
            $source = $this->htmlspecialchars($source[$line - 1]);
6148
        } else {
6149
            $source = "";
6150
        } //Error $nr in $file at $line: <div><code>$source</code></div>
6151
6152
        $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...
6153
    }
6154
6155
    /**
6156
     * @param string $msg
6157
     * @param string $query
6158
     * @param bool $is_error
6159
     * @param string $nr
6160
     * @param string $file
6161
     * @param string $source
6162
     * @param string $text
6163
     * @param string $line
6164
     * @param string $output
6165
     * @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...
6166
     */
6167
    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...
6168
    {
6169
6170
        if (0 < $this->messageQuitCount) {
6171
            return;
6172
        }
6173
        $this->messageQuitCount++;
6174
        $MakeTable = new Support\MakeTable();
6175
        $MakeTable->setTableClass('grid');
6176
        $MakeTable->setRowRegularClass('gridItem');
6177
        $MakeTable->setRowAlternateClass('gridAltItem');
6178
        $MakeTable->setColumnWidths(array('100px'));
6179
6180
        $table = array();
6181
6182
        $version = isset ($GLOBALS['modx_version']) ? $GLOBALS['modx_version'] : '';
6183
        $release_date = isset ($GLOBALS['release_date']) ? $GLOBALS['release_date'] : '';
6184
        $request_uri = "http://" . $_SERVER['HTTP_HOST'] . ($_SERVER["SERVER_PORT"] == 80 ? "" : (":" . $_SERVER["SERVER_PORT"])) . $_SERVER['REQUEST_URI'];
6185
        $request_uri = $this->htmlspecialchars($request_uri, ENT_QUOTES, $this->config['modx_charset']);
6186
        $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...
6187
        $referer = $this->htmlspecialchars($_SERVER['HTTP_REFERER'], ENT_QUOTES, $this->config['modx_charset']);
6188
        if ($is_error) {
6189
            $str = '<h2 style="color:red">&laquo; Evo Parse Error &raquo;</h2>';
6190
            if ($msg != 'PHP Parse Error') {
6191
                $str .= '<h3 style="color:red">' . $msg . '</h3>';
6192
            }
6193
        } else {
6194
            $str = '<h2 style="color:#003399">&laquo; Evo Debug/ stop message &raquo;</h2>';
6195
            $str .= '<h3 style="color:#003399">' . $msg . '</h3>';
6196
        }
6197
6198
        if (!empty ($query)) {
6199
            $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>';
6200
        }
6201
6202
        $errortype = array(
6203
            E_ERROR => "ERROR",
6204
            E_WARNING => "WARNING",
6205
            E_PARSE => "PARSING ERROR",
6206
            E_NOTICE => "NOTICE",
6207
            E_CORE_ERROR => "CORE ERROR",
6208
            E_CORE_WARNING => "CORE WARNING",
6209
            E_COMPILE_ERROR => "COMPILE ERROR",
6210
            E_COMPILE_WARNING => "COMPILE WARNING",
6211
            E_USER_ERROR => "USER ERROR",
6212
            E_USER_WARNING => "USER WARNING",
6213
            E_USER_NOTICE => "USER NOTICE",
6214
            E_STRICT => "STRICT NOTICE",
6215
            E_RECOVERABLE_ERROR => "RECOVERABLE ERROR",
6216
            E_DEPRECATED => "DEPRECATED",
6217
            E_USER_DEPRECATED => "USER DEPRECATED"
6218
        );
6219
6220
        if (!empty($nr) || !empty($file)) {
6221
            if ($text != '') {
6222
                $str .= '<div style="font-weight:bold;border:1px solid #ccc;padding:8px;color:#333;background-color:#ffffcd;margin-bottom:15px;">Error : ' . $text . '</div>';
6223
            }
6224
            if ($output != '') {
6225
                $str .= '<div style="font-weight:bold;border:1px solid #ccc;padding:8px;color:#333;background-color:#ffffcd;margin-bottom:15px;">' . $output . '</div>';
6226
            }
6227
            if ($nr !== '') {
6228
                $table[] = array('ErrorType[num]', $errortype [$nr] . "[" . $nr . "]");
6229
            }
6230
            if ($file) {
6231
                $table[] = array('File', $file);
6232
            }
6233
            if ($line) {
6234
                $table[] = array('Line', $line);
6235
            }
6236
6237
        }
6238
6239
        if ($source != '') {
6240
            $table[] = array("Source", $source);
6241
        }
6242
6243
        if (!empty($this->currentSnippet)) {
6244
            $table[] = array('Current Snippet', $this->currentSnippet);
6245
        }
6246
6247
        if (!empty($this->event->activePlugin)) {
6248
            $table[] = array('Current Plugin', $this->event->activePlugin . '(' . $this->event->name . ')');
6249
        }
6250
6251
        $str .= $MakeTable->create($table, array('Error information', ''));
6252
        $str .= "<br />";
6253
6254
        $table = array();
6255
        $table[] = array('REQUEST_URI', $request_uri);
6256
6257
        if ($this->manager->action) {
6258
            include_once(MODX_MANAGER_PATH . 'includes/actionlist.inc.php');
6259
            global $action_list;
6260
            $actionName = (isset($action_list[$this->manager->action])) ? " - {$action_list[$this->manager->action]}" : '';
6261
6262
            $table[] = array('Manager action', $this->manager->action . $actionName);
6263
        }
6264
6265
        if (preg_match('@^[0-9]+@', $this->documentIdentifier)) {
6266
            $resource = $this->getDocumentObject('id', $this->documentIdentifier);
6267
            $url = $this->makeUrl($this->documentIdentifier, '', '', 'full');
6268
            $table[] = array('Resource', '[' . $this->documentIdentifier . '] <a href="' . $url . '" target="_blank">' . $resource['pagetitle'] . '</a>');
6269
        }
6270
        $table[] = array('Referer', $referer);
6271
        $table[] = array('User Agent', $ua);
6272
        $table[] = array('IP', $_SERVER['REMOTE_ADDR']);
6273
        $table[] = array('Current time', date("Y-m-d H:i:s", $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time']));
6274
        $str .= $MakeTable->create($table, array('Basic info', ''));
6275
        $str .= "<br />";
6276
6277
        $table = array();
6278
        $table[] = array('MySQL', '[^qt^] ([^q^] Requests)');
6279
        $table[] = array('PHP', '[^p^]');
6280
        $table[] = array('Total', '[^t^]');
6281
        $table[] = array('Memory', '[^m^]');
6282
        $str .= $MakeTable->create($table, array('Benchmarks', ''));
6283
        $str .= "<br />";
6284
6285
        $totalTime = ($this->getMicroTime() - $this->tstart);
6286
6287
        $mem = memory_get_peak_usage(true);
6288
        $total_mem = $mem - $this->mstart;
6289
        $total_mem = ($total_mem / 1024 / 1024) . ' mb';
6290
6291
        $queryTime = $this->queryTime;
6292
        $phpTime = $totalTime - $queryTime;
6293
        $queries = isset ($this->executedQueries) ? $this->executedQueries : 0;
6294
        $queryTime = sprintf("%2.4f s", $queryTime);
6295
        $totalTime = sprintf("%2.4f s", $totalTime);
6296
        $phpTime = sprintf("%2.4f s", $phpTime);
6297
6298
        $str = str_replace('[^q^]', $queries, $str);
6299
        $str = str_replace('[^qt^]', $queryTime, $str);
6300
        $str = str_replace('[^p^]', $phpTime, $str);
6301
        $str = str_replace('[^t^]', $totalTime, $str);
6302
        $str = str_replace('[^m^]', $total_mem, $str);
6303
6304
        if (isset($php_errormsg) && !empty($php_errormsg)) {
6305
            $str = "<b>{$php_errormsg}</b><br />\n{$str}";
6306
        }
6307
        $str .= $this->get_backtrace(debug_backtrace());
6308
        // Log error
6309
        if (!empty($this->currentSnippet)) {
6310
            $source = 'Snippet - ' . $this->currentSnippet;
6311
        } elseif (!empty($this->event->activePlugin)) {
6312
            $source = 'Plugin - ' . $this->event->activePlugin;
6313
        } elseif ($source !== '') {
6314
            $source = 'Parser - ' . $source;
6315
        } elseif ($query !== '') {
6316
            $source = 'SQL Query';
6317
        } else {
6318
            $source = 'Parser';
6319
        }
6320
        if ($msg) {
6321
            $source .= ' / ' . $msg;
6322
        }
6323
        if (isset($actionName) && !empty($actionName)) {
6324
            $source .= $actionName;
6325
        }
6326 View Code Duplication
        switch ($nr) {
6327
            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...
6328
            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...
6329
            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...
6330
            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...
6331
            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...
6332
                $error_level = 2;
6333
                break;
6334
            default:
6335
                $error_level = 3;
6336
        }
6337
        $this->logEvent(0, $error_level, $str, $source);
6338
6339
        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...
6340
            return true;
6341
        }
6342
        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...
6343
            return true;
6344
        }
6345
6346
        // Set 500 response header
6347
        if ($error_level !== 2) {
6348
            header('HTTP/1.1 500 Internal Server Error');
6349
        }
6350
6351
        // Display error
6352
        if (isset($_SESSION['mgrValidated'])) {
6353
            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>
6354
                 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
6355
                 <link rel="stylesheet" type="text/css" href="' . $this->config['site_manager_url'] . 'media/style/' . $this->config['manager_theme'] . '/style.css" />
6356
                 <style type="text/css">body { padding:10px; } td {font:inherit;}</style>
6357
                 </head><body>
6358
                 ' . $str . '</body></html>';
6359
6360
        } else {
6361
            echo 'Error';
6362
        }
6363
        ob_end_flush();
6364
        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...
6365
    }
6366
6367
    /**
6368
     * @param $backtrace
6369
     * @return string
6370
     */
6371
    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...
6372
    {
6373
        $MakeTable = new Support\MakeTable();
6374
        $MakeTable->setTableClass('grid');
6375
        $MakeTable->setRowRegularClass('gridItem');
6376
        $MakeTable->setRowAlternateClass('gridAltItem');
6377
        $table = array();
6378
        $backtrace = array_reverse($backtrace);
6379
        foreach ($backtrace as $key => $val) {
6380
            $key++;
6381
            if (substr($val['function'], 0, 11) === 'messageQuit') {
6382
                break;
6383
            } elseif (substr($val['function'], 0, 8) === 'phpError') {
6384
                break;
6385
            }
6386
            $path = str_replace('\\', '/', $val['file']);
6387
            if (strpos($path, MODX_BASE_PATH) === 0) {
6388
                $path = substr($path, strlen(MODX_BASE_PATH));
6389
            }
6390
            switch ($val['type']) {
6391
                case '->':
6392
                case '::':
6393
                    $functionName = $val['function'] = $val['class'] . $val['type'] . $val['function'];
6394
                    break;
6395
                default:
6396
                    $functionName = $val['function'];
6397
            }
6398
            $tmp = 1;
6399
            $_ = (!empty($val['args'])) ? count($val['args']) : 0;
6400
            $args = array_pad(array(), $_, '$var');
6401
            $args = implode(", ", $args);
6402
            $modx = &$this;
6403
            $args = preg_replace_callback('/\$var/', function () use ($modx, &$tmp, $val) {
6404
                $arg = $val['args'][$tmp - 1];
6405
                switch (true) {
6406
                    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...
6407
                        $out = 'NULL';
6408
                        break;
6409
                    }
6410
                    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...
6411
                        $out = $arg;
6412
                        break;
6413
                    }
6414
                    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...
6415
                        $out = strlen($arg) > 20 ? 'string $var' . $tmp : ("'" . $this->htmlspecialchars(str_replace("'", "\\'", $arg)) . "'");
6416
                        break;
6417
                    }
6418
                    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...
6419
                        $out = $arg ? 'TRUE' : 'FALSE';
6420
                        break;
6421
                    }
6422
                    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...
6423
                        $out = 'array $var' . $tmp;
6424
                        break;
6425
                    }
6426
                    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...
6427
                        $out = get_class($arg) . ' $var' . $tmp;
6428
                        break;
6429
                    }
6430
                    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...
6431
                        $out = '$var' . $tmp;
6432
                    }
6433
                }
6434
                $tmp++;
6435
                return $out;
6436
            }, $args);
6437
            $line = array(
6438
                "<strong>" . $functionName . "</strong>(" . $args . ")",
6439
                $path . " on line " . $val['line']
6440
            );
6441
            $table[] = array(implode("<br />", $line));
6442
        }
6443
        return $MakeTable->create($table, array('Backtrace'));
6444
    }
6445
6446
    /**
6447
     * @return string
6448
     */
6449
    public function getRegisteredClientScripts()
6450
    {
6451
        return implode("\n", $this->jscripts);
6452
    }
6453
6454
    /**
6455
     * @return string
6456
     */
6457
    public function getRegisteredClientStartupScripts()
6458
    {
6459
        return implode("\n", $this->sjscripts);
6460
    }
6461
6462
    /**
6463
     * Format alias to be URL-safe. Strip invalid characters.
6464
     *
6465
     * @param string $alias Alias to be formatted
6466
     * @return string Safe alias
6467
     */
6468
    public function stripAlias($alias)
6469
    {
6470
        // let add-ons overwrite the default behavior
6471
        $results = $this->invokeEvent('OnStripAlias', array('alias' => $alias));
6472
        if (!empty($results)) {
6473
            // if multiple plugins are registered, only the last one is used
6474
            return end($results);
6475
        } else {
6476
            // default behavior: strip invalid characters and replace spaces with dashes.
6477
            $alias = strip_tags($alias); // strip HTML
6478
            $alias = preg_replace('/[^\.A-Za-z0-9 _-]/', '', $alias); // strip non-alphanumeric characters
6479
            $alias = preg_replace('/\s+/', '-', $alias); // convert white-space to dash
6480
            $alias = preg_replace('/-+/', '-', $alias);  // convert multiple dashes to one
6481
            $alias = trim($alias, '-'); // trim excess
6482
            return $alias;
6483
        }
6484
    }
6485
6486
    /**
6487
     * @param $size
6488
     * @return string
6489
     */
6490
    public function nicesize($size)
6491
    {
6492
        $sizes = array('Tb' => 1099511627776, 'Gb' => 1073741824, 'Mb' => 1048576, 'Kb' => 1024, 'b' => 1);
6493
        $precisions = count($sizes) - 1;
6494
        foreach ($sizes as $unit => $bytes) {
6495
            if ($size >= $bytes) {
6496
                return number_format($size / $bytes, $precisions) . ' ' . $unit;
6497
            }
6498
            $precisions--;
6499
        }
6500
        return '0 b';
6501
    }
6502
6503
    /**
6504
     * @param $parentid
6505
     * @param $alias
6506
     * @return bool
6507
     */
6508
    public function getHiddenIdFromAlias($parentid, $alias)
6509
    {
6510
        $table = $this->getFullTableName('site_content');
6511
        $query = $this->db->query("SELECT sc.id, children.id AS child_id, children.alias, COUNT(children2.id) AS children_count
6512
            FROM {$table} sc
6513
            JOIN {$table} children ON children.parent = sc.id
6514
            LEFT JOIN {$table} children2 ON children2.parent = children.id
6515
            WHERE sc.parent = {$parentid} AND sc.alias_visible = '0' GROUP BY children.id;");
6516
6517
        while ($child = $this->db->getRow($query)) {
6518
            if ($child['alias'] == $alias || $child['child_id'] == $alias) {
6519
                return $child['child_id'];
6520
            }
6521
6522
            if ($child['children_count'] > 0) {
6523
                $id = $this->getHiddenIdFromAlias($child['id'], $alias);
6524
                if ($id) {
6525
                    return $id;
6526
                }
6527
            }
6528
        }
6529
6530
        return false;
6531
    }
6532
6533
    /**
6534
     * @param $alias
6535
     * @return bool|int
6536
     */
6537
    public function getIdFromAlias($alias)
6538
    {
6539
        if (isset($this->documentListing[$alias])) {
6540
            return $this->documentListing[$alias];
6541
        }
6542
6543
        $tbl_site_content = $this->getFullTableName('site_content');
6544
        if ($this->config['use_alias_path'] == 1) {
6545
            if ($alias == '.') {
6546
                return 0;
6547
            }
6548
6549
            if (strpos($alias, '/') !== false) {
6550
                $_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...
6551
            } else {
6552
                $_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...
6553
            }
6554
            $id = 0;
6555
6556
            foreach ($_a as $alias) {
6557
                if ($id === false) {
6558
                    break;
6559
                }
6560
                $alias = $this->db->escape($alias);
6561
                $rs = $this->db->select('id', $tbl_site_content, "deleted=0 and parent='{$id}' and alias='{$alias}'");
6562
                if ($this->db->getRecordCount($rs) == 0) {
6563
                    $rs = $this->db->select('id', $tbl_site_content, "deleted=0 and parent='{$id}' and id='{$alias}'");
6564
                }
6565
                $next = $this->db->getValue($rs);
6566
                $id = !$next ? $this->getHiddenIdFromAlias($id, $alias) : $next;
6567
            }
6568
        } else {
6569
            $rs = $this->db->select('id', $tbl_site_content, "deleted=0 and alias='{$alias}'", 'parent, menuindex');
6570
            $id = $this->db->getValue($rs);
6571
            if (!$id) {
6572
                $id = false;
6573
            }
6574
        }
6575
        return $id;
6576
    }
6577
6578
    /**
6579
     * @param string $str
6580
     * @return bool|mixed|string
6581
     */
6582
    public function atBindInclude($str = '')
6583
    {
6584
        if (strpos($str, '@INCLUDE') !== 0) {
6585
            return $str;
6586
        }
6587 View Code Duplication
        if (strpos($str, "\n") !== false) {
6588
            $str = substr($str, 0, strpos("\n", $str));
6589
        }
6590
6591
        $str = substr($str, 9);
6592
        $str = trim($str);
6593
        $str = str_replace('\\', '/', $str);
6594
        $str = ltrim($str, '/');
6595
6596
        $tpl_dir = 'assets/templates/';
6597
6598
        if (strpos($str, MODX_MANAGER_PATH) === 0) {
6599
            return false;
6600
        } elseif (is_file(MODX_BASE_PATH . $str)) {
6601
            $file_path = MODX_BASE_PATH . $str;
6602
        } elseif (is_file(MODX_BASE_PATH . "{$tpl_dir}{$str}")) {
6603
            $file_path = MODX_BASE_PATH . $tpl_dir . $str;
6604
        } else {
6605
            return false;
6606
        }
6607
6608
        if (!$file_path || !is_file($file_path)) {
6609
            return false;
6610
        }
6611
6612
        ob_start();
6613
        $modx = &$this;
6614
        $result = include($file_path);
6615
        if ($result === 1) {
6616
            $result = '';
6617
        }
6618
        $content = ob_get_clean();
6619
        if (!$content && $result) {
6620
            $content = $result;
6621
        }
6622
        return $content;
6623
    }
6624
6625
    // php compat
6626
6627
    /**
6628
     * @param $str
6629
     * @param int $flags
6630
     * @param string $encode
6631
     * @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...
6632
     */
6633
    public function htmlspecialchars($str, $flags = ENT_COMPAT, $encode = '')
6634
    {
6635
        $this->loadExtension('PHPCOMPAT');
6636
        return $this->phpcompat->htmlspecialchars($str, $flags, $encode);
6637
    }
6638
6639
    /**
6640
     * @param $string
6641
     * @param bool $returnData
6642
     * @return bool|mixed
6643
     */
6644
    public function isJson($string, $returnData = false)
6645
    {
6646
        $data = json_decode($string, true);
6647
        return (json_last_error() == JSON_ERROR_NONE) ? ($returnData ? $data : true) : false;
6648
    }
6649
6650
    /**
6651
     * @param $key
6652
     * @return array
6653
     */
6654
    public function splitKeyAndFilter($key)
6655
    {
6656
        if ($this->config['enable_filter'] == 1 && strpos($key, ':') !== false && stripos($key, '@FILE') !== 0) {
6657
            list($key, $modifiers) = explode(':', $key, 2);
6658
        } else {
6659
            $modifiers = false;
6660
        }
6661
6662
        $key = trim($key);
6663
        if ($modifiers !== false) {
6664
            $modifiers = trim($modifiers);
6665
        }
6666
6667
        return array($key, $modifiers);
6668
    }
6669
6670
    /**
6671
     * @param string $value
6672
     * @param bool $modifiers
6673
     * @param string $key
6674
     * @return string
6675
     */
6676
    public function applyFilter($value = '', $modifiers = false, $key = '')
6677
    {
6678
        if ($modifiers === false || $modifiers == 'raw') {
6679
            return $value;
6680
        }
6681
        if ($modifiers !== false) {
6682
            $modifiers = trim($modifiers);
6683
        }
6684
6685
        $this->loadExtension('MODIFIERS');
6686
        return $this->filter->phxFilter($key, $value, $modifiers);
0 ignored issues
show
Bug introduced by
It seems like $modifiers defined by parameter $modifiers on line 6676 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...
6687
    }
6688
6689
    // End of class.
6690
6691
6692
    /**
6693
     * Get Clean Query String
6694
     *
6695
     * Fixes the issue where passing an array into the q get variable causes errors
6696
     *
6697
     */
6698
    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...
6699
    {
6700
        $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...
6701
6702
        //Return null if the query doesn't exist
6703
        if (empty($q)) {
6704
            return null;
6705
        }
6706
6707
        //If we have a string, return it
6708
        if (is_string($q)) {
6709
            return $q;
6710
        }
6711
6712
        //If we have an array, return the first element
6713
        if (is_array($q)) {
6714
            return $q[0];
6715
        }
6716
    }
6717
6718
    /**
6719
     * @param string $title
6720
     * @param string $msg
6721
     * @param int $type
6722
     */
6723
    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...
6724
    {
6725
        if ($title === '') {
6726
            $title = 'no title';
6727
        }
6728
        if (is_array($msg)) {
6729
            $msg = '<pre>' . print_r($msg, true) . '</pre>';
6730
        } elseif ($msg === '') {
6731
            $msg = $_SERVER['REQUEST_URI'];
6732
        }
6733
        $this->logEvent(0, $type, $msg, $title);
6734
    }
6735
6736
}
6737