Completed
Push — develop ( d0bb9b...5613ed )
by Maxim
05:28
created

DocumentParser::_getCleanQueryString()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 19
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 8
nc 8
nop 0
dl 0
loc 19
rs 8.8571
c 0
b 0
f 0
1
<?php
2
/**
3
 *    MODX Document Parser
4
 *    Function: This class contains the main document parsing functions
5
 *
6
 */
7
if (!defined('E_DEPRECATED')) {
8
    define('E_DEPRECATED', 8192);
9
}
10
if (!defined('E_USER_DEPRECATED')) {
11
    define('E_USER_DEPRECATED', 16384);
12
}
13
14
class DocumentParser
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...
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
15
{
16
    /**
17
     * This is New evolution
18
     * @var string
19
     */
20
    public $apiVersion = '1.0.0';
21
22
    /**
23
     * db object
24
     * @var DBAPI
25
     * @see /manager/includes/extenders/ex_dbapi.inc.php
26
     * @example $this->loadExtension('DBAPI')
27
     */
28
    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...
29
30
    /**
31
     * @var MODxMailer
32
     * @see /manager/includes/extenders/ex_modxmailer.inc.php
33
     * @example $this->loadExtension('MODxMailer');
34
     */
35
    public $mail;
36
37
    /**
38
     * @var PHPCOMPAT
39
     * @see /manager/includes/extenders/ex_phpcompat.inc.php
40
     * @example $this->loadExtension('PHPCOMPAT');
41
     */
42
    public $phpcompat;
43
44
    /**
45
     * @var MODIFIERS
46
     * @see /manager/includes/extenders/ex_modifiers.inc.php
47
     * @example $this->loadExtension('MODIFIERS');
48
     */
49
    public $filter;
50
51
    /**
52
     * @var EXPORT_SITE
53
     * @see /manager/includes/extenders/ex_export_site.inc.php
54
     * @example $this->loadExtension('EXPORT_SITE');
55
     */
56
    public $export;
57
58
    /**
59
     * @var MakeTable
60
     * @see /manager/includes/extenders/ex_maketable.inc.php
61
     * @example $this->loadExtension('makeTable');
62
     */
63
    public $table;
64
65
    /**
66
     * @var ManagerAPI
67
     * @see /manager/includes/extenders/ex_managerapi.inc.php
68
     * @example $this->loadExtension('ManagerAPI');
69
     */
70
    public $manager;
71
72
    /**
73
     * @var PasswordHash
74
     * @see manager/includes/extenders/ex_phpass.inc.php
75
     * @example $this->loadExtension('phpass');
76
     */
77
    public $phpass;
78
79
    /**
80
     * event object
81
     * @var SystemEvent
82
     */
83
84
    public $event;
85
    /**
86
     * event object
87
     * @var SystemEvent
88
     */
89
    public $Event;
90
91
    /**
92
     * @var array
93
     */
94
    public $pluginEvent = array();
95
96
    /**
97
     * @var array
98
     */
99
    public $config = array();
100
    /**
101
     * @var array
102
     */
103
    public $dbConfig = array();
104
    public $configGlobal = null; // contains backup of settings overwritten by user-settings
105
    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...
106
    public $result;
107
    public $sql;
108
    public $table_prefix;
109
    public $debug = false;
110
    public $documentIdentifier;
111
    public $documentMethod;
112
    public $documentGenerated;
113
    public $documentContent;
114
    public $documentOutput;
115
    public $tstart;
116
    public $mstart;
117
    public $minParserPasses;
118
    public $maxParserPasses;
119
    public $documentObject;
120
    public $templateObject;
121
    public $snippetObjects;
122
    public $stopOnNotice = false;
123
    public $executedQueries;
124
    public $queryTime;
125
    public $currentSnippet;
126
    public $documentName;
127
    public $aliases;
128
    public $visitor;
129
    public $entrypage;
130
    public $documentListing;
131
    /**
132
     * feed the parser the execution start time
133
     * @var bool
134
     */
135
    public $dumpSnippets = false;
136
    public $snippetsCode;
137
    public $snippetsTime = array();
138
    public $chunkCache;
139
    public $snippetCache;
140
    public $contentTypes;
141
    public $dumpSQL = false;
142
    public $queryCode;
143
    public $virtualDir;
144
    public $placeholders;
145
    public $sjscripts = array();
146
    public $jscripts = array();
147
    public $loadedjscripts = array();
148
    public $documentMap;
149
    public $forwards = 3;
150
    public $error_reporting = 1;
151
    public $dumpPlugins = false;
152
    public $pluginsCode;
153
    public $pluginsTime = array();
154
    public $pluginCache = array();
155
    public $aliasListing;
156
    public $lockedElements = null;
157
    public $tmpCache = array();
158
    private $version = array();
159
    public $extensions = array();
160
    public $cacheKey = null;
161
    public $recentUpdate = 0;
162
    public $useConditional = false;
163
    protected $systemCacheKey = null;
164
    public $snipLapCount = 0;
165
    public $messageQuitCount;
166
    public $time;
167
    public $sid;
168
    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...
169
    public $decoded_request_uri;
170
    /**
171
     * @var OldFunctions
172
     */
173
    public $old;
174
175
    /**
176
     * Hold the class instance.
177
     * @var DocumentParser
178
     */
179
    private static $instance = null;
180
181
    /**
182
     * Document constructor
183
     * PUBLIC access for backward compatibility !!!!
184
     *
185
     * @return DocumentParser
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...
186
     */
187
    final 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...
188
    {
189
        if ($this->isLoggedIn()) {
190
            ini_set('display_errors', 1);
191
        }
192
        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...
193
        if (substr(PHP_OS, 0, 3) === 'WIN' && $database_server === 'localhost') {
194
            $database_server = '127.0.0.1';
195
        }
196
        $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...
197
        $this->dbConfig = &$this->db->config; // alias for backward compatibility
198
        // events
199
        $this->event = new SystemEvent();
200
        $this->Event = &$this->event; //alias for backward compatibility
201
        // set track_errors ini variable
202
        @ ini_set("track_errors", "1"); // enable error tracking in $php_errormsg
203
        $this->time = $_SERVER['REQUEST_TIME']; // for having global timestamp
204
205
        $this->q = self::_getCleanQueryString();
206
    }
207
208
    final private function __clone()
209
    {
210
    }
211
212
    /**
213
     * @return DocumentParser
214
     */
215
    public static function getInstance()
216
    {
217
        if (self::$instance === null) {
218
            self::$instance = new DocumentParser();
219
        }
220
221
        return self::$instance;
222
    }
223
224
    /**
225
     * @param $method_name
226
     * @param $arguments
227
     * @return mixed
228
     */
229
    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...
230
    {
231
        include_once(MODX_MANAGER_PATH . 'includes/extenders/deprecated.functions.inc.php');
232
        if (method_exists($this->old, $method_name)) {
233
            $error_type = 1;
234
        } else {
235
            $error_type = 3;
236
        }
237
238
        if (!isset($this->config['error_reporting']) || 1 < $this->config['error_reporting']) {
239
            if ($error_type == 1) {
240
                $title = 'Call deprecated method';
241
                $msg = $this->htmlspecialchars("\$modx->{$method_name}() is deprecated function");
242
            } else {
243
                $title = 'Call undefined method';
244
                $msg = $this->htmlspecialchars("\$modx->{$method_name}() is undefined function");
245
            }
246
            $info = debug_backtrace();
247
            $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...
248
            if (!empty($this->currentSnippet)) {
249
                $m[] = 'Snippet - ' . $this->currentSnippet;
250
            } elseif (!empty($this->event->activePlugin)) {
251
                $m[] = 'Plugin - ' . $this->event->activePlugin;
252
            }
253
            $m[] = $this->decoded_request_uri;
254
            $m[] = str_replace('\\', '/', $info[0]['file']) . '(line:' . $info[0]['line'] . ')';
255
            $msg = implode('<br />', $m);
256
            $this->logEvent(0, $error_type, $msg, $title);
257
        }
258
        if (method_exists($this->old, $method_name)) {
259
            return call_user_func_array(array($this->old, $method_name), $arguments);
260
        }
261
    }
262
263
    /**
264
     * @param string $connector
265
     * @return bool
266
     */
267
    public function checkSQLconnect($connector = 'db')
268
    {
269
        $flag = false;
270
        if (is_scalar($connector) && !empty($connector) && isset($this->{$connector}) && $this->{$connector} instanceof DBAPI) {
271
            $flag = (bool)$this->{$connector}->conn;
272
        }
273
274
        return $flag;
275
    }
276
277
    /**
278
     * Loads an extension from the extenders folder.
279
     * You can load any extension creating a boot file:
280
     * MODX_MANAGER_PATH."includes/extenders/ex_{$extname}.inc.php"
281
     * $extname - extension name in lowercase
282
     *
283
     * @param $extname
284
     * @param bool $reload
285
     * @return bool
286
     */
287
    public function loadExtension($extname, $reload = true)
288
    {
289
        $out = false;
290
        $flag = ($reload || !in_array($extname, $this->extensions));
291
        if ($this->checkSQLconnect('db') && $flag) {
292
            $evtOut = $this->invokeEvent('OnBeforeLoadExtension', array('name' => $extname, 'reload' => $reload));
293
            if (is_array($evtOut) && count($evtOut) > 0) {
294
                $out = array_pop($evtOut);
295
            }
296
        }
297
        if (!$out && $flag) {
298
            $extname = trim(str_replace(array('..', '/', '\\'), '', strtolower($extname)));
299
            $filename = MODX_MANAGER_PATH . "includes/extenders/ex_{$extname}.inc.php";
300
            $out = is_file($filename) ? include $filename : false;
301
        }
302
        if ($out && !in_array($extname, $this->extensions)) {
303
            $this->extensions[] = $extname;
304
        }
305
306
        return $out;
307
    }
308
309
    /**
310
     * Returns the current micro time
311
     *
312
     * @return float
313
     */
314
    public function getMicroTime()
315
    {
316
        list ($usec, $sec) = explode(' ', microtime());
317
318
        return ((float)$usec + (float)$sec);
319
    }
320
321
    /**
322
     * Redirect
323
     *
324
     * @param string $url
325
     * @param int $count_attempts
326
     * @param string $type $type
327
     * @param string $responseCode
328
     * @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...
329
     * @global string $base_url
330
     * @global string $site_url
331
     */
332
    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...
333
    {
334
        $header = '';
335
        if (empty ($url)) {
336
            return false;
337
        }
338
        if ($count_attempts == 1) {
339
            // append the redirect count string to the url
340
            $currentNumberOfRedirects = isset ($_REQUEST['err']) ? $_REQUEST['err'] : 0;
341
            if ($currentNumberOfRedirects > 3) {
342
                $this->messageQuit('Redirection attempt failed - please ensure the document you\'re trying to redirect to exists. <p>Redirection URL: <i>' . $url . '</i></p>');
343
            } else {
344
                $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...
345
                if (strpos($url, "?") > 0) {
346
                    $url .= "&err=$currentNumberOfRedirects";
347
                } else {
348
                    $url .= "?err=$currentNumberOfRedirects";
349
                }
350
            }
351
        }
352
        if ($type == 'REDIRECT_REFRESH') {
353
            $header = 'Refresh: 0;URL=' . $url;
354
        } elseif ($type == 'REDIRECT_META') {
355
            $header = '<META HTTP-EQUIV="Refresh" CONTENT="0; URL=' . $url . '" />';
356
            echo $header;
357
            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...
358
        } elseif ($type == 'REDIRECT_HEADER' || empty ($type)) {
359
            // check if url has /$base_url
360
            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...
361
            if (substr($url, 0, strlen($base_url)) == $base_url) {
362
                // append $site_url to make it work with Location:
363
                $url = $site_url . substr($url, strlen($base_url));
364
            }
365
            if (strpos($url, "\n") === false) {
366
                $header = 'Location: ' . $url;
367
            } else {
368
                $this->messageQuit('No newline allowed in redirect url.');
369
            }
370
        }
371
        if ($responseCode && (strpos($responseCode, '30') !== false)) {
372
            header($responseCode);
373
        }
374
375
        if (!empty($header)) {
376
            header($header);
377
        }
378
379
        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...
380
    }
381
382
    /**
383
     * Forward to another page
384
     *
385
     * @param int|string $id
386
     * @param string $responseCode
387
     */
388
    public function sendForward($id, $responseCode = '')
389
    {
390
        if ($this->forwards > 0) {
391
            $this->forwards = $this->forwards - 1;
392
            $this->documentIdentifier = $id;
393
            $this->documentMethod = 'id';
394
            if ($responseCode) {
395
                header($responseCode);
396
            }
397
            $this->prepareResponse();
398
            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...
399
        } else {
400
            $this->messageQuit("Internal Server Error id={$id}");
401
            header('HTTP/1.0 500 Internal Server Error');
402
            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...
403
        }
404
    }
405
406
    /**
407
     * Redirect to the error page, by calling sendForward(). This is called for example when the page was not found.
408
     * @param bool $noEvent
409
     */
410
    public function sendErrorPage($noEvent = false)
411
    {
412
        $this->systemCacheKey = 'notfound';
413
        if (!$noEvent) {
414
            // invoke OnPageNotFound event
415
            $this->invokeEvent('OnPageNotFound');
416
        }
417
        $url = $this->config['error_page'] ? $this->config['error_page'] : $this->config['site_start'];
418
419
        $this->sendForward($url, 'HTTP/1.0 404 Not Found');
420
        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...
421
    }
422
423
    /**
424
     * @param bool $noEvent
425
     */
426
    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...
427
    {
428
        // invoke OnPageUnauthorized event
429
        $_REQUEST['refurl'] = $this->documentIdentifier;
430
        $this->systemCacheKey = 'unauth';
431
        if (!$noEvent) {
432
            $this->invokeEvent('OnPageUnauthorized');
433
        }
434
        if ($this->config['unauthorized_page']) {
435
            $unauthorizedPage = $this->config['unauthorized_page'];
436
        } elseif ($this->config['error_page']) {
437
            $unauthorizedPage = $this->config['error_page'];
438
        } else {
439
            $unauthorizedPage = $this->config['site_start'];
440
        }
441
        $this->sendForward($unauthorizedPage, 'HTTP/1.1 401 Unauthorized');
442
        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...
443
    }
444
445
    /**
446
     * Get MODX settings including, but not limited to, the system_settings table
447
     */
448
    public function getSettings()
449
    {
450
        if (!isset($this->config['site_name'])) {
451
            $this->recoverySiteCache();
452
        }
453
454
        // setup default site id - new installation should generate a unique id for the site.
455
        if (!isset($this->config['site_id'])) {
456
            $this->config['site_id'] = "MzGeQ2faT4Dw06+U49x3";
457
        }
458
459
        // store base_url and base_path inside config array
460
        $this->config['base_url'] = MODX_BASE_URL;
461
        $this->config['base_path'] = MODX_BASE_PATH;
462
        $this->config['site_url'] = MODX_SITE_URL;
463
        $this->config['valid_hostnames'] = MODX_SITE_HOSTNAMES;
464
        $this->config['site_manager_url'] = MODX_MANAGER_URL;
465
        $this->config['site_manager_path'] = MODX_MANAGER_PATH;
466
        $this->error_reporting = $this->config['error_reporting'];
467
        $this->config['filemanager_path'] = str_replace('[(base_path)]', MODX_BASE_PATH,
468
            $this->config['filemanager_path']);
469
        $this->config['rb_base_dir'] = str_replace('[(base_path)]', MODX_BASE_PATH, $this->config['rb_base_dir']);
470
471
        if (!isset($this->config['enable_at_syntax'])) {
472
            $this->config['enable_at_syntax'] = 1;
473
        } // @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...
474
475
        // now merge user settings into evo-configuration
476
        $this->getUserSettings();
477
    }
478
479
    private function recoverySiteCache()
480
    {
481
        $site_cache_dir = MODX_BASE_PATH . $this->getCacheFolder();
482
        $site_cache_path = $site_cache_dir . 'siteCache.idx.php';
483
484
        if (is_file($site_cache_path)) {
485
            include($site_cache_path);
486
        }
487
        if (isset($this->config['site_name'])) {
488
            return;
489
        }
490
491
        include_once(MODX_MANAGER_PATH . 'processors/cache_sync.class.processor.php');
492
        $cache = new synccache();
493
        $cache->setCachepath($site_cache_dir);
494
        $cache->setReport(false);
495
        $cache->buildCache($this);
496
497
        clearstatcache();
498
        if (is_file($site_cache_path)) {
499
            include($site_cache_path);
500
        }
501
        if (isset($this->config['site_name'])) {
502
            return;
503
        }
504
505
        $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...
506
        while ($row = $this->db->getRow($rs)) {
507
            $this->config[$row['setting_name']] = $row['setting_value'];
508
        }
509
510
        if (!$this->config['enable_filter']) {
511
            return;
512
        }
513
514
        $where = "plugincode LIKE '%phx.parser.class.inc.php%OnParseDocument();%' AND disabled != 1";
515
        $rs = $this->db->select('id', '[+prefix+]site_plugins', $where);
516
        if ($this->db->getRecordCount($rs)) {
517
            $this->config['enable_filter'] = '0';
518
        }
519
    }
520
521
    /**
522
     * Get user settings and merge into MODX configuration
523
     * @return array
524
     */
525
    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...
526
    {
527
        $tbl_web_user_settings = $this->getFullTableName('web_user_settings');
528
        $tbl_user_settings = $this->getFullTableName('user_settings');
529
530
        // load user setting if user is logged in
531
        $usrSettings = array();
532
        if ($id = $this->getLoginUserID()) {
533
            $usrType = $this->getLoginUserType();
534
            if (isset ($usrType) && $usrType == 'manager') {
535
                $usrType = 'mgr';
536
            }
537
538
            if ($usrType == 'mgr' && $this->isBackend()) {
539
                // invoke the OnBeforeManagerPageInit event, only if in backend
540
                $this->invokeEvent("OnBeforeManagerPageInit");
541
            }
542
543
            if (isset ($_SESSION[$usrType . 'UsrConfigSet'])) {
544
                $usrSettings = &$_SESSION[$usrType . 'UsrConfigSet'];
545
            } else {
546
                if ($usrType == 'web') {
547
                    $from = $tbl_web_user_settings;
548
                    $where = "webuser='{$id}'";
549
                } else {
550
                    $from = $tbl_user_settings;
551
                    $where = "user='{$id}'";
552
                }
553
554
                $which_browser_default = $this->configGlobal['which_browser'] ? $this->configGlobal['which_browser'] : $this->config['which_browser'];
555
556
                $result = $this->db->select('setting_name, setting_value', $from, $where);
557
                while ($row = $this->db->getRow($result)) {
558 View Code Duplication
                    if ($row['setting_name'] == 'which_browser' && $row['setting_value'] == 'default') {
559
                        $row['setting_value'] = $which_browser_default;
560
                    }
561
                    $usrSettings[$row['setting_name']] = $row['setting_value'];
562
                }
563
                if (isset ($usrType)) {
564
                    $_SESSION[$usrType . 'UsrConfigSet'] = $usrSettings;
565
                } // store user settings in session
566
            }
567
        }
568
        if ($this->isFrontend() && $mgrid = $this->getLoginUserID('mgr')) {
569
            $musrSettings = array();
570
            if (isset ($_SESSION['mgrUsrConfigSet'])) {
571
                $musrSettings = &$_SESSION['mgrUsrConfigSet'];
572
            } else {
573
                if ($result = $this->db->select('setting_name, setting_value', $tbl_user_settings, "user='{$mgrid}'")) {
574
                    while ($row = $this->db->getRow($result)) {
575
                        $musrSettings[$row['setting_name']] = $row['setting_value'];
576
                    }
577
                    $_SESSION['mgrUsrConfigSet'] = $musrSettings; // store user settings in session
578
                }
579
            }
580
            if (!empty ($musrSettings)) {
581
                $usrSettings = array_merge($musrSettings, $usrSettings);
582
            }
583
        }
584
        // save global values before overwriting/merging array
585
        foreach ($usrSettings as $param => $value) {
586
            if (isset($this->config[$param])) {
587
                $this->configGlobal[$param] = $this->config[$param];
588
            }
589
        }
590
591
        $this->config = array_merge($this->config, $usrSettings);
592
        $this->config['filemanager_path'] = str_replace('[(base_path)]', MODX_BASE_PATH,
593
            $this->config['filemanager_path']);
594
        $this->config['rb_base_dir'] = str_replace('[(base_path)]', MODX_BASE_PATH, $this->config['rb_base_dir']);
595
596
        return $usrSettings;
597
    }
598
599
    /**
600
     * Returns the document identifier of the current request
601
     *
602
     * @param string $method id and alias are allowed
603
     * @return int
604
     */
605
    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...
606
    {
607
        // function to test the query and find the retrieval method
608
        if ($method === 'alias') {
609
            return $this->db->escape($_REQUEST['q']);
610
        }
611
612
        $id_ = filter_input(INPUT_GET, 'id');
613
        if ($id_) {
614
            if (preg_match('@^[1-9][0-9]*$@', $id_)) {
615
                return $id_;
616
            } else {
617
                $this->sendErrorPage();
618
            }
619
        } elseif (strpos($_SERVER['REQUEST_URI'], 'index.php/') !== false) {
620
            $this->sendErrorPage();
621
        } else {
622
            return $this->config['site_start'];
623
        }
624
    }
625
626
    /**
627
     * Check for manager or webuser login session since v1.2
628
     *
629
     * @param string $context
630
     * @return bool
631
     */
632
    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...
633
    {
634
        if (substr($context, 0, 1) == 'm') {
635
            $_ = '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...
636
        } else {
637
            $_ = 'webValidated';
638
        }
639
640
        if (MODX_CLI || (isset($_SESSION[$_]) && !empty($_SESSION[$_]))) {
0 ignored issues
show
Coding Style introduced by
The if-else statement can be simplified to return (bool) (MODX_CLI ...!empty($_SESSION[$_]));.
Loading history...
641
            return true;
642
        } else {
643
            return false;
644
        }
645
    }
646
647
    /**
648
     * Check for manager login session
649
     *
650
     * @return boolean
651
     */
652
    public function checkSession()
653
    {
654
        return $this->isLoggedin();
655
    }
656
657
    /**
658
     * Checks, if a the result is a preview
659
     *
660
     * @return boolean
661
     */
662
    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...
663
    {
664
        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...
665
            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...
666
                return true;
667
            } else {
668
                return false;
669
            }
670
        } else {
671
            return false;
672
        }
673
    }
674
675
    /**
676
     * check if site is offline
677
     *
678
     * @return boolean
679
     */
680
    public function checkSiteStatus()
681
    {
682
        if ($this->config['site_status']) {
683
            return true;
684
        }  // site online
685
        elseif ($this->isLoggedin()) {
686
            return true;
687
        }  // site offline but launched via the manager
688
        else {
689
            return false;
690
        } // site is offline
691
    }
692
693
    /**
694
     * Create a 'clean' document identifier with path information, friendly URL suffix and prefix.
695
     *
696
     * @param string $qOrig
697
     * @return string
698
     */
699
    public function cleanDocumentIdentifier($qOrig)
700
    {
701
        if (!$qOrig) {
702
            $qOrig = $this->config['site_start'];
703
        }
704
        $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...
705
706
        $pre = $this->config['friendly_url_prefix'];
707
        $suf = $this->config['friendly_url_suffix'];
708
        $pre = preg_quote($pre, '/');
709
        $suf = preg_quote($suf, '/');
710 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...
711
            $q = $_[1];
712
        }
713 View Code Duplication
        if ($suf && preg_match('@(.*)' . $suf . '$@', $q, $_)) {
714
            $q = $_[1];
715
        }
716
717
        /* First remove any / before or after */
718
        $q = trim($q, '/');
719
720
        /* Save path if any */
721
        /* FS#476 and FS#308: only return virtualDir if friendly paths are enabled */
722
        if ($this->config['use_alias_path'] == 1) {
723
            $_ = strrpos($q, '/');
724
            $this->virtualDir = $_ !== false ? substr($q, 0, $_) : '';
725
            if ($_ !== false) {
726
                $q = preg_replace('@.*/@', '', $q);
727
            }
728
        } else {
729
            $this->virtualDir = '';
730
        }
731
732
        if (preg_match('@^[1-9][0-9]*$@',
733
                $q) && !isset($this->documentListing[$q])) { /* we got an ID returned, check to make sure it's not an alias */
734
            /* FS#476 and FS#308: check that id is valid in terms of virtualDir structure */
735
            if ($this->config['use_alias_path'] == 1) {
736
                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,
737
                                $this->getChildIds($this->documentListing[$this->virtualDir],
738
                                    1))) || ($this->virtualDir == '' && in_array($q, $this->getChildIds(0, 1))))) {
739
                    $this->documentMethod = 'id';
740
741
                    return $q;
742
                } else { /* not a valid id in terms of virtualDir, treat as alias */
743
                    $this->documentMethod = 'alias';
744
745
                    return $q;
746
                }
747
            } else {
748
                $this->documentMethod = 'id';
749
750
                return $q;
751
            }
752
        } else { /* we didn't get an ID back, so instead we assume it's an alias */
753
            if ($this->config['friendly_alias_urls'] != 1) {
754
                $q = $qOrig;
755
            }
756
            $this->documentMethod = 'alias';
757
758
            return $q;
759
        }
760
    }
761
762
    /**
763
     * @return string
764
     */
765
    public function getCacheFolder()
766
    {
767
        return "assets/cache/";
768
    }
769
770
    /**
771
     * @param $key
772
     * @return string
773
     */
774
    public function getHashFile($key)
775
    {
776
        return $this->getCacheFolder() . "docid_" . $key . ".pageCache.php";
777
    }
778
779
    /**
780
     * @param $id
781
     * @return array|mixed|null|string
782
     */
783
    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...
784
    {
785
        $hash = $id;
786
        $tmp = null;
787
        $params = array();
788
        if (!empty($this->systemCacheKey)) {
789
            $hash = $this->systemCacheKey;
790
        } else {
791
            if (!empty($_GET)) {
792
                // Sort GET parameters so that the order of parameters on the HTTP request don't affect the generated cache ID.
793
                $params = $_GET;
794
                ksort($params);
795
                $hash .= '_' . md5(http_build_query($params));
796
            }
797
        }
798
        $evtOut = $this->invokeEvent("OnMakePageCacheKey", array("hash" => $hash, "id" => $id, 'params' => $params));
799
        if (is_array($evtOut) && count($evtOut) > 0) {
800
            $tmp = array_pop($evtOut);
801
        }
802
803
        return empty($tmp) ? $hash : $tmp;
804
    }
805
806
    /**
807
     * @param $id
808
     * @param bool $loading
809
     * @return string
810
     */
811
    public function checkCache($id, $loading = false)
812
    {
813
        return $this->getDocumentObjectFromCache($id, $loading);
814
    }
815
816
    /**
817
     * Check the cache for a specific document/resource
818
     *
819
     * @param int $id
820
     * @param bool $loading
821
     * @return string
822
     */
823
    public function getDocumentObjectFromCache($id, $loading = false)
