Completed
Push — develop ( 4a389e...d0bb9b )
by Maxim
15s
created

DocumentParser::safeEval()   C

Complexity

Conditions 10
Paths 56

Size

Total Lines 47
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 32
nc 56
nop 3
dl 0
loc 47
rs 5.1578
c 0
b 0
f 0

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

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