824
    {
825
        $key = ($this->config['cache_type'] == 2) ? $this->makePageCacheKey($id) : $id;
826
        if ($loading) {
827
            $this->cacheKey = $key;
828
        }
829
830
        $cache_path = $this->getHashFile($key);
831
832
        if (!is_file($cache_path)) {
833
            $this->documentGenerated = 1;
834
835
            return '';
836
        }
837
        $content = file_get_contents($cache_path, false);
838
        if (substr($content, 0, 5) === '<?php') {
839
            $content = substr($content, strpos($content, '?>') + 2);
840
        } // remove php header
841
        $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...
842
        if (count($a) == 1) {
843
            $result = $a[0];
844
        } // return only document content
845
        else {
846
            $docObj = unserialize($a[0]); // rebuild document object
847
            // check page security
848
            if ($docObj['privateweb'] && isset ($docObj['__MODxDocGroups__'])) {
849
                $pass = false;
850
                $usrGrps = $this->getUserDocGroups();
851
                $docGrps = explode(',', $docObj['__MODxDocGroups__']);
852
                // check is user has access to doc groups
853
                if (is_array($usrGrps)) {
854
                    foreach ($usrGrps as $k => $v) {
855
                        if (!in_array($v, $docGrps)) {
856
                            continue;
857
                        }
858
                        $pass = true;
859
                        break;
860
                    }
861
                }
862
                // diplay error pages if user has no access to cached doc
863
                if (!$pass) {
864
                    if ($this->config['unauthorized_page']) {
865
                        // check if file is not public
866
                        $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...
867
                        $total = $this->db->getValue($rs);
868
                    } else {
869
                        $total = 0;
870
                    }
871
872
                    if ($total > 0) {
873
                        $this->sendUnauthorizedPage();
874
                    } else {
875
                        $this->sendErrorPage();
876
                    }
877
878
                    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...
879
                }
880
            }
881
            // Grab the Scripts
882
            if (isset($docObj['__MODxSJScripts__'])) {
883
                $this->sjscripts = $docObj['__MODxSJScripts__'];
884
            }
885
            if (isset($docObj['__MODxJScripts__'])) {
886
                $this->jscripts = $docObj['__MODxJScripts__'];
887
            }
888
889
            // Remove intermediate variables
890
            unset($docObj['__MODxDocGroups__'], $docObj['__MODxSJScripts__'], $docObj['__MODxJScripts__']);
891
892
            $this->documentObject = $docObj;
893
894
            $result = $a[1]; // return document content
895
        }
896
897
        $this->documentGenerated = 0;
898
        // invoke OnLoadWebPageCache  event
899
        $this->documentContent = $result;
900
        $this->invokeEvent('OnLoadWebPageCache');
901
902
        return $result;
903
    }
904
905
    /**
906
     * Final processing and output of the document/resource.
907
     *
908
     * - runs uncached snippets
909
     * - add javascript to <head>
910
     * - removes unused placeholders
911
     * - converts URL tags [~...~] to URLs
912
     *
913
     * @param boolean $noEvent Default: false
914
     */
915
    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...
916
    {
917
        $this->documentOutput = $this->documentContent;
918
919
        if ($this->documentGenerated == 1 && $this->documentObject['cacheable'] == 1 && $this->documentObject['type'] == 'document' && $this->documentObject['published'] == 1) {
920
            if (!empty($this->sjscripts)) {
921
                $this->documentObject['__MODxSJScripts__'] = $this->sjscripts;
922
            }
923
            if (!empty($this->jscripts)) {
924
                $this->documentObject['__MODxJScripts__'] = $this->jscripts;
925
            }
926
        }
927
928
        // check for non-cached snippet output
929
        if (strpos($this->documentOutput, '[!') > -1) {
930
            $this->recentUpdate = $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'];
931
932
            $this->documentOutput = str_replace('[!', '[[', $this->documentOutput);
933
            $this->documentOutput = str_replace('!]', ']]', $this->documentOutput);
934
935
            // Parse document source
936
            $this->documentOutput = $this->parseDocumentSource($this->documentOutput);
937
        }
938
939
        // Moved from prepareResponse() by sirlancelot
940
        // Insert Startup jscripts & CSS scripts into template - template must have a <head> tag
941
        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...
942
            // change to just before closing </head>
943
            // $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...
944
            $this->documentOutput = preg_replace("/(<\/head>)/i", $js . "\n\\1", $this->documentOutput);
945
        }
946
947
        // Insert jscripts & html block into template - template must have a </body> tag
948
        if ($js = $this->getRegisteredClientScripts()) {
949
            $this->documentOutput = preg_replace("/(<\/body>)/i", $js . "\n\\1", $this->documentOutput);
950
        }
951
        // End fix by sirlancelot
952
953
        $this->documentOutput = $this->cleanUpMODXTags($this->documentOutput);
954
955
        $this->documentOutput = $this->rewriteUrls($this->documentOutput);
956
957
        // send out content-type and content-disposition headers
958
        if (IN_PARSER_MODE == "true") {
959
            $type = !empty ($this->contentTypes[$this->documentIdentifier]) ? $this->contentTypes[$this->documentIdentifier] : "text/html";
960
            header('Content-Type: ' . $type . '; charset=' . $this->config['modx_charset']);
961
            //            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...
962
            //                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...
963
            if (!$this->checkPreview() && $this->documentObject['content_dispo'] == 1) {
964
                if ($this->documentObject['alias']) {
965
                    $name = $this->documentObject['alias'];
966
                } else {
967
                    // strip title of special characters
968
                    $name = $this->documentObject['pagetitle'];
969
                    $name = strip_tags($name);
970
                    $name = $this->cleanUpMODXTags($name);
971
                    $name = strtolower($name);
972
                    $name = preg_replace('/&.+?;/', '', $name); // kill entities
973
                    $name = preg_replace('/[^\.%a-z0-9 _-]/', '', $name);
974
                    $name = preg_replace('/\s+/', '-', $name);
975
                    $name = preg_replace('|-+|', '-', $name);
976
                    $name = trim($name, '-');
977
                }
978
                $header = 'Content-Disposition: attachment; filename=' . $name;
979
                header($header);
980
            }
981
        }
982
        $this->setConditional();
983
984
        $stats = $this->getTimerStats($this->tstart);
985
986
        $out =& $this->documentOutput;
987
        $out = str_replace("[^q^]", $stats['queries'], $out);
988
        $out = str_replace("[^qt^]", $stats['queryTime'], $out);
989
        $out = str_replace("[^p^]", $stats['phpTime'], $out);
990
        $out = str_replace("[^t^]", $stats['totalTime'], $out);
991
        $out = str_replace("[^s^]", $stats['source'], $out);
992
        $out = str_replace("[^m^]", $stats['phpMemory'], $out);
993
        //$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...
994
995
        // invoke OnWebPagePrerender event
996
        if (!$noEvent) {
997
            $evtOut = $this->invokeEvent('OnWebPagePrerender', array('documentOutput' => $this->documentOutput));
998
            if (is_array($evtOut) && count($evtOut) > 0) {
999
                $this->documentOutput = $evtOut['0'];
1000
            }
1001
        }
1002
1003
        $this->documentOutput = $this->removeSanitizeSeed($this->documentOutput);
1004
1005
        if (strpos($this->documentOutput, '\{') !== false) {
1006
            $this->documentOutput = $this->RecoveryEscapedTags($this->documentOutput);
1007
        } elseif (strpos($this->documentOutput, '\[') !== false) {
1008
            $this->documentOutput = $this->RecoveryEscapedTags($this->documentOutput);
1009
        }
1010
1011
        echo $this->documentOutput;
1012
1013
        if ($this->dumpSQL) {
1014
            echo $this->queryCode;
1015
        }
1016
        if ($this->dumpSnippets) {
1017
            $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...
1018
            $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...
1019
            foreach ($this->snippetsTime as $s => $v) {
1020
                $t = $v['time'];
1021
                $sname = $v['sname'];
1022
                $sc .= sprintf("%s. %s (%s)<br>", $s, $sname, sprintf("%2.2f ms", $t)); // currentSnippet
1023
                $tt += $t;
1024
            }
1025
            echo "<fieldset><legend><b>Snippets</b> (" . count($this->snippetsTime) . " / " . sprintf("%2.2f ms",
1026
                    $tt) . ")</legend>{$sc}</fieldset><br />";
1027
            echo $this->snippetsCode;
1028
        }
1029
        if ($this->dumpPlugins) {
1030
            $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...
1031
            $tt = 0;
1032
            foreach ($this->pluginsTime as $s => $t) {
1033
                $ps .= "$s (" . sprintf("%2.2f ms", $t * 1000) . ")<br>";
1034
                $tt += $t;
1035
            }
1036
            echo "<fieldset><legend><b>Plugins</b> (" . count($this->pluginsTime) . " / " . sprintf("%2.2f ms",
1037
                    $tt * 1000) . ")</legend>{$ps}</fieldset><br />";
1038
            echo $this->pluginsCode;
1039
        }
1040
1041
        ob_end_flush();
1042
    }
1043
1044
    /**
1045
     * @param $contents
1046
     * @return mixed
1047
     */
1048
    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...
1049
    {
1050
        list($sTags, $rTags) = $this->getTagsForEscape();
1051
1052
        return str_replace($rTags, $sTags, $contents);
1053
    }
1054
1055
    /**
1056
     * @param string $tags
1057
     * @return array[]
1058
     */
1059
    public function getTagsForEscape($tags = '{{,}},[[,]],[!,!],[*,*],[(,)],[+,+],[~,~],[^,^]')
1060
    {
1061
        $srcTags = explode(',', $tags);
1062
        $repTags = array();
1063
        foreach ($srcTags as $tag) {
1064
            $repTags[] = '\\' . $tag[0] . '\\' . $tag[1];
1065
        }
1066
1067
        return array($srcTags, $repTags);
1068
    }
1069
1070
    /**
1071
     * @param $tstart
1072
     * @return array
1073
     */
1074
    public function getTimerStats($tstart)
1075
    {
1076
        $stats = array();
1077
1078
        $stats['totalTime'] = ($this->getMicroTime() - $tstart);
1079
        $stats['queryTime'] = $this->queryTime;
1080
        $stats['phpTime'] = $stats['totalTime'] - $stats['queryTime'];
1081
1082
        $stats['queryTime'] = sprintf("%2.4f s", $stats['queryTime']);
1083
        $stats['totalTime'] = sprintf("%2.4f s", $stats['totalTime']);
1084
        $stats['phpTime'] = sprintf("%2.4f s", $stats['phpTime']);
1085
        $stats['source'] = $this->documentGenerated == 1 ? "database" : "cache";
1086
        $stats['queries'] = isset ($this->executedQueries) ? $this->executedQueries : 0;
1087
        $stats['phpMemory'] = (memory_get_peak_usage(true) / 1024 / 1024) . " mb";
1088
1089
        return $stats;
1090
    }
1091
1092
    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...
1093
    {
1094
        if (!empty($_POST) || (defined('MODX_API_MODE') && MODX_API_MODE) || $this->getLoginUserID('mgr') || !$this->useConditional || empty($this->recentUpdate)) {
1095
            return;
1096
        }
1097
        $last_modified = gmdate('D, d M Y H:i:s T', $this->recentUpdate);
1098
        $etag = md5($last_modified);
1099
        $HTTP_IF_MODIFIED_SINCE = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? $_SERVER['HTTP_IF_MODIFIED_SINCE'] : false;
1100
        $HTTP_IF_NONE_MATCH = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? $_SERVER['HTTP_IF_NONE_MATCH'] : false;
1101
        header('Pragma: no-cache');
1102
1103
        if ($HTTP_IF_MODIFIED_SINCE == $last_modified || strpos($HTTP_IF_NONE_MATCH, $etag) !== false) {
1104
            header('HTTP/1.1 304 Not Modified');
1105
            header('Content-Length: 0');
1106
            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...
1107
        } else {
1108
            header("Last-Modified: {$last_modified}");
1109
            header("ETag: '{$etag}'");
1110
        }
1111
    }
1112
1113
    /**
1114
     * Checks the publish state of page
1115
     */
1116
    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...
1117
    {
1118
        $cacheRefreshTime = 0;
1119
        $recent_update = 0;
1120
        @include(MODX_BASE_PATH . $this->getCacheFolder() . 'sitePublishing.idx.php');
1121
        $this->recentUpdate = $recent_update;
1122
1123
        $timeNow = $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'];
1124
        if ($timeNow < $cacheRefreshTime || $cacheRefreshTime == 0) {
1125
            return;
1126
        }
1127
1128
        // now, check for documents that need publishing
1129
        $field = array('published' => 1, 'publishedon' => $timeNow);
1130
        $where = "pub_date <= {$timeNow} AND pub_date!=0 AND published=0";
1131
        $this->db->update($field, '[+prefix+]site_content', $where);
1132
1133
        // now, check for documents that need un-publishing
1134
        $field = array('published' => 0, 'publishedon' => 0);
1135
        $where = "unpub_date <= {$timeNow} AND unpub_date!=0 AND published=1";
1136
        $this->db->update($field, '[+prefix+]site_content', $where);
1137
1138
        $this->recentUpdate = $timeNow;
1139
1140
        // clear the cache
1141
        $this->clearCache('full');
1142
    }
1143
1144
    public function checkPublishStatus()
1145
    {
1146
        $this->updatePubStatus();
1147
    }
1148
1149
    /**
1150
     * Final jobs.
1151
     *
1152
     * - cache page
1153
     */
1154
    public function postProcess()
1155
    {
1156
        // if the current document was generated, cache it!
1157
        $cacheable = ($this->config['enable_cache'] && $this->documentObject['cacheable']) ? 1 : 0;
1158
        if ($cacheable && $this->documentGenerated && $this->documentObject['type'] == 'document' && $this->documentObject['published']) {
1159
            // invoke OnBeforeSaveWebPageCache event
1160
            $this->invokeEvent("OnBeforeSaveWebPageCache");
1161
1162
            if (!empty($this->cacheKey) && is_scalar($this->cacheKey)) {
1163
                // get and store document groups inside document object. Document groups will be used to check security on cache pages
1164
                $where = "document='{$this->documentIdentifier}'";
1165
                $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...
1166
                $docGroups = $this->db->getColumn('document_group', $rs);
1167
1168
                // Attach Document Groups and Scripts
1169
                if (is_array($docGroups)) {
1170
                    $this->documentObject['__MODxDocGroups__'] = implode(",", $docGroups);
1171
                }
1172
1173
                $docObjSerial = serialize($this->documentObject);
1174
                $cacheContent = $docObjSerial . "<!--__MODxCacheSpliter__-->" . $this->documentContent;
1175
                $page_cache_path = MODX_BASE_PATH . $this->getHashFile($this->cacheKey);
1176
                file_put_contents($page_cache_path, "<?php die('Unauthorized access.'); ?>$cacheContent");
1177
            }
1178
        }
1179
1180
        // Useful for example to external page counters/stats packages
1181
        $this->invokeEvent('OnWebPageComplete');
1182
1183
        // end post processing
1184
    }
1185
1186
    /**
1187
     * @param $content
1188
     * @param string $left
1189
     * @param string $right
1190
     * @return array
1191
     */
1192
    public function getTagsFromContent($content, $left = '[+', $right = '+]')
1193
    {
1194
        $_ = $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...
1195
        if (empty($_)) {
1196
            return array();
1197
        }
1198
        foreach ($_ as $v) {
1199
            $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...
1200
            $tags[1][] = $v;
1201
        }
1202
1203
        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...
1204
    }
1205
1206
    /**
1207
     * @param $content
1208
     * @param string $left
1209
     * @param string $right
1210
     * @return array
1211
     */
1212
    public function _getTagsFromContent($content, $left = '[+', $right = '+]')
1213
    {
1214
        if (strpos($content, $left) === false) {
1215
            return array();
1216
        }
1217
        $spacer = md5('<<<EVO>>>');
1218
        if ($left === '{{' && strpos($content, ';}}') !== false) {
1219
            $content = str_replace(';}}', sprintf(';}%s}', $spacer), $content);
1220
        }
1221
        if ($left === '{{' && strpos($content, '{{}}') !== false) {
1222
            $content = str_replace('{{}}', sprintf('{%$1s{}%$1s}', $spacer), $content);
1223
        }
1224
        if ($left === '[[' && strpos($content, ']]]]') !== false) {
1225
            $content = str_replace(']]]]', sprintf(']]%s]]', $spacer), $content);
1226
        }
1227
        if ($left === '[[' && strpos($content, ']]]') !== false) {
1228
            $content = str_replace(']]]', sprintf(']%s]]', $spacer), $content);
1229
        }
1230
1231
        $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...
1232
        $pos[']]>'] = strpos($content, ']]>');
1233
1234
        if ($pos['<![CDATA['] !== false && $pos[']]>'] !== false) {
1235
            $content = substr($content, 0, $pos['<![CDATA[']) . substr($content, $pos[']]>'] + 3);
1236
        }
1237
1238
        $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...
1239
        $piece = array();
1240
        foreach ($lp as $lc => $lv) {
1241
            if ($lc !== 0) {
1242
                $piece[] = $left;
1243
            }
1244
            if (strpos($lv, $right) === false) {
1245
                $piece[] = $lv;
1246
            } else {
1247
                $rp = explode($right, $lv);
1248
                foreach ($rp as $rc => $rv) {
1249
                    if ($rc !== 0) {
1250
                        $piece[] = $right;
1251
                    }
1252
                    $piece[] = $rv;
1253
                }
1254
            }
1255
        }
1256
        $lc = 0;
1257
        $rc = 0;
1258
        $fetch = '';
1259
        $tags = array();
1260
        foreach ($piece as $v) {
1261
            if ($v === $left) {
1262
                if (0 < $lc) {
1263
                    $fetch .= $left;
1264
                }
1265
                $lc++;
1266
            } elseif ($v === $right) {
1267
                if ($lc === 0) {
1268
                    continue;
1269
                }
1270
                $rc++;
1271
                if ($lc === $rc) {
1272
                    // #1200 Enable modifiers in Wayfinder - add nested placeholders to $tags like for $fetch = "phx:input=`[+wf.linktext+]`:test"
1273
                    if (strpos($fetch, $left) !== false) {
1274
                        $nested = $this->_getTagsFromContent($fetch, $left, $right);
1275
                        foreach ($nested as $tag) {
1276
                            if (!in_array($tag, $tags)) {
1277
                                $tags[] = $tag;
1278
                            }
1279
                        }
1280
                    }
1281
1282
                    if (!in_array($fetch, $tags)) {  // Avoid double Matches
1283
                        $tags[] = $fetch; // Fetch
1284
                    };
1285
                    $fetch = ''; // and reset
1286
                    $lc = 0;
1287
                    $rc = 0;
1288
                } else {
1289
                    $fetch .= $right;
1290
                }
1291
            } else {
1292
                if (0 < $lc) {
1293
                    $fetch .= $v;
1294
                } else {
1295
                    continue;
1296
                }
1297
            }
1298
        }
1299
        foreach ($tags as $i => $tag) {
1300
            if (strpos($tag, $spacer) !== false) {
1301
                $tags[$i] = str_replace($spacer, '', $tag);
1302
            }
1303
        }
1304
1305
        return $tags;
1306
    }
1307
1308
    /**
1309
     * Merge content fields and TVs
1310
     *
1311
     * @param $content
1312
     * @param bool $ph
1313
     * @return string
1314
     * @internal param string $template
1315
     */
1316
    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...
1317
    {
1318 View Code Duplication
        if ($this->config['enable_at_syntax']) {
1319
            if (stripos($content, '<@LITERAL>') !== false) {
1320
                $content = $this->escapeLiteralTagsContent($content);
1321
            }
1322
        }
1323
        if (strpos($content, '[*') === false) {
1324
            return $content;
1325
        }
1326
        if (!isset($this->documentIdentifier)) {
1327
            return $content;
1328
        }
1329
        if (!isset($this->documentObject) || empty($this->documentObject)) {
1330
            return $content;
1331
        }
1332
1333
        if (!$ph) {
1334
            $ph = $this->documentObject;
1335
        }
1336
1337
        $matches = $this->getTagsFromContent($content, '[*', '*]');
1338
        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...
1339
            return $content;
1340
        }
1341
1342
        foreach ($matches[1] as $i => $key) {
1343
            if (strpos($key, '[+') !== false) {
1344
                continue;
1345
            } // 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...
1346
            if (substr($key, 0, 1) == '#') {
1347
                $key = substr($key, 1);
1348
            } // remove # for QuickEdit format
1349
1350
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1351
            if (strpos($key, '@') !== false) {
1352
                list($key, $context) = explode('@', $key, 2);
1353
            } else {
1354
                $context = false;
1355
            }
1356
1357
            // 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...
1358
            if ($context) {
1359
                $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...
1360
            } else {
1361
                $value = isset($ph[$key]) ? $ph[$key] : '';
1362
            }
1363
1364
            if (is_array($value)) {
1365
                include_once(MODX_MANAGER_PATH . 'includes/tmplvars.format.inc.php');
1366
                include_once(MODX_MANAGER_PATH . 'includes/tmplvars.commands.inc.php');
1367
                $value = getTVDisplayFormat($value[0], $value[1], $value[2], $value[3], $value[4]);
1368
            }
1369
1370
            $s = &$matches[0][$i];
1371
            if ($modifiers !== false) {
1372
                $value = $this->applyFilter($value, $modifiers, $key);
1373
            }
1374
1375 View Code Duplication
            if (strpos($content, $s) !== false) {
1376
                $content = str_replace($s, $value, $content);
1377
            } elseif ($this->debug) {
1378
                $this->addLog('mergeDocumentContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1379
            }
1380
        }
1381
1382
        return $content;
1383
    }
1384
1385
    /**
1386
     * @param $key
1387
     * @param bool|int $parent
1388
     * @return bool|mixed|string
1389
     */
1390
    public function _contextValue($key, $parent = false)
1391
    {
1392
        if (preg_match('/@\d+\/u/', $key)) {
1393
            $key = str_replace(array('@', '/u'), array('@u(', ')'), $key);
1394
        }
1395
        list($key, $str) = explode('@', $key, 2);
1396
1397
        if (strpos($str, '(')) {
1398
            list($context, $option) = explode('(', $str, 2);
1399
        } else {
1400
            list($context, $option) = array($str, false);
1401
        }
1402
1403
        if ($option) {
1404
            $option = trim($option, ')(\'"`');
1405
        }
1406
1407
        switch (strtolower($context)) {
1408
            case 'site_start':
1409
                $docid = $this->config['site_start'];
1410
                break;
1411
            case 'parent':
1412
            case 'p':
1413
                $docid = $parent;
1414
                if ($docid == 0) {
1415
                    $docid = $this->config['site_start'];
1416
                }
1417
                break;
1418
            case 'ultimateparent':
1419
            case 'uparent':
1420
            case 'up':
1421
            case 'u':
1422 View Code Duplication
                if (strpos($str, '(') !== false) {
1423
                    $top = substr($str, strpos($str, '('));
1424
                    $top = trim($top, '()"\'');
1425
                } else {
1426
                    $top = 0;
1427
                }
1428
                $docid = $this->getUltimateParentId($this->documentIdentifier, $top);
1429
                break;
1430
            case 'alias':
1431
                $str = substr($str, strpos($str, '('));
1432
                $str = trim($str, '()"\'');
1433
                $docid = $this->getIdFromAlias($str);
1434
                break;
1435 View Code Duplication
            case 'prev':
1436
                if (!$option) {
1437
                    $option = 'menuindex,ASC';
1438
                } elseif (strpos($option, ',') === false) {
1439
                    $option .= ',ASC';
1440
                }
1441
                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...
1442
                $children = $this->getActiveChildren($parent, $by, $dir);
1443
                $find = false;
1444
                $prev = false;
1445
                foreach ($children as $row) {
1446
                    if ($row['id'] == $this->documentIdentifier) {
1447
                        $find = true;
1448
                        break;
1449
                    }
1450
                    $prev = $row;
1451
                }
1452
                if ($find) {
1453
                    if (isset($prev[$key])) {
1454
                        return $prev[$key];
1455
                    } else {
1456
                        $docid = $prev['id'];
1457
                    }
1458
                } else {
1459
                    $docid = '';
1460
                }
1461
                break;
1462 View Code Duplication
            case 'next':
1463
                if (!$option) {
1464
                    $option = 'menuindex,ASC';
1465
                } elseif (strpos($option, ',') === false) {
1466
                    $option .= ',ASC';
1467
                }
1468
                list($by, $dir) = explode(',', $option, 2);
1469
                $children = $this->getActiveChildren($parent, $by, $dir);
1470
                $find = false;
1471
                $next = false;
1472
                foreach ($children as $row) {
1473
                    if ($find) {
1474
                        $next = $row;
1475
                        break;
1476
                    }
1477
                    if ($row['id'] == $this->documentIdentifier) {
1478
                        $find = true;
1479
                    }
1480
                }
1481
                if ($find) {
1482
                    if (isset($next[$key])) {
1483
                        return $next[$key];
1484
                    } else {
1485
                        $docid = $next['id'];
1486
                    }
1487
                } else {
1488
                    $docid = '';
1489
                }
1490
                break;
1491
            default:
1492
                $docid = $str;
1493
        }
1494
        if (preg_match('@^[1-9][0-9]*$@', $docid)) {
1495
            $value = $this->getField($key, $docid);
1496
        } else {
1497
            $value = '';
1498
        }
1499
1500
        return $value;
1501
    }
1502
1503
    /**
1504
     * Merge system settings
1505
     *
1506
     * @param $content
1507
     * @param bool|array $ph
1508
     * @return string
1509
     * @internal param string $template
1510
     */
1511
    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...
1512
    {
1513 View Code Duplication
        if ($this->config['enable_at_syntax']) {
1514
            if (stripos($content, '<@LITERAL>') !== false) {
1515
                $content = $this->escapeLiteralTagsContent($content);
1516
            }
1517
        }
1518
        if (strpos($content, '[(') === false) {
1519
            return $content;
1520
        }
1521
1522
        if (empty($ph)) {
1523
            $ph = $this->config;
1524
        }
1525
1526
        $matches = $this->getTagsFromContent($content, '[(', ')]');
1527
        if (empty($matches)) {
1528
            return $content;
1529
        }
1530
1531
        foreach ($matches[1] as $i => $key) {
1532
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1533
1534
            if (isset($ph[$key])) {
1535
                $value = $ph[$key];
1536
            } else {
1537
                continue;
1538
            }
1539
1540
            if ($modifiers !== false) {
1541
                $value = $this->applyFilter($value, $modifiers, $key);
1542
            }
1543
            $s = &$matches[0][$i];
1544 View Code Duplication
            if (strpos($content, $s) !== false) {
1545
                $content = str_replace($s, $value, $content);
1546
            } elseif ($this->debug) {
1547
                $this->addLog('mergeSettingsContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1548
            }
1549
        }
1550
1551
        return $content;
1552
    }
1553
1554
    /**
1555
     * Merge chunks
1556
     *
1557
     * @param string $content
1558
     * @param bool|array $ph
1559
     * @return string
1560
     */
1561
    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...
1562
    {
1563
        if ($this->config['enable_at_syntax']) {
1564
            if (strpos($content, '{{ ') !== false) {
1565
                $content = str_replace(array('{{ ', ' }}'), array('\{\{ ', ' \}\}'), $content);
1566
            }
1567
            if (stripos($content, '<@LITERAL>') !== false) {
1568
                $content = $this->escapeLiteralTagsContent($content);
1569
            }
1570
        }
1571
        if (strpos($content, '{{') === false) {
1572
            return $content;
1573
        }
1574
1575
        if (empty($ph)) {
1576
            $ph = $this->chunkCache;
1577
        }
1578
1579
        $matches = $this->getTagsFromContent($content, '{{', '}}');
1580
        if (empty($matches)) {
1581
            return $content;
1582
        }
1583
1584
        foreach ($matches[1] as $i => $key) {
1585
            $snip_call = $this->_split_snip_call($key);
1586
            $key = $snip_call['name'];
1587
            $params = $this->getParamsFromString($snip_call['params']);
1588
1589
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1590
1591
            if (!isset($ph[$key])) {
1592
                $ph[$key] = $this->getChunk($key);
1593
            }
1594
            $value = $ph[$key];
1595
1596
            if (is_null($value)) {
1597
                continue;
1598
            }
1599
1600
            $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 1587 can also be of type null; however, DocumentParser::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...
1601
            $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 1587 can also be of type null; however, DocumentParser::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...
1602
            if ($this->config['enable_at_syntax']) {
1603
                $value = $this->mergeConditionalTagsContent($value);
1604
            }
1605
            $value = $this->mergeDocumentContent($value);
1606
            $value = $this->mergeSettingsContent($value);
1607
            $value = $this->mergeChunkContent($value);
1608
1609
            if ($modifiers !== false) {
1610
                $value = $this->applyFilter($value, $modifiers, $key);
1611
            }
1612
1613
            $s = &$matches[0][$i];
1614 View Code Duplication
            if (strpos($content, $s) !== false) {
1615
                $content = str_replace($s, $value, $content);
1616
            } elseif ($this->debug) {
1617
                $this->addLog('mergeChunkContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1618
            }
1619
        }
1620
1621
        return $content;
1622
    }
1623
1624
    /**
1625
     * Merge placeholder values
1626
     *
1627
     * @param string $content
1628
     * @param bool|array $ph
1629
     * @return string
1630
     */
1631
    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...
1632
    {
1633
1634 View Code Duplication
        if ($this->config['enable_at_syntax']) {
1635
            if (stripos($content, '<@LITERAL>') !== false) {
1636
                $content = $this->escapeLiteralTagsContent($content);
1637
            }
1638
        }
1639
        if (strpos($content, '[+') === false) {
1640
            return $content;
1641
        }
1642
1643
        if (empty($ph)) {
1644
            $ph = $this->placeholders;
1645
        }
1646
1647
        if ($this->config['enable_at_syntax']) {
1648
            $content = $this->mergeConditionalTagsContent($content);
1649
        }
1650
1651
        $content = $this->mergeDocumentContent($content);
1652
        $content = $this->mergeSettingsContent($content);
1653
        $matches = $this->getTagsFromContent($content, '[+', '+]');
1654
        if (empty($matches)) {
1655
            return $content;
1656
        }
1657
        foreach ($matches[1] as $i => $key) {
1658
1659
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1660
1661
            if (isset($ph[$key])) {
1662
                $value = $ph[$key];
1663
            } elseif ($key === 'phx') {
1664
                $value = '';
1665
            } else {
1666
                continue;
1667
            }
1668
1669
            if ($modifiers !== false) {
1670
                $modifiers = $this->mergePlaceholderContent($modifiers);
1671
                $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...
1672
            }
1673
            $s = &$matches[0][$i];
1674 View Code Duplication
            if (strpos($content, $s) !== false) {
1675
                $content = str_replace($s, $value, $content);
1676
            } elseif ($this->debug) {
1677
                $this->addLog('mergePlaceholderContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1678
            }
1679
        }
1680
1681
        return $content;
1682
    }
1683
1684
    /**
1685
     * @param $content
1686
     * @param string $iftag
1687
     * @param string $elseiftag
1688
     * @param string $elsetag
1689
     * @param string $endiftag
1690
     * @return mixed|string
1691
     */
1692
    public function mergeConditionalTagsContent(
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...
1693
        $content,
1694
        $iftag = '<@IF:',
1695
        $elseiftag = '<@ELSEIF:',
1696
        $elsetag = '<@ELSE>',
1697
        $endiftag = '<@ENDIF>'
1698
    ) {
1699
        if (strpos($content, '@IF') !== false) {
1700
            $content = $this->_prepareCTag($content, $iftag, $elseiftag, $elsetag, $endiftag);
1701
        }
1702
1703
        if (strpos($content, $iftag) === false) {
1704
            return $content;
1705
        }
1706
1707
        $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...
1708
        $content = str_replace(array('<?php', '<?=', '<?', '?>'), array("{$sp}b", "{$sp}p", "{$sp}s", "{$sp}e"),
1709
            $content);
1710
1711
        $pieces = explode('<@IF:', $content);
1712 View Code Duplication
        foreach ($pieces as $i => $split) {
1713
            if ($i === 0) {
1714
                $content = $split;
1715
                continue;
1716
            }
1717
            list($cmd, $text) = explode('>', $split, 2);
1718
            $cmd = str_replace("'", "\'", $cmd);
1719
            $content .= "<?php if(\$this->_parseCTagCMD('" . $cmd . "')): ?>";
1720
            $content .= $text;
1721
        }
1722
        $pieces = explode('<@ELSEIF:', $content);
1723 View Code Duplication
        foreach ($pieces as $i => $split) {
1724
            if ($i === 0) {
1725
                $content = $split;
1726
                continue;
1727
            }
1728
            list($cmd, $text) = explode('>', $split, 2);
1729
            $cmd = str_replace("'", "\'", $cmd);
1730
            $content .= "<?php elseif(\$this->_parseCTagCMD('" . $cmd . "')): ?>";
1731
            $content .= $text;
1732
        }
1733
1734
        $content = str_replace(array('<@ELSE>', '<@ENDIF>'), array('<?php else:?>', '<?php endif;?>'), $content);
1735
        ob_start();
1736
        $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...
1737
        $content = ob_get_clean();
1738
        $content = str_replace(array("{$sp}b", "{$sp}p", "{$sp}s", "{$sp}e"), array('<?php', '<?=', '<?', '?>'),
1739
            $content);
1740
1741
        return $content;
1742
    }
1743
1744
    /**
1745
     * @param $content
1746
     * @param string $iftag
1747
     * @param string $elseiftag
1748
     * @param string $elsetag
1749
     * @param string $endiftag
1750
     * @return mixed
1751
     */
1752
    private function _prepareCTag(
1753
        $content,
1754
        $iftag = '<@IF:',
1755
        $elseiftag = '<@ELSEIF:',
1756
        $elsetag = '<@ELSE>',
1757
        $endiftag = '<@ENDIF>'
1758
    ) {
1759
        if (strpos($content, '<!--@IF ') !== false) {
1760
            $content = str_replace('<!--@IF ', $iftag, $content);
1761
        } // for jp
1762
        if (strpos($content, '<!--@IF:') !== false) {
1763
            $content = str_replace('<!--@IF:', $iftag, $content);
1764
        }
1765
        if (strpos($content, $iftag) === false) {
1766
            return $content;
1767
        }
1768
        if (strpos($content, '<!--@ELSEIF:') !== false) {
1769
            $content = str_replace('<!--@ELSEIF:', $elseiftag, $content);
1770
        } // for jp
1771
        if (strpos($content, '<!--@ELSE-->') !== false) {
1772
            $content = str_replace('<!--@ELSE-->', $elsetag, $content);
1773
        }  // for jp
1774
        if (strpos($content, '<!--@ENDIF-->') !== false) {
1775
            $content = str_replace('<!--@ENDIF-->', $endiftag, $content);
1776
        }    // for jp
1777
        if (strpos($content, '<@ENDIF-->') !== false) {
1778
            $content = str_replace('<@ENDIF-->', $endiftag, $content);
1779
        }
1780
        $tags = array($iftag, $elseiftag, $elsetag, $endiftag);
1781
        $content = str_ireplace($tags, $tags, $content); // Change to capital letters
1782
1783
        return $content;
1784
    }
1785
1786
    /**
1787
     * @param $cmd
1788
     * @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...
1789
     */
1790
    private function _parseCTagCMD($cmd)
1791
    {
1792
        $cmd = trim($cmd);
1793
        $reverse = substr($cmd, 0, 1) === '!' ? true : false;
1794
        if ($reverse) {
1795
            $cmd = ltrim($cmd, '!');
1796
        }
1797
        if (strpos($cmd, '[!') !== false) {
1798
            $cmd = str_replace(array('[!', '!]'), array('[[', ']]'), $cmd);
1799
        }
1800
        $safe = 0;
1801
        while ($safe < 20) {
1802
            $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...
1803
            if (strpos($cmd, '[*') !== false) {
1804
                $cmd = $this->mergeDocumentContent($cmd);
1805
            }
1806
            if (strpos($cmd, '[(') !== false) {
1807
                $cmd = $this->mergeSettingsContent($cmd);
1808
            }
1809
            if (strpos($cmd, '{{') !== false) {
1810
                $cmd = $this->mergeChunkContent($cmd);
1811
            }
1812
            if (strpos($cmd, '[[') !== false) {
1813
                $cmd = $this->evalSnippets($cmd);
1814
            }
1815
            if (strpos($cmd, '[+') !== false && strpos($cmd, '[[') === false) {
1816
                $cmd = $this->mergePlaceholderContent($cmd);
1817
            }
1818
            if ($bt === md5($cmd)) {
1819
                break;
1820
            }
1821
            $safe++;
1822
        }
1823
        $cmd = ltrim($cmd);
1824
        $cmd = rtrim($cmd, '-');
1825
        $cmd = str_ireplace(array(' and ', ' or '), array('&&', '||'), $cmd);
1826
1827
        if (!preg_match('@^[0-9]*$@', $cmd) && preg_match('@^[0-9<= \-\+\*/\(\)%!&|]*$@', $cmd)) {
1828
            $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...
1829
        } else {
1830
            $_ = 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...
1831
            foreach ($_ as $left) {
1832
                if (strpos($cmd, $left) !== false) {
1833
                    $cmd = 0;
1834
                    break;
1835
                }
1836
            }
1837
        }
1838
        $cmd = trim($cmd);
1839
        if (!preg_match('@^[0-9]+$@', $cmd)) {
1840
            $cmd = empty($cmd) ? 0 : 1;
1841
        } elseif ($cmd <= 0) {
1842
            $cmd = 0;
1843
        }
1844
1845
        if ($reverse) {
1846
            $cmd = !$cmd;
1847
        }
1848
1849
        return $cmd;
1850
    }
1851
1852
    /**
1853
     * Remove Comment-Tags from output like <!--@- Comment -@-->
1854
     * @param $content
1855
     * @param string $left
1856
     * @param string $right
1857
     * @return mixed
1858
     */
1859
    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...
1860
    {
1861
        if (strpos($content, $left) === false) {
1862
            return $content;
1863
        }
1864
1865
        $matches = $this->getTagsFromContent($content, $left, $right);
1866
        if (!empty($matches)) {
1867
            foreach ($matches[0] as $i => $v) {
1868
                $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...
1869
            }
1870
            $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...
1871
            if (strpos($content, $left) !== false) {
1872
                $content = str_replace($matches[0], '', $content);
1873
            }
1874
        }
1875
1876
        return $content;
1877
    }
1878
1879
    /**
1880
     * @param $content
1881
     * @param string $left
1882
     * @param string $right
1883
     * @return mixed
1884
     */
1885
    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...
1886
    {
1887
        if (stripos($content, $left) === false) {
1888
            return $content;
1889
        }
1890
1891
        $matches = $this->getTagsFromContent($content, $left, $right);
1892
        if (empty($matches)) {
1893
            return $content;
1894
        }
1895
1896
        list($sTags, $rTags) = $this->getTagsForEscape();
1897
        foreach ($matches[1] as $i => $v) {
1898
            $v = str_ireplace($sTags, $rTags, $v);
1899
            $s = &$matches[0][$i];
1900 View Code Duplication
            if (strpos($content, $s) !== false) {
1901
                $content = str_replace($s, $v, $content);
1902
            } elseif ($this->debug) {
1903
                $this->addLog('ignoreCommentedTagsContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1904
            }
1905
        }
1906
1907
        return $content;
1908
    }
1909
1910
    /**
1911
     * Detect PHP error according to MODX error level
1912
     *
1913
     * @param integer $error PHP error level
1914
     * @return boolean Error detected
1915
     */
1916
1917
    public function detectError($error)
1918
    {
1919
        $detected = false;
1920
        if ($this->config['error_reporting'] == 99 && $error) {
1921
            $detected = true;
1922
        } elseif ($this->config['error_reporting'] == 2 && ($error & ~E_NOTICE)) {
1923
            $detected = true;
1924
        } elseif ($this->config['error_reporting'] == 1 && ($error & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT)) {
1925
            $detected = true;
1926
        }
1927
1928
        return $detected;
1929
    }
1930
1931
    /**
1932
     * Run a plugin
1933
     *
1934
     * @param string $pluginCode Code to run
1935
     * @param array $params
1936
     */
1937
    public function evalPlugin($pluginCode, $params)
1938
    {
1939
        $modx = &$this;
1940
        $modx->event->params = &$params; // store params inside event object
1941
        if (is_array($params)) {
1942
            extract($params, EXTR_SKIP);
1943
        }
1944
        /* 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...
1945
        // 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.
1946
        // Related to https://github.com/modxcms/evolution/issues/1130
1947
        $lock_file_path = MODX_BASE_PATH . 'assets/cache/lock_' . str_replace(' ','-',strtolower($this->event->activePlugin)) . '.pageCache.php';
1948
        if($this->isBackend()) {
1949
            if(is_file($lock_file_path)) {
1950
                $msg = sprintf("Plugin parse error, Temporarily disabled '%s'.", $this->event->activePlugin);
1951
                $this->logEvent(0, 3, $msg, $msg);
1952
                return;
1953
            }
1954
            elseif(stripos($this->event->activePlugin,'ElementsInTree')===false) touch($lock_file_path);
1955
        }*/
1956
        ob_start();
1957
        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...
1958
        $msg = ob_get_contents();
1959
        ob_end_clean();
1960
        // When reached here, no fatal error occured so the lock should be removed.
1961
        /*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...
1962
1963 View Code Duplication
        if ((0 < $this->config['error_reporting']) && $msg && isset($php_errormsg)) {
1964
            $error_info = error_get_last();
1965
            if ($this->detectError($error_info['type'])) {
1966
                $msg = ($msg === false) ? 'ob_get_contents() error' : $msg;
1967
                $this->messageQuit('PHP Parse Error', '', true, $error_info['type'], $error_info['file'], 'Plugin',
1968
                    $error_info['message'], $error_info['line'], $msg);
1969
                if ($this->isBackend()) {
1970
                    $this->event->alert('An error occurred while loading. Please see the event log for more information.<p>' . $msg . '</p>');
1971
                }
1972
            }
1973
        } else {
1974
            echo $msg;
1975
        }
1976
        unset($modx->event->params);
1977
    }
1978
1979
    /**
1980
     * Run a snippet
1981
     *
1982
     * @param $phpcode
1983
     * @param array $params
1984
     * @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...
1985
     * @internal param string $snippet Code to run
1986
     */
1987
    public function evalSnippet($phpcode, $params)
1988
    {
1989
        $modx = &$this;
1990
        /*
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...
1991
        if(isset($params) && is_array($params)) {
1992
            foreach($params as $k=>$v) {
1993
                $v = strtolower($v);
1994
                if($v==='false')    $params[$k] = false;
1995
                elseif($v==='true') $params[$k] = true;
1996
            }
1997
        }*/
1998
        $modx->event->params = &$params; // store params inside event object
1999
        if (is_array($params)) {
2000
            extract($params, EXTR_SKIP);
2001
        }
2002
        ob_start();
2003
        if (strpos($phpcode, ';') !== false) {
2004
            $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...
2005
        } else {
2006
            $return = call_user_func_array($phpcode, array($params));
2007
        }
2008
        $echo = ob_get_contents();
2009
        ob_end_clean();
2010 View Code Duplication
        if ((0 < $this->config['error_reporting']) && isset($php_errormsg)) {
2011
            $error_info = error_get_last();
2012
            if ($this->detectError($error_info['type'])) {
2013
                $echo = ($echo === false) ? 'ob_get_contents() error' : $echo;
2014
                $this->messageQuit('PHP Parse Error', '', true, $error_info['type'], $error_info['file'], 'Snippet',
2015
                    $error_info['message'], $error_info['line'], $echo);
2016
                if ($this->isBackend()) {
2017
                    $this->event->alert('An error occurred while loading. Please see the event log for more information<p>' . $echo . $return . '</p>');
2018
                }
2019
            }
2020
        }
2021
        unset($modx->event->params);
2022
        if (is_array($return) || is_object($return)) {
2023
            return $return;
2024
        } else {
2025
            return $echo . $return;
2026
        }
2027
    }
2028
2029
    /**
2030
     * Run snippets as per the tags in $documentSource and replace the tags with the returned values.
2031
     *
2032
     * @param $content
2033
     * @return string
2034
     * @internal param string $documentSource
2035
     */
2036
    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...
2037
    {
2038
        if (strpos($content, '[[') === false) {
2039
            return $content;
2040
        }
2041
2042
        $matches = $this->getTagsFromContent($content, '[[', ']]');
2043
2044
        if (empty($matches)) {
2045
            return $content;
2046
        }
2047
2048
        $this->snipLapCount++;
2049
        if ($this->dumpSnippets) {
2050
            $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>',
2051
                $this->snipLapCount);
2052
        }
2053
2054
        foreach ($matches[1] as $i => $call) {
2055
            $s = &$matches[0][$i];
2056
            if (substr($call, 0, 2) === '$_') {
2057
                if (strpos($content, '_PHX_INTERNAL_') === false) {
2058
                    $value = $this->_getSGVar($call);
2059
                } else {
2060
                    $value = $s;
2061
                }
2062 View Code Duplication
                if (strpos($content, $s) !== false) {
2063
                    $content = str_replace($s, $value, $content);
2064
                } elseif ($this->debug) {
2065
                    $this->addLog('evalSnippetsSGVar parse error', $_SERVER['REQUEST_URI'] . $s, 2);
2066
                }
2067
                continue;
2068
            }
2069
            $value = $this->_get_snip_result($call);
2070
            if (is_null($value)) {
2071
                continue;
2072
            }
2073
2074 View Code Duplication
            if (strpos($content, $s) !== false) {
2075
                $content = str_replace($s, $value, $content);
2076
            } elseif ($this->debug) {
2077
                $this->addLog('evalSnippets parse error', $_SERVER['REQUEST_URI'] . $s, 2);
2078
            }
2079
        }
2080
2081
        if ($this->dumpSnippets) {
2082
            $this->snippetsCode .= '</fieldset><br />';
2083
        }
2084
2085
        return $content;
2086
    }
2087
2088
    /**
2089
     * @param $value
2090
     * @return mixed|string
2091
     */
2092
    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...
2093
    { // Get super globals
2094
        $key = $value;
2095
        $_ = $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...
2096
        $this->config['enable_filter'] = 1;
2097
        list($key, $modifiers) = $this->splitKeyAndFilter($key);
2098
        $this->config['enable_filter'] = $_;
2099
        $key = str_replace(array('(', ')'), array("['", "']"), $key);
2100
        $key = rtrim($key, ';');
2101
        if (strpos($key, '$_SESSION') !== false) {
2102
            $_ = $_SESSION;
2103
            $key = str_replace('$_SESSION', '$_', $key);
2104
            if (isset($_['mgrFormValues'])) {
2105
                unset($_['mgrFormValues']);
2106
            }
2107
            if (isset($_['token'])) {
2108
                unset($_['token']);
2109
            }
2110
        }
2111
        if (strpos($key, '[') !== false) {
2112
            $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...
2113
        } 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...
2114
            $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...
2115
        } else {
2116
            $value = '';
2117
        }
2118
        if ($modifiers !== false) {
2119
            $value = $this->applyFilter($value, $modifiers, $key);
2120
        }
2121
2122
        return $value;
2123
    }
2124
2125
    /**
2126
     * @param $piece
2127
     * @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...
2128
     */
2129
    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...
2130
    {
2131
        if (ltrim($piece) !== $piece) {
2132
            return '';
2133
        }
2134
2135
        $eventtime = $this->dumpSnippets ? $this->getMicroTime() : 0;
2136
2137
        $snip_call = $this->_split_snip_call($piece);
2138
        $key = $snip_call['name'];
2139
2140
        list($key, $modifiers) = $this->splitKeyAndFilter($key);
2141
        $snip_call['name'] = $key;
2142
        $snippetObject = $this->_getSnippetObject($key);
2143
        if (is_null($snippetObject['content'])) {
2144
            return null;
2145
        }
2146
2147
        $this->currentSnippet = $snippetObject['name'];
2148
2149
        // current params
2150
        $params = $this->getParamsFromString($snip_call['params']);
2151
2152
        if (!isset($snippetObject['properties'])) {
2153
            $snippetObject['properties'] = '';
2154
        }
2155
        $default_params = $this->parseProperties($snippetObject['properties'], $this->currentSnippet, 'snippet');
2156
        $params = array_merge($default_params, $params);
2157
2158
        $value = $this->evalSnippet($snippetObject['content'], $params);
2159
        $this->currentSnippet = '';
2160
        if ($modifiers !== false) {
2161
            $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, DocumentParser::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...
2162
        }
2163
2164
        if ($this->dumpSnippets) {
2165
            $eventtime = $this->getMicroTime() - $eventtime;
2166
            $eventtime = sprintf('%2.2f ms', $eventtime * 1000);
2167
            $code = str_replace("\t", '  ', $this->htmlspecialchars($value));
2168
            $piece = str_replace("\t", '  ', $this->htmlspecialchars($piece));
2169
            $print_r_params = str_replace("\t", '  ',
2170
                $this->htmlspecialchars('$modx->event->params = ' . print_r($params, true)));
2171
            $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>',
2172
                $snippetObject['name'], $eventtime, $piece, $print_r_params, $code);
2173
            $this->snippetsTime[] = array('sname' => $key, 'time' => $eventtime);
2174
        }
2175
2176
        return $value;
2177
    }
2178
2179
    /**
2180
     * @param string $string
2181
     * @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...
2182
     */
2183
    public function getParamsFromString($string = '')
2184
    {
2185
        if (empty($string)) {
2186
            return array();
2187
        }
2188
2189
        if (strpos($string, '&_PHX_INTERNAL_') !== false) {
2190
            $string = str_replace(array('&_PHX_INTERNAL_091_&', '&_PHX_INTERNAL_093_&'), array('[', ']'), $string);
2191
        }
2192
2193
        $_ = $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...
2194
        $this->documentOutput = $string;
2195
        $this->invokeEvent('OnBeforeParseParams');
2196
        $string = $this->documentOutput;
2197
        $this->documentOutput = $_;
2198
2199
        $_tmp = $string;
2200
        $_tmp = ltrim($_tmp, '?&');
2201
        $temp_params = array();
2202
        $key = '';
2203
        $value = null;
2204
        while ($_tmp !== '') {
2205
            $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...
2206
            $char = substr($_tmp, 0, 1);
2207
            $_tmp = substr($_tmp, 1);
2208
2209
            if ($char === '=') {
2210
                $_tmp = trim($_tmp);
2211
                $delim = substr($_tmp, 0, 1);
2212
                if (in_array($delim, array('"', "'", '`'))) {
2213
                    $null = null;
2214
                    //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...
2215
                    list($null, $value, $_tmp) = explode($delim, $_tmp, 3);
2216
                    unset($null);
2217
2218
                    if (substr(trim($_tmp), 0, 2) === '//') {
2219
                        $_tmp = strstr(trim($_tmp), "\n");
2220
                    }
2221
                    $i = 0;
2222
                    while ($delim === '`' && substr(trim($_tmp), 0, 1) !== '&' && 1 < substr_count($_tmp, '`')) {
2223
                        list($inner, $outer, $_tmp) = explode('`', $_tmp, 3);
2224
                        $value .= "`{$inner}`{$outer}";
2225
                        $i++;
2226
                        if (100 < $i) {
2227
                            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...
2228
                        }
2229
                    }
2230
                    if ($i && $delim === '`') {
2231
                        $value = rtrim($value, '`');
2232
                    }
2233
                } elseif (strpos($_tmp, '&') !== false) {
2234
                    list($value, $_tmp) = explode('&', $_tmp, 2);
2235
                    $value = trim($value);
2236
                } else {
2237
                    $value = $_tmp;
2238
                    $_tmp = '';
2239
                }
2240
            } elseif ($char === '&') {
2241
                if (trim($key) !== '') {
2242
                    $value = '1';
2243
                } else {
2244
                    continue;
2245
                }
2246
            } elseif ($_tmp === '') {
2247
                $key .= $char;
2248
                $value = '1';
2249
            } elseif ($key !== '' || trim($char) !== '') {
2250
                $key .= $char;
2251
            }
2252
2253
            if (isset($value) && !is_null($value)) {
2254
                if (strpos($key, 'amp;') !== false) {
2255
                    $key = str_replace('amp;', '', $key);
2256
                }
2257
                $key = trim($key);
2258 View Code Duplication
                if (strpos($value, '[!') !== false) {
2259
                    $value = str_replace(array('[!', '!]'), array('[[', ']]'), $value);
2260
                }
2261
                $value = $this->mergeDocumentContent($value);
2262
                $value = $this->mergeSettingsContent($value);
2263
                $value = $this->mergeChunkContent($value);
2264
                $value = $this->evalSnippets($value);
2265
                if (substr($value, 0, 6) !== '@CODE:') {
2266
                    $value = $this->mergePlaceholderContent($value);
2267
                }
2268
2269
                $temp_params[][$key] = $value;
2270
2271
                $key = '';
2272
                $value = null;
2273
2274
                $_tmp = ltrim($_tmp, " ,\t");
2275
                if (substr($_tmp, 0, 2) === '//') {
2276
                    $_tmp = strstr($_tmp, "\n");
2277
                }
2278
            }
2279
2280
            if ($_tmp === $bt) {
2281
                $key = trim($key);
2282
                if ($key !== '') {
2283
                    $temp_params[][$key] = '';
2284
                }
2285
                break;
2286
            }
2287
        }
2288
2289
        foreach ($temp_params as $p) {
2290
            $k = key($p);
2291
            if (substr($k, -2) === '[]') {
2292
                $k = substr($k, 0, -2);
2293
                $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...
2294
            } elseif (strpos($k, '[') !== false && substr($k, -1) === ']') {
2295
                list($k, $subk) = explode('[', $k, 2);
2296
                $subk = substr($subk, 0, -1);
2297
                $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...
2298
            } else {
2299
                $params[$k] = current($p);
2300
            }
2301
        }
2302
2303
        return $params;
2304
    }
2305
2306
    /**
2307
     * @param $str
2308
     * @return bool|int
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use integer|false.

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

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

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

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

class Id
{
    public $id;

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

}

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

$account_id = false;

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

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
3076
            $udperms->role = $_SESSION['mgrRole'];
3077
            // Doesn't have access to this document
3078
            if (!$udperms->checkPermissions()) {
3079
                $this->sendErrorPage();
3080
            }
3081
        }
3082
    }
3083
3084
    /**
3085
     * @param $url
3086
     */
3087
    public function _sendRedirectForRefPage($url)
3088
    {
3089
        // check whether it's a reference
3090
        if (preg_match('@^[1-9][0-9]*$@', $url)) {
3091
            $url = $this->makeUrl($url); // if it's a bare document id
3092
        } elseif (strpos($url, '[~') !== false) {
3093
            $url = $this->rewriteUrls($url); // if it's an internal docid tag, process it
3094
        }
3095
        $this->sendRedirect($url, 0, '', 'HTTP/1.0 301 Moved Permanently');
3096
        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...
3097
    }
3098
3099
    /**
3100
     * @param $templateID
3101
     * @return mixed
3102
     */
3103
    public function _getTemplateCodeFromDB($templateID)
3104
    {
3105
        $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...
3106
        if ($this->db->getRecordCount($rs) == 1) {
3107
            return $this->db->getValue($rs);
3108
        } else {
3109
            $this->messageQuit('Incorrect number of templates returned from database');
3110
        }
3111
    }
3112
3113
    /**
3114
     * Returns an array of all parent record IDs for the id passed.
3115
     *
3116
     * @param int $id Docid to get parents for.
3117
     * @param int $height The maximum number of levels to go up, default 10.
3118
     * @return array
3119
     */
3120
    public function getParentIds($id, $height = 10)
3121
    {
3122
        $parents = array();
3123
        while ($id && $height--) {
3124
            $thisid = $id;
3125
            if ($this->config['aliaslistingfolder'] == 1) {
3126
                $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");
3127
                if (!$id || $id == '0') {
3128
                    break;
3129
                }
3130
            } else {
3131
                $id = $this->aliasListing[$id]['parent'];
3132
                if (!$id) {
3133
                    break;
3134
                }
3135
            }
3136
            $parents[$thisid] = $id;
3137
        }
3138
3139
        return $parents;
3140
    }
3141
3142
    /**
3143
     * @param $id
3144
     * @param int $top
3145
     * @return mixed
3146
     */
3147
    public function getUltimateParentId($id, $top = 0)
3148
    {
3149
        $i = 0;
3150
        while ($id && $i < 20) {
3151
            if ($top == $this->aliasListing[$id]['parent']) {
3152
                break;
3153
            }
3154
            $id = $this->aliasListing[$id]['parent'];
3155
            $i++;
3156
        }
3157
3158
        return $id;
3159
    }
3160
3161
    /**
3162
     * Returns an array of child IDs belonging to the specified parent.
3163
     *
3164
     * @param int $id The parent resource/document to start from
3165
     * @param int $depth How many levels deep to search for children, default: 10
3166
     * @param array $children Optional array of docids to merge with the result.
3167
     * @return array Contains the document Listing (tree) like the sitemap
3168
     */
3169
    public function getChildIds($id, $depth = 10, $children = array())
3170
    {
3171
3172
        $cacheKey = md5(print_r(func_get_args(), true));
3173
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3174
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3175
        }
3176
3177
        if ($this->config['aliaslistingfolder'] == 1) {
3178
3179
            $res = $this->db->select("id,alias,isfolder,parent", $this->getFullTableName('site_content'),
3180
                "parent IN (" . $id . ") AND deleted = '0'");
3181
            $idx = array();
3182
            while ($row = $this->db->getRow($res)) {
3183
                $pAlias = '';
3184
                if (isset($this->aliasListing[$row['parent']])) {
3185
                    $pAlias .= !empty($this->aliasListing[$row['parent']]['path']) ? $this->aliasListing[$row['parent']]['path'] . '/' : '';
3186
                    $pAlias .= !empty($this->aliasListing[$row['parent']]['alias']) ? $this->aliasListing[$row['parent']]['alias'] . '/' : '';
3187
                };
3188
                $children[$pAlias . $row['alias']] = $row['id'];
3189
                if ($row['isfolder'] == 1) {
3190
                    $idx[] = $row['id'];
3191
                }
3192
            }
3193
            $depth--;
3194
            $idx = implode(',', $idx);
3195
            if (!empty($idx)) {
3196
                if ($depth) {
3197
                    $children = $this->getChildIds($idx, $depth, $children);
3198
                }
3199
            }
3200
            $this->tmpCache[__FUNCTION__][$cacheKey] = $children;
3201
3202
            return $children;
3203
3204
        } else {
3205
3206
            // Initialise a static array to index parents->children
3207
            static $documentMap_cache = array();
3208
            if (!count($documentMap_cache)) {
3209
                foreach ($this->documentMap as $document) {
3210
                    foreach ($document as $p => $c) {
3211
                        $documentMap_cache[$p][] = $c;
3212
                    }
3213
                }
3214
            }
3215
3216
            // Get all the children for this parent node
3217
            if (isset($documentMap_cache[$id])) {
3218
                $depth--;
3219
3220
                foreach ($documentMap_cache[$id] as $childId) {
3221
                    $pkey = (strlen($this->aliasListing[$childId]['path']) ? "{$this->aliasListing[$childId]['path']}/" : '') . $this->aliasListing[$childId]['alias'];
3222
                    if (!strlen($pkey)) {
3223
                        $pkey = "{$childId}";
3224
                    }
3225
                    $children[$pkey] = $childId;
3226
3227
                    if ($depth && isset($documentMap_cache[$childId])) {
3228
                        $children += $this->getChildIds($childId, $depth);
3229
                    }
3230
                }
3231
            }
3232
            $this->tmpCache[__FUNCTION__][$cacheKey] = $children;
3233
3234
            return $children;
3235
3236
        }
3237
    }
3238
3239
    /**
3240
     * Displays a javascript alert message in the web browser and quit
3241
     *
3242
     * @param string $msg Message to show
3243
     * @param string $url URL to redirect to
3244
     */
3245
    public function webAlertAndQuit($msg, $url = "")
3246
    {
3247
        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...
3248
        if (substr(strtolower($url), 0, 11) == "javascript:") {
3249
            $fnc = substr($url, 11);
3250
        } elseif ($url) {
3251
            $fnc = "window.location.href='" . addslashes($url) . "';";
3252
        } else {
3253
            $fnc = "history.back(-1);";
3254
        }
3255
        echo "<html><head>
3256
            <title>MODX :: Alert</title>
3257
            <meta http-equiv=\"Content-Type\" content=\"text/html; charset={$modx_manager_charset};\">
3258
            <script>
3259
                function __alertQuit() {
3260
                    alert('" . addslashes($msg) . "');
3261
                    {$fnc}
3262
                }
3263
                window.setTimeout('__alertQuit();',100);
3264
            </script>
3265
            </head><body>
3266
            <p>{$msg}</p>
3267
            </body></html>";
3268
        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...
3269
    }
3270
3271
    /**
3272
     * Returns 1 if user has the currect permission
3273
     *
3274
     * @param string $pm Permission name
3275
     * @return int Why not bool?
3276
     */
3277
    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...
3278
    {
3279
        $state = 0;
3280
        $pms = $_SESSION['mgrPermissions'];
3281
        if ($pms) {
3282
            $state = ((bool)$pms[$pm] === true);
3283
        }
3284
3285
        return (int)$state;
3286
    }
3287
3288
    /**
3289
     * Returns true if element is locked
3290
     *
3291
     * @param int $type Types: 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3292
     * @param int $id Element- / Resource-id
3293
     * @param bool $includeThisUser true = Return also info about actual user
3294
     * @return array lock-details or null
3295
     */
3296
    public function elementIsLocked($type, $id, $includeThisUser = false)
3297
    {
3298
        $id = (int)$id;
3299
        $type = (int)$type;
3300
        if (!$type || !$id) {
3301
            return null;
3302
        }
3303
3304
        // Build lockedElements-Cache at first call
3305
        $this->buildLockedElementsCache();
3306
3307
        if (!$includeThisUser && $this->lockedElements[$type][$id]['sid'] == $this->sid) {
3308
            return null;
3309
        }
3310
3311
        if (isset($this->lockedElements[$type][$id])) {
3312
            return $this->lockedElements[$type][$id];
3313
        } else {
3314
            return null;
3315
        }
3316
    }
3317
3318
    /**
3319
     * Returns Locked Elements as Array
3320
     *
3321
     * @param int $type Types: 0=all, 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3322
     * @param bool $minimumDetails true =
3323
     * @return array|mixed|null
3324
     */
3325
    public function getLockedElements($type = 0, $minimumDetails = false)
3326
    {
3327
        $this->buildLockedElementsCache();
3328
3329
        if (!$minimumDetails) {
3330
            $lockedElements = $this->lockedElements;
3331
        } else {
3332
            // Minimum details for HTML / Ajax-requests
3333
            $lockedElements = array();
3334
            foreach ($this->lockedElements as $elType => $elements) {
3335
                foreach ($elements as $elId => $el) {
3336
                    $lockedElements[$elType][$elId] = array(
3337
                        'username'   => $el['username'],
3338
                        'lasthit_df' => $el['lasthit_df'],
3339
                        'state'      => $this->determineLockState($el['internalKey'])
3340
                    );
3341
                }
3342
            }
3343
        }
3344
3345
        if ($type == 0) {
3346
            return $lockedElements;
3347
        }
3348
3349
        $type = (int)$type;
3350
        if (isset($lockedElements[$type])) {
3351
            return $lockedElements[$type];
3352
        } else {
3353
            return array();
3354
        }
3355
    }
3356
3357
    /**
3358
     * Builds the Locked Elements Cache once
3359
     */
3360
    public function buildLockedElementsCache()
3361
    {
3362
        if (is_null($this->lockedElements)) {
3363
            $this->lockedElements = array();
3364
            $this->cleanupExpiredLocks();
3365
3366
            $rs = $this->db->select('sid,internalKey,elementType,elementId,lasthit,username',
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...
3367
                $this->getFullTableName('active_user_locks') . " ul
3368
                LEFT JOIN {$this->getFullTableName('manager_users')} mu on ul.internalKey = mu.id");
3369
            while ($row = $this->db->getRow($rs)) {
3370
                $this->lockedElements[$row['elementType']][$row['elementId']] = array(
3371
                    'sid'         => $row['sid'],
3372
                    'internalKey' => $row['internalKey'],
3373
                    'username'    => $row['username'],
3374
                    'elementType' => $row['elementType'],
3375
                    'elementId'   => $row['elementId'],
3376
                    'lasthit'     => $row['lasthit'],
3377
                    'lasthit_df'  => $this->toDateFormat($row['lasthit']),
3378
                    'state'       => $this->determineLockState($row['sid'])
3379
                );
3380
            }
3381
        }
3382
    }
3383
3384
    /**
3385
     * Cleans up the active user locks table
3386
     */
3387
    public function cleanupExpiredLocks()
3388
    {
3389
        // Clean-up active_user_sessions first
3390
        $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
3391
        $validSessionTimeLimit = $this->time - $timeout;
3392
        $this->db->delete($this->getFullTableName('active_user_sessions'), "lasthit < {$validSessionTimeLimit}");
3393
3394
        // Clean-up active_user_locks
3395
        $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...
3396
        $count = $this->db->getRecordCount($rs);
3397
        if ($count) {
3398
            $rs = $this->db->makeArray($rs);
3399
            $userSids = array();
3400
            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...
3401
                $userSids[] = $row['sid'];
3402
            }
3403
            $userSids = "'" . implode("','", $userSids) . "'";
3404
            $this->db->delete($this->getFullTableName('active_user_locks'), "sid NOT IN({$userSids})");
3405
        } else {
3406
            $this->db->delete($this->getFullTableName('active_user_locks'));
3407
        }
3408
3409
    }
3410
3411
    /**
3412
     * Cleans up the active users table
3413
     */
3414
    public function cleanupMultipleActiveUsers()
3415
    {
3416
        $timeout = 20 * 60; // Delete multiple user-sessions after 20min
3417
        $validSessionTimeLimit = $this->time - $timeout;
3418
3419
        $activeUserSids = array();
3420
        $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...
3421
        $count = $this->db->getRecordCount($rs);
3422
        if ($count) {
3423
            $rs = $this->db->makeArray($rs);
3424
            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...
3425
                $activeUserSids[] = $row['sid'];
3426
            }
3427
        }
3428
3429
        $rs = $this->db->select("sid,internalKey,lasthit", "{$this->getFullTableName('active_users')}", "",
3430
            "lasthit DESC");
3431
        if ($this->db->getRecordCount($rs)) {
3432
            $rs = $this->db->makeArray($rs);
3433
            $internalKeyCount = array();
3434
            $deleteSids = '';
3435
            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...
3436
                if (!isset($internalKeyCount[$row['internalKey']])) {
3437
                    $internalKeyCount[$row['internalKey']] = 0;
3438
                }
3439
                $internalKeyCount[$row['internalKey']]++;
3440
3441
                if ($internalKeyCount[$row['internalKey']] > 1 && !in_array($row['sid'],
3442
                        $activeUserSids) && $row['lasthit'] < $validSessionTimeLimit) {
3443
                    $deleteSids .= $deleteSids == '' ? '' : ' OR ';
3444
                    $deleteSids .= "sid='{$row['sid']}'";
3445
                };
3446
3447
            }
3448
            if ($deleteSids) {
3449
                $this->db->delete($this->getFullTableName('active_users'), $deleteSids);
3450
            }
3451
        }
3452
3453
    }
3454
3455
    /**
3456
     * Determines state of a locked element acc. to user-permissions
3457
     *
3458
     * @param $sid
3459
     * @return int $state States: 0=No display, 1=viewing this element, 2=locked, 3=show unlock-button
3460
     * @internal param int $internalKey : ID of User who locked actual element
3461
     */
3462
    public function determineLockState($sid)
3463
    {
3464
        $state = 0;
3465
        if ($this->hasPermission('display_locks')) {
3466
            if ($sid == $this->sid) {
3467
                $state = 1;
3468
            } else {
3469
                if ($this->hasPermission('remove_locks')) {
3470
                    $state = 3;
3471
                } else {
3472
                    $state = 2;
3473
                }
3474
            }
3475
        }
3476
3477
        return $state;
3478
    }
3479
3480
    /**
3481
     * Locks an element
3482
     *
3483
     * @param int $type Types: 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3484
     * @param int $id Element- / Resource-id
3485
     * @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...
3486
     */
3487
    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...
3488
    {
3489
        $userId = $this->isBackend() && $_SESSION['mgrInternalKey'] ? $_SESSION['mgrInternalKey'] : 0;
3490
        $type = (int)$type;
3491
        $id = (int)$id;
3492
        if (!$type || !$id || !$userId) {
3493
            return false;
3494
        }
3495
3496
        $sql = sprintf('REPLACE INTO %s (internalKey, elementType, elementId, lasthit, sid)
3497
                VALUES (%d, %d, %d, %d, \'%s\')', $this->getFullTableName('active_user_locks'), $userId, $type, $id,
3498
            $this->time, $this->sid);
3499
        $this->db->query($sql);
3500
    }
3501
3502
    /**
3503
     * Unlocks an element
3504
     *
3505
     * @param int $type Types: 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3506
     * @param int $id Element- / Resource-id
3507
     * @param bool $includeAllUsers true = Deletes not only own user-locks
3508
     * @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...
3509
     */
3510
    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...
3511
    {
3512
        $userId = $this->isBackend() && $_SESSION['mgrInternalKey'] ? $_SESSION['mgrInternalKey'] : 0;
3513
        $type = (int)$type;
3514
        $id = (int)$id;
3515
        if (!$type || !$id) {
3516
            return false;
3517
        }
3518
3519
        if (!$includeAllUsers) {
3520
            $sql = sprintf('DELETE FROM %s WHERE internalKey = %d AND elementType = %d AND elementId = %d;',
3521
                $this->getFullTableName('active_user_locks'), $userId, $type, $id);
3522
        } else {
3523
            $sql = sprintf('DELETE FROM %s WHERE elementType = %d AND elementId = %d;',
3524
                $this->getFullTableName('active_user_locks'), $type, $id);
3525
        }
3526
        $this->db->query($sql);
3527
    }
3528
3529
    /**
3530
     * Updates table "active_user_sessions" with userid, lasthit, IP
3531
     */
3532
    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...
3533
    {
3534
        if (!$this->sid) {
3535
            return;
3536
        }
3537
3538
        // web users are stored with negative keys
3539
        $userId = $this->getLoginUserType() == 'manager' ? $this->getLoginUserID() : -$this->getLoginUserID();
3540
3541
        // Get user IP
3542 View Code Duplication
        if ($cip = getenv("HTTP_CLIENT_IP")) {
3543
            $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...
3544
        } elseif ($cip = getenv("HTTP_X_FORWARDED_FOR")) {
3545
            $ip = $cip;
3546
        } elseif ($cip = getenv("REMOTE_ADDR")) {
3547
            $ip = $cip;
3548
        } else {
3549
            $ip = "UNKNOWN";
3550
        }
3551
        $_SESSION['ip'] = $ip;
3552
3553
        $sql = sprintf('REPLACE INTO %s (internalKey, lasthit, ip, sid)
3554
            VALUES (%d, %d, \'%s\', \'%s\')', $this->getFullTableName('active_user_sessions'), $userId, $this->time,
3555
            $ip, $this->sid);
3556
        $this->db->query($sql);
3557
    }
3558
3559
    /**
3560
     * Add an a alert message to the system event log
3561
     *
3562
     * @param int $evtid Event ID
3563
     * @param int $type Types: 1 = information, 2 = warning, 3 = error
3564
     * @param string $msg Message to be logged
3565
     * @param string $source source of the event (module, snippet name, etc.)
3566
     *                       Default: Parser
3567
     */
3568
    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...
3569
    {
3570
        $msg = $this->db->escape($msg);
3571
        if (strpos($GLOBALS['database_connection_charset'], 'utf8') === 0 && extension_loaded('mbstring')) {
3572
            $esc_source = mb_substr($source, 0, 50, "UTF-8");
3573
        } else {
3574
            $esc_source = substr($source, 0, 50);
3575
        }
3576
        $esc_source = $this->db->escape($esc_source);
3577
3578
        $LoginUserID = $this->getLoginUserID();
3579
        if ($LoginUserID == '') {
3580
            $LoginUserID = 0;
3581
        }
3582
3583
        $usertype = $this->isFrontend() ? 1 : 0;
3584
        $evtid = (int)$evtid;
3585
        $type = (int)$type;
3586
3587
        // Types: 1 = information, 2 = warning, 3 = error
3588
        if ($type < 1) {
3589
            $type = 1;
3590
        } elseif ($type > 3) {
3591
            $type = 3;
3592
        }
3593
3594
        $this->db->insert(array(
3595
            'eventid'     => $evtid,
3596
            'type'        => $type,
3597
            'createdon'   => $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'],
3598
            'source'      => $esc_source,
3599
            'description' => $msg,
3600
            'user'        => $LoginUserID,
3601
            'usertype'    => $usertype
3602
        ), $this->getFullTableName("event_log"));
3603
3604
        if (isset($this->config['send_errormail']) && $this->config['send_errormail'] !== '0') {
3605
            if ($this->config['send_errormail'] <= $type) {
3606
                $this->sendmail(array(
3607
                    'subject' => 'MODX System Error on ' . $this->config['site_name'],
3608
                    'body'    => 'Source: ' . $source . ' - The details of the error could be seen in the MODX system events log.',
3609
                    'type'    => 'text'
3610
                ));
3611
            }
3612
        }
3613
    }
3614
3615
    /**
3616
     * @param array $params
3617
     * @param string $msg
3618
     * @param array $files
3619
     * @return mixed
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use 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...
3620
     */
3621
    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...
3622
    {
3623
        if (isset($params) && is_string($params)) {
3624
            if (strpos($params, '=') === false) {
3625
                if (strpos($params, '@') !== false) {
3626
                    $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...
3627
                } else {
3628
                    $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...
3629
                }
3630
            } else {
3631
                $params_array = explode(',', $params);
3632
                foreach ($params_array as $k => $v) {
3633
                    $k = trim($k);
3634
                    $v = trim($v);
3635
                    $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...
3636
                }
3637
            }
3638
        } else {
3639
            $p = $params;
3640
            unset($params);
3641
        }
3642
        if (isset($p['sendto'])) {
3643
            $p['to'] = $p['sendto'];
3644
        }
3645
3646
        if (isset($p['to']) && preg_match('@^[0-9]+$@', $p['to'])) {
3647
            $userinfo = $this->getUserInfo($p['to']);
3648
            $p['to'] = $userinfo['email'];
3649
        }
3650
        if (isset($p['from']) && preg_match('@^[0-9]+$@', $p['from'])) {
3651
            $userinfo = $this->getUserInfo($p['from']);
3652
            $p['from'] = $userinfo['email'];
3653
            $p['fromname'] = $userinfo['username'];
3654
        }
3655
        if ($msg === '' && !isset($p['body'])) {
3656
            $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...
3657
        } elseif (is_string($msg) && 0 < strlen($msg)) {
3658
            $p['body'] = $msg;
3659
        }
3660
3661
        $this->loadExtension('MODxMailer');
3662
        $sendto = (!isset($p['to'])) ? $this->config['emailsender'] : $p['to'];
3663
        $sendto = explode(',', $sendto);
3664
        foreach ($sendto as $address) {
3665
            list($name, $address) = $this->mail->address_split($address);
3666
            $this->mail->AddAddress($address, $name);
3667
        }
3668 View Code Duplication
        if (isset($p['cc'])) {
3669
            $p['cc'] = explode(',', $p['cc']);
3670
            foreach ($p['cc'] as $address) {
3671
                list($name, $address) = $this->mail->address_split($address);
3672
                $this->mail->AddCC($address, $name);
3673
            }
3674
        }
3675 View Code Duplication
        if (isset($p['bcc'])) {
3676
            $p['bcc'] = explode(',', $p['bcc']);
3677
            foreach ($p['bcc'] as $address) {
3678
                list($name, $address) = $this->mail->address_split($address);
3679
                $this->mail->AddBCC($address, $name);
3680
            }
3681
        }
3682
        if (isset($p['from']) && strpos($p['from'], '<') !== false && substr($p['from'], -1) === '>') {
3683
            list($p['fromname'], $p['from']) = $this->mail->address_split($p['from']);
3684
        }
3685
        $this->mail->From = (!isset($p['from'])) ? $this->config['emailsender'] : $p['from'];
3686
        $this->mail->FromName = (!isset($p['fromname'])) ? $this->config['site_name'] : $p['fromname'];
3687
        $this->mail->Subject = (!isset($p['subject'])) ? $this->config['emailsubject'] : $p['subject'];
3688
        $this->mail->Body = $p['body'];
3689
        if (isset($p['type']) && $p['type'] == 'text') {
3690
            $this->mail->IsHTML(false);
3691
        }
3692
        if (!is_array($files)) {
3693
            $files = array();
3694
        }
3695
        foreach ($files as $f) {
3696
            if (file_exists(MODX_BASE_PATH . $f) && is_file(MODX_BASE_PATH . $f) && is_readable(MODX_BASE_PATH . $f)) {
3697
                $this->mail->AddAttachment(MODX_BASE_PATH . $f);
3698
            }
3699
        }
3700
        $rs = $this->mail->send();
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...
3701
3702
        return $rs;
3703
    }
3704
3705
    /**
3706
     * @param string $target
3707
     * @param int $limit
3708
     * @param int $trim
3709
     */
3710
    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...
3711
    {
3712
        if ($limit < $trim) {
3713
            $trim = $limit;
3714
        }
3715
3716
        $table_name = $this->getFullTableName($target);
3717
        $count = $this->db->getValue($this->db->select('COUNT(id)', $table_name));
3718
        $over = $count - $limit;
3719
        if (0 < $over) {
3720
            $trim = ($over + $trim);
3721
            $this->db->delete($table_name, '', '', $trim);
3722
        }
3723
        $this->db->optimize($table_name);
3724
    }
3725
3726
    /**
3727
     * Returns true if we are currently in the manager backend
3728
     *
3729
     * @return boolean
3730
     */
3731
    public function isBackend()
3732
    {
3733
        return (defined('IN_MANAGER_MODE') && IN_MANAGER_MODE === true);
3734
    }
3735
3736
    /**
3737
     * Returns true if we are currently in the frontend
3738
     *
3739
     * @return boolean
3740
     */
3741
    public function isFrontend()
3742
    {
3743
        return !$this->isBackend();
3744
    }
3745
3746
    /**
3747
     * Gets all child documents of the specified document, including those which are unpublished or deleted.
3748
     *
3749
     * @param int $id The Document identifier to start with
3750
     * @param string $sort Sort field
3751
     *                     Default: menuindex
3752
     * @param string $dir Sort direction, ASC and DESC is possible
3753
     *                    Default: ASC
3754
     * @param string $fields Default: id, pagetitle, description, parent, alias, menutitle
3755
     * @return array
3756
     */
3757 View Code Duplication
    public function getAllChildren(
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...
3758
        $id = 0,
3759
        $sort = 'menuindex',
3760
        $dir = 'ASC',
3761
        $fields = 'id, pagetitle, description, parent, alias, menutitle'
3762
    ) {
3763
3764
        $cacheKey = md5(print_r(func_get_args(), true));
3765
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3766
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3767
        }
3768
3769
        $tblsc = $this->getFullTableName("site_content");
3770
        $tbldg = $this->getFullTableName("document_groups");
3771
        // modify field names to use sc. table reference
3772
        $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3773
        $sort = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
3774
        // get document groups for current user
3775
        if ($docgrp = $this->getUserDocGroups()) {
3776
            $docgrp = implode(",", $docgrp);
3777
        }
3778
        // build query
3779
        $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
3780
        $result = $this->db->select("DISTINCT {$fields}", "{$tblsc} sc
3781
                LEFT JOIN {$tbldg} dg on dg.document = sc.id", "sc.parent = '{$id}' AND ({$access}) GROUP BY sc.id",
3782
            "{$sort} {$dir}");
3783
        $resourceArray = $this->db->makeArray($result);
3784
        $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
3785
3786
        return $resourceArray;
3787
    }
3788
3789
    /**
3790
     * Gets all active child documents of the specified document, i.e. those which published and not deleted.
3791
     *
3792
     * @param int $id The Document identifier to start with
3793
     * @param string $sort Sort field
3794
     *                     Default: menuindex
3795
     * @param string $dir Sort direction, ASC and DESC is possible
3796
     *                    Default: ASC
3797
     * @param string $fields Default: id, pagetitle, description, parent, alias, menutitle
3798
     * @return array
3799
     */
3800 View Code Duplication
    public function getActiveChildren(
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...
3801
        $id = 0,
3802
        $sort = 'menuindex',
3803
        $dir = 'ASC',
3804
        $fields = 'id, pagetitle, description, parent, alias, menutitle'
3805
    ) {
3806
        $cacheKey = md5(print_r(func_get_args(), true));
3807
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3808
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3809
        }
3810
3811
        $tblsc = $this->getFullTableName("site_content");
3812
        $tbldg = $this->getFullTableName("document_groups");
3813
3814
        // modify field names to use sc. table reference
3815
        $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3816
        $sort = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
3817
        // get document groups for current user
3818
        if ($docgrp = $this->getUserDocGroups()) {
3819
            $docgrp = implode(",", $docgrp);
3820
        }
3821
        // build query
3822
        $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
3823
        $result = $this->db->select("DISTINCT {$fields}", "{$tblsc} sc
3824
                LEFT JOIN {$tbldg} dg on dg.document = sc.id",
3825
            "sc.parent = '{$id}' AND sc.published=1 AND sc.deleted=0 AND ({$access}) GROUP BY sc.id", "{$sort} {$dir}");
3826
        $resourceArray = $this->db->makeArray($result);
3827
3828
        $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
3829
3830
        return $resourceArray;
3831
    }
3832
3833
    /**
3834
     * getDocumentChildren
3835
     * @version 1.1.1 (2014-02-19)
3836
     *
3837
     * @desc Returns the children of the selected document/folder as an associative array.
3838
     *
3839
     * @param $parentid {integer} - The parent document identifier. Default: 0 (site root).
3840
     * @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.
3841
     * @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.
3842
     * @param $fields {comma separated string; '*'} - Comma separated list of document fields to get. Default: '*' (all fields).
3843
     * @param $where {string} - Where condition in SQL style. Should include a leading 'AND '. Default: ''.
3844
     * @param $sort {comma separated string} - Should be a comma-separated list of field names on which to sort. Default: 'menuindex'.
3845
     * @param $dir {'ASC'; 'DESC'} - Sort direction, ASC and DESC is possible. Default: 'ASC'.
3846
     * @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).
3847
     *
3848
     * @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...
3849
     */
3850
    public function getDocumentChildren(
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...
3851
        $parentid = 0,
3852
        $published = 1,
3853
        $deleted = 0,
3854
        $fields = '*',
3855
        $where = '',
3856
        $sort = 'menuindex',
3857
        $dir = 'ASC',
3858
        $limit = ''
3859
    ) {
3860
3861
        $cacheKey = md5(print_r(func_get_args(), true));
3862
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3863
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3864
        }
3865
3866
        $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...
3867
        $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...
3868
3869
        if ($where != '') {
3870
            $where = 'AND ' . $where;
3871
        }
3872
3873
        // modify field names to use sc. table reference
3874
        $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3875
        $sort = ($sort == '') ? '' : 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
3876
3877
        // get document groups for current user
3878
        if ($docgrp = $this->getUserDocGroups()) {
3879
            $docgrp = implode(',', $docgrp);
3880
        }
3881
3882
        // build query
3883
        $access = ($this->isFrontend() ? 'sc.privateweb=0' : '1="' . $_SESSION['mgrRole'] . '" OR sc.privatemgr=0') . (!$docgrp ? '' : ' OR dg.document_group IN (' . $docgrp . ')');
3884
3885
        $tblsc = $this->getFullTableName('site_content');
3886
        $tbldg = $this->getFullTableName('document_groups');
3887
3888
        $result = $this->db->select("DISTINCT {$fields}", "{$tblsc} sc
3889
                LEFT JOIN {$tbldg} dg on dg.document = sc.id",
3890
            "sc.parent = '{$parentid}' {$published} {$deleted} {$where} AND ({$access}) GROUP BY sc.id",
3891
            ($sort ? "{$sort} {$dir}" : ""), $limit);
3892
3893
        $resourceArray = $this->db->makeArray($result);
3894
3895
        $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
3896
3897
        return $resourceArray;
3898
    }
3899
3900
    /**
3901
     * getDocuments
3902
     * @version 1.1.1 (2013-02-19)
3903
     *
3904
     * @desc Returns required documents (their fields).
3905
     *
3906
     * @param $ids {array; comma separated string} - Documents Ids to get. @required
3907
     * @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.
3908
     * @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.
3909
     * @param $fields {comma separated string; '*'} - Documents fields to get. Default: '*'.
3910
     * @param $where {string} - SQL WHERE clause. Default: ''.
3911
     * @param $sort {comma separated string} - A comma-separated list of field names to sort by. Default: 'menuindex'.
3912
     * @param $dir {'ASC'; 'DESC'} - Sorting direction. Default: 'ASC'.
3913
     * @param $limit {string} - SQL LIMIT (without 'LIMIT '). An empty string means no limit. Default: ''.
3914
     *
3915
     * @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...
3916
     */
3917
    public function getDocuments(
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...
3918
        $ids = array(),
3919
        $published = 1,
3920
        $deleted = 0,
3921
        $fields = '*',
3922
        $where = '',
3923
        $sort = 'menuindex',
3924
        $dir = 'ASC',
3925
        $limit = ''
3926
    ) {
3927
3928
        $cacheKey = md5(print_r(func_get_args(), true));
3929
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3930
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3931
        }
3932
3933
        if (is_string($ids)) {
3934
            if (strpos($ids, ',') !== false) {
3935
                $ids = array_filter(array_map('intval', explode(',', $ids)));
3936
            } else {
3937
                $ids = array($ids);
3938
            }
3939
        }
3940
        if (count($ids) == 0) {
3941
            $this->tmpCache[__FUNCTION__][$cacheKey] = false;
3942
3943
            return false;
3944
        } else {
3945
            // modify field names to use sc. table reference
3946
            $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3947
            $sort = ($sort == '') ? '' : 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
3948
            if ($where != '') {
3949
                $where = 'AND ' . $where;
3950
            }
3951
3952
            $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...
3953
            $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...
3954
3955
            // get document groups for current user
3956
            if ($docgrp = $this->getUserDocGroups()) {
3957
                $docgrp = implode(',', $docgrp);
3958
            }
3959
3960
            $access = ($this->isFrontend() ? 'sc.privateweb=0' : '1="' . $_SESSION['mgrRole'] . '" OR sc.privatemgr=0') . (!$docgrp ? '' : ' OR dg.document_group IN (' . $docgrp . ')');
3961
3962
            $tblsc = $this->getFullTableName('site_content');
3963
            $tbldg = $this->getFullTableName('document_groups');
3964
3965
            $result = $this->db->select("DISTINCT {$fields}", "{$tblsc} sc
3966
                    LEFT JOIN {$tbldg} dg on dg.document = sc.id", "(sc.id IN (" . implode(',',
3967
                    $ids) . ") {$published} {$deleted} {$where}) AND ({$access}) GROUP BY sc.id",
3968
                ($sort ? "{$sort} {$dir}" : ""), $limit);
3969
3970
            $resourceArray = $this->db->makeArray($result);
3971
3972
            $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
3973
3974
            return $resourceArray;
3975
        }
3976
    }
3977
3978
    /**
3979
     * getDocument
3980
     * @version 1.0.1 (2014-02-19)
3981
     *
3982
     * @desc Returns required fields of a document.
3983
     *
3984
     * @param int $id {integer}
3985
     * - Id of a document which data has to be gained. @required
3986
     * @param string $fields {comma separated string; '*'}
3987
     * - Comma separated list of document fields to get. Default: '*'.
3988
     * @param int $published {0; 1; 'all'}
3989
     * - 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.
3990
     * @param int $deleted {0; 1; 'all'}
3991
     * - 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.
3992
     * @return bool {array; false} - Result array with fields or false.
3993
     * - Result array with fields or false.
3994
     */
3995 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...
3996
    {
3997
        if ($id == 0) {
3998
            return false;
3999
        } else {
4000
            $docs = $this->getDocuments(array($id), $published, $deleted, $fields, '', '', '', 1);
4001
4002
            if ($docs != false) {
4003
                return $docs[0];
4004
            } else {
4005
                return false;
4006
            }
4007
        }
4008
    }
4009
4010
    /**
4011
     * @param string $field
4012
     * @param string $docid
4013
     * @return bool|mixed
4014
     */
4015
    public function getField($field = 'content', $docid = '')
4016
    {
4017
        if (empty($docid) && isset($this->documentIdentifier)) {
4018
            $docid = $this->documentIdentifier;
4019
        } elseif (!preg_match('@^[0-9]+$@', $docid)) {
4020
            $docid = $this->getIdFromAlias($docid);
4021
        }
4022
4023
        if (empty($docid)) {
4024
            return false;
4025
        }
4026
4027
        $cacheKey = md5(print_r(func_get_args(), true));
4028
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
4029
            return $this->tmpCache[__FUNCTION__][$cacheKey];
4030
        }
4031
4032
        $doc = $this->getDocumentObject('id', $docid);
4033
        if (is_array($doc[$field])) {
4034
            $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...
4035
            $content = $tvs[$field];
4036
        } else {
4037
            $content = $doc[$field];
4038
        }
4039
4040
        $this->tmpCache[__FUNCTION__][$cacheKey] = $content;
4041
4042
        return $content;
4043
    }
4044
4045
    /**
4046
     * Returns the page information as database row, the type of result is
4047
     * defined with the parameter $rowMode
4048
     *
4049
     * @param int $pageid The parent document identifier
4050
     *                    Default: -1 (no result)
4051
     * @param int $active Should we fetch only published and undeleted documents/resources?
4052
     *                     1 = yes, 0 = no
4053
     *                     Default: 1
4054
     * @param string $fields List of fields
4055
     *                       Default: id, pagetitle, description, alias
4056
     * @return boolean|array
4057
     */
4058
    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...
4059
    {
4060
4061
        $cacheKey = md5(print_r(func_get_args(), true));
4062
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
4063
            return $this->tmpCache[__FUNCTION__][$cacheKey];
4064
        }
4065
4066
        if ($pageid == 0) {
4067
            return false;
4068
        } else {
4069
            $tblsc = $this->getFullTableName("site_content");
4070
            $tbldg = $this->getFullTableName("document_groups");
4071
            $activeSql = $active == 1 ? "AND sc.published=1 AND sc.deleted=0" : "";
4072
            // modify field names to use sc. table reference
4073
            $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
4074
            // get document groups for current user
4075
            if ($docgrp = $this->getUserDocGroups()) {
4076
                $docgrp = implode(",", $docgrp);
4077
            }
4078
            $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
4079
            $result = $this->db->select($fields, "{$tblsc} sc LEFT JOIN {$tbldg} dg on dg.document = sc.id",
4080
                "(sc.id='{$pageid}' {$activeSql}) AND ({$access})", "", 1);
4081
            $pageInfo = $this->db->getRow($result);
4082
4083
            $this->tmpCache[__FUNCTION__][$cacheKey] = $pageInfo;
4084
4085
            return $pageInfo;
4086
        }
4087
    }
4088
4089
    /**
4090
     * Returns the parent document/resource of the given docid
4091
     *
4092
     * @param int $pid The parent docid. If -1, then fetch the current document/resource's parent
4093
     *                 Default: -1
4094
     * @param int $active Should we fetch only published and undeleted documents/resources?
4095
     *                     1 = yes, 0 = no
4096
     *                     Default: 1
4097
     * @param string $fields List of fields
4098
     *                       Default: id, pagetitle, description, alias
4099
     * @return boolean|array
4100
     */
4101
    public function getParent($pid = -1, $active = 1, $fields = 'id, pagetitle, description, alias, parent')
4102
    {
4103
        if ($pid == -1) {
4104
            $pid = $this->documentObject['parent'];
4105
4106
            return ($pid == 0) ? false : $this->getPageInfo($pid, $active, $fields);
4107
        } else {
4108
            if ($pid == 0) {
4109
                return false;
4110
            } else {
4111
                // first get the child document
4112
                $child = $this->getPageInfo($pid, $active, "parent");
4113
                // now return the child's parent
4114
                $pid = ($child['parent']) ? $child['parent'] : 0;
4115
4116
                return ($pid == 0) ? false : $this->getPageInfo($pid, $active, $fields);
4117
            }
4118
        }
4119
    }
4120
4121
    /**
4122
     * Returns the id of the current snippet.
4123
     *
4124
     * @return int
4125
     */
4126
    public function getSnippetId()
4127
    {
4128
        if ($this->currentSnippet) {
4129
            $tbl = $this->getFullTableName("site_snippets");
4130
            $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...
4131
            if ($snippetId = $this->db->getValue($rs)) {
4132
                return $snippetId;
4133
            }
4134
        }
4135
4136
        return 0;
4137
    }
4138
4139
    /**
4140
     * Returns the name of the current snippet.
4141
     *
4142
     * @return string
4143
     */
4144
    public function getSnippetName()
4145
    {
4146
        return $this->currentSnippet;
4147
    }
4148
4149
    /**
4150
     * Clear the cache of MODX.
4151
     *
4152
     * @param string $type
4153
     * @param bool $report
4154
     * @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...
4155
     */
4156
    public function clearCache($type = '', $report = false)
4157
    {
4158
        $cache_dir = MODX_BASE_PATH . $this->getCacheFolder();
4159
        if (is_array($type)) {
4160
            foreach ($type as $_) {
4161
                $this->clearCache($_, $report);
4162
            }
4163
        } elseif ($type == 'full') {
4164
            include_once(MODX_MANAGER_PATH . 'processors/cache_sync.class.processor.php');
4165
            $sync = new synccache();
4166
            $sync->setCachepath($cache_dir);
4167
            $sync->setReport($report);
4168
            $sync->emptyCache();
4169
        } elseif (preg_match('@^[1-9][0-9]*$@', $type)) {
4170
            $key = ($this->config['cache_type'] == 2) ? $this->makePageCacheKey($type) : $type;
4171
            $file_name = "docid_" . $key . "_*.pageCache.php";
4172
            $cache_path = $cache_dir . $file_name;
4173
            $files = glob($cache_path);
4174
            $files[] = $cache_dir . "docid_" . $key . ".pageCache.php";
4175
            foreach ($files as $file) {
4176
                if (!is_file($file)) {
4177
                    continue;
4178
                }
4179
                unlink($file);
4180
            }
4181
        } else {
4182
            $files = glob($cache_dir . '*');
4183
            foreach ($files as $file) {
4184
                $name = basename($file);
4185
                if (strpos($name, '.pageCache.php') === false) {
4186
                    continue;
4187
                }
4188
                if (!is_file($file)) {
4189
                    continue;
4190
                }
4191
                unlink($file);
4192
            }
4193
        }
4194
    }
4195
4196
    /**
4197
     * makeUrl
4198
     *
4199
     * @desc Create an URL for the given document identifier. The url prefix and postfix are used, when “friendly_url” is active.
4200
     *
4201
     * @param $id {integer} - The document identifier. @required
4202
     * @param string $alias {string}
4203
     * - The alias name for the document. Default: ''.
4204
     * @param string $args {string}
4205
     * - The paramaters to add to the URL. Default: ''.
4206
     * @param string $scheme {string}
4207
     * - With full as valus, the site url configuration is used. Default: ''.
4208
     * @return mixed|string {string} - Result URL.
4209
     * - Result URL.
4210
     */
4211
    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...
4212
    {
4213
        $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...
4214
        $virtualDir = isset($this->config['virtual_dir']) ? $this->config['virtual_dir'] : '';
4215
        $f_url_prefix = $this->config['friendly_url_prefix'];
4216
        $f_url_suffix = $this->config['friendly_url_suffix'];
4217
4218
        if (!is_numeric($id)) {
4219
            $this->messageQuit("`{$id}` is not numeric and may not be passed to makeUrl()");
4220
        }
4221
4222
        if ($args !== '') {
4223
            // add ? or & to $args if missing
4224
            $args = ltrim($args, '?&');
4225
            $_ = 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...
4226
4227
            if ($_ === false && $this->config['friendly_urls'] == 1) {
4228
                $args = "?{$args}";
4229
            } else {
4230
                $args = "&{$args}";
4231
            }
4232
        }
4233
4234
        if ($id != $this->config['site_start']) {
4235
            if ($this->config['friendly_urls'] == 1 && $alias == '') {
4236
                $alias = $id;
4237
                $alPath = '';
4238
4239
                if ($this->config['friendly_alias_urls'] == 1) {
4240
4241
                    if ($this->config['aliaslistingfolder'] == 1) {
4242
                        $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...
4243
                    } else {
4244
                        $al = $this->aliasListing[$id];
4245
                    }
4246
4247
                    if ($al['isfolder'] === 1 && $this->config['make_folders'] === '1') {
4248
                        $f_url_suffix = '/';
4249
                    }
4250
4251
                    $alPath = !empty ($al['path']) ? $al['path'] . '/' : '';
4252
4253
                    if ($al && $al['alias']) {
4254
                        $alias = $al['alias'];
4255
                    }
4256
4257
                }
4258
4259
                $alias = $alPath . $f_url_prefix . $alias . $f_url_suffix;
4260
                $url = "{$alias}{$args}";
4261
            } else {
4262
                $url = "index.php?id={$id}{$args}";
4263
            }
4264
        } else {
4265
            $url = $args;
4266
        }
4267
4268
        $host = $this->config['base_url'];
4269
4270
        // check if scheme argument has been set
4271
        if ($scheme != '') {
4272
            // for backward compatibility - check if the desired scheme is different than the current scheme
4273
            if (is_numeric($scheme) && $scheme != $_SERVER['HTTPS']) {
4274
                $scheme = ($_SERVER['HTTPS'] ? 'http' : 'https');
4275
            }
4276
4277
            //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...
4278
            $host = $scheme == 'full' ? $this->config['site_url'] : $scheme . '://' . $_SERVER['HTTP_HOST'] . $host;
4279
        }
4280
4281
        //fix strictUrl by Bumkaka
4282
        if ($this->config['seostrict'] == '1') {
4283
            $url = $this->toAlias($url);
4284
        }
4285
4286
        if ($this->config['xhtml_urls']) {
4287
            $url = preg_replace("/&(?!amp;)/", "&amp;", $host . $virtualDir . $url);
4288
        } else {
4289
            $url = $host . $virtualDir . $url;
4290
        }
4291
4292
        $evtOut = $this->invokeEvent('OnMakeDocUrl', array(
4293
            'id'  => $id,
4294
            'url' => $url
4295
        ));
4296
4297
        if (is_array($evtOut) && count($evtOut) > 0) {
4298
            $url = array_pop($evtOut);
4299
        }
4300
4301
        return $url;
4302
    }
4303
4304
    /**
4305
     * @param $id
4306
     * @return mixed
4307
     */
4308
    public function getAliasListing($id)
4309
    {
4310
        if (isset($this->aliasListing[$id])) {
4311
            $out = $this->aliasListing[$id];
4312
        } else {
4313
            $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...
4314
            if ($this->db->getRecordCount($q) == '1') {
4315
                $q = $this->db->getRow($q);
4316
                $this->aliasListing[$id] = array(
4317
                    'id'       => (int)$q['id'],
4318
                    'alias'    => $q['alias'] == '' ? $q['id'] : $q['alias'],
4319
                    'parent'   => (int)$q['parent'],
4320
                    'isfolder' => (int)$q['isfolder'],
4321
                );
4322
                if ($this->aliasListing[$id]['parent'] > 0) {
4323
                    //fix alias_path_usage
4324
                    if ($this->config['use_alias_path'] == '1') {
4325
                        //&& $tmp['path'] != '' - fix error slash with epty path
4326
                        $tmp = $this->getAliasListing($this->aliasListing[$id]['parent']);
4327
                        $this->aliasListing[$id]['path'] = $tmp['path'] . ($tmp['alias_visible'] ? (($tmp['parent'] > 0 && $tmp['path'] != '') ? '/' : '') . $tmp['alias'] : '');
4328
                    } else {
4329
                        $this->aliasListing[$id]['path'] = '';
4330
                    }
4331
                }
4332
4333
                $out = $this->aliasListing[$id];
4334
            }
4335
        }
4336
4337
        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...
4338
    }
4339
4340
    /**
4341
     * Returns an entry from the config
4342
     *
4343
     * Note: most code accesses the config array directly and we will continue to support this.
4344
     *
4345
     * @param string $name
4346
     * @return bool|string
4347
     */
4348
    public function getConfig($name = '')
4349
    {
4350
        if (!empty ($this->config[$name])) {
4351
            return $this->config[$name];
4352
        } else {
4353
            return false;
4354
        }
4355
    }
4356
4357
    /**
4358
     * Returns the MODX version information as version, branch, release date and full application name.
4359
     *
4360
     * @param null $data
4361
     * @return array
4362
     */
4363
4364
    public function getVersionData($data = null)
4365
    {
4366
        $out = array();
4367
        if (empty($this->version) || !is_array($this->version)) {
4368
            //include for compatibility modx version < 1.0.10
4369
            include MODX_MANAGER_PATH . "includes/version.inc.php";
4370
            $this->version = array();
4371
            $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...
4372
            $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...
4373
            $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...
4374
            $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...
4375
            $this->version['new_version'] = isset($this->config['newversiontext']) ? $this->config['newversiontext'] : '';
4376
        }
4377
4378
        return (!is_null($data) && is_array($this->version) && isset($this->version[$data])) ? $this->version[$data] : $this->version;
4379
    }
4380
4381
    /**
4382
     * Executes a snippet.
4383
     *
4384
     * @param string $snippetName
4385
     * @param array $params Default: Empty array
4386
     * @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...
4387
     */
4388
    public function runSnippet($snippetName, $params = array())
4389
    {
4390
        if (isset ($this->snippetCache[$snippetName])) {
4391
            $snippet = $this->snippetCache[$snippetName];
4392
            $properties = !empty($this->snippetCache[$snippetName . "Props"]) ? $this->snippetCache[$snippetName . "Props"] : '';
4393
        } else { // not in cache so let's check the db
4394
            $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;";
4395
            $result = $this->db->query($sql);
4396
            if ($this->db->getRecordCount($result) == 1) {
4397
                $row = $this->db->getRow($result);
4398
                $snippet = $this->snippetCache[$snippetName] = $row['snippet'];
4399
                $mergedProperties = array_merge($this->parseProperties($row['properties']),
4400
                    $this->parseProperties($row['sharedproperties']));
4401
                $properties = $this->snippetCache[$snippetName . "Props"] = json_encode($mergedProperties);
4402
            } else {
4403
                $snippet = $this->snippetCache[$snippetName] = "return false;";
4404
                $properties = $this->snippetCache[$snippetName . "Props"] = '';
4405
            }
4406
        }
4407
        // load default params/properties
4408
        $parameters = $this->parseProperties($properties, $snippetName, 'snippet');
4409
        $parameters = array_merge($parameters, $params);
4410
4411
        // run snippet
4412
        return $this->evalSnippet($snippet, $parameters);
4413
    }
4414
4415
    /**
4416
     * Returns the chunk content for the given chunk name
4417
     *
4418
     * @param string $chunkName
4419
     * @return boolean|string
4420
     */
4421
    public function getChunk($chunkName)
4422
    {
4423
        $out = null;
4424
        if (empty($chunkName)) {
4425
            return $out;
4426
        }
4427
        if (isset ($this->chunkCache[$chunkName])) {
4428
            $out = $this->chunkCache[$chunkName];
4429
        } else {
4430
            if (stripos($chunkName, '@FILE') === 0) {
4431
                $out = $this->chunkCache[$chunkName] = $this->atBindFileContent($chunkName);
4432
            } else {
4433
                $where = sprintf("`name`='%s' AND disabled=0", $this->db->escape($chunkName));
4434
                $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...
4435
                if ($this->db->getRecordCount($rs) == 1) {
4436
                    $row = $this->db->getRow($rs);
4437
                    $out = $this->chunkCache[$chunkName] = $row['snippet'];
4438
                } else {
4439
                    $out = $this->chunkCache[$chunkName] = null;
4440
                }
4441
            }
4442
        }
4443
4444
        return $out;
4445
    }
4446
4447
    /**
4448
     * parseText
4449
     * @version 1.0 (2013-10-17)
4450
     *
4451
     * @desc Replaces placeholders in text with required values.
4452
     *
4453
     * @param string $tpl
4454
     * @param array $ph
4455
     * @param string $left
4456
     * @param string $right
4457
     * @param bool $execModifier
4458
     * @return string {string} - Parsed text.
4459
     * - Parsed text.
4460
     * @internal param $chunk {string} - String to parse. - String to parse. @required
4461
     * @internal param $chunkArr {array} - Array of values. Key — placeholder name, value — value. - Array of values. Key — placeholder name, value — value. @required
4462
     * @internal param $prefix {string} - Placeholders prefix. Default: '[+'. - Placeholders prefix. Default: '[+'.
4463
     * @internal param $suffix {string} - Placeholders suffix. Default: '+]'. - Placeholders suffix. Default: '+]'.
4464
     *
4465
     */
4466
    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...
4467
    {
4468
        if (empty($ph) || empty($tpl)) {
4469
            return $tpl;
4470
        }
4471
4472 View Code Duplication
        if ($this->config['enable_at_syntax']) {
4473
            if (stripos($tpl, '<@LITERAL>') !== false) {
4474
                $tpl = $this->escapeLiteralTagsContent($tpl);
4475
            }
4476
        }
4477
4478
        $matches = $this->getTagsFromContent($tpl, $left, $right);
4479
        if (empty($matches)) {
4480
            return $tpl;
4481
        }
4482
4483
        foreach ($matches[1] as $i => $key) {
4484
4485
            if (strpos($key, ':') !== false && $execModifier) {
4486
                list($key, $modifiers) = $this->splitKeyAndFilter($key);
4487
            } else {
4488
                $modifiers = false;
4489
            }
4490
4491
            //          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...
4492
            if (!array_key_exists($key, $ph)) {
4493
                continue;
4494
            } //NULL values must be saved in placeholders, if we got them from database string
4495
4496
            $value = $ph[$key];
4497
4498
            $s = &$matches[0][$i];
4499
            if ($modifiers !== false) {
4500
                if (strpos($modifiers, $left) !== false) {
4501
                    $modifiers = $this->parseText($modifiers, $ph, $left, $right);
4502
                }
4503
                $value = $this->applyFilter($value, $modifiers, $key);
4504
            }
4505 View Code Duplication
            if (strpos($tpl, $s) !== false) {
4506
                $tpl = str_replace($s, $value, $tpl);
4507
            } elseif ($this->debug) {
4508
                $this->addLog('parseText parse error', $_SERVER['REQUEST_URI'] . $s, 2);
4509
            }
4510
        }
4511
4512
        return $tpl;
4513
    }
4514
4515
    /**
4516
     * parseChunk
4517
     * @version 1.1 (2013-10-17)
4518
     *
4519
     * @desc Replaces placeholders in a chunk with required values.
4520
     *
4521
     * @param $chunkName {string} - Name of chunk to parse. @required
4522
     * @param $chunkArr {array} - Array of values. Key — placeholder name, value — value. @required
4523
     * @param string $prefix {string}
4524
     * - Placeholders prefix. Default: '{'.
4525
     * @param string $suffix {string}
4526
     * - Placeholders suffix. Default: '}'.
4527
     * @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...
4528
     * - Parsed chunk or false if $chunkArr is not array.
4529
     */
4530
    public function parseChunk($chunkName, $chunkArr, $prefix = '{', $suffix = '}')
4531
    {
4532
        //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...
4533
        if (!is_array($chunkArr)) {
4534
            return false;
4535
        }
4536
4537
        return $this->parseText($this->getChunk($chunkName), $chunkArr, $prefix, $suffix);
0 ignored issues
show
Bug introduced by
It seems like $this->getChunk($chunkName) targeting DocumentParser::getChunk() can also be of type boolean; however, DocumentParser::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...
4538
    }
4539
4540
    /**
4541
     * getTpl
4542
     * get template for snippets
4543
     * @param $tpl {string}
4544
     * @return bool|string {string}
4545
     */
4546
    public function getTpl($tpl)
4547
    {
4548
        $template = $tpl;
4549
        if (preg_match("~^@([^:\s]+)[:\s]+(.+)$~", $tpl, $match)) {
4550
            $command = strtoupper($match[1]);
4551
            $template = $match[2];
4552
        }
4553
        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...
4554
            case 'CODE':
4555
                break;
4556
            case 'FILE':
4557
                $template = file_get_contents(MODX_BASE_PATH . $template);
4558
                break;
4559
            case 'CHUNK':
4560
                $template = $this->getChunk($template);
4561
                break;
4562
            case 'DOCUMENT':
4563
                $doc = $this->getDocument($template, 'content', 'all');
4564
                $template = $doc['content'];
4565
                break;
4566
            case 'SELECT':
4567
                $this->db->getValue($this->db->query("SELECT {$template}"));
4568
                break;
4569
            default:
4570
                if (!($template = $this->getChunk($tpl))) {
4571
                    $template = $tpl;
4572
                }
4573
        }
4574
4575
        return $template;
4576
    }
4577
4578
    /**
4579
     * Returns the timestamp in the date format defined in $this->config['datetime_format']
4580
     *
4581
     * @param int $timestamp Default: 0
4582
     * @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.
4583
     * @return string
4584
     */
4585
    public function toDateFormat($timestamp = 0, $mode = '')
4586
    {
4587
        $timestamp = trim($timestamp);
4588
        if ($mode !== 'formatOnly' && empty($timestamp)) {
4589
            return '-';
4590
        }
4591
        $timestamp = (int)$timestamp;
4592
4593
        switch ($this->config['datetime_format']) {
4594
            case 'YYYY/mm/dd':
4595
                $dateFormat = '%Y/%m/%d';
4596
                break;
4597
            case 'dd-mm-YYYY':
4598
                $dateFormat = '%d-%m-%Y';
4599
                break;
4600
            case 'mm/dd/YYYY':
4601
                $dateFormat = '%m/%d/%Y';
4602
                break;
4603
            /*
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...
4604
            case 'dd-mmm-YYYY':
4605
                $dateFormat = '%e-%b-%Y';
4606
                break;
4607
            */
4608
        }
4609
4610
        if (empty($mode)) {
4611
            $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...
4612
        } elseif ($mode == 'dateOnly') {
4613
            $strTime = strftime($dateFormat, $timestamp);
4614
        } elseif ($mode == 'formatOnly') {
4615
            $strTime = $dateFormat;
4616
        }
4617
4618
        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...
4619
    }
4620
4621
    /**
4622
     * Make a timestamp from a string corresponding to the format in $this->config['datetime_format']
4623
     *
4624
     * @param string $str
4625
     * @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...
4626
     */
4627
    public function toTimeStamp($str)
4628
    {
4629
        $str = trim($str);
4630
        if (empty($str)) {
4631
            return '';
4632
        }
4633
4634
        switch ($this->config['datetime_format']) {
4635 View Code Duplication
            case 'YYYY/mm/dd':
4636
                if (!preg_match('/^[0-9]{4}\/[0-9]{2}\/[0-9]{2}[0-9 :]*$/', $str)) {
4637
                    return '';
4638
                }
4639
                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...
4640
                break;
4641 View Code Duplication
            case 'dd-mm-YYYY':
4642
                if (!preg_match('/^[0-9]{2}-[0-9]{2}-[0-9]{4}[0-9 :]*$/', $str)) {
4643
                    return '';
4644
                }
4645
                list ($d, $m, $Y, $H, $M, $S) = sscanf($str, '%2d-%2d-%4d %2d:%2d:%2d');
4646
                break;
4647 View Code Duplication
            case 'mm/dd/YYYY':
4648
                if (!preg_match('/^[0-9]{2}\/[0-9]{2}\/[0-9]{4}[0-9 :]*$/', $str)) {
4649
                    return '';
4650
                }
4651
                list ($m, $d, $Y, $H, $M, $S) = sscanf($str, '%2d/%2d/%4d %2d:%2d:%2d');
4652
                break;
4653
            /*
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...
4654
            case 'dd-mmm-YYYY':
4655
                if (!preg_match('/^[0-9]{2}-[0-9a-z]+-[0-9]{4}[0-9 :]*$/i', $str)) {return '';}
4656
                list ($m, $d, $Y, $H, $M, $S) = sscanf($str, '%2d-%3s-%4d %2d:%2d:%2d');
4657
                break;
4658
            */
4659
        }
4660
        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...
4661
            $H = 0;
4662
            $M = 0;
4663
            $S = 0;
4664
        }
4665
        $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...
4666
        $timeStamp = (int)$timeStamp;
4667
4668
        return $timeStamp;
4669
    }
4670
4671
    /**
4672
     * Get the TVs of a document's children. Returns an array where each element represents one child doc.
4673
     *
4674
     * Ignores deleted children. Gets all children - there is no where clause available.
4675
     *
4676
     * @param int $parentid The parent docid
4677
     *                 Default: 0 (site root)
4678
     * @param array $tvidnames . Which TVs to fetch - Can relate to the TV ids in the db (array elements should be numeric only)
4679
     *                                               or the TV names (array elements should be names only)
4680
     *                      Default: Empty array
4681
     * @param int $published Whether published or unpublished documents are in the result
4682
     *                      Default: 1
4683
     * @param string $docsort How to sort the result array (field)
4684
     *                      Default: menuindex
4685
     * @param ASC|string $docsortdir How to sort the result array (direction)
4686
     *                      Default: ASC
4687
     * @param string $tvfields Fields to fetch from site_tmplvars, default '*'
4688
     *                      Default: *
4689
     * @param string $tvsort How to sort each element of the result array i.e. how to sort the TVs (field)
4690
     *                      Default: rank
4691
     * @param string $tvsortdir How to sort each element of the result array i.e. how to sort the TVs (direction)
4692
     *                      Default: ASC
4693
     * @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...
4694
     */
4695
    public function getDocumentChildrenTVars(
4696
        $parentid = 0,
4697
        $tvidnames = array(),
4698
        $published = 1,
4699
        $docsort = "menuindex",
4700
        $docsortdir = "ASC",
4701
        $tvfields = "*",
4702
        $tvsort = "rank",
4703
        $tvsortdir = "ASC"
4704
    ) {
4705
        $docs = $this->getDocumentChildren($parentid, $published, 0, '*', '', $docsort, $docsortdir);
4706
        if (!$docs) {
4707
            return false;
4708
        } else {
4709
            $result = array();
4710
            // get user defined template variables
4711
            if ($tvfields) {
4712
                $_ = 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...
4713
                foreach ($_ as $i => $v) {
4714
                    if ($v === 'value') {
4715
                        unset($_[$i]);
4716
                    } else {
4717
                        $_[$i] = 'tv.' . $v;
4718
                    }
4719
                }
4720
                $fields = implode(',', $_);
4721
            } else {
4722
                $fields = "tv.*";
4723
            }
4724
4725
            if ($tvsort != '') {
4726
                $tvsort = 'tv.' . implode(',tv.', array_filter(array_map('trim', explode(',', $tvsort))));
4727
            }
4728 View Code Duplication
            if ($tvidnames == "*") {
4729
                $query = "tv.id<>0";
4730
            } else {
4731
                $query = (is_numeric($tvidnames[0]) ? "tv.id" : "tv.name") . " IN ('" . implode("','",
4732
                        $tvidnames) . "')";
4733
            }
4734
4735
            $this->getUserDocGroups();
4736
4737
            foreach ($docs as $doc) {
4738
4739
                $docid = $doc['id'];
4740
4741
                $rs = $this->db->select("{$fields}, IF(tvc.value!='',tvc.value,tv.default_text) as value ", "[+prefix+]site_tmplvars tv
4742
                        INNER JOIN [+prefix+]site_tmplvar_templates tvtpl ON tvtpl.tmplvarid = tv.id
4743
                        LEFT JOIN [+prefix+]site_tmplvar_contentvalues tvc ON tvc.tmplvarid=tv.id AND tvc.contentid='{$docid}'",
4744
                    "{$query} AND tvtpl.templateid = '{$doc['template']}'", ($tvsort ? "{$tvsort} {$tvsortdir}" : ""));
4745
                $tvs = $this->db->makeArray($rs);
4746
4747
                // get default/built-in template variables
4748
                ksort($doc);
4749
                foreach ($doc as $key => $value) {
4750
                    if ($tvidnames == '*' || in_array($key, $tvidnames)) {
4751
                        $tvs[] = array('name' => $key, 'value' => $value);
4752
                    }
4753
                }
4754
                if (is_array($tvs) && count($tvs)) {
4755
                    $result[] = $tvs;
4756
                }
4757
            }
4758
4759
            return $result;
4760
        }
4761
    }
4762
4763
    /**
4764
     * getDocumentChildrenTVarOutput
4765
     * @version 1.1 (2014-02-19)
4766
     *
4767
     * @desc Returns an array where each element represents one child doc and contains the result from getTemplateVarOutput().
4768
     *
4769
     * @param int $parentid {integer}
4770
     * - Id of parent document. Default: 0 (site root).
4771
     * @param array $tvidnames {array; '*'}
4772
     * - Which TVs to fetch. In the form expected by getTemplateVarOutput(). Default: array().
4773
     * @param int $published {0; 1; 'all'}
4774
     * - 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.
4775
     * @param string $sortBy {string}
4776
     * - How to sort the result array (field). Default: 'menuindex'.
4777
     * @param string $sortDir {'ASC'; 'DESC'}
4778
     * - How to sort the result array (direction). Default: 'ASC'.
4779
     * @param string $where {string}
4780
     * - SQL WHERE condition (use only document fields, not TV). Default: ''.
4781
     * @param string $resultKey {string; false}
4782
     * - Field, which values are keys into result array. Use the “false”, that result array keys just will be numbered. Default: 'id'.
4783
     * @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...
4784
     * - Result array, or false.
4785
     */
4786
    public function getDocumentChildrenTVarOutput(
4787
        $parentid = 0,
4788
        $tvidnames = array(),
4789
        $published = 1,
4790
        $sortBy = 'menuindex',
4791
        $sortDir = 'ASC',
4792
        $where = '',
4793
        $resultKey = 'id'
4794
    ) {
4795
        $docs = $this->getDocumentChildren($parentid, $published, 0, 'id', $where, $sortBy, $sortDir);
4796
4797
        if (!$docs) {
4798
            return false;
4799
        } else {
4800
            $result = array();
4801
4802
            $unsetResultKey = false;
4803
4804
            if ($resultKey !== false) {
4805
                if (is_array($tvidnames)) {
4806
                    if (count($tvidnames) != 0 && !in_array($resultKey, $tvidnames)) {
4807
                        $tvidnames[] = $resultKey;
4808
                        $unsetResultKey = true;
4809
                    }
4810
                } else {
4811
                    if ($tvidnames != '*' && $tvidnames != $resultKey) {
4812
                        $tvidnames = array($tvidnames, $resultKey);
4813
                        $unsetResultKey = true;
4814
                    }
4815
                }
4816
            }
4817
4818
            for ($i = 0; $i < count($docs); $i++) {
4819
                $tvs = $this->getTemplateVarOutput($tvidnames, $docs[$i]['id'], $published);
4820
4821
                if ($tvs) {
4822
                    if ($resultKey !== false && array_key_exists($resultKey, $tvs)) {
4823
                        $result[$tvs[$resultKey]] = $tvs;
4824
4825
                        if ($unsetResultKey) {
4826
                            unset($result[$tvs[$resultKey]][$resultKey]);
4827
                        }
4828
                    } else {
4829
                        $result[] = $tvs;
4830
                    }
4831
                }
4832
            }
4833
4834
            return $result;
4835
        }
4836
    }
4837
4838
    /**
4839
     * Modified by Raymond for TV - Orig Modified by Apodigm - DocVars
4840
     * Returns a single site_content field or TV record from the db.
4841
     *
4842
     * If a site content field the result is an associative array of 'name' and 'value'.
4843
     *
4844
     * If a TV the result is an array representing a db row including the fields specified in $fields.
4845
     *
4846
     * @param string $idname Can be a TV id or name
4847
     * @param string $fields Fields to fetch from site_tmplvars. Default: *
4848
     * @param string|type $docid Docid. Defaults to empty string which indicates the current document.
4849
     * @param int $published Whether published or unpublished documents are in the result
4850
     *                        Default: 1
4851
     * @return bool
4852
     */
4853 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...
4854
    {
4855
        if ($idname == "") {
4856
            return false;
4857
        } else {
4858
            $result = $this->getTemplateVars(array($idname), $fields, $docid, $published, "",
4859
                ""); //remove sorting for speed
4860
4861
            return ($result != false) ? $result[0] : false;
4862
        }
4863
    }
4864
4865
    /**
4866
     * getTemplateVars
4867
     * @version 1.0.1 (2014-02-19)
4868
     *
4869
     * @desc Returns an array of site_content field fields and/or TV records from the db.
4870
     * Elements representing a site content field consist of an associative array of 'name' and 'value'.
4871
     * Elements representing a TV consist of an array representing a db row including the fields specified in $fields.
4872
     *
4873
     * @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
4874
     * @param $fields {comma separated string; '*'} - Fields names in the TV table of MODx database. Default: '*'
4875
     * @param $docid {integer; ''} - Id of a document to get. Default: an empty string which indicates the current document.
4876
     * @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.
4877
     * @param $sort {comma separated string} - Fields of the TV table to sort by. Default: 'rank'.
4878
     * @param $dir {'ASC'; 'DESC'} - How to sort the result array (direction). Default: 'ASC'.
4879
     *
4880
     * @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...
4881
     */
4882
    public function getTemplateVars(
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...
4883
        $idnames = array(),
4884
        $fields = '*',
4885
        $docid = '',
4886
        $published = 1,
4887
        $sort = 'rank',
4888
        $dir = 'ASC'
4889
    ) {
4890
        $cacheKey = md5(print_r(func_get_args(), true));
4891
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
4892
            return $this->tmpCache[__FUNCTION__][$cacheKey];
4893
        }
4894
4895
        if (($idnames != '*' && !is_array($idnames)) || empty($idnames)) {
4896
            return false;
4897
        } else {
4898
4899
            // get document record
4900
            if ($docid == '') {
4901
                $docid = $this->documentIdentifier;
4902
                $docRow = $this->documentObject;
4903
            } else {
4904
                $docRow = $this->getDocument($docid, '*', $published);
4905
4906
                if (!$docRow) {
4907
                    $this->tmpCache[__FUNCTION__][$cacheKey] = false;
4908
4909
                    return false;
4910
                }
4911
            }
4912
4913
            // get user defined template variables
4914
            $fields = ($fields == '') ? 'tv.*' : 'tv.' . implode(',tv.',
4915
                    array_filter(array_map('trim', explode(',', $fields))));
4916
            $sort = ($sort == '') ? '' : 'tv.' . implode(',tv.', array_filter(array_map('trim', explode(',', $sort))));
4917
4918 View Code Duplication
            if ($idnames == '*') {
4919
                $query = 'tv.id<>0';
4920
            } else {
4921
                $query = (is_numeric($idnames[0]) ? 'tv.id' : 'tv.name') . " IN ('" . implode("','", $idnames) . "')";
4922
            }
4923
4924
            $rs = $this->db->select("{$fields}, IF(tvc.value != '', tvc.value, tv.default_text) as value",
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...
4925
                $this->getFullTableName('site_tmplvars') . " tv
4926
                    INNER JOIN " . $this->getFullTableName('site_tmplvar_templates') . " tvtpl ON tvtpl.tmplvarid = tv.id
4927
                    LEFT JOIN " . $this->getFullTableName('site_tmplvar_contentvalues') . " tvc ON tvc.tmplvarid=tv.id AND tvc.contentid = '{$docid}'",
4928
                "{$query} AND tvtpl.templateid = '{$docRow['template']}'", ($sort ? "{$sort} {$dir}" : ""));
4929
4930
            $result = $this->db->makeArray($rs);
4931
4932
            // get default/built-in template variables
4933
            if (is_array($docRow)) {
4934
                ksort($docRow);
4935
4936
                foreach ($docRow as $key => $value) {
4937
                    if ($idnames == '*' || in_array($key, $idnames)) {
4938
                        array_push($result, array(
4939
                            'name'  => $key,
4940
                            'value' => $value
4941
                        ));
4942
                    }
4943
                }
4944
            }
4945
4946
            $this->tmpCache[__FUNCTION__][$cacheKey] = $result;
4947
4948
            return $result;
4949
        }
4950
    }
4951
4952
    /**
4953
     * getTemplateVarOutput
4954
     * @version 1.0.1 (2014-02-19)
4955
     *
4956
     * @desc Returns an associative array containing TV rendered output values.
4957
     *
4958
     * @param array $idnames {array; '*'}
4959
     * - 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
4960
     * @param string $docid {integer; ''}
4961
     * - Id of a document to get. Default: an empty string which indicates the current document.
4962
     * @param int $published {0; 1; 'all'}
4963
     * - 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.
4964
     * @param string $sep {string}
4965
     * - Separator that is used while concatenating in getTVDisplayFormat(). Default: ''.
4966
     * @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...
4967
     * - Result array, or false.
4968
     */
4969
    public function getTemplateVarOutput($idnames = array(), $docid = '', $published = 1, $sep = '')
4970
    {
4971
        if (is_array($idnames) && empty($idnames)) {
4972
            return false;
4973
        } else {
4974
            $output = array();
4975
            $vars = ($idnames == '*' || is_array($idnames)) ? $idnames : array($idnames);
4976
4977
            $docid = (int)$docid > 0 ? (int)$docid : $this->documentIdentifier;
4978
            // remove sort for speed
4979
            $result = $this->getTemplateVars($vars, '*', $docid, $published, '', '');
4980
4981
            if ($result == false) {
4982
                return false;
4983
            } else {
4984
                $baspath = MODX_MANAGER_PATH . 'includes';
4985
                include_once $baspath . '/tmplvars.format.inc.php';
4986
                include_once $baspath . '/tmplvars.commands.inc.php';
4987
4988
                for ($i = 0; $i < count($result); $i++) {
4989
                    $row = $result[$i];
4990
4991
                    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...
4992
                        $output[$row['name']] = $row['value'];
4993
                    } else {
4994
                        $output[$row['name']] = getTVDisplayFormat($row['name'], $row['value'], $row['display'],
4995
                            $row['display_params'], $row['type'], $docid, $sep);
4996
                    }
4997
                }
4998
4999
                return $output;
5000
            }
5001
        }
5002
    }
5003
5004
    /**
5005
     * Returns the full table name based on db settings
5006
     *
5007
     * @param string $tbl Table name
5008
     * @return string Table name with prefix
5009
     */
5010
    public function getFullTableName($tbl)
5011
    {
5012
        return $this->db->config['dbase'] . ".`" . $this->db->config['table_prefix'] . $tbl . "`";
5013
    }
5014
5015
    /**
5016
     * Returns the placeholder value
5017
     *
5018
     * @param string $name Placeholder name
5019
     * @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...
5020
     */
5021
    public function getPlaceholder($name)
5022
    {
5023
        return isset($this->placeholders[$name]) ? $this->placeholders[$name] : null;
5024
    }
5025
5026
    /**
5027
     * Sets a value for a placeholder
5028
     *
5029
     * @param string $name The name of the placeholder
5030
     * @param string $value The value of the placeholder
5031
     */
5032
    public function setPlaceholder($name, $value)
5033
    {
5034
        $this->placeholders[$name] = $value;
5035
    }
5036
5037
    /**
5038
     * Set placeholders en masse via an array or object.
5039
     *
5040
     * @param object|array $subject
5041
     * @param string $prefix
5042
     */
5043
    public function toPlaceholders($subject, $prefix = '')
5044
    {
5045
        if (is_object($subject)) {
5046
            $subject = get_object_vars($subject);
5047
        }
5048
        if (is_array($subject)) {
5049
            foreach ($subject as $key => $value) {
5050
                $this->toPlaceholder($key, $value, $prefix);
5051
            }
5052
        }
5053
    }
5054
5055
    /**
5056
     * For use by toPlaceholders(); For setting an array or object element as placeholder.
5057
     *
5058
     * @param string $key
5059
     * @param object|array $value
5060
     * @param string $prefix
5061
     */
5062
    public function toPlaceholder($key, $value, $prefix = '')
5063
    {
5064
        if (is_array($value) || is_object($value)) {
5065
            $this->toPlaceholders($value, "{$prefix}{$key}.");
5066
        } else {
5067
            $this->setPlaceholder("{$prefix}{$key}", $value);
5068
        }
5069
    }
5070
5071
    /**
5072
     * Returns the manager relative URL/path with respect to the site root.
5073
     *
5074
     * @global string $base_url
5075
     * @return string The complete URL to the manager folder
5076
     */
5077
    public function getManagerPath()
5078
    {
5079
        return MODX_MANAGER_URL;
5080
    }
5081
5082
    /**
5083
     * Returns the cache relative URL/path with respect to the site root.
5084
     *
5085
     * @global string $base_url
5086
     * @return string The complete URL to the cache folder
5087
     */
5088
    public function getCachePath()
5089
    {
5090
        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...
5091
        $pth = $base_url . $this->getCacheFolder();
5092
5093
        return $pth;
5094
    }
5095
5096
    /**
5097
     * Sends a message to a user's message box.
5098
     *
5099
     * @param string $type Type of the message
5100
     * @param string $to The recipient of the message
5101
     * @param string $from The sender of the message
5102
     * @param string $subject The subject of the message
5103
     * @param string $msg The message body
5104
     * @param int $private Whether it is a private message, or not
5105
     *                     Default : 0
5106
     */
5107
    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...
5108
    {
5109
        $private = ($private) ? 1 : 0;
5110 View Code Duplication
        if (!is_numeric($to)) {
5111
            // Query for the To ID
5112
            $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...
5113
            $to = $this->db->getValue($rs);
5114
        }
5115 View Code Duplication
        if (!is_numeric($from)) {
5116
            // Query for the From ID
5117
            $rs = $this->db->select('id', $this->getFullTableName("manager_users"), "username='{$from}'");
5118
            $from = $this->db->getValue($rs);
5119
        }
5120
        // insert a new message into user_messages
5121
        $this->db->insert(array(
5122
            'type'        => $type,
5123
            'subject'     => $subject,
5124
            'message'     => $msg,
5125
            'sender'      => $from,
5126
            'recipient'   => $to,
5127
            'private'     => $private,
5128
            'postdate'    => $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'],
5129
            'messageread' => 0,
5130
        ), $this->getFullTableName('user_messages'));
5131
    }
5132
5133
    /**
5134
     * Returns current user id.
5135
     *
5136
     * @param string $context . Default is an empty string which indicates the method should automatically pick 'web (frontend) or 'mgr' (backend)
5137
     * @return string
5138
     */
5139 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...
5140
    {
5141
        $out = false;
5142
5143
        if (!empty($context)) {
5144
            if (is_scalar($context) && isset($_SESSION[$context . 'Validated'])) {
5145
                $out = $_SESSION[$context . 'InternalKey'];
5146
            }
5147
        } else {
5148
            switch (true) {
5149
                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...
5150
                    $out = $_SESSION['webInternalKey'];
5151
                    break;
5152
                }
5153
                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...
5154
                    $out = $_SESSION['mgrInternalKey'];
5155
                    break;
5156
                }
5157
            }
5158
        }
5159
5160
        return $out;
5161
    }
5162
5163
    /**
5164
     * Returns current user name
5165
     *
5166
     * @param string $context . Default is an empty string which indicates the method should automatically pick 'web (frontend) or 'mgr' (backend)
5167
     * @return string
5168
     */
5169 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...
5170
    {
5171
        $out = false;
5172
5173
        if (!empty($context)) {
5174
            if (is_scalar($context) && isset($_SESSION[$context . 'Validated'])) {
5175
                $out = $_SESSION[$context . 'Shortname'];
5176
            }
5177
        } else {
5178
            switch (true) {
5179
                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...
5180
                    $out = $_SESSION['webShortname'];
5181
                    break;
5182
                }
5183
                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...
5184
                    $out = $_SESSION['mgrShortname'];
5185
                    break;
5186
                }
5187
            }
5188
        }
5189
5190
        return $out;
5191
    }
5192
5193
    /**
5194
     * Returns current login user type - web or manager
5195
     *
5196
     * @return string
5197
     */
5198
    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...
5199
    {
5200
        if ($this->isFrontend() && isset ($_SESSION['webValidated'])) {
5201
            return 'web';
5202
        } elseif ($this->isBackend() && isset ($_SESSION['mgrValidated'])) {
5203
            return 'manager';
5204
        } else {
5205
            return '';
5206
        }
5207
    }
5208
5209
    /**
5210
     * Returns a user info record for the given manager user
5211
     *
5212
     * @param int $uid
5213
     * @return boolean|string
5214
     */
5215
    public function getUserInfo($uid)
5216
    {
5217
        if (isset($this->tmpCache[__FUNCTION__][$uid])) {
5218
            return $this->tmpCache[__FUNCTION__][$uid];
5219
        }
5220
5221
        $from = '[+prefix+]manager_users mu INNER JOIN [+prefix+]user_attributes mua ON mua.internalkey=mu.id';
5222
        $where = sprintf("mu.id='%s'", $this->db->escape($uid));
5223
        $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...
5224
5225
        if (!$this->db->getRecordCount($rs)) {
5226
            return $this->tmpCache[__FUNCTION__][$uid] = false;
5227
        }
5228
5229
        $row = $this->db->getRow($rs);
5230 View Code Duplication
        if (!isset($row['usertype']) || !$row['usertype']) {
5231
            $row['usertype'] = 'manager';
5232
        }
5233
5234
        $this->tmpCache[__FUNCTION__][$uid] = $row;
5235
5236
        return $row;
5237
    }
5238
5239
    /**
5240
     * Returns a record for the web user
5241
     *
5242
     * @param int $uid
5243
     * @return boolean|string
5244
     */
5245
    public function getWebUserInfo($uid)
5246
    {
5247
        $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...
5248
                INNER JOIN " . $this->getFullTableName("web_user_attributes") . " wua ON wua.internalkey=wu.id",
5249
            "wu.id='{$uid}'");
5250
        if ($row = $this->db->getRow($rs)) {
5251 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...
5252
                $row["usertype"] = "web";
5253
            }
5254
5255
            return $row;
5256
        }
5257
    }
5258
5259
    /**
5260
     * Returns an array of document groups that current user is assigned to.
5261
     * This function will first return the web user doc groups when running from
5262
     * frontend otherwise it will return manager user's docgroup.
5263
     *
5264
     * @param boolean $resolveIds Set to true to return the document group names
5265
     *                            Default: false
5266
     * @return string|array
5267
     */
5268
    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...
5269
    {
5270
        if ($this->isFrontend() && isset($_SESSION['webDocgroups']) && isset($_SESSION['webValidated'])) {
5271
            $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...
5272
            $dgn = isset($_SESSION['webDocgrpNames']) ? $_SESSION['webDocgrpNames'] : false;
5273
        } else {
5274
            if ($this->isBackend() && isset($_SESSION['mgrDocgroups']) && isset($_SESSION['mgrValidated'])) {
5275
                $dg = $_SESSION['mgrDocgroups'];
5276
                $dgn = isset($_SESSION['mgrDocgrpNames']) ? $_SESSION['mgrDocgrpNames'] : false;
5277
            } else {
5278
                $dg = '';
5279
            }
5280
        }
5281
        if (!$resolveIds) {
5282
            return $dg;
5283
        } else {
5284
            if (is_array($dgn)) {
5285
                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...
5286
            } else {
5287
                if (is_array($dg)) {
5288
                    // resolve ids to names
5289
                    $dgn = array();
5290
                    $ds = $this->db->select('name', $this->getFullTableName("documentgroup_names"),
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...
5291
                        "id IN (" . implode(",", $dg) . ")");
5292
                    while ($row = $this->db->getRow($ds)) {
5293
                        $dgn[] = $row['name'];
5294
                    }
5295
                    // cache docgroup names to session
5296
                    if ($this->isFrontend()) {
5297
                        $_SESSION['webDocgrpNames'] = $dgn;
5298
                    } else {
5299
                        $_SESSION['mgrDocgrpNames'] = $dgn;
5300
                    }
5301
5302
                    return $dgn;
5303
                }
5304
            }
5305
        }
5306
    }
5307
5308
    /**
5309
     * Change current web user's password
5310
     *
5311
     * @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...
5312
     * @param string $oldPwd
5313
     * @param string $newPwd
5314
     * @return string|boolean Returns true if successful, oterhwise return error
5315
     *                        message
5316
     */
5317
    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...
5318
    {
5319
        $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...
5320
        if ($_SESSION["webValidated"] == 1) {
5321
            $tbl = $this->getFullTableName("web_users");
5322
            $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...
5323
            if ($row = $this->db->getRow($ds)) {
5324
                if ($row["password"] == md5($oldPwd)) {
5325
                    if (strlen($newPwd) < 6) {
5326
                        return "Password is too short!";
5327
                    } elseif ($newPwd == "") {
5328
                        return "You didn't specify a password for this user!";
5329
                    } else {
5330
                        $this->db->update(array(
5331
                            'password' => $this->db->escape($newPwd),
5332
                        ), $tbl, "id='" . $this->getLoginUserID() . "'");
5333
                        // invoke OnWebChangePassword event
5334
                        $this->invokeEvent("OnWebChangePassword", array(
5335
                            "userid"       => $row["id"],
5336
                            "username"     => $row["username"],
5337
                            "userpassword" => $newPwd
5338
                        ));
5339
5340
                        return true;
5341
                    }
5342
                } else {
5343
                    return "Incorrect password.";
5344
                }
5345
            }
5346
        }
5347
5348
        return $rt;
5349
    }
5350
5351
    /**
5352
     * Returns true if the current web user is a member the specified groups
5353
     *
5354
     * @param array $groupNames
5355
     * @return boolean
5356
     */
5357
    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...
5358
    {
5359
        if (!is_array($groupNames)) {
5360
            return false;
5361
        }
5362
        // check cache
5363
        $grpNames = isset ($_SESSION['webUserGroupNames']) ? $_SESSION['webUserGroupNames'] : false;
5364
        if (!is_array($grpNames)) {
5365
            $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...
5366
                    INNER JOIN " . $this->getFullTableName("web_groups") . " wg ON wg.webgroup=wgn.id AND wg.webuser='" . $this->getLoginUserID() . "'");
5367
            $grpNames = $this->db->getColumn("name", $rs);
5368
            // save to cache
5369
            $_SESSION['webUserGroupNames'] = $grpNames;
5370
        }
5371
        foreach ($groupNames as $k => $v) {
5372
            if (in_array(trim($v), $grpNames)) {
5373
                return true;
5374
            }
5375
        }
5376
5377
        return false;
5378
    }
5379
5380
    /**
5381
     * Registers Client-side CSS scripts - these scripts are loaded at inside
5382
     * the <head> tag
5383
     *
5384
     * @param string $src
5385
     * @param string $media Default: Empty string
5386
     * @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...
5387
     */
5388
    public function regClientCSS($src, $media = '')
5389
    {
5390
        if (empty($src) || isset ($this->loadedjscripts[$src])) {
5391
            return '';
5392
        }
5393
        $nextpos = max(array_merge(array(0), array_keys($this->sjscripts))) + 1;
5394
        $this->loadedjscripts[$src]['startup'] = true;
5395
        $this->loadedjscripts[$src]['version'] = '0';
5396
        $this->loadedjscripts[$src]['pos'] = $nextpos;
5397
        if (strpos(strtolower($src), "<style") !== false || strpos(strtolower($src), "<link") !== false) {
5398
            $this->sjscripts[$nextpos] = $src;
5399
        } else {
5400
            $this->sjscripts[$nextpos] = "\t" . '<link rel="stylesheet" type="text/css" href="' . $src . '" ' . ($media ? 'media="' . $media . '" ' : '') . '/>';
5401
        }
5402
    }
5403
5404
    /**
5405
     * Registers Startup Client-side JavaScript - these scripts are loaded at inside the <head> tag
5406
     *
5407
     * @param string $src
5408
     * @param array $options Default: 'name'=>'', 'version'=>'0', 'plaintext'=>false
5409
     */
5410
    public function regClientStartupScript($src, $options = array('name' => '', 'version' => '0', 'plaintext' => false))
5411
    {
5412
        $this->regClientScript($src, $options, true);
5413
    }
5414
5415
    /**
5416
     * Registers Client-side JavaScript these scripts are loaded at the end of the page unless $startup is true
5417
     *
5418
     * @param string $src
5419
     * @param array $options Default: 'name'=>'', 'version'=>'0', 'plaintext'=>false
5420
     * @param boolean $startup Default: false
5421
     * @return string
5422
     */
5423
    public function regClientScript(
5424
        $src,
5425
        $options = array('name' => '', 'version' => '0', 'plaintext' => false),
5426
        $startup = false
5427
    ) {
5428
        if (empty($src)) {
5429
            return '';
5430
        } // nothing to register
5431
        if (!is_array($options)) {
5432
            if (is_bool($options))  // backward compatibility with old plaintext parameter
5433
            {
5434
                $options = array('plaintext' => $options);
5435
            } elseif (is_string($options)) // Also allow script name as 2nd param
5436
            {
5437
                $options = array('name' => $options);
5438
            } else {
5439
                $options = array();
5440
            }
5441
        }
5442
        $name = isset($options['name']) ? strtolower($options['name']) : '';
5443
        $version = isset($options['version']) ? $options['version'] : '0';
5444
        $plaintext = isset($options['plaintext']) ? $options['plaintext'] : false;
5445
        $key = !empty($name) ? $name : $src;
5446
        unset($overwritepos); // probably unnecessary--just making sure
5447
5448
        $useThisVer = true;
5449
        if (isset($this->loadedjscripts[$key])) { // a matching script was found
5450
            // if existing script is a startup script, make sure the candidate is also a startup script
5451
            if ($this->loadedjscripts[$key]['startup']) {
5452
                $startup = true;
5453
            }
5454
5455
            if (empty($name)) {
5456
                $useThisVer = false; // if the match was based on identical source code, no need to replace the old one
5457
            } else {
5458
                $useThisVer = version_compare($this->loadedjscripts[$key]['version'], $version, '<');
5459
            }
5460
5461
            if ($useThisVer) {
5462
                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...
5463
                    // remove old script from the bottom of the page (new one will be at the top)
5464
                    unset($this->jscripts[$this->loadedjscripts[$key]['pos']]);
5465
                } else {
5466
                    // overwrite the old script (the position may be important for dependent scripts)
5467
                    $overwritepos = $this->loadedjscripts[$key]['pos'];
5468
                }
5469
            } else { // Use the original version
5470
                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...
5471
                    // need to move the exisiting script to the head
5472
                    $version = $this->loadedjscripts[$key][$version];
5473
                    $src = $this->jscripts[$this->loadedjscripts[$key]['pos']];
5474
                    unset($this->jscripts[$this->loadedjscripts[$key]['pos']]);
5475
                } else {
5476
                    return ''; // the script is already in the right place
5477
                }
5478
            }
5479
        }
5480
5481
        if ($useThisVer && $plaintext != true && (strpos(strtolower($src), "<script") === false)) {
5482
            $src = "\t" . '<script type="text/javascript" src="' . $src . '"></script>';
5483
        }
5484
        if ($startup) {
5485
            $pos = isset($overwritepos) ? $overwritepos : max(array_merge(array(0), array_keys($this->sjscripts))) + 1;
5486
            $this->sjscripts[$pos] = $src;
5487
        } else {
5488
            $pos = isset($overwritepos) ? $overwritepos : max(array_merge(array(0), array_keys($this->jscripts))) + 1;
5489
            $this->jscripts[$pos] = $src;
5490
        }
5491
        $this->loadedjscripts[$key]['version'] = $version;
5492
        $this->loadedjscripts[$key]['startup'] = $startup;
5493
        $this->loadedjscripts[$key]['pos'] = $pos;
5494
5495
        return '';
5496
    }
5497
5498
    /**
5499
     * Returns all registered JavaScripts
5500
     *
5501
     * @return string
5502
     */
5503
    public function regClientStartupHTMLBlock($html)
5504
    {
5505
        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...
5506
    }
5507
5508
    /**
5509
     * Returns all registered startup scripts
5510
     *
5511
     * @return string
5512
     */
5513
    public function regClientHTMLBlock($html)
5514
    {
5515
        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...
5516
    }
5517
5518
    /**
5519
     * Remove unwanted html tags and snippet, settings and tags
5520
     *
5521
     * @param string $html
5522
     * @param string $allowed Default: Empty string
5523
     * @return string
5524
     */
5525
    public function stripTags($html, $allowed = "")
5526
    {
5527
        $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...
5528
        $t = preg_replace('~\[\*(.*?)\*\]~', "", $t); //tv
5529
        $t = preg_replace('~\[\[(.*?)\]\]~', "", $t); //snippet
5530
        $t = preg_replace('~\[\!(.*?)\!\]~', "", $t); //snippet
5531
        $t = preg_replace('~\[\((.*?)\)\]~', "", $t); //settings
5532
        $t = preg_replace('~\[\+(.*?)\+\]~', "", $t); //placeholders
5533
        $t = preg_replace('~{{(.*?)}}~', "", $t); //chunks
5534
5535
        return $t;
5536
    }
5537
5538
    /**
5539
     * Add an event listener to a plugin - only for use within the current execution cycle
5540
     *
5541
     * @param string $evtName
5542
     * @param string $pluginName
5543
     * @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...
5544
     */
5545
    public function addEventListener($evtName, $pluginName)
5546
    {
5547
        if (!$evtName || !$pluginName) {
5548
            return false;
5549
        }
5550
        if (!array_key_exists($evtName, $this->pluginEvent)) {
5551
            $this->pluginEvent[$evtName] = array();
5552
        }
5553
5554
        return array_push($this->pluginEvent[$evtName], $pluginName); // return array count
5555
    }
5556
5557
    /**
5558
     * Remove event listener - only for use within the current execution cycle
5559
     *
5560
     * @param string $evtName
5561
     * @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...
5562
     */
5563
    public function removeEventListener($evtName)
5564
    {
5565
        if (!$evtName) {
5566
            return false;
5567
        }
5568
        unset ($this->pluginEvent[$evtName]);
5569
    }
5570
5571
    /**
5572
     * Remove all event listeners - only for use within the current execution cycle
5573
     */
5574
    public function removeAllEventListener()
5575
    {
5576
        unset ($this->pluginEvent);
5577
        $this->pluginEvent = array();
5578
    }
5579
5580
    /**
5581
     * Invoke an event.
5582
     *
5583
     * @param string $evtName
5584
     * @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.
5585
     * @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...
5586
     */
5587
    public function invokeEvent($evtName, $extParams = array())
5588
    {
5589
        if (!$evtName) {
5590
            return false;
5591
        }
5592
        if (!isset ($this->pluginEvent[$evtName])) {
5593
            return false;
5594
        }
5595
5596
        $results = null;
5597
        foreach ($this->pluginEvent[$evtName] as $pluginName) { // start for loop
5598
            if ($this->dumpPlugins) {
5599
                $eventtime = $this->getMicroTime();
5600
            }
5601
            // reset event object
5602
            $e = &$this->event;
5603
            $e->_resetEventObject();
5604
            $e->name = $evtName;
5605
            $e->activePlugin = $pluginName;
5606
5607
            // get plugin code
5608
            $_ = $this->getPluginCode($pluginName);
5609
            $pluginCode = $_['code'];
5610
            $pluginProperties = $_['props'];
5611
5612
            // load default params/properties
5613
            $parameter = $this->parseProperties($pluginProperties);
5614
            if (!is_array($parameter)) {
5615
                $parameter = array();
5616
            }
5617
            if (!empty($extParams)) {
5618
                $parameter = array_merge($parameter, $extParams);
5619
            }
5620
5621
            // eval plugin
5622
            $this->evalPlugin($pluginCode, $parameter);
5623
5624
            if (class_exists('PHxParser')) {
5625
                $this->config['enable_filter'] = 0;
5626
            }
5627
5628
            if ($this->dumpPlugins) {
5629
                $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...
5630
                $this->pluginsCode .= sprintf('<fieldset><legend><b>%s / %s</b> (%2.2f ms)</legend>', $evtName,
5631
                    $pluginName, $eventtime * 1000);
5632
                foreach ($parameter as $k => $v) {
5633
                    $this->pluginsCode .= "{$k} => " . print_r($v, true) . '<br>';
5634
                }
5635
                $this->pluginsCode .= '</fieldset><br />';
5636
                $this->pluginsTime["{$evtName} / {$pluginName}"] += $eventtime;
5637
            }
5638
            if ($e->_output != '') {
5639
                $results[] = $e->_output;
5640
            }
5641
            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...
5642
                break;
5643
            }
5644
        }
5645
5646
        $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...
5647
5648
        return $results;
5649
    }
5650
5651
    /**
5652
     * Returns plugin-code and properties
5653
     *
5654
     * @param string $pluginName
5655
     * @return array Associative array consisting of 'code' and 'props'
5656
     */
5657
    public function getPluginCode($pluginName)
5658
    {
5659
        $plugin = array();
5660
        if (isset ($this->pluginCache[$pluginName])) {
5661
            $pluginCode = $this->pluginCache[$pluginName];
5662
            $pluginProperties = isset($this->pluginCache[$pluginName . "Props"]) ? $this->pluginCache[$pluginName . "Props"] : '';
5663
        } else {
5664
            $pluginName = $this->db->escape($pluginName);
5665
            $result = $this->db->select('name, plugincode, properties', $this->getFullTableName("site_plugins"),
5666
                "name='{$pluginName}' AND disabled=0");
5667
            if ($row = $this->db->getRow($result)) {
5668
                $pluginCode = $this->pluginCache[$row['name']] = $row['plugincode'];
5669
                $pluginProperties = $this->pluginCache[$row['name'] . "Props"] = $row['properties'];
5670
            } else {
5671
                $pluginCode = $this->pluginCache[$pluginName] = "return false;";
5672
                $pluginProperties = '';
5673
            }
5674
        }
5675
        $plugin['code'] = $pluginCode;
5676
        $plugin['props'] = $pluginProperties;
5677
5678
        return $plugin;
5679
    }
5680
5681
    /**
5682
     * Parses a resource property string and returns the result as an array
5683
     *
5684
     * @param string $propertyString
5685
     * @param string|null $elementName
5686
     * @param string|null $elementType
5687
     * @return array Associative array in the form property name => property value
5688
     */
5689
    public function parseProperties($propertyString, $elementName = null, $elementType = null)
5690
    {
5691
        $propertyString = trim($propertyString);
5692
        $propertyString = str_replace('{}', '', $propertyString);
5693
        $propertyString = str_replace('} {', ',', $propertyString);
5694
        if (empty($propertyString)) {
5695
            return array();
5696
        }
5697
        if ($propertyString == '{}') {
5698
            return array();
5699
        }
5700
5701
        $jsonFormat = $this->isJson($propertyString, true);
5702
        $property = array();
5703
        // old format
5704
        if ($jsonFormat === false) {
5705
            $props = explode('&', $propertyString);
5706
            foreach ($props as $prop) {
5707
5708
                if (empty($prop)) {
5709
                    continue;
5710
                } elseif (strpos($prop, '=') === false) {
5711
                    $property[trim($prop)] = '';
5712
                    continue;
5713
                }
5714
5715
                $_ = explode('=', $prop, 2);
5716
                $key = trim($_[0]);
5717
                $p = explode(';', trim($_[1]));
5718
                switch ($p[1]) {
5719
                    case 'list':
5720
                    case 'list-multi':
5721
                    case 'checkbox':
5722
                    case 'radio':
5723
                        $value = !isset($p[3]) ? '' : $p[3];
5724
                        break;
5725
                    default:
5726
                        $value = !isset($p[2]) ? '' : $p[2];
5727
                }
5728
                if (!empty($key)) {
5729
                    $property[$key] = $value;
5730
                }
5731
            }
5732
            // new json-format
5733
        } else {
5734
            if (!empty($jsonFormat)) {
5735
                foreach ($jsonFormat as $key => $row) {
5736
                    if (!empty($key)) {
5737
                        if (is_array($row)) {
5738
                            if (isset($row[0]['value'])) {
5739
                                $value = $row[0]['value'];
5740
                            }
5741
                        } else {
5742
                            $value = $row;
5743
                        }
5744
                        if (isset($value) && $value !== '') {
5745
                            $property[$key] = $value;
5746
                        }
5747
                    }
5748
                }
5749
            }
5750
        }
5751
        if (!empty($elementName) && !empty($elementType)) {
5752
            $out = $this->invokeEvent('OnParseProperties', array(
5753
                'element' => $elementName,
5754
                'type'    => $elementType,
5755
                'args'    => $property
5756
            ));
5757
            if (is_array($out)) {
5758
                $out = array_pop($out);
5759
            }
5760
            if (is_array($out)) {
5761
                $property = $out;
5762
            }
5763
        }
5764
5765
        return $property;
5766
    }
5767
5768
    /**
5769
     * Parses docBlock from a file and returns the result as an array
5770
     *
5771
     * @param string $element_dir
5772
     * @param string $filename
5773
     * @param boolean $escapeValues
5774
     * @return array Associative array in the form property name => property value
5775
     */
5776
    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...
5777
    {
5778
        $params = array();
5779
        $fullpath = $element_dir . '/' . $filename;
5780
        if (is_readable($fullpath)) {
5781
            $tpl = @fopen($fullpath, "r");
5782
            if ($tpl) {
5783
                $params['filename'] = $filename;
5784
                $docblock_start_found = false;
5785
                $name_found = false;
5786
                $description_found = false;
5787
                $docblock_end_found = false;
5788
                $arrayParams = array('author', 'documentation', 'reportissues', 'link');
5789
5790
                while (!feof($tpl)) {
5791
                    $line = fgets($tpl);
5792
                    $r = $this->parseDocBlockLine($line, $docblock_start_found, $name_found, $description_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...
5793
                        $docblock_end_found);
5794
                    $docblock_start_found = $r['docblock_start_found'];
5795
                    $name_found = $r['name_found'];
5796
                    $description_found = $r['description_found'];
5797
                    $docblock_end_found = $r['docblock_end_found'];
5798
                    $param = $r['param'];
5799
                    $val = $r['val'];
5800
                    if (!$docblock_end_found) {
5801
                        break;
5802
                    }
5803
                    if (!$docblock_start_found || !$name_found || !$description_found || empty($param)) {
5804
                        continue;
5805
                    }
5806 View Code Duplication
                    if (!empty($param)) {
5807
                        if (in_array($param, $arrayParams)) {
5808
                            if (!isset($params[$param])) {
5809
                                $params[$param] = array();
5810
                            }
5811
                            $params[$param][] = $escapeValues ? $this->db->escape($val) : $val;
5812
                        } else {
5813
                            $params[$param] = $escapeValues ? $this->db->escape($val) : $val;
5814
                        }
5815
                    }
5816
                }
5817
                @fclose($tpl);
5818
            }
5819
        }
5820
5821
        return $params;
5822
    }
5823
5824
    /**
5825
     * Parses docBlock from string and returns the result as an array
5826
     *
5827
     * @param string $string
5828
     * @param boolean $escapeValues
5829
     * @return array Associative array in the form property name => property value
5830
     */
5831
    public function parseDocBlockFromString($string, $escapeValues = false)
5832
    {
5833
        $params = array();
5834
        if (!empty($string)) {
5835
            $string = str_replace('\r\n', '\n', $string);
5836
            $exp = explode('\n', $string);
5837
            $docblock_start_found = false;
5838
            $name_found = false;
5839
            $description_found = false;
5840
            $docblock_end_found = false;
5841
            $arrayParams = array('author', 'documentation', 'reportissues', 'link');
5842
5843
            foreach ($exp as $line) {
5844
                $r = $this->parseDocBlockLine($line, $docblock_start_found, $name_found, $description_found,
5845
                    $docblock_end_found);
5846
                $docblock_start_found = $r['docblock_start_found'];
5847
                $name_found = $r['name_found'];
5848
                $description_found = $r['description_found'];
5849
                $docblock_end_found = $r['docblock_end_found'];
5850
                $param = $r['param'];
5851
                $val = $r['val'];
5852
                if (!$docblock_start_found) {
5853
                    continue;
5854
                }
5855
                if ($docblock_end_found) {
5856
                    break;
5857
                }
5858 View Code Duplication
                if (!empty($param)) {
5859
                    if (in_array($param, $arrayParams)) {
5860
                        if (!isset($params[$param])) {
5861
                            $params[$param] = array();
5862
                        }
5863
                        $params[$param][] = $escapeValues ? $this->db->escape($val) : $val;
5864
                    } else {
5865
                        $params[$param] = $escapeValues ? $this->db->escape($val) : $val;
5866
                    }
5867
                }
5868
            }
5869
        }
5870
5871
        return $params;
5872
    }
5873
5874
    /**
5875
     * Parses docBlock of a component´s source-code and returns the result as an array
5876
     * (modified parseDocBlock() from modules/stores/setup.info.php by Bumkaka & Dmi3yy)
5877
     *
5878
     * @param string $line
5879
     * @param boolean $docblock_start_found
5880
     * @param boolean $name_found
5881
     * @param boolean $description_found
5882
     * @param boolean $docblock_end_found
5883
     * @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...
5884
     */
5885
    public function parseDocBlockLine(
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...
5886
        $line,
5887
        $docblock_start_found,
5888
        $name_found,
5889
        $description_found,
5890
        $docblock_end_found
5891
    ) {
5892
        $param = '';
5893
        $val = '';
5894
        $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...
5895
        if (!$docblock_start_found) {
5896
            // find docblock start
5897
            if (strpos($line, '/**') !== false) {
5898
                $docblock_start_found = true;
5899
            }
5900 View Code Duplication
        } elseif (!$name_found) {
5901
            // find name
5902
            if (preg_match("/^\s+\*\s+(.+)/", $line, $ma)) {
5903
                $param = 'name';
5904
                $val = trim($ma[1]);
5905
                $name_found = !empty($val);
5906
            }
5907
        } elseif (!$description_found) {
5908
            // find description
5909
            if (preg_match("/^\s+\*\s+(.+)/", $line, $ma)) {
5910
                $param = 'description';
5911
                $val = trim($ma[1]);
5912
                $description_found = !empty($val);
5913
            }
5914
        } else {
5915
            if (preg_match("/^\s+\*\s+\@([^\s]+)\s+(.+)/", $line, $ma)) {
5916
                $param = trim($ma[1]);
5917
                $val = trim($ma[2]);
5918 View Code Duplication
                if (!empty($param) && !empty($val)) {
5919
                    if ($param == 'internal') {
5920
                        $ma = null;
5921
                        if (preg_match("/\@([^\s]+)\s+(.+)/", $val, $ma)) {
5922
                            $param = trim($ma[1]);
5923
                            $val = trim($ma[2]);
5924
                        }
5925
                    }
5926
                }
5927
            } elseif (preg_match("/^\s*\*\/\s*$/", $line)) {
5928
                $docblock_end_found = true;
5929
            }
5930
        }
5931
5932
        return array(
5933
            'docblock_start_found' => $docblock_start_found,
5934
            'name_found'           => $name_found,
5935
            'description_found'    => $description_found,
5936
            'docblock_end_found'   => $docblock_end_found,
5937
            'param'                => $param,
5938
            'val'                  => $val
5939
        );
5940
    }
5941
5942
    /**
5943
     * Renders docBlock-parameters into human readable list
5944
     *
5945
     * @param array $parsed
5946
     * @return string List in HTML-format
5947
     */
5948
    public function convertDocBlockIntoList($parsed)
5949
    {
5950
        global $_lang;
5951
5952
        // Replace special placeholders & make URLs + Emails clickable
5953
        $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...
5954
        $regexUrl = "/((http|https|ftp|ftps)\:\/\/[^\/]+(\/[^\s]+[^,.?!:;\s])?)/";
5955
        $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';
5956
        $emailSubject = isset($parsed['name']) ? '?subject=' . $parsed['name'] : '';
5957
        $emailSubject .= isset($parsed['version']) ? ' v' . $parsed['version'] : '';
5958
        foreach ($parsed as $key => $val) {
5959
            if (is_array($val)) {
5960
                foreach ($val as $key2 => $val2) {
5961
                    $val2 = $this->parseText($val2, $ph);
5962 View Code Duplication
                    if (preg_match($regexUrl, $val2, $url)) {
5963
                        $val2 = preg_replace($regexUrl, "<a href=\"{$url[0]}\" target=\"_blank\">{$url[0]}</a> ",
5964
                            $val2);
5965
                    }
5966 View Code Duplication
                    if (preg_match($regexEmail, $val2, $url)) {
5967
                        $val2 = preg_replace($regexEmail, '<a href="mailto:\\1' . $emailSubject . '">\\1</a>', $val2);
5968
                    }
5969
                    $parsed[$key][$key2] = $val2;
5970
                }
5971
            } else {
5972
                $val = $this->parseText($val, $ph);
5973 View Code Duplication
                if (preg_match($regexUrl, $val, $url)) {
5974
                    $val = preg_replace($regexUrl, "<a href=\"{$url[0]}\" target=\"_blank\">{$url[0]}</a> ", $val);
5975
                }
5976 View Code Duplication
                if (preg_match($regexEmail, $val, $url)) {
5977
                    $val = preg_replace($regexEmail, '<a href="mailto:\\1' . $emailSubject . '">\\1</a>', $val);
5978
                }
5979
                $parsed[$key] = $val;
5980
            }
5981
        }
5982
5983
        $arrayParams = array(
5984
            'documentation' => $_lang['documentation'],
5985
            'reportissues'  => $_lang['report_issues'],
5986
            'link'          => $_lang['further_info'],
5987
            'author'        => $_lang['author_infos']
5988
        );
5989
5990
        $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...
5991
        $list = isset($parsed['logo']) ? '<img src="' . $this->config['base_url'] . ltrim($parsed['logo'],
5992
                "/") . '" style="float:right;max-width:100px;height:auto;" />' . $nl : '';
5993
        $list .= '<p>' . $nl;
5994
        $list .= isset($parsed['name']) ? '<strong>' . $parsed['name'] . '</strong><br/>' . $nl : '';
5995
        $list .= isset($parsed['description']) ? $parsed['description'] . $nl : '';
5996
        $list .= '</p><br/>' . $nl;
5997
        $list .= isset($parsed['version']) ? '<p><strong>' . $_lang['version'] . ':</strong> ' . $parsed['version'] . '</p>' . $nl : '';
5998
        $list .= isset($parsed['license']) ? '<p><strong>' . $_lang['license'] . ':</strong> ' . $parsed['license'] . '</p>' . $nl : '';
5999
        $list .= isset($parsed['lastupdate']) ? '<p><strong>' . $_lang['last_update'] . ':</strong> ' . $parsed['lastupdate'] . '</p>' . $nl : '';
6000
        $list .= '<br/>' . $nl;
6001
        $first = true;
6002
        foreach ($arrayParams as $param => $label) {
6003
            if (isset($parsed[$param])) {
6004
                if ($first) {
6005
                    $list .= '<p><strong>' . $_lang['references'] . '</strong></p>' . $nl;
6006
                    $list .= '<ul class="docBlockList">' . $nl;
6007
                    $first = false;
6008
                }
6009
                $list .= '    <li><strong>' . $label . '</strong>' . $nl;
6010
                $list .= '        <ul>' . $nl;
6011
                foreach ($parsed[$param] as $val) {
6012
                    $list .= '            <li>' . $val . '</li>' . $nl;
6013
                }
6014
                $list .= '        </ul></li>' . $nl;
6015
            }
6016
        }
6017
        $list .= !$first ? '</ul>' . $nl : '';
6018
6019
        return $list;
6020
    }
6021
6022
    /**
6023
     * @param string $string
6024
     * @return string
6025
     */
6026
    public function removeSanitizeSeed($string = '')
6027
    {
6028
        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...
6029
6030
        if (!$string || strpos($string, $sanitize_seed) === false) {
6031
            return $string;
6032
        }
6033
6034
        return str_replace($sanitize_seed, '', $string);
6035
    }
6036
6037
    /**
6038
     * @param string $content
6039
     * @return string
6040
     */
6041
    public function cleanUpMODXTags($content = '')
6042
    {
6043
        if ($this->minParserPasses < 1) {
6044
            return $content;
6045
        }
6046
6047
        $enable_filter = $this->config['enable_filter'];
6048
        $this->config['enable_filter'] = 1;
6049
        $_ = 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...
6050
        foreach ($_ as $brackets) {
6051
            list($left, $right) = explode(' ', $brackets);
6052
            if (strpos($content, $left) !== false) {
6053
                if ($left === '[*') {
6054
                    $content = $this->mergeDocumentContent($content);
6055
                } elseif ($left === '[(') {
6056
                    $content = $this->mergeSettingsContent($content);
6057
                } elseif ($left === '{{') {
6058
                    $content = $this->mergeChunkContent($content);
6059
                } elseif ($left === '[[') {
6060
                    $content = $this->evalSnippets($content);
6061
                }
6062
            }
6063
        }
6064
        foreach ($_ as $brackets) {
6065
            list($left, $right) = explode(' ', $brackets);
6066
            if (strpos($content, $left) !== false) {
6067
                $matches = $this->getTagsFromContent($content, $left, $right);
6068
                $content = str_replace($matches[0], '', $content);
6069
            }
6070
        }
6071
        $this->config['enable_filter'] = $enable_filter;
6072
6073
        return $content;
6074
    }
6075
6076
    /**
6077
     * @param string $str
6078
     * @param string $allowable_tags
6079
     * @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...
6080
     */
6081
    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...
6082
    {
6083
        $str = strip_tags($str, $allowable_tags);
6084
        modx_sanitize_gpc($str);
6085
6086
        return $str;
6087
    }
6088
6089
    /**
6090
     * @param string $name
6091
     * @param string $phpCode
6092
     */
6093
    public function addSnippet($name, $phpCode)
6094
    {
6095
        $this->snippetCache['#' . $name] = $phpCode;
6096
    }
6097
6098
    /**
6099
     * @param string $name
6100
     * @param string $text
6101
     */
6102
    public function addChunk($name, $text)
6103
    {
6104
        $this->chunkCache['#' . $name] = $text;
6105
    }
6106
6107
    /**
6108
     * @param string $phpcode
6109
     * @param string $evalmode
6110
     * @param string $safe_functions
6111
     * @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...
6112
     */
6113
    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...
6114
    {
6115
        if ($evalmode == '') {
6116
            $evalmode = $this->config['allow_eval'];
6117
        }
6118
        if ($safe_functions == '') {
6119
            $safe_functions = $this->config['safe_functions_at_eval'];
6120
        }
6121
6122
        modx_sanitize_gpc($phpcode);
6123
6124
        switch ($evalmode) {
6125
            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...
6126
                $isSafe = $this->isSafeCode($phpcode, $safe_functions);
0 ignored issues
show
Bug introduced by
It seems like $phpcode can also be of type array; however, DocumentParser::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...
6127
                break;
6128
            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...
6129
                $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, DocumentParser::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...
6130
                break;
6131
            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...
6132
                $isSafe = true;
6133
                break; // Should debug only
6134
            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...
6135
            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...
6136
                return $phpcode;
6137
        }
6138
6139
        if (!$isSafe) {
6140
            $msg = $phpcode . "\n" . $this->currentSnippet . "\n" . print_r($_SERVER, true);
6141
            $title = sprintf('Unknown eval was executed (%s)', $this->htmlspecialchars(substr(trim($phpcode), 0, 50)));
6142
            $this->messageQuit($title, '', true, '', '', 'Parser', $msg);
6143
6144
            return;
6145
        }
6146
6147
        ob_start();
6148
        $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...
6149
        $echo = ob_get_clean();
6150
6151
        if (is_array($return)) {
6152
            return 'array()';
6153
        }
6154
6155
        $output = $echo . $return;
6156
        modx_sanitize_gpc($output);
6157
6158
        return $this->htmlspecialchars($output); // Maybe, all html tags are dangerous
6159
    }
6160
6161
    /**
6162
     * @param string $phpcode
6163
     * @param string $safe_functions
6164
     * @return bool
6165
     */
6166
    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...
6167
    { // 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...
6168
        if ($safe_functions == '') {
6169
            return false;
6170
        }
6171
6172
        $safe = explode(',', $safe_functions);
6173
6174
        $phpcode = rtrim($phpcode, ';') . ';';
6175
        $tokens = token_get_all('<?php ' . $phpcode);
6176
        foreach ($tokens as $i => $token) {
6177
            if (!is_array($token)) {
6178
                continue;
6179
            }
6180
            $tokens[$i]['token_name'] = token_name($token[0]);
6181
        }
6182
        foreach ($tokens as $token) {
6183
            if (!is_array($token)) {
6184
                continue;
6185
            }
6186
            switch ($token['token_name']) {
6187
                case 'T_STRING':
6188
                    if (!in_array($token[1], $safe)) {
6189
                        return false;
6190
                    }
6191
                    break;
6192
                case 'T_VARIABLE':
6193
                    if ($token[1] == '$GLOBALS') {
6194
                        return false;
6195
                    }
6196
                    break;
6197
                case 'T_EVAL':
6198
                    return false;
6199
            }
6200
        }
6201
6202
        return true;
6203
    }
6204
6205
    /**
6206
     * @param string $str
6207
     * @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...
6208
     */
6209
    public function atBindFileContent($str = '')
6210
    {
6211
6212
        $search_path = array(
6213
            'assets/tvs/',
6214
            'assets/chunks/',
6215
            'assets/templates/',
6216
            $this->config['rb_base_url'] . 'files/',
6217
            ''
6218
        );
6219
6220
        if (stripos($str, '@FILE') !== 0) {
6221
            return $str;
6222
        }
6223 View Code Duplication
        if (strpos($str, "\n") !== false) {
6224
            $str = substr($str, 0, strpos("\n", $str));
6225
        }
6226
6227
        if ($this->getExtFromFilename($str) === '.php') {
6228
            return 'Could not retrieve PHP file.';
6229
        }
6230
6231
        $str = substr($str, 6);
6232
        $str = trim($str);
6233
        if (strpos($str, '\\') !== false) {
6234
            $str = str_replace('\\', '/', $str);
6235
        }
6236
        $str = ltrim($str, '/');
6237
6238
        $errorMsg = sprintf("Could not retrieve string '%s'.", $str);
6239
6240
        foreach ($search_path as $path) {
6241
            $file_path = MODX_BASE_PATH . $path . $str;
6242
            if (strpos($file_path, MODX_MANAGER_PATH) === 0) {
6243
                return $errorMsg;
6244
            } elseif (is_file($file_path)) {
6245
                break;
6246
            } else {
6247
                $file_path = false;
6248
            }
6249
        }
6250
6251
        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...
6252
            return $errorMsg;
6253
        }
6254
6255
        $content = (string)file_get_contents($file_path);
6256
        if ($content === false) {
6257
            return $errorMsg;
6258
        }
6259
6260
        return $content;
6261
    }
6262
6263
    /**
6264
     * @param $str
6265
     * @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...
6266
     */
6267
    public function getExtFromFilename($str)
6268
    {
6269
        $str = strtolower(trim($str));
6270
        $pos = strrpos($str, '.');
6271
        if ($pos === false) {
6272
            return false;
6273
        } else {
6274
            return substr($str, $pos);
6275
        }
6276
    }
6277
    /***************************************************************************************/
6278
    /* End of API functions                                       */
6279
    /***************************************************************************************/
6280
6281
    /**
6282
     * PHP error handler set by http://www.php.net/manual/en/function.set-error-handler.php
6283
     *
6284
     * Checks the PHP error and calls messageQuit() unless:
6285
     *  - error_reporting() returns 0, or
6286
     *  - the PHP error level is 0, or
6287
     *  - the PHP error level is 8 (E_NOTICE) and stopOnNotice is false
6288
     *
6289
     * @param int $nr The PHP error level as per http://www.php.net/manual/en/errorfunc.constants.php
6290
     * @param string $text Error message
6291
     * @param string $file File where the error was detected
6292
     * @param string $line Line number within $file
6293
     * @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...
6294
     */
6295
    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...
6296
    {
6297
        if (error_reporting() == 0 || $nr == 0) {
6298
            return true;
6299
        }
6300
        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...
6301
            switch ($nr) {
6302
                case E_NOTICE:
6303
                    if ($this->error_reporting <= 2) {
6304
                        return true;
6305
                    }
6306
                    $isError = false;
6307
                    $msg = 'PHP Minor Problem (this message show logged in only)';
6308
                    break;
6309
                case E_STRICT:
6310 View Code Duplication
                case E_DEPRECATED:
6311
                    if ($this->error_reporting <= 1) {
6312
                        return true;
6313
                    }
6314
                    $isError = true;
6315
                    $msg = 'PHP Strict Standards Problem';
6316
                    break;
6317 View Code Duplication
                default:
6318
                    if ($this->error_reporting === 0) {
6319
                        return true;
6320
                    }
6321
                    $isError = true;
6322
                    $msg = 'PHP Parse Error';
6323
            }
6324
        }
6325
        if (is_readable($file)) {
6326
            $source = file($file);
6327
            $source = $this->htmlspecialchars($source[$line - 1]);
6328
        } else {
6329
            $source = "";
6330
        } //Error $nr in $file at $line: <div><code>$source</code></div>
6331
6332
        $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...
6333
    }
6334
6335
    /**
6336
     * @param string $msg
6337
     * @param string $query
6338
     * @param bool $is_error
6339
     * @param string $nr
6340
     * @param string $file
6341
     * @param string $source
6342
     * @param string $text
6343
     * @param string $line
6344
     * @param string $output
6345
     * @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...
6346
     */
6347
    public function messageQuit(
0 ignored issues
show
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...
6348
        $msg = 'unspecified error',
6349
        $query = '',
6350
        $is_error = true,
6351
        $nr = '',
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...
6352
        $file = '',
6353
        $source = '',
6354
        $text = '',
6355
        $line = '',
6356
        $output = ''
6357
    ) {
6358
6359
        if (0 < $this->messageQuitCount) {
6360
            return;
6361
        }
6362
        $this->messageQuitCount++;
6363
6364
        if (!class_exists('makeTable')) {
6365
            include_once('extenders/maketable.class.php');
6366
        }
6367
        $MakeTable = new MakeTable();
6368
        $MakeTable->setTableClass('grid');
6369
        $MakeTable->setRowRegularClass('gridItem');
6370
        $MakeTable->setRowAlternateClass('gridAltItem');
6371
        $MakeTable->setColumnWidths(array('100px'));
6372
6373
        $table = array();
6374
6375
        $version = isset ($GLOBALS['modx_version']) ? $GLOBALS['modx_version'] : '';
6376
        $release_date = isset ($GLOBALS['release_date']) ? $GLOBALS['release_date'] : '';
6377
        $request_uri = "http://" . $_SERVER['HTTP_HOST'] . ($_SERVER["SERVER_PORT"] == 80 ? "" : (":" . $_SERVER["SERVER_PORT"])) . $_SERVER['REQUEST_URI'];
6378
        $request_uri = $this->htmlspecialchars($request_uri, ENT_QUOTES, $this->config['modx_charset']);
6379
        $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...
6380
        $referer = $this->htmlspecialchars($_SERVER['HTTP_REFERER'], ENT_QUOTES, $this->config['modx_charset']);
6381
        if ($is_error) {
6382
            $str = '<h2 style="color:red">&laquo; Evo Parse Error &raquo;</h2>';
6383
            if ($msg != 'PHP Parse Error') {
6384
                $str .= '<h3 style="color:red">' . $msg . '</h3>';
6385
            }
6386
        } else {
6387
            $str = '<h2 style="color:#003399">&laquo; Evo Debug/ stop message &raquo;</h2>';
6388
            $str .= '<h3 style="color:#003399">' . $msg . '</h3>';
6389
        }
6390
6391
        if (!empty ($query)) {
6392
            $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>';
6393
        }
6394
6395
        $errortype = array(
6396
            E_ERROR             => "ERROR",
6397
            E_WARNING           => "WARNING",
6398
            E_PARSE             => "PARSING ERROR",
6399
            E_NOTICE            => "NOTICE",
6400
            E_CORE_ERROR        => "CORE ERROR",
6401
            E_CORE_WARNING      => "CORE WARNING",
6402
            E_COMPILE_ERROR     => "COMPILE ERROR",
6403
            E_COMPILE_WARNING   => "COMPILE WARNING",
6404
            E_USER_ERROR        => "USER ERROR",
6405
            E_USER_WARNING      => "USER WARNING",
6406
            E_USER_NOTICE       => "USER NOTICE",
6407
            E_STRICT            => "STRICT NOTICE",
6408
            E_RECOVERABLE_ERROR => "RECOVERABLE ERROR",
6409
            E_DEPRECATED        => "DEPRECATED",
6410
            E_USER_DEPRECATED   => "USER DEPRECATED"
6411
        );
6412
6413
        if (!empty($nr) || !empty($file)) {
6414
            if ($text != '') {
6415
                $str .= '<div style="font-weight:bold;border:1px solid #ccc;padding:8px;color:#333;background-color:#ffffcd;margin-bottom:15px;">Error : ' . $text . '</div>';
6416
            }
6417
            if ($output != '') {
6418
                $str .= '<div style="font-weight:bold;border:1px solid #ccc;padding:8px;color:#333;background-color:#ffffcd;margin-bottom:15px;">' . $output . '</div>';
6419
            }
6420
            if ($nr !== '') {
6421
                $table[] = array('ErrorType[num]', $errortype [$nr] . "[" . $nr . "]");
6422
            }
6423
            if ($file) {
6424
                $table[] = array('File', $file);
6425
            }
6426
            if ($line) {
6427
                $table[] = array('Line', $line);
6428
            }
6429
6430
        }
6431
6432
        if ($source != '') {
6433
            $table[] = array("Source", $source);
6434
        }
6435
6436
        if (!empty($this->currentSnippet)) {
6437
            $table[] = array('Current Snippet', $this->currentSnippet);
6438
        }
6439
6440
        if (!empty($this->event->activePlugin)) {
6441
            $table[] = array('Current Plugin', $this->event->activePlugin . '(' . $this->event->name . ')');
6442
        }
6443
6444
        $str .= $MakeTable->create($table, array('Error information', ''));
6445
        $str .= "<br />";
6446
6447
        $table = array();
6448
        $table[] = array('REQUEST_URI', $request_uri);
6449
6450
        if ($this->manager->action) {
6451
            include_once(MODX_MANAGER_PATH . 'includes/actionlist.inc.php');
6452
            global $action_list;
6453
            $actionName = (isset($action_list[$this->manager->action])) ? " - {$action_list[$this->manager->action]}" : '';
6454
6455
            $table[] = array('Manager action', $this->manager->action . $actionName);
6456
        }
6457
6458
        if (preg_match('@^[0-9]+@', $this->documentIdentifier)) {
6459
            $resource = $this->getDocumentObject('id', $this->documentIdentifier);
6460
            $url = $this->makeUrl($this->documentIdentifier, '', '', 'full');
6461
            $table[] = array(
6462
                'Resource',
6463
                '[' . $this->documentIdentifier . '] <a href="' . $url . '" target="_blank">' . $resource['pagetitle'] . '</a>'
6464
            );
6465
        }
6466
        $table[] = array('Referer', $referer);
6467
        $table[] = array('User Agent', $ua);
6468
        $table[] = array('IP', $_SERVER['REMOTE_ADDR']);
6469
        $table[] = array(
6470
            'Current time',
6471
            date("Y-m-d H:i:s", $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'])
6472
        );
6473
        $str .= $MakeTable->create($table, array('Basic info', ''));
6474
        $str .= "<br />";
6475
6476
        $table = array();
6477
        $table[] = array('MySQL', '[^qt^] ([^q^] Requests)');
6478
        $table[] = array('PHP', '[^p^]');
6479
        $table[] = array('Total', '[^t^]');
6480
        $table[] = array('Memory', '[^m^]');
6481
        $str .= $MakeTable->create($table, array('Benchmarks', ''));
6482
        $str .= "<br />";
6483
6484
        $totalTime = ($this->getMicroTime() - $this->tstart);
6485
6486
        $mem = memory_get_peak_usage(true);
6487
        $total_mem = $mem - $this->mstart;
6488
        $total_mem = ($total_mem / 1024 / 1024) . ' mb';
6489
6490
        $queryTime = $this->queryTime;
6491
        $phpTime = $totalTime - $queryTime;
6492
        $queries = isset ($this->executedQueries) ? $this->executedQueries : 0;
6493
        $queryTime = sprintf("%2.4f s", $queryTime);
6494
        $totalTime = sprintf("%2.4f s", $totalTime);
6495
        $phpTime = sprintf("%2.4f s", $phpTime);
6496
6497
        $str = str_replace('[^q^]', $queries, $str);
6498
        $str = str_replace('[^qt^]', $queryTime, $str);
6499
        $str = str_replace('[^p^]', $phpTime, $str);
6500
        $str = str_replace('[^t^]', $totalTime, $str);
6501
        $str = str_replace('[^m^]', $total_mem, $str);
6502
6503
        if (isset($php_errormsg) && !empty($php_errormsg)) {
6504
            $str = "<b>{$php_errormsg}</b><br />\n{$str}";
6505
        }
6506
        $str .= $this->get_backtrace(debug_backtrace());
6507
        // Log error
6508
        if (!empty($this->currentSnippet)) {
6509
            $source = 'Snippet - ' . $this->currentSnippet;
6510
        } elseif (!empty($this->event->activePlugin)) {
6511
            $source = 'Plugin - ' . $this->event->activePlugin;
6512
        } elseif ($source !== '') {
6513
            $source = 'Parser - ' . $source;
6514
        } elseif ($query !== '') {
6515
            $source = 'SQL Query';
6516
        } else {
6517
            $source = 'Parser';
6518
        }
6519
        if ($msg) {
6520
            $source .= ' / ' . $msg;
6521
        }
6522
        if (isset($actionName) && !empty($actionName)) {
6523
            $source .= $actionName;
6524
        }
6525 View Code Duplication
        switch ($nr) {
6526
            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...
6527
            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...
6528
            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...
6529
            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...
6530
            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...
6531
                $error_level = 2;
6532
                break;
6533
            default:
6534
                $error_level = 3;
6535
        }
6536
        $this->logEvent(0, $error_level, $str, $source);
6537
6538
        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...
6539
            return true;
6540
        }
6541
        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...
6542
            return true;
6543
        }
6544
6545
        // Set 500 response header
6546
        if ($error_level !== 2) {
6547
            header('HTTP/1.1 500 Internal Server Error');
6548
        }
6549
6550
        // Display error
6551
        if (isset($_SESSION['mgrValidated'])) {
6552
            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>
6553
                 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
6554
                 <link rel="stylesheet" type="text/css" href="' . $this->config['site_manager_url'] . 'media/style/' . $this->config['manager_theme'] . '/style.css" />
6555
                 <style type="text/css">body { padding:10px; } td {font:inherit;}</style>
6556
                 </head><body>
6557
                 ' . $str . '</body></html>';
6558
6559
        } else {
6560
            echo 'Error';
6561
        }
6562
        ob_end_flush();
6563
        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...
6564
    }
6565
6566
    /**
6567
     * @param $backtrace
6568
     * @return string
6569
     */
6570
    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...
6571
    {
6572
        if (!class_exists('makeTable')) {
6573
            include_once('extenders/maketable.class.php');
6574
        }
6575
        $MakeTable = new MakeTable();
6576
        $MakeTable->setTableClass('grid');
6577
        $MakeTable->setRowRegularClass('gridItem');
6578
        $MakeTable->setRowAlternateClass('gridAltItem');
6579
        $table = array();
6580
        $backtrace = array_reverse($backtrace);
6581
        foreach ($backtrace as $key => $val) {
6582
            $key++;
6583
            if (substr($val['function'], 0, 11) === 'messageQuit') {
6584
                break;
6585
            } elseif (substr($val['function'], 0, 8) === 'phpError') {
6586
                break;
6587
            }
6588
            $path = str_replace('\\', '/', $val['file']);
6589
            if (strpos($path, MODX_BASE_PATH) === 0) {
6590
                $path = substr($path, strlen(MODX_BASE_PATH));
6591
            }
6592
            switch ($val['type']) {
6593
                case '->':
6594
                case '::':
6595
                    $functionName = $val['function'] = $val['class'] . $val['type'] . $val['function'];
6596
                    break;
6597
                default:
6598
                    $functionName = $val['function'];
6599
            }
6600
            $tmp = 1;
6601
            $_ = (!empty($val['args'])) ? count($val['args']) : 0;
6602
            $args = array_pad(array(), $_, '$var');
6603
            $args = implode(", ", $args);
6604
            $modx = &$this;
6605
            $args = preg_replace_callback('/\$var/', function () use ($modx, &$tmp, $val) {
6606
                $arg = $val['args'][$tmp - 1];
6607
                switch (true) {
6608
                    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...
6609
                        $out = 'NULL';
6610
                        break;
6611
                    }
6612
                    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...
6613
                        $out = $arg;
6614
                        break;
6615
                    }
6616
                    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...
6617
                        $out = strlen($arg) > 20 ? 'string $var' . $tmp : ("'" . $this->htmlspecialchars(str_replace("'",
6618
                                "\\'", $arg)) . "'");
6619
                        break;
6620
                    }
6621
                    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...
6622
                        $out = $arg ? 'TRUE' : 'FALSE';
6623
                        break;
6624
                    }
6625
                    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...
6626
                        $out = 'array $var' . $tmp;
6627
                        break;
6628
                    }
6629
                    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...
6630
                        $out = get_class($arg) . ' $var' . $tmp;
6631
                        break;
6632
                    }
6633
                    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...
6634
                        $out = '$var' . $tmp;
6635
                    }
6636
                }
6637
                $tmp++;
6638
6639
                return $out;
6640
            }, $args);
6641
            $line = array(
6642
                "<strong>" . $functionName . "</strong>(" . $args . ")",
6643
                $path . " on line " . $val['line']
6644
            );
6645
            $table[] = array(implode("<br />", $line));
6646
        }
6647
6648
        return $MakeTable->create($table, array('Backtrace'));
6649
    }
6650
6651
    /**
6652
     * @return string
6653
     */
6654
    public function getRegisteredClientScripts()
6655
    {
6656
        return implode("\n", $this->jscripts);
6657
    }
6658
6659
    /**
6660
     * @return string
6661
     */
6662
    public function getRegisteredClientStartupScripts()
6663
    {
6664
        return implode("\n", $this->sjscripts);
6665
    }
6666
6667
    /**
6668
     * Format alias to be URL-safe. Strip invalid characters.
6669
     *
6670
     * @param string $alias Alias to be formatted
6671
     * @return string Safe alias
6672
     */
6673
    public function stripAlias($alias)
6674
    {
6675
        // let add-ons overwrite the default behavior
6676
        $results = $this->invokeEvent('OnStripAlias', array('alias' => $alias));
6677
        if (!empty($results)) {
6678
            // if multiple plugins are registered, only the last one is used
6679
            return end($results);
6680
        } else {
6681
            // default behavior: strip invalid characters and replace spaces with dashes.
6682
            $alias = strip_tags($alias); // strip HTML
6683
            $alias = preg_replace('/[^\.A-Za-z0-9 _-]/', '', $alias); // strip non-alphanumeric characters
6684
            $alias = preg_replace('/\s+/', '-', $alias); // convert white-space to dash
6685
            $alias = preg_replace('/-+/', '-', $alias);  // convert multiple dashes to one
6686
            $alias = trim($alias, '-'); // trim excess
6687
6688
            return $alias;
6689
        }
6690
    }
6691
6692
    /**
6693
     * @param $size
6694
     * @return string
6695
     */
6696
    public function nicesize($size)
6697
    {
6698
        $sizes = array('Tb' => 1099511627776, 'Gb' => 1073741824, 'Mb' => 1048576, 'Kb' => 1024, 'b' => 1);
6699
        $precisions = count($sizes) - 1;
6700
        foreach ($sizes as $unit => $bytes) {
6701
            if ($size >= $bytes) {
6702
                return number_format($size / $bytes, $precisions) . ' ' . $unit;
6703
            }
6704
            $precisions--;
6705
        }
6706
6707
        return '0 b';
6708
    }
6709
6710
    /**
6711
     * @param $parentid
6712
     * @param $alias
6713
     * @return bool
6714
     */
6715
    public function getHiddenIdFromAlias($parentid, $alias)
6716
    {
6717
        $table = $this->getFullTableName('site_content');
6718
        $query = $this->db->query("SELECT sc.id, children.id AS child_id, children.alias, COUNT(children2.id) AS children_count
6719
            FROM {$table} sc
6720
            JOIN {$table} children ON children.parent = sc.id
6721
            LEFT JOIN {$table} children2 ON children2.parent = children.id
6722
            WHERE sc.parent = {$parentid} AND sc.alias_visible = '0' GROUP BY children.id;");
6723
6724
        while ($child = $this->db->getRow($query)) {
6725
            if ($child['alias'] == $alias || $child['child_id'] == $alias) {
6726
                return $child['child_id'];
6727
            }
6728
6729
            if ($child['children_count'] > 0) {
6730
                $id = $this->getHiddenIdFromAlias($child['id'], $alias);
6731
                if ($id) {
6732
                    return $id;
6733
                }
6734
            }
6735
        }
6736
6737
        return false;
6738
    }
6739
6740
    /**
6741
     * @param $alias
6742
     * @return bool|int
6743
     */
6744
    public function getIdFromAlias($alias)
6745
    {
6746
        if (isset($this->documentListing[$alias])) {
6747
            return $this->documentListing[$alias];
6748
        }
6749
6750
        $tbl_site_content = $this->getFullTableName('site_content');
6751
        if ($this->config['use_alias_path'] == 1) {
6752
            if ($alias == '.') {
6753
                return 0;
6754
            }
6755
6756
            if (strpos($alias, '/') !== false) {
6757
                $_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...
6758
            } else {
6759
                $_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...
6760
            }
6761
            $id = 0;
6762
6763
            foreach ($_a as $alias) {
6764
                if ($id === false) {
6765
                    break;
6766
                }
6767
                $alias = $this->db->escape($alias);
6768
                $rs = $this->db->select('id', $tbl_site_content, "deleted=0 and parent='{$id}' and alias='{$alias}'");
6769
                if ($this->db->getRecordCount($rs) == 0) {
6770
                    $rs = $this->db->select('id', $tbl_site_content, "deleted=0 and parent='{$id}' and id='{$alias}'");
6771
                }
6772
                $next = $this->db->getValue($rs);
6773
                $id = !$next ? $this->getHiddenIdFromAlias($id, $alias) : $next;
6774
            }
6775
        } else {
6776
            $rs = $this->db->select('id', $tbl_site_content, "deleted=0 and alias='{$alias}'", 'parent, menuindex');
6777
            $id = $this->db->getValue($rs);
6778
            if (!$id) {
6779
                $id = false;
6780
            }
6781
        }
6782
6783
        return $id;
6784
    }
6785
6786
    /**
6787
     * @param string $str
6788
     * @return bool|mixed|string
6789
     */
6790
    public function atBindInclude($str = '')
6791
    {
6792
        if (strpos($str, '@INCLUDE') !== 0) {
6793
            return $str;
6794
        }
6795 View Code Duplication
        if (strpos($str, "\n") !== false) {
6796
            $str = substr($str, 0, strpos("\n", $str));
6797
        }
6798
6799
        $str = substr($str, 9);
6800
        $str = trim($str);
6801
        $str = str_replace('\\', '/', $str);
6802
        $str = ltrim($str, '/');
6803
6804
        $tpl_dir = 'assets/templates/';
6805
6806
        if (strpos($str, MODX_MANAGER_PATH) === 0) {
6807
            return false;
6808
        } elseif (is_file(MODX_BASE_PATH . $str)) {
6809
            $file_path = MODX_BASE_PATH . $str;
6810
        } elseif (is_file(MODX_BASE_PATH . "{$tpl_dir}{$str}")) {
6811
            $file_path = MODX_BASE_PATH . $tpl_dir . $str;
6812
        } else {
6813
            return false;
6814
        }
6815
6816
        if (!$file_path || !is_file($file_path)) {
6817
            return false;
6818
        }
6819
6820
        ob_start();
6821
        $modx = &$this;
6822
        $result = include($file_path);
6823
        if ($result === 1) {
6824
            $result = '';
6825
        }
6826
        $content = ob_get_clean();
6827
        if (!$content && $result) {
6828
            $content = $result;
6829
        }
6830
6831
        return $content;
6832
    }
6833
6834
    // php compat
6835
6836
    /**
6837
     * @param $str
6838
     * @param int $flags
6839
     * @param string $encode
6840
     * @return mixed
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...
6841
     */
6842
    public function htmlspecialchars($str, $flags = ENT_COMPAT, $encode = '')
6843
    {
6844
        $this->loadExtension('PHPCOMPAT');
6845
6846
        return $this->phpcompat->htmlspecialchars($str, $flags, $encode);
6847
    }
6848
6849
    /**
6850
     * @param $string
6851
     * @param bool $returnData
6852
     * @return bool|mixed
6853
     */
6854
    public function isJson($string, $returnData = false)
6855
    {
6856
        $data = json_decode($string, true);
6857
6858
        return (json_last_error() == JSON_ERROR_NONE) ? ($returnData ? $data : true) : false;
6859
    }
6860
6861
    /**
6862
     * @param $key
6863
     * @return array
6864
     */
6865
    public function splitKeyAndFilter($key)
6866
    {
6867
        if ($this->config['enable_filter'] == 1 && strpos($key, ':') !== false && stripos($key, '@FILE') !== 0) {
6868
            list($key, $modifiers) = explode(':', $key, 2);
6869
        } else {
6870
            $modifiers = false;
6871
        }
6872
6873
        $key = trim($key);
6874
        if ($modifiers !== false) {
6875
            $modifiers = trim($modifiers);
6876
        }
6877
6878
        return array($key, $modifiers);
6879
    }
6880
6881
    /**
6882
     * @param string $value
6883
     * @param bool $modifiers
6884
     * @param string $key
6885
     * @return string
6886
     */
6887
    public function applyFilter($value = '', $modifiers = false, $key = '')
6888
    {
6889
        if ($modifiers === false || $modifiers == 'raw') {
6890
            return $value;
6891
        }
6892
        if ($modifiers !== false) {
6893
            $modifiers = trim($modifiers);
6894
        }
6895
6896
        $this->loadExtension('MODIFIERS');
6897
6898
        return $this->filter->phxFilter($key, $value, $modifiers);
0 ignored issues
show
Bug introduced by
It seems like $modifiers defined by parameter $modifiers on line 6887 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...
6899
    }
6900
6901
    // End of class.
6902
6903
6904
    /**
6905
     * Get Clean Query String
6906
     *
6907
     * Fixes the issue where passing an array into the q get variable causes errors
6908
     *
6909
     */
6910
    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...
6911
    {
6912
        $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...
6913
6914
        //Return null if the query doesn't exist
6915
        if (empty($q)) {
6916
            return null;
6917
        }
6918
6919
        //If we have a string, return it
6920
        if (is_string($q)) {
6921
            return $q;
6922
        }
6923
6924
        //If we have an array, return the first element
6925
        if (is_array($q)) {
6926
            return $q[0];
6927
        }
6928
    }
6929
6930
    /**
6931
     * @param string $title
6932
     * @param string $msg
6933
     * @param int $type
6934
     */
6935
    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...
6936
    {
6937
        if ($title === '') {
6938
            $title = 'no title';
6939
        }
6940
        if (is_array($msg)) {
6941
            $msg = '<pre>' . print_r($msg, true) . '</pre>';
6942
        } elseif ($msg === '') {
6943
            $msg = $_SERVER['REQUEST_URI'];
6944
        }
6945
        $this->logEvent(0, $type, $msg, $title);
6946
    }
6947
6948
}
6949
6950
/**
6951
 * System Event Class
6952
 */
6953
class SystemEvent
0 ignored issues
show
Coding Style introduced by
The property $_propagate 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 $_output 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 Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
6954
{
6955
    public $name = '';
6956
    public $_propagate = true;
6957
    public $_output = '';
6958
    public $activated = false;
6959
    public $activePlugin = '';
6960
    public $params = array();
6961
6962
    /**
6963
     * @param string $name Name of the event
6964
     */
6965
    public function __construct($name = "")
6966
    {
6967
        $this->_resetEventObject();
6968
        $this->name = $name;
6969
    }
6970
6971
    /**
6972
     * Display a message to the user
6973
     *
6974
     * @global array $SystemAlertMsgQueque
6975
     * @param string $msg The message
6976
     */
6977
    public function alert($msg)
6978
    {
6979
        global $SystemAlertMsgQueque;
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...
6980
        if ($msg == "") {
6981
            return;
6982
        }
6983
        if (is_array($SystemAlertMsgQueque)) {
6984
            $title = '';
6985
            if ($this->name && $this->activePlugin) {
6986
                $title = "<div><b>" . $this->activePlugin . "</b> - <span style='color:maroon;'>" . $this->name . "</span></div>";
6987
            }
6988
            $SystemAlertMsgQueque[] = "$title<div style='margin-left:10px;margin-top:3px;'>$msg</div>";
6989
        }
6990
    }
6991
6992
    /**
6993
     * Output
6994
     *
6995
     * @param string $msg
6996
     */
6997
    public function output($msg)
6998
    {
6999
        $this->_output .= $msg;
7000
    }
7001
7002
    /**
7003
     * Stop event propogation
7004
     */
7005
    public function stopPropagation()
7006
    {
7007
        $this->_propagate = false;
7008
    }
7009
7010
    public function _resetEventObject()
7011
    {
7012
        unset ($this->returnedValues);
7013
        $this->name = "";
7014
        $this->_output = "";
7015
        $this->_propagate = true;
7016
        $this->activated = false;
7017
    }
7018
}
7019