Completed
Pull Request — develop (#546)
by Maxim
05:36
created

DocumentParser::safeEval()   D

Complexity

Conditions 10
Paths 56

Size

Total Lines 45
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 32
nc 56
nop 3
dl 0
loc 45
rs 4.8196
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
     * Document constructor
177
     *
178
     * @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...
179
     */
180
    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...
181
    {
182
        if ($this->isLoggedIn()) {
183
            ini_set('display_errors', 1);
184
        }
185
        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...
186
        if (substr(PHP_OS, 0, 3) === 'WIN' && $database_server === 'localhost') {
187
            $database_server = '127.0.0.1';
188
        }
189
        $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...
190
        $this->dbConfig = &$this->db->config; // alias for backward compatibility
191
        // events
192
        $this->event = new SystemEvent();
193
        $this->Event = &$this->event; //alias for backward compatibility
194
        // set track_errors ini variable
195
        @ ini_set("track_errors", "1"); // enable error tracking in $php_errormsg
196
        $this->time = $_SERVER['REQUEST_TIME']; // for having global timestamp
197
198
        $this->q = self::_getCleanQueryString();
199
    }
200
201
    /**
202
     * @param $method_name
203
     * @param $arguments
204
     * @return mixed
205
     */
206
    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...
207
    {
208
        include_once(MODX_MANAGER_PATH . 'includes/extenders/deprecated.functions.inc.php');
209
        if (method_exists($this->old, $method_name)) {
210
            $error_type = 1;
211
        } else {
212
            $error_type = 3;
213
        }
214
215
        if (!isset($this->config['error_reporting']) || 1 < $this->config['error_reporting']) {
216
            if ($error_type == 1) {
217
                $title = 'Call deprecated method';
218
                $msg = $this->htmlspecialchars("\$modx->{$method_name}() is deprecated function");
219
            } else {
220
                $title = 'Call undefined method';
221
                $msg = $this->htmlspecialchars("\$modx->{$method_name}() is undefined function");
222
            }
223
            $info = debug_backtrace();
224
            $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...
225
            if (!empty($this->currentSnippet)) {
226
                $m[] = 'Snippet - ' . $this->currentSnippet;
227
            } elseif (!empty($this->event->activePlugin)) {
228
                $m[] = 'Plugin - ' . $this->event->activePlugin;
229
            }
230
            $m[] = $this->decoded_request_uri;
231
            $m[] = str_replace('\\', '/', $info[0]['file']) . '(line:' . $info[0]['line'] . ')';
232
            $msg = implode('<br />', $m);
233
            $this->logEvent(0, $error_type, $msg, $title);
234
        }
235
        if (method_exists($this->old, $method_name)) {
236
            return call_user_func_array(array($this->old, $method_name), $arguments);
237
        }
238
    }
239
240
    /**
241
     * @param string $connector
242
     * @return bool
243
     */
244
    public function checkSQLconnect($connector = 'db')
245
    {
246
        $flag = false;
247
        if (is_scalar($connector) && !empty($connector) && isset($this->{$connector}) && $this->{$connector} instanceof DBAPI) {
248
            $flag = (bool)$this->{$connector}->conn;
249
        }
250
        return $flag;
251
    }
252
253
    /**
254
     * Loads an extension from the extenders folder.
255
     * You can load any extension creating a boot file:
256
     * MODX_MANAGER_PATH."includes/extenders/ex_{$extname}.inc.php"
257
     * $extname - extension name in lowercase
258
     *
259
     * @param $extname
260
     * @param bool $reload
261
     * @return bool
262
     */
263
    public function loadExtension($extname, $reload = true)
264
    {
265
        $out = false;
266
        $flag = ($reload || !in_array($extname, $this->extensions));
267
        if ($this->checkSQLconnect('db') && $flag) {
268
            $evtOut = $this->invokeEvent('OnBeforeLoadExtension', array('name' => $extname, 'reload' => $reload));
269
            if (is_array($evtOut) && count($evtOut) > 0) {
270
                $out = array_pop($evtOut);
271
            }
272
        }
273
        if (!$out && $flag) {
274
            $extname = trim(str_replace(array('..', '/', '\\'), '', strtolower($extname)));
275
            $filename = MODX_MANAGER_PATH . "includes/extenders/ex_{$extname}.inc.php";
276
            $out = is_file($filename) ? include $filename : false;
277
        }
278
        if ($out && !in_array($extname, $this->extensions)) {
279
            $this->extensions[] = $extname;
280
        }
281
        return $out;
282
    }
283
284
    /**
285
     * Returns the current micro time
286
     *
287
     * @return float
288
     */
289
    public function getMicroTime()
290
    {
291
        list ($usec, $sec) = explode(' ', microtime());
292
        return ((float)$usec + (float)$sec);
293
    }
294
295
    /**
296
     * Redirect
297
     *
298
     * @param string $url
299
     * @param int $count_attempts
300
     * @param string $type $type
301
     * @param string $responseCode
302
     * @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...
303
     * @global string $base_url
304
     * @global string $site_url
305
     */
306
    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...
307
    {
308
        $header = '';
309
        if (empty ($url)) {
310
            return false;
311
        }
312
        if ($count_attempts == 1) {
313
            // append the redirect count string to the url
314
            $currentNumberOfRedirects = isset ($_REQUEST['err']) ? $_REQUEST['err'] : 0;
315
            if ($currentNumberOfRedirects > 3) {
316
                $this->messageQuit('Redirection attempt failed - please ensure the document you\'re trying to redirect to exists. <p>Redirection URL: <i>' . $url . '</i></p>');
317
            } else {
318
                $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...
319
                if (strpos($url, "?") > 0) {
320
                    $url .= "&err=$currentNumberOfRedirects";
321
                } else {
322
                    $url .= "?err=$currentNumberOfRedirects";
323
                }
324
            }
325
        }
326
        if ($type == 'REDIRECT_REFRESH') {
327
            $header = 'Refresh: 0;URL=' . $url;
328
        } elseif ($type == 'REDIRECT_META') {
329
            $header = '<META HTTP-EQUIV="Refresh" CONTENT="0; URL=' . $url . '" />';
330
            echo $header;
331
            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...
332
        } elseif ($type == 'REDIRECT_HEADER' || empty ($type)) {
333
            // check if url has /$base_url
334
            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...
335
            if (substr($url, 0, strlen($base_url)) == $base_url) {
336
                // append $site_url to make it work with Location:
337
                $url = $site_url . substr($url, strlen($base_url));
338
            }
339
            if (strpos($url, "\n") === false) {
340
                $header = 'Location: ' . $url;
341
            } else {
342
                $this->messageQuit('No newline allowed in redirect url.');
343
            }
344
        }
345
        if ($responseCode && (strpos($responseCode, '30') !== false)) {
346
            header($responseCode);
347
        }
348
349
        if(!empty($header)) {
350
            header($header);
351
        }
352
353
        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...
354
    }
355
356
    /**
357
     * Forward to another page
358
     *
359
     * @param int|string $id
360
     * @param string $responseCode
361
     */
362
    public function sendForward($id, $responseCode = '')
363
    {
364
        if ($this->forwards > 0) {
365
            $this->forwards = $this->forwards - 1;
366
            $this->documentIdentifier = $id;
367
            $this->documentMethod = 'id';
368
            if ($responseCode) {
369
                header($responseCode);
370
            }
371
            $this->prepareResponse();
372
            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...
373
        } else {
374
            $this->messageQuit("Internal Server Error id={$id}");
375
            header('HTTP/1.0 500 Internal Server Error');
376
            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...
377
        }
378
    }
379
380
    /**
381
     * Redirect to the error page, by calling sendForward(). This is called for example when the page was not found.
382
     * @param bool $noEvent
383
     */
384
    public function sendErrorPage($noEvent = false)
385
    {
386
        $this->systemCacheKey = 'notfound';
387
        if (!$noEvent) {
388
            // invoke OnPageNotFound event
389
            $this->invokeEvent('OnPageNotFound');
390
        }
391
        $url = $this->config['error_page'] ? $this->config['error_page'] : $this->config['site_start'];
392
393
        $this->sendForward($url, 'HTTP/1.0 404 Not Found');
394
        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...
395
    }
396
397
    /**
398
     * @param bool $noEvent
399
     */
400
    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...
401
    {
402
        // invoke OnPageUnauthorized event
403
        $_REQUEST['refurl'] = $this->documentIdentifier;
404
        $this->systemCacheKey = 'unauth';
405
        if (!$noEvent) {
406
            $this->invokeEvent('OnPageUnauthorized');
407
        }
408
        if ($this->config['unauthorized_page']) {
409
            $unauthorizedPage = $this->config['unauthorized_page'];
410
        } elseif ($this->config['error_page']) {
411
            $unauthorizedPage = $this->config['error_page'];
412
        } else {
413
            $unauthorizedPage = $this->config['site_start'];
414
        }
415
        $this->sendForward($unauthorizedPage, 'HTTP/1.1 401 Unauthorized');
416
        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...
417
    }
418
419
    /**
420
     * Get MODX settings including, but not limited to, the system_settings table
421
     */
422
    public function getSettings()
423
    {
424
        if (!isset($this->config['site_name'])) {
425
            $this->recoverySiteCache();
426
        }
427
428
        // setup default site id - new installation should generate a unique id for the site.
429
        if (!isset($this->config['site_id'])) {
430
            $this->config['site_id'] = "MzGeQ2faT4Dw06+U49x3";
431
        }
432
433
        // store base_url and base_path inside config array
434
        $this->config['base_url'] = MODX_BASE_URL;
435
        $this->config['base_path'] = MODX_BASE_PATH;
436
        $this->config['site_url'] = MODX_SITE_URL;
437
        $this->config['valid_hostnames'] = MODX_SITE_HOSTNAMES;
438
        $this->config['site_manager_url'] = MODX_MANAGER_URL;
439
        $this->config['site_manager_path'] = MODX_MANAGER_PATH;
440
        $this->error_reporting = $this->config['error_reporting'];
441
        $this->config['filemanager_path'] = str_replace('[(base_path)]', MODX_BASE_PATH, $this->config['filemanager_path']);
442
        $this->config['rb_base_dir'] = str_replace('[(base_path)]', MODX_BASE_PATH, $this->config['rb_base_dir']);
443
444
        if (!isset($this->config['enable_at_syntax'])) {
445
            $this->config['enable_at_syntax'] = 1;
446
        } // @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...
447
448
        // now merge user settings into evo-configuration
449
        $this->getUserSettings();
450
    }
451
452
    private function recoverySiteCache()
453
    {
454
        $site_cache_dir = MODX_BASE_PATH . $this->getCacheFolder();
455
        $site_cache_path = $site_cache_dir . 'siteCache.idx.php';
456
457
        if (is_file($site_cache_path)) {
458
            include($site_cache_path);
459
        }
460
        if (isset($this->config['site_name'])) {
461
            return;
462
        }
463
464
        include_once(MODX_MANAGER_PATH . 'processors/cache_sync.class.processor.php');
465
        $cache = new synccache();
466
        $cache->setCachepath($site_cache_dir);
467
        $cache->setReport(false);
468
        $cache->buildCache($this);
469
470
        clearstatcache();
471
        if (is_file($site_cache_path)) {
472
            include($site_cache_path);
473
        }
474
        if (isset($this->config['site_name'])) {
475
            return;
476
        }
477
478
        $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...
479
        while ($row = $this->db->getRow($rs)) {
480
            $this->config[$row['setting_name']] = $row['setting_value'];
481
        }
482
483
        if (!$this->config['enable_filter']) {
484
            return;
485
        }
486
487
        $where = "plugincode LIKE '%phx.parser.class.inc.php%OnParseDocument();%' AND disabled != 1";
488
        $rs = $this->db->select('id', '[+prefix+]site_plugins', $where);
489
        if ($this->db->getRecordCount($rs)) {
490
            $this->config['enable_filter'] = '0';
491
        }
492
    }
493
494
    /**
495
     * Get user settings and merge into MODX configuration
496
     * @return array
497
     */
498
    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...
499
    {
500
        $tbl_web_user_settings = $this->getFullTableName('web_user_settings');
501
        $tbl_user_settings = $this->getFullTableName('user_settings');
502
503
        // load user setting if user is logged in
504
        $usrSettings = array();
505
        if ($id = $this->getLoginUserID()) {
506
            $usrType = $this->getLoginUserType();
507
            if (isset ($usrType) && $usrType == 'manager') {
508
                $usrType = 'mgr';
509
            }
510
511
            if ($usrType == 'mgr' && $this->isBackend()) {
512
                // invoke the OnBeforeManagerPageInit event, only if in backend
513
                $this->invokeEvent("OnBeforeManagerPageInit");
514
            }
515
516
            if (isset ($_SESSION[$usrType . 'UsrConfigSet'])) {
517
                $usrSettings = &$_SESSION[$usrType . 'UsrConfigSet'];
518
            } else {
519
                if ($usrType == 'web') {
520
                    $from = $tbl_web_user_settings;
521
                    $where = "webuser='{$id}'";
522
                } else {
523
                    $from = $tbl_user_settings;
524
                    $where = "user='{$id}'";
525
                }
526
527
                $which_browser_default = $this->configGlobal['which_browser'] ? $this->configGlobal['which_browser'] : $this->config['which_browser'];
528
529
                $result = $this->db->select('setting_name, setting_value', $from, $where);
530
                while ($row = $this->db->getRow($result)) {
531 View Code Duplication
                    if ($row['setting_name'] == 'which_browser' && $row['setting_value'] == 'default') {
532
                        $row['setting_value'] = $which_browser_default;
533
                    }
534
                    $usrSettings[$row['setting_name']] = $row['setting_value'];
535
                }
536
                if (isset ($usrType)) {
537
                    $_SESSION[$usrType . 'UsrConfigSet'] = $usrSettings;
538
                } // store user settings in session
539
            }
540
        }
541
        if ($this->isFrontend() && $mgrid = $this->getLoginUserID('mgr')) {
542
            $musrSettings = array();
543
            if (isset ($_SESSION['mgrUsrConfigSet'])) {
544
                $musrSettings = &$_SESSION['mgrUsrConfigSet'];
545
            } else {
546
                if ($result = $this->db->select('setting_name, setting_value', $tbl_user_settings, "user='{$mgrid}'")) {
547
                    while ($row = $this->db->getRow($result)) {
548
                        $musrSettings[$row['setting_name']] = $row['setting_value'];
549
                    }
550
                    $_SESSION['mgrUsrConfigSet'] = $musrSettings; // store user settings in session
551
                }
552
            }
553
            if (!empty ($musrSettings)) {
554
                $usrSettings = array_merge($musrSettings, $usrSettings);
555
            }
556
        }
557
        // save global values before overwriting/merging array
558
        foreach ($usrSettings as $param => $value) {
559
            if (isset($this->config[$param])) {
560
                $this->configGlobal[$param] = $this->config[$param];
561
            }
562
        }
563
564
        $this->config = array_merge($this->config, $usrSettings);
565
        $this->config['filemanager_path'] = str_replace('[(base_path)]', MODX_BASE_PATH, $this->config['filemanager_path']);
566
        $this->config['rb_base_dir'] = str_replace('[(base_path)]', MODX_BASE_PATH, $this->config['rb_base_dir']);
567
568
        return $usrSettings;
569
    }
570
571
    /**
572
     * Returns the document identifier of the current request
573
     *
574
     * @param string $method id and alias are allowed
575
     * @return int
576
     */
577
    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...
578
    {
579
        // function to test the query and find the retrieval method
580
        if ($method === 'alias') {
581
            return $this->db->escape($_REQUEST['q']);
582
        }
583
584
        $id_ = filter_input(INPUT_GET, 'id');
585
        if ($id_) {
586
            if (preg_match('@^[1-9][0-9]*$@', $id_)) {
587
                return $id_;
588
            } else {
589
                $this->sendErrorPage();
590
            }
591
        } elseif (strpos($_SERVER['REQUEST_URI'], 'index.php/') !== false) {
592
            $this->sendErrorPage();
593
        } else {
594
            return $this->config['site_start'];
595
        }
596
    }
597
598
    /**
599
     * Check for manager or webuser login session since v1.2
600
     *
601
     * @param string $context
602
     * @return bool
603
     */
604
    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...
605
    {
606
        if (substr($context, 0, 1) == 'm') {
607
            $_ = '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...
608
        } else {
609
            $_ = 'webValidated';
610
        }
611
612
        if (MODX_CLI || (isset($_SESSION[$_]) && !empty($_SESSION[$_]))) {
0 ignored issues
show
Coding Style introduced by
The if-else statement can be simplified to return (bool) (MODX_CLI ...!empty($_SESSION[$_]));.
Loading history...
613
            return true;
614
        } else {
615
            return false;
616
        }
617
    }
618
619
    /**
620
     * Check for manager login session
621
     *
622
     * @return boolean
623
     */
624
    public function checkSession()
625
    {
626
        return $this->isLoggedin();
627
    }
628
629
    /**
630
     * Checks, if a the result is a preview
631
     *
632
     * @return boolean
633
     */
634
    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...
635
    {
636
        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...
637
            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...
638
                return true;
639
            } else {
640
                return false;
641
            }
642
        } else {
643
            return false;
644
        }
645
    }
646
647
    /**
648
     * check if site is offline
649
     *
650
     * @return boolean
651
     */
652
    public function checkSiteStatus()
653
    {
654
        if ($this->config['site_status']) {
655
            return true;
656
        }  // site online
657
        elseif ($this->isLoggedin()) {
658
            return true;
659
        }  // site offline but launched via the manager
660
        else {
661
            return false;
662
        } // site is offline
663
    }
664
665
    /**
666
     * Create a 'clean' document identifier with path information, friendly URL suffix and prefix.
667
     *
668
     * @param string $qOrig
669
     * @return string
670
     */
671
    public function cleanDocumentIdentifier($qOrig)
672
    {
673
        if (!$qOrig) {
674
            $qOrig = $this->config['site_start'];
675
        }
676
        $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...
677
678
        $pre = $this->config['friendly_url_prefix'];
679
        $suf = $this->config['friendly_url_suffix'];
680
        $pre = preg_quote($pre, '/');
681
        $suf = preg_quote($suf, '/');
682 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...
683
            $q = $_[1];
684
        }
685 View Code Duplication
        if ($suf && preg_match('@(.*)' . $suf . '$@', $q, $_)) {
686
            $q = $_[1];
687
        }
688
689
        /* First remove any / before or after */
690
        $q = trim($q, '/');
691
692
        /* Save path if any */
693
        /* FS#476 and FS#308: only return virtualDir if friendly paths are enabled */
694
        if ($this->config['use_alias_path'] == 1) {
695
            $_ = strrpos($q, '/');
696
            $this->virtualDir = $_ !== false ? substr($q, 0, $_) : '';
697
            if ($_ !== false) {
698
                $q = preg_replace('@.*/@', '', $q);
699
            }
700
        } else {
701
            $this->virtualDir = '';
702
        }
703
704
        if (preg_match('@^[1-9][0-9]*$@', $q) && !isset($this->documentListing[$q])) { /* we got an ID returned, check to make sure it's not an alias */
705
            /* FS#476 and FS#308: check that id is valid in terms of virtualDir structure */
706
            if ($this->config['use_alias_path'] == 1) {
707
                if (($this->virtualDir != '' && !isset($this->documentListing[$this->virtualDir . '/' . $q]) || ($this->virtualDir == '' && !isset($this->documentListing[$q]))) && (($this->virtualDir != '' && isset($this->documentListing[$this->virtualDir]) && in_array($q, $this->getChildIds($this->documentListing[$this->virtualDir], 1))) || ($this->virtualDir == '' && in_array($q, $this->getChildIds(0, 1))))) {
708
                    $this->documentMethod = 'id';
709
                    return $q;
710
                } else { /* not a valid id in terms of virtualDir, treat as alias */
711
                    $this->documentMethod = 'alias';
712
                    return $q;
713
                }
714
            } else {
715
                $this->documentMethod = 'id';
716
                return $q;
717
            }
718
        } else { /* we didn't get an ID back, so instead we assume it's an alias */
719
            if ($this->config['friendly_alias_urls'] != 1) {
720
                $q = $qOrig;
721
            }
722
            $this->documentMethod = 'alias';
723
            return $q;
724
        }
725
    }
726
727
    /**
728
     * @return string
729
     */
730
    public function getCacheFolder()
731
    {
732
        return "assets/cache/";
733
    }
734
735
    /**
736
     * @param $key
737
     * @return string
738
     */
739
    public function getHashFile($key)
740
    {
741
        return $this->getCacheFolder() . "docid_" . $key . ".pageCache.php";
742
    }
743
744
    /**
745
     * @param $id
746
     * @return array|mixed|null|string
747
     */
748
    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...
749
        $hash = $id;
750
        $tmp = null;
751
        $params = array();
752
        if(!empty($this->systemCacheKey)){
753
            $hash = $this->systemCacheKey;
754
        }else {
755
            if (!empty($_GET)) {
756
                // Sort GET parameters so that the order of parameters on the HTTP request don't affect the generated cache ID.
757
                $params = $_GET;
758
                ksort($params);
759
                $hash .= '_'.md5(http_build_query($params));
760
            }
761
        }
762
        $evtOut = $this->invokeEvent("OnMakePageCacheKey", array ("hash" => $hash, "id" => $id, 'params' => $params));
763
        if (is_array($evtOut) && count($evtOut) > 0){
764
            $tmp = array_pop($evtOut);
765
        }
766
        return empty($tmp) ? $hash : $tmp;
767
    }
768
769
    /**
770
     * @param $id
771
     * @param bool $loading
772
     * @return string
773
     */
774
    public function checkCache($id, $loading = false)
775
    {
776
        return $this->getDocumentObjectFromCache($id, $loading);
777
    }
778
779
    /**
780
     * Check the cache for a specific document/resource
781
     *
782
     * @param int $id
783
     * @param bool $loading
784
     * @return string
785
     */
786
    public function getDocumentObjectFromCache($id, $loading = false)
787
    {
788
        $key = ($this->config['cache_type'] == 2) ? $this->makePageCacheKey($id) : $id;
789
        if ($loading) {
790
            $this->cacheKey = $key;
791
        }
792
793
        $cache_path = $this->getHashFile($key);
794
795
        if (!is_file($cache_path)) {
796
            $this->documentGenerated = 1;
797
            return '';
798
        }
799
        $content = file_get_contents($cache_path, false);
800
        if (substr($content, 0, 5) === '<?php') {
801
            $content = substr($content, strpos($content, '?>') + 2);
802
        } // remove php header
803
        $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...
804
        if (count($a) == 1) {
805
            $result = $a[0];
806
        } // return only document content
807
        else {
808
            $docObj = unserialize($a[0]); // rebuild document object
809
            // check page security
810
            if ($docObj['privateweb'] && isset ($docObj['__MODxDocGroups__'])) {
811
                $pass = false;
812
                $usrGrps = $this->getUserDocGroups();
813
                $docGrps = explode(',', $docObj['__MODxDocGroups__']);
814
                // check is user has access to doc groups
815
                if (is_array($usrGrps)) {
816
                    foreach ($usrGrps as $k => $v) {
817
                        if (!in_array($v, $docGrps)) {
818
                            continue;
819
                        }
820
                        $pass = true;
821
                        break;
822
                    }
823
                }
824
                // diplay error pages if user has no access to cached doc
825
                if (!$pass) {
826
                    if ($this->config['unauthorized_page']) {
827
                        // check if file is not public
828
                        $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...
829
                        $total = $this->db->getValue($rs);
830
                    } else {
831
                        $total = 0;
832
                    }
833
834
                    if ($total > 0) {
835
                        $this->sendUnauthorizedPage();
836
                    } else {
837
                        $this->sendErrorPage();
838
                    }
839
840
                    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...
841
                }
842
            }
843
            // Grab the Scripts
844
            if (isset($docObj['__MODxSJScripts__'])) {
845
                $this->sjscripts = $docObj['__MODxSJScripts__'];
846
            }
847
            if (isset($docObj['__MODxJScripts__'])) {
848
                $this->jscripts = $docObj['__MODxJScripts__'];
849
            }
850
851
            // Remove intermediate variables
852
            unset($docObj['__MODxDocGroups__'], $docObj['__MODxSJScripts__'], $docObj['__MODxJScripts__']);
853
854
            $this->documentObject = $docObj;
855
856
            $result = $a[1]; // return document content
857
        }
858
859
        $this->documentGenerated = 0;
860
        // invoke OnLoadWebPageCache  event
861
        $this->documentContent = $result;
862
        $this->invokeEvent('OnLoadWebPageCache');
863
        return $result;
864
    }
865
866
    /**
867
     * Final processing and output of the document/resource.
868
     *
869
     * - runs uncached snippets
870
     * - add javascript to <head>
871
     * - removes unused placeholders
872
     * - converts URL tags [~...~] to URLs
873
     *
874
     * @param boolean $noEvent Default: false
875
     */
876
    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...
877
    {
878
        $this->documentOutput = $this->documentContent;
879
880
        if ($this->documentGenerated == 1 && $this->documentObject['cacheable'] == 1 && $this->documentObject['type'] == 'document' && $this->documentObject['published'] == 1) {
881
            if (!empty($this->sjscripts)) {
882
                $this->documentObject['__MODxSJScripts__'] = $this->sjscripts;
883
            }
884
            if (!empty($this->jscripts)) {
885
                $this->documentObject['__MODxJScripts__'] = $this->jscripts;
886
            }
887
        }
888
889
        // check for non-cached snippet output
890
        if (strpos($this->documentOutput, '[!') > -1) {
891
            $this->recentUpdate = $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'];
892
893
            $this->documentOutput = str_replace('[!', '[[', $this->documentOutput);
894
            $this->documentOutput = str_replace('!]', ']]', $this->documentOutput);
895
896
            // Parse document source
897
            $this->documentOutput = $this->parseDocumentSource($this->documentOutput);
898
        }
899
900
        // Moved from prepareResponse() by sirlancelot
901
        // Insert Startup jscripts & CSS scripts into template - template must have a <head> tag
902
        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...
903
            // change to just before closing </head>
904
            // $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...
905
            $this->documentOutput = preg_replace("/(<\/head>)/i", $js . "\n\\1", $this->documentOutput);
906
        }
907
908
        // Insert jscripts & html block into template - template must have a </body> tag
909
        if ($js = $this->getRegisteredClientScripts()) {
910
            $this->documentOutput = preg_replace("/(<\/body>)/i", $js . "\n\\1", $this->documentOutput);
911
        }
912
        // End fix by sirlancelot
913
914
        $this->documentOutput = $this->cleanUpMODXTags($this->documentOutput);
915
916
        $this->documentOutput = $this->rewriteUrls($this->documentOutput);
917
918
        // send out content-type and content-disposition headers
919
        if (IN_PARSER_MODE == "true") {
920
            $type = !empty ($this->contentTypes[$this->documentIdentifier]) ? $this->contentTypes[$this->documentIdentifier] : "text/html";
921
            header('Content-Type: ' . $type . '; charset=' . $this->config['modx_charset']);
922
            //            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...
923
            //                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...
924
            if (!$this->checkPreview() && $this->documentObject['content_dispo'] == 1) {
925
                if ($this->documentObject['alias']) {
926
                    $name = $this->documentObject['alias'];
927
                } else {
928
                    // strip title of special characters
929
                    $name = $this->documentObject['pagetitle'];
930
                    $name = strip_tags($name);
931
                    $name = $this->cleanUpMODXTags($name);
932
                    $name = strtolower($name);
933
                    $name = preg_replace('/&.+?;/', '', $name); // kill entities
934
                    $name = preg_replace('/[^\.%a-z0-9 _-]/', '', $name);
935
                    $name = preg_replace('/\s+/', '-', $name);
936
                    $name = preg_replace('|-+|', '-', $name);
937
                    $name = trim($name, '-');
938
                }
939
                $header = 'Content-Disposition: attachment; filename=' . $name;
940
                header($header);
941
            }
942
        }
943
        $this->setConditional();
944
945
        $stats = $this->getTimerStats($this->tstart);
946
947
        $out =& $this->documentOutput;
948
        $out = str_replace("[^q^]", $stats['queries'], $out);
949
        $out = str_replace("[^qt^]", $stats['queryTime'], $out);
950
        $out = str_replace("[^p^]", $stats['phpTime'], $out);
951
        $out = str_replace("[^t^]", $stats['totalTime'], $out);
952
        $out = str_replace("[^s^]", $stats['source'], $out);
953
        $out = str_replace("[^m^]", $stats['phpMemory'], $out);
954
        //$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...
955
956
        // invoke OnWebPagePrerender event
957
        if (!$noEvent) {
958
            $evtOut = $this->invokeEvent('OnWebPagePrerender', array('documentOutput' => $this->documentOutput));
959
            if (is_array($evtOut) && count($evtOut) > 0) {
960
                $this->documentOutput = $evtOut['0'];
961
            }
962
        }
963
964
        $this->documentOutput = $this->removeSanitizeSeed($this->documentOutput);
965
966
        if (strpos($this->documentOutput, '\{') !== false) {
967
            $this->documentOutput = $this->RecoveryEscapedTags($this->documentOutput);
968
        } elseif (strpos($this->documentOutput, '\[') !== false) {
969
            $this->documentOutput = $this->RecoveryEscapedTags($this->documentOutput);
970
        }
971
972
        echo $this->documentOutput;
973
974
        if ($this->dumpSQL) {
975
            echo $this->queryCode;
976
        }
977
        if ($this->dumpSnippets) {
978
            $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...
979
            $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...
980
            foreach ($this->snippetsTime as $s => $v) {
981
                $t = $v['time'];
982
                $sname = $v['sname'];
983
                $sc .= sprintf("%s. %s (%s)<br>", $s, $sname, sprintf("%2.2f ms", $t)); // currentSnippet
984
                $tt += $t;
985
            }
986
            echo "<fieldset><legend><b>Snippets</b> (" . count($this->snippetsTime) . " / " . sprintf("%2.2f ms", $tt) . ")</legend>{$sc}</fieldset><br />";
987
            echo $this->snippetsCode;
988
        }
989
        if ($this->dumpPlugins) {
990
            $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...
991
            $tt = 0;
992
            foreach ($this->pluginsTime as $s => $t) {
993
                $ps .= "$s (" . sprintf("%2.2f ms", $t * 1000) . ")<br>";
994
                $tt += $t;
995
            }
996
            echo "<fieldset><legend><b>Plugins</b> (" . count($this->pluginsTime) . " / " . sprintf("%2.2f ms", $tt * 1000) . ")</legend>{$ps}</fieldset><br />";
997
            echo $this->pluginsCode;
998
        }
999
1000
        ob_end_flush();
1001
    }
1002
1003
    /**
1004
     * @param $contents
1005
     * @return mixed
1006
     */
1007
    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...
1008
    {
1009
        list($sTags, $rTags) = $this->getTagsForEscape();
1010
        return str_replace($rTags, $sTags, $contents);
1011
    }
1012
1013
    /**
1014
     * @param string $tags
1015
     * @return array[]
1016
     */
1017
    public function getTagsForEscape($tags = '{{,}},[[,]],[!,!],[*,*],[(,)],[+,+],[~,~],[^,^]')
1018
    {
1019
        $srcTags = explode(',', $tags);
1020
        $repTags = array();
1021
        foreach ($srcTags as $tag) {
1022
            $repTags[] = '\\' . $tag[0] . '\\' . $tag[1];
1023
        }
1024
        return array($srcTags, $repTags);
1025
    }
1026
1027
    /**
1028
     * @param $tstart
1029
     * @return array
1030
     */
1031
    public function getTimerStats($tstart)
1032
    {
1033
        $stats = array();
1034
1035
        $stats['totalTime'] = ($this->getMicroTime() - $tstart);
1036
        $stats['queryTime'] = $this->queryTime;
1037
        $stats['phpTime'] = $stats['totalTime'] - $stats['queryTime'];
1038
1039
        $stats['queryTime'] = sprintf("%2.4f s", $stats['queryTime']);
1040
        $stats['totalTime'] = sprintf("%2.4f s", $stats['totalTime']);
1041
        $stats['phpTime'] = sprintf("%2.4f s", $stats['phpTime']);
1042
        $stats['source'] = $this->documentGenerated == 1 ? "database" : "cache";
1043
        $stats['queries'] = isset ($this->executedQueries) ? $this->executedQueries : 0;
1044
        $stats['phpMemory'] = (memory_get_peak_usage(true) / 1024 / 1024) . " mb";
1045
1046
        return $stats;
1047
    }
1048
1049
    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...
1050
    {
1051
        if (!empty($_POST) || (defined('MODX_API_MODE') && MODX_API_MODE) || $this->getLoginUserID('mgr') || !$this->useConditional || empty($this->recentUpdate)) {
1052
            return;
1053
        }
1054
        $last_modified = gmdate('D, d M Y H:i:s T', $this->recentUpdate);
1055
        $etag = md5($last_modified);
1056
        $HTTP_IF_MODIFIED_SINCE = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? $_SERVER['HTTP_IF_MODIFIED_SINCE'] : false;
1057
        $HTTP_IF_NONE_MATCH = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? $_SERVER['HTTP_IF_NONE_MATCH'] : false;
1058
        header('Pragma: no-cache');
1059
1060
        if ($HTTP_IF_MODIFIED_SINCE == $last_modified || strpos($HTTP_IF_NONE_MATCH, $etag) !== false) {
1061
            header('HTTP/1.1 304 Not Modified');
1062
            header('Content-Length: 0');
1063
            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...
1064
        } else {
1065
            header("Last-Modified: {$last_modified}");
1066
            header("ETag: '{$etag}'");
1067
        }
1068
    }
1069
1070
    /**
1071
     * Checks the publish state of page
1072
     */
1073
    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...
1074
    {
1075
        $cacheRefreshTime = 0;
1076
        $recent_update = 0;
1077
        @include(MODX_BASE_PATH . $this->getCacheFolder() . 'sitePublishing.idx.php');
1078
        $this->recentUpdate = $recent_update;
1079
1080
        $timeNow = $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'];
1081
        if ($timeNow < $cacheRefreshTime || $cacheRefreshTime == 0) {
1082
            return;
1083
        }
1084
1085
        // now, check for documents that need publishing
1086
        $field = array('published' => 1, 'publishedon' => $timeNow);
1087
        $where = "pub_date <= {$timeNow} AND pub_date!=0 AND published=0";
1088
        $this->db->update($field, '[+prefix+]site_content', $where);
1089
1090
        // now, check for documents that need un-publishing
1091
        $field = array('published' => 0, 'publishedon' => 0);
1092
        $where = "unpub_date <= {$timeNow} AND unpub_date!=0 AND published=1";
1093
        $this->db->update($field, '[+prefix+]site_content', $where);
1094
1095
        $this->recentUpdate = $timeNow;
1096
1097
        // clear the cache
1098
        $this->clearCache('full');
1099
    }
1100
1101
    public function checkPublishStatus()
1102
    {
1103
        $this->updatePubStatus();
1104
    }
1105
1106
    /**
1107
     * Final jobs.
1108
     *
1109
     * - cache page
1110
     */
1111
    public function postProcess()
1112
    {
1113
        // if the current document was generated, cache it!
1114
        $cacheable = ($this->config['enable_cache'] && $this->documentObject['cacheable']) ? 1 : 0;
1115
        if ($cacheable && $this->documentGenerated && $this->documentObject['type'] == 'document' && $this->documentObject['published']) {
1116
            // invoke OnBeforeSaveWebPageCache event
1117
            $this->invokeEvent("OnBeforeSaveWebPageCache");
1118
1119
            if (!empty($this->cacheKey) && is_scalar($this->cacheKey)) {
1120
                // get and store document groups inside document object. Document groups will be used to check security on cache pages
1121
                $where = "document='{$this->documentIdentifier}'";
1122
                $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...
1123
                $docGroups = $this->db->getColumn('document_group', $rs);
1124
1125
                // Attach Document Groups and Scripts
1126
                if (is_array($docGroups)) {
1127
                    $this->documentObject['__MODxDocGroups__'] = implode(",", $docGroups);
1128
                }
1129
1130
                $docObjSerial = serialize($this->documentObject);
1131
                $cacheContent = $docObjSerial . "<!--__MODxCacheSpliter__-->" . $this->documentContent;
1132
                $page_cache_path = MODX_BASE_PATH . $this->getHashFile($this->cacheKey);
1133
                file_put_contents($page_cache_path, "<?php die('Unauthorized access.'); ?>$cacheContent");
1134
            }
1135
        }
1136
1137
        // Useful for example to external page counters/stats packages
1138
        $this->invokeEvent('OnWebPageComplete');
1139
1140
        // end post processing
1141
    }
1142
1143
    /**
1144
     * @param $content
1145
     * @param string $left
1146
     * @param string $right
1147
     * @return array
1148
     */
1149
    public function getTagsFromContent($content, $left = '[+', $right = '+]')
1150
    {
1151
        $_ = $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...
1152
        if (empty($_)) {
1153
            return array();
1154
        }
1155
        foreach ($_ as $v) {
1156
            $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...
1157
            $tags[1][] = $v;
1158
        }
1159
        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...
1160
    }
1161
1162
    /**
1163
     * @param $content
1164
     * @param string $left
1165
     * @param string $right
1166
     * @return array
1167
     */
1168
    public function _getTagsFromContent($content, $left = '[+', $right = '+]')
1169
    {
1170
        if (strpos($content, $left) === false) {
1171
            return array();
1172
        }
1173
        $spacer = md5('<<<EVO>>>');
1174
        if($left==='{{' && strpos($content,';}}')!==false)  $content = str_replace(';}}', sprintf(';}%s}',   $spacer),$content);
1175
        if($left==='{{' && strpos($content,'{{}}')!==false) $content = str_replace('{{}}',sprintf('{%$1s{}%$1s}',$spacer),$content);
1176
        if($left==='[[' && strpos($content,']]]]')!==false) $content = str_replace(']]]]',sprintf(']]%s]]',  $spacer),$content);
1177
        if($left==='[[' && strpos($content,']]]')!==false)  $content = str_replace(']]]', sprintf(']%s]]',   $spacer),$content);
1178
1179
        $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...
1180
        $pos[']]>'] = strpos($content, ']]>');
1181
1182
        if ($pos['<![CDATA['] !== false && $pos[']]>'] !== false) {
1183
            $content = substr($content, 0, $pos['<![CDATA[']) . substr($content, $pos[']]>'] + 3);
1184
        }
1185
1186
        $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...
1187
        $piece = array();
1188
        foreach ($lp as $lc => $lv) {
1189
            if ($lc !== 0) {
1190
                $piece[] = $left;
1191
            }
1192
            if (strpos($lv, $right) === false) {
1193
                $piece[] = $lv;
1194
            } else {
1195
                $rp = explode($right, $lv);
1196
                foreach ($rp as $rc => $rv) {
1197
                    if ($rc !== 0) {
1198
                        $piece[] = $right;
1199
                    }
1200
                    $piece[] = $rv;
1201
                }
1202
            }
1203
        }
1204
        $lc = 0;
1205
        $rc = 0;
1206
        $fetch = '';
1207
        $tags = array();
1208
        foreach ($piece as $v) {
1209
            if ($v === $left) {
1210
                if (0 < $lc) {
1211
                    $fetch .= $left;
1212
                }
1213
                $lc++;
1214
            } elseif ($v === $right) {
1215
                if ($lc === 0) {
1216
                    continue;
1217
                }
1218
                $rc++;
1219
                if ($lc === $rc) {
1220
                    // #1200 Enable modifiers in Wayfinder - add nested placeholders to $tags like for $fetch = "phx:input=`[+wf.linktext+]`:test"
1221
                    if (strpos($fetch, $left) !== false) {
1222
                        $nested = $this->_getTagsFromContent($fetch, $left, $right);
1223
                        foreach ($nested as $tag) {
1224
                            if (!in_array($tag, $tags)) {
1225
                                $tags[] = $tag;
1226
                            }
1227
                        }
1228
                    }
1229
1230
                    if (!in_array($fetch, $tags)) {  // Avoid double Matches
1231
                        $tags[] = $fetch; // Fetch
1232
                    };
1233
                    $fetch = ''; // and reset
1234
                    $lc = 0;
1235
                    $rc = 0;
1236
                } else {
1237
                    $fetch .= $right;
1238
                }
1239
            } else {
1240
                if (0 < $lc) {
1241
                    $fetch .= $v;
1242
                } else {
1243
                    continue;
1244
                }
1245
            }
1246
        }
1247
        foreach($tags as $i=>$tag) {
1248
            if(strpos($tag,$spacer)!==false) $tags[$i] = str_replace($spacer, '', $tag);
1249
        }
1250
        return $tags;
1251
    }
1252
1253
    /**
1254
     * Merge content fields and TVs
1255
     *
1256
     * @param $content
1257
     * @param bool $ph
1258
     * @return string
1259
     * @internal param string $template
1260
     */
1261
    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...
1262
    {
1263 View Code Duplication
        if ($this->config['enable_at_syntax']) {
1264
            if (stripos($content, '<@LITERAL>') !== false) {
1265
                $content = $this->escapeLiteralTagsContent($content);
1266
            }
1267
        }
1268
        if (strpos($content, '[*') === false) {
1269
            return $content;
1270
        }
1271
        if (!isset($this->documentIdentifier)) {
1272
            return $content;
1273
        }
1274
        if (!isset($this->documentObject) || empty($this->documentObject)) {
1275
            return $content;
1276
        }
1277
1278
        if (!$ph) {
1279
            $ph = $this->documentObject;
1280
        }
1281
1282
        $matches = $this->getTagsFromContent($content, '[*', '*]');
1283
        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...
1284
            return $content;
1285
        }
1286
1287
        foreach ($matches[1] as $i => $key) {
1288
            if(strpos($key,'[+')!==false) continue; // Allow chunk {{chunk?&param=`xxx`}} with [*tv_name_[+param+]*] as content
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1289
            if (substr($key, 0, 1) == '#') {
1290
                $key = substr($key, 1);
1291
            } // remove # for QuickEdit format
1292
1293
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1294
            if (strpos($key, '@') !== false) {
1295
                list($key, $context) = explode('@', $key, 2);
1296
            } else {
1297
                $context = false;
1298
            }
1299
1300
            // 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...
1301
            if ($context) {
1302
                $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...
1303
            } else {
1304
                $value = isset($ph[$key]) ? $ph[$key] : '';
1305
            }
1306
1307
            if (is_array($value)) {
1308
                include_once(MODX_MANAGER_PATH . 'includes/tmplvars.format.inc.php');
1309
                include_once(MODX_MANAGER_PATH . 'includes/tmplvars.commands.inc.php');
1310
                $value = getTVDisplayFormat($value[0], $value[1], $value[2], $value[3], $value[4]);
1311
            }
1312
1313
            $s = &$matches[0][$i];
1314
            if ($modifiers !== false) {
1315
                $value = $this->applyFilter($value, $modifiers, $key);
1316
            }
1317
1318 View Code Duplication
            if (strpos($content, $s) !== false) {
1319
                $content = str_replace($s, $value, $content);
1320
            } elseif($this->debug) {
1321
                $this->addLog('mergeDocumentContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1322
            }
1323
        }
1324
1325
        return $content;
1326
    }
1327
1328
    /**
1329
     * @param $key
1330
     * @param bool|int $parent
1331
     * @return bool|mixed|string
1332
     */
1333
    public function _contextValue($key, $parent = false)
1334
    {
1335
        if (preg_match('/@\d+\/u/', $key)) {
1336
            $key = str_replace(array('@', '/u'), array('@u(', ')'), $key);
1337
        }
1338
        list($key, $str) = explode('@', $key, 2);
1339
1340
        if (strpos($str, '(')) {
1341
            list($context, $option) = explode('(', $str, 2);
1342
        } else {
1343
            list($context, $option) = array($str, false);
1344
        }
1345
1346
        if ($option) {
1347
            $option = trim($option, ')(\'"`');
1348
        }
1349
1350
        switch (strtolower($context)) {
1351
            case 'site_start':
1352
                $docid = $this->config['site_start'];
1353
                break;
1354
            case 'parent':
1355
            case 'p':
1356
                $docid = $parent;
1357
                if ($docid == 0) {
1358
                    $docid = $this->config['site_start'];
1359
                }
1360
                break;
1361
            case 'ultimateparent':
1362
            case 'uparent':
1363
            case 'up':
1364
            case 'u':
1365 View Code Duplication
                if (strpos($str, '(') !== false) {
1366
                    $top = substr($str, strpos($str, '('));
1367
                    $top = trim($top, '()"\'');
1368
                } else {
1369
                    $top = 0;
1370
                }
1371
                $docid = $this->getUltimateParentId($this->documentIdentifier, $top);
1372
                break;
1373
            case 'alias':
1374
                $str = substr($str, strpos($str, '('));
1375
                $str = trim($str, '()"\'');
1376
                $docid = $this->getIdFromAlias($str);
1377
                break;
1378 View Code Duplication
            case 'prev':
1379
                if (!$option) {
1380
                    $option = 'menuindex,ASC';
1381
                } elseif (strpos($option, ',') === false) {
1382
                    $option .= ',ASC';
1383
                }
1384
                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...
1385
                $children = $this->getActiveChildren($parent, $by, $dir);
1386
                $find = false;
1387
                $prev = false;
1388
                foreach ($children as $row) {
1389
                    if ($row['id'] == $this->documentIdentifier) {
1390
                        $find = true;
1391
                        break;
1392
                    }
1393
                    $prev = $row;
1394
                }
1395
                if ($find) {
1396
                    if (isset($prev[$key])) {
1397
                        return $prev[$key];
1398
                    } else {
1399
                        $docid = $prev['id'];
1400
                    }
1401
                } else {
1402
                    $docid = '';
1403
                }
1404
                break;
1405 View Code Duplication
            case 'next':
1406
                if (!$option) {
1407
                    $option = 'menuindex,ASC';
1408
                } elseif (strpos($option, ',') === false) {
1409
                    $option .= ',ASC';
1410
                }
1411
                list($by, $dir) = explode(',', $option, 2);
1412
                $children = $this->getActiveChildren($parent, $by, $dir);
1413
                $find = false;
1414
                $next = false;
1415
                foreach ($children as $row) {
1416
                    if ($find) {
1417
                        $next = $row;
1418
                        break;
1419
                    }
1420
                    if ($row['id'] == $this->documentIdentifier) {
1421
                        $find = true;
1422
                    }
1423
                }
1424
                if ($find) {
1425
                    if (isset($next[$key])) {
1426
                        return $next[$key];
1427
                    } else {
1428
                        $docid = $next['id'];
1429
                    }
1430
                } else {
1431
                    $docid = '';
1432
                }
1433
                break;
1434
            default:
1435
                $docid = $str;
1436
        }
1437
        if (preg_match('@^[1-9][0-9]*$@', $docid)) {
1438
            $value = $this->getField($key, $docid);
1439
        } else {
1440
            $value = '';
1441
        }
1442
        return $value;
1443
    }
1444
1445
    /**
1446
     * Merge system settings
1447
     *
1448
     * @param $content
1449
     * @param bool|array $ph
1450
     * @return string
1451
     * @internal param string $template
1452
     */
1453
    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...
1454
    {
1455 View Code Duplication
        if ($this->config['enable_at_syntax']) {
1456
            if (stripos($content, '<@LITERAL>') !== false) {
1457
                $content = $this->escapeLiteralTagsContent($content);
1458
            }
1459
        }
1460
        if (strpos($content, '[(') === false) {
1461
            return $content;
1462
        }
1463
1464
        if (empty($ph)) {
1465
            $ph = $this->config;
1466
        }
1467
1468
        $matches = $this->getTagsFromContent($content, '[(', ')]');
1469
        if (empty($matches)) {
1470
            return $content;
1471
        }
1472
1473
        foreach ($matches[1] as $i => $key) {
1474
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1475
1476
            if (isset($ph[$key])) {
1477
                $value = $ph[$key];
1478
            } else {
1479
                continue;
1480
            }
1481
1482
            if ($modifiers !== false) {
1483
                $value = $this->applyFilter($value, $modifiers, $key);
1484
            }
1485
            $s = &$matches[0][$i];
1486 View Code Duplication
            if (strpos($content, $s) !== false) {
1487
                $content = str_replace($s, $value, $content);
1488
            } elseif($this->debug) {
1489
                $this->addLog('mergeSettingsContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1490
            }
1491
        }
1492
        return $content;
1493
    }
1494
1495
    /**
1496
     * Merge chunks
1497
     *
1498
     * @param string $content
1499
     * @param bool|array $ph
1500
     * @return string
1501
     */
1502
    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...
1503
    {
1504
        if ($this->config['enable_at_syntax']) {
1505
            if (strpos($content, '{{ ') !== false) {
1506
                $content = str_replace(array('{{ ', ' }}'), array('\{\{ ', ' \}\}'), $content);
1507
            }
1508
            if (stripos($content, '<@LITERAL>') !== false) {
1509
                $content = $this->escapeLiteralTagsContent($content);
1510
            }
1511
        }
1512
        if (strpos($content, '{{') === false) {
1513
            return $content;
1514
        }
1515
1516
        if (empty($ph)) {
1517
            $ph = $this->chunkCache;
1518
        }
1519
1520
        $matches = $this->getTagsFromContent($content, '{{', '}}');
1521
        if (empty($matches)) {
1522
            return $content;
1523
        }
1524
1525
        foreach ($matches[1] as $i => $key) {
1526
            $snip_call = $this->_split_snip_call($key);
1527
            $key = $snip_call['name'];
1528
            $params = $this->getParamsFromString($snip_call['params']);
1529
1530
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1531
1532
            if (!isset($ph[$key])) {
1533
                $ph[$key] = $this->getChunk($key);
1534
            }
1535
            $value = $ph[$key];
1536
1537
            if (is_null($value)) {
1538
                continue;
1539
            }
1540
1541
            $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 1528 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...
1542
            $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 1528 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...
1543
            if ($this->config['enable_at_syntax']) {
1544
                $value = $this->mergeConditionalTagsContent($value);
1545
            }
1546
            $value = $this->mergeDocumentContent($value);
1547
            $value = $this->mergeSettingsContent($value);
1548
            $value = $this->mergeChunkContent($value);
1549
1550
            if ($modifiers !== false) {
1551
                $value = $this->applyFilter($value, $modifiers, $key);
1552
            }
1553
1554
            $s = &$matches[0][$i];
1555 View Code Duplication
            if (strpos($content, $s) !== false) {
1556
                $content = str_replace($s, $value, $content);
1557
            } elseif($this->debug) {
1558
                $this->addLog('mergeChunkContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1559
            }
1560
        }
1561
        return $content;
1562
    }
1563
1564
    /**
1565
     * Merge placeholder values
1566
     *
1567
     * @param string $content
1568
     * @param bool|array $ph
1569
     * @return string
1570
     */
1571
    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...
1572
    {
1573
1574 View Code Duplication
        if ($this->config['enable_at_syntax']) {
1575
            if (stripos($content, '<@LITERAL>') !== false) {
1576
                $content = $this->escapeLiteralTagsContent($content);
1577
            }
1578
        }
1579
        if (strpos($content, '[+') === false) {
1580
            return $content;
1581
        }
1582
1583
        if (empty($ph)) {
1584
            $ph = $this->placeholders;
1585
        }
1586
1587
        if ($this->config['enable_at_syntax']) {
1588
            $content = $this->mergeConditionalTagsContent($content);
1589
        }
1590
1591
        $content = $this->mergeDocumentContent($content);
1592
        $content = $this->mergeSettingsContent($content);
1593
        $matches = $this->getTagsFromContent($content, '[+', '+]');
1594
        if (empty($matches)) {
1595
            return $content;
1596
        }
1597
        foreach ($matches[1] as $i => $key) {
1598
1599
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1600
1601
            if (isset($ph[$key])) {
1602
                $value = $ph[$key];
1603
            } elseif ($key === 'phx') {
1604
                $value = '';
1605
            } else {
1606
                continue;
1607
            }
1608
1609
            if ($modifiers !== false) {
1610
                $modifiers = $this->mergePlaceholderContent($modifiers);
1611
                $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...
1612
            }
1613
            $s = &$matches[0][$i];
1614 View Code Duplication
            if (strpos($content, $s) !== false) {
1615
                $content = str_replace($s, $value, $content);
1616
            } elseif($this->debug) {
1617
                $this->addLog('mergePlaceholderContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1618
            }
1619
        }
1620
        return $content;
1621
    }
1622
1623
    /**
1624
     * @param $content
1625
     * @param string $iftag
1626
     * @param string $elseiftag
1627
     * @param string $elsetag
1628
     * @param string $endiftag
1629
     * @return mixed|string
1630
     */
1631
    public function mergeConditionalTagsContent($content, $iftag = '<@IF:', $elseiftag = '<@ELSEIF:', $elsetag = '<@ELSE>', $endiftag = '<@ENDIF>')
0 ignored issues
show
Coding Style introduced by
mergeConditionalTagsContent uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1632
    {
1633
        if (strpos($content, '@IF') !== false) {
1634
            $content = $this->_prepareCTag($content, $iftag, $elseiftag, $elsetag, $endiftag);
1635
        }
1636
1637
        if (strpos($content, $iftag) === false) {
1638
            return $content;
1639
        }
1640
1641
        $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...
1642
        $content = str_replace(array('<?php', '<?=', '<?', '?>'), array("{$sp}b", "{$sp}p", "{$sp}s", "{$sp}e"), $content);
1643
1644
        $pieces = explode('<@IF:', $content);
1645 View Code Duplication
        foreach ($pieces as $i => $split) {
1646
            if ($i === 0) {
1647
                $content = $split;
1648
                continue;
1649
            }
1650
            list($cmd, $text) = explode('>', $split, 2);
1651
            $cmd = str_replace("'", "\'", $cmd);
1652
            $content .= "<?php if(\$this->_parseCTagCMD('" . $cmd . "')): ?>";
1653
            $content .= $text;
1654
        }
1655
        $pieces = explode('<@ELSEIF:', $content);
1656 View Code Duplication
        foreach ($pieces as $i => $split) {
1657
            if ($i === 0) {
1658
                $content = $split;
1659
                continue;
1660
            }
1661
            list($cmd, $text) = explode('>', $split, 2);
1662
            $cmd = str_replace("'", "\'", $cmd);
1663
            $content .= "<?php elseif(\$this->_parseCTagCMD('" . $cmd . "')): ?>";
1664
            $content .= $text;
1665
        }
1666
1667
        $content = str_replace(array('<@ELSE>', '<@ENDIF>'), array('<?php else:?>', '<?php endif;?>'), $content);
1668
        ob_start();
1669
        $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...
1670
        $content = ob_get_clean();
1671
        $content = str_replace(array("{$sp}b", "{$sp}p", "{$sp}s", "{$sp}e"), array('<?php', '<?=', '<?', '?>'), $content);
1672
1673
        return $content;
1674
    }
1675
1676
    /**
1677
     * @param $content
1678
     * @param string $iftag
1679
     * @param string $elseiftag
1680
     * @param string $elsetag
1681
     * @param string $endiftag
1682
     * @return mixed
1683
     */
1684
    private function _prepareCTag($content, $iftag = '<@IF:', $elseiftag = '<@ELSEIF:', $elsetag = '<@ELSE>', $endiftag = '<@ENDIF>')
1685
    {
1686
        if (strpos($content, '<!--@IF ') !== false) {
1687
            $content = str_replace('<!--@IF ', $iftag, $content);
1688
        } // for jp
1689
        if (strpos($content, '<!--@IF:') !== false) {
1690
            $content = str_replace('<!--@IF:', $iftag, $content);
1691
        }
1692
        if (strpos($content, $iftag) === false) {
1693
            return $content;
1694
        }
1695
        if (strpos($content, '<!--@ELSEIF:') !== false) {
1696
            $content = str_replace('<!--@ELSEIF:', $elseiftag, $content);
1697
        } // for jp
1698
        if (strpos($content, '<!--@ELSE-->') !== false) {
1699
            $content = str_replace('<!--@ELSE-->', $elsetag, $content);
1700
        }  // for jp
1701
        if (strpos($content, '<!--@ENDIF-->') !== false) {
1702
            $content = str_replace('<!--@ENDIF-->', $endiftag, $content);
1703
        }    // for jp
1704
        if (strpos($content, '<@ENDIF-->') !== false) {
1705
            $content = str_replace('<@ENDIF-->', $endiftag, $content);
1706
        }
1707
        $tags = array($iftag, $elseiftag, $elsetag, $endiftag);
1708
        $content = str_ireplace($tags, $tags, $content); // Change to capital letters
1709
        return $content;
1710
    }
1711
1712
    /**
1713
     * @param $cmd
1714
     * @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...
1715
     */
1716
    private function _parseCTagCMD($cmd)
1717
    {
1718
        $cmd = trim($cmd);
1719
        $reverse = substr($cmd, 0, 1) === '!' ? true : false;
1720
        if ($reverse) {
1721
            $cmd = ltrim($cmd, '!');
1722
        }
1723
        if (strpos($cmd, '[!') !== false) {
1724
            $cmd = str_replace(array('[!', '!]'), array('[[', ']]'), $cmd);
1725
        }
1726
        $safe = 0;
1727
        while ($safe < 20) {
1728
            $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...
1729
            if (strpos($cmd, '[*') !== false) {
1730
                $cmd = $this->mergeDocumentContent($cmd);
1731
            }
1732
            if (strpos($cmd, '[(') !== false) {
1733
                $cmd = $this->mergeSettingsContent($cmd);
1734
            }
1735
            if (strpos($cmd, '{{') !== false) {
1736
                $cmd = $this->mergeChunkContent($cmd);
1737
            }
1738
            if (strpos($cmd, '[[') !== false) {
1739
                $cmd = $this->evalSnippets($cmd);
1740
            }
1741
            if (strpos($cmd, '[+') !== false && strpos($cmd, '[[') === false) {
1742
                $cmd = $this->mergePlaceholderContent($cmd);
1743
            }
1744
            if ($bt === md5($cmd)) {
1745
                break;
1746
            }
1747
            $safe++;
1748
        }
1749
        $cmd = ltrim($cmd);
1750
        $cmd = rtrim($cmd, '-');
1751
        $cmd = str_ireplace(array(' and ', ' or '), array('&&', '||'), $cmd);
1752
1753
        if (!preg_match('@^[0-9]*$@', $cmd) && preg_match('@^[0-9<= \-\+\*/\(\)%!&|]*$@', $cmd)) {
1754
            $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...
1755
        } else {
1756
            $_ = 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...
1757
            foreach ($_ as $left) {
1758
                if (strpos($cmd, $left) !== false) {
1759
                    $cmd = 0;
1760
                    break;
1761
                }
1762
            }
1763
        }
1764
        $cmd = trim($cmd);
1765
        if (!preg_match('@^[0-9]+$@', $cmd)) {
1766
            $cmd = empty($cmd) ? 0 : 1;
1767
        } elseif ($cmd <= 0) {
1768
            $cmd = 0;
1769
        }
1770
1771
        if ($reverse) {
1772
            $cmd = !$cmd;
1773
        }
1774
1775
        return $cmd;
1776
    }
1777
1778
    /**
1779
     * Remove Comment-Tags from output like <!--@- Comment -@-->
1780
     * @param $content
1781
     * @param string $left
1782
     * @param string $right
1783
     * @return mixed
1784
     */
1785
    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...
1786
    {
1787
        if (strpos($content, $left) === false) {
1788
            return $content;
1789
        }
1790
1791
        $matches = $this->getTagsFromContent($content, $left, $right);
1792
        if (!empty($matches)) {
1793
            foreach ($matches[0] as $i => $v) {
1794
                $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...
1795
            }
1796
            $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...
1797
            if (strpos($content, $left) !== false) {
1798
                $content = str_replace($matches[0], '', $content);
1799
            }
1800
        }
1801
        return $content;
1802
    }
1803
1804
    /**
1805
     * @param $content
1806
     * @param string $left
1807
     * @param string $right
1808
     * @return mixed
1809
     */
1810
    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...
1811
    {
1812
        if (stripos($content, $left) === false) {
1813
            return $content;
1814
        }
1815
1816
        $matches = $this->getTagsFromContent($content, $left, $right);
1817
        if (empty($matches)) {
1818
            return $content;
1819
        }
1820
1821
        list($sTags, $rTags) = $this->getTagsForEscape();
1822
        foreach ($matches[1] as $i => $v) {
1823
            $v = str_ireplace($sTags, $rTags, $v);
1824
            $s = &$matches[0][$i];
1825 View Code Duplication
            if (strpos($content, $s) !== false) {
1826
                $content = str_replace($s, $v, $content);
1827
            } elseif($this->debug) {
1828
                $this->addLog('ignoreCommentedTagsContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1829
            }
1830
        }
1831
        return $content;
1832
    }
1833
1834
    /**
1835
     * Detect PHP error according to MODX error level
1836
     *
1837
     * @param integer $error PHP error level
1838
     * @return boolean Error detected
1839
     */
1840
1841
    public function detectError($error)
1842
    {
1843
        $detected = false;
1844
        if ($this->config['error_reporting'] == 99 && $error) {
1845
            $detected = true;
1846
        } elseif ($this->config['error_reporting'] == 2 && ($error & ~E_NOTICE)) {
1847
            $detected = true;
1848
        } elseif ($this->config['error_reporting'] == 1 && ($error & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT)) {
1849
            $detected = true;
1850
        }
1851
        return $detected;
1852
    }
1853
1854
    /**
1855
     * Run a plugin
1856
     *
1857
     * @param string $pluginCode Code to run
1858
     * @param array $params
1859
     */
1860
    public function evalPlugin($pluginCode, $params)
1861
    {
1862
        $modx = &$this;
1863
        $modx->event->params = &$params; // store params inside event object
1864
        if (is_array($params)) {
1865
            extract($params, EXTR_SKIP);
1866
        }
1867
        /* 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...
1868
        // 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.
1869
        // Related to https://github.com/modxcms/evolution/issues/1130
1870
        $lock_file_path = MODX_BASE_PATH . 'assets/cache/lock_' . str_replace(' ','-',strtolower($this->event->activePlugin)) . '.pageCache.php';
1871
        if($this->isBackend()) {
1872
            if(is_file($lock_file_path)) {
1873
                $msg = sprintf("Plugin parse error, Temporarily disabled '%s'.", $this->event->activePlugin);
1874
                $this->logEvent(0, 3, $msg, $msg);
1875
                return;
1876
            }
1877
            elseif(stripos($this->event->activePlugin,'ElementsInTree')===false) touch($lock_file_path);
1878
        }*/
1879
        ob_start();
1880
        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...
1881
        $msg = ob_get_contents();
1882
        ob_end_clean();
1883
        // When reached here, no fatal error occured so the lock should be removed.
1884
        /*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...
1885
1886 View Code Duplication
        if ((0 < $this->config['error_reporting']) && $msg && isset($php_errormsg)) {
1887
            $error_info = error_get_last();
1888
            if ($this->detectError($error_info['type'])) {
1889
                $msg = ($msg === false) ? 'ob_get_contents() error' : $msg;
1890
                $this->messageQuit('PHP Parse Error', '', true, $error_info['type'], $error_info['file'], 'Plugin', $error_info['message'], $error_info['line'], $msg);
1891
                if ($this->isBackend()) {
1892
                    $this->event->alert('An error occurred while loading. Please see the event log for more information.<p>' . $msg . '</p>');
1893
                }
1894
            }
1895
        } else {
1896
            echo $msg;
1897
        }
1898
        unset($modx->event->params);
1899
    }
1900
1901
    /**
1902
     * Run a snippet
1903
     *
1904
     * @param $phpcode
1905
     * @param array $params
1906
     * @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...
1907
     * @internal param string $snippet Code to run
1908
     */
1909
    public function evalSnippet($phpcode, $params)
1910
    {
1911
        $modx = &$this;
1912
        /*
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...
1913
        if(isset($params) && is_array($params)) {
1914
            foreach($params as $k=>$v) {
1915
                $v = strtolower($v);
1916
                if($v==='false')    $params[$k] = false;
1917
                elseif($v==='true') $params[$k] = true;
1918
            }
1919
        }*/
1920
        $modx->event->params = &$params; // store params inside event object
1921
        if (is_array($params)) {
1922
            extract($params, EXTR_SKIP);
1923
        }
1924
        ob_start();
1925
        if (strpos($phpcode, ';') !== false) {
1926
            $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...
1927
        } else {
1928
            $return = call_user_func_array($phpcode, array($params));
1929
        }
1930
        $echo = ob_get_contents();
1931
        ob_end_clean();
1932 View Code Duplication
        if ((0 < $this->config['error_reporting']) && isset($php_errormsg)) {
1933
            $error_info = error_get_last();
1934
            if ($this->detectError($error_info['type'])) {
1935
                $echo = ($echo === false) ? 'ob_get_contents() error' : $echo;
1936
                $this->messageQuit('PHP Parse Error', '', true, $error_info['type'], $error_info['file'], 'Snippet', $error_info['message'], $error_info['line'], $echo);
1937
                if ($this->isBackend()) {
1938
                    $this->event->alert('An error occurred while loading. Please see the event log for more information<p>' . $echo . $return . '</p>');
1939
                }
1940
            }
1941
        }
1942
        unset($modx->event->params);
1943
        if (is_array($return) || is_object($return)) {
1944
            return $return;
1945
        } else {
1946
            return $echo . $return;
1947
        }
1948
    }
1949
1950
    /**
1951
     * Run snippets as per the tags in $documentSource and replace the tags with the returned values.
1952
     *
1953
     * @param $content
1954
     * @return string
1955
     * @internal param string $documentSource
1956
     */
1957
    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...
1958
    {
1959
        if (strpos($content, '[[') === false) {
1960
            return $content;
1961
        }
1962
1963
        $matches = $this->getTagsFromContent($content, '[[', ']]');
1964
1965
        if (empty($matches)) {
1966
            return $content;
1967
        }
1968
1969
        $this->snipLapCount++;
1970
        if ($this->dumpSnippets) {
1971
            $this->snippetsCode .= sprintf('<fieldset><legend><b style="color: #821517;">PARSE PASS %s</b></legend><p>The following snippets (if any) were parsed during this pass.</p>', $this->snipLapCount);
1972
        }
1973
1974
        foreach ($matches[1] as $i => $call) {
1975
            $s = &$matches[0][$i];
1976
            if (substr($call, 0, 2) === '$_') {
1977
                if (strpos($content, '_PHX_INTERNAL_') === false) {
1978
                    $value = $this->_getSGVar($call);
1979
                } else {
1980
                    $value = $s;
1981
                }
1982 View Code Duplication
                if (strpos($content, $s) !== false) {
1983
                    $content = str_replace($s, $value, $content);
1984
                } elseif($this->debug) {
1985
                    $this->addLog('evalSnippetsSGVar parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1986
                }
1987
                continue;
1988
            }
1989
            $value = $this->_get_snip_result($call);
1990
            if (is_null($value)) {
1991
                continue;
1992
            }
1993
1994 View Code Duplication
            if (strpos($content, $s) !== false) {
1995
                $content = str_replace($s, $value, $content);
1996
            } elseif($this->debug) {
1997
                $this->addLog('evalSnippets parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1998
            }
1999
        }
2000
2001
        if ($this->dumpSnippets) {
2002
            $this->snippetsCode .= '</fieldset><br />';
2003
        }
2004
2005
        return $content;
2006
    }
2007
2008
    /**
2009
     * @param $value
2010
     * @return mixed|string
2011
     */
2012
    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...
2013
    { // Get super globals
2014
        $key = $value;
2015
        $_ = $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...
2016
        $this->config['enable_filter'] = 1;
2017
        list($key, $modifiers) = $this->splitKeyAndFilter($key);
2018
        $this->config['enable_filter'] = $_;
2019
        $key = str_replace(array('(', ')'), array("['", "']"), $key);
2020
        $key = rtrim($key, ';');
2021
        if (strpos($key, '$_SESSION') !== false) {
2022
            $_ = $_SESSION;
2023
            $key = str_replace('$_SESSION', '$_', $key);
2024
            if (isset($_['mgrFormValues'])) {
2025
                unset($_['mgrFormValues']);
2026
            }
2027
            if (isset($_['token'])) {
2028
                unset($_['token']);
2029
            }
2030
        }
2031
        if (strpos($key, '[') !== false) {
2032
            $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...
2033
        } 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...
2034
            $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...
2035
        } else {
2036
            $value = '';
2037
        }
2038
        if ($modifiers !== false) {
2039
            $value = $this->applyFilter($value, $modifiers, $key);
2040
        }
2041
        return $value;
2042
    }
2043
2044
    /**
2045
     * @param $piece
2046
     * @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...
2047
     */
2048
    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...
2049
    {
2050
        if (ltrim($piece) !== $piece) {
2051
            return '';
2052
        }
2053
2054
        $eventtime = $this->dumpSnippets ? $this->getMicroTime() : 0;
2055
2056
        $snip_call = $this->_split_snip_call($piece);
2057
        $key = $snip_call['name'];
2058
2059
        list($key, $modifiers) = $this->splitKeyAndFilter($key);
2060
        $snip_call['name'] = $key;
2061
        $snippetObject = $this->_getSnippetObject($key);
2062
        if (is_null($snippetObject['content'])) {
2063
            return null;
2064
        }
2065
2066
        $this->currentSnippet = $snippetObject['name'];
2067
2068
        // current params
2069
        $params = $this->getParamsFromString($snip_call['params']);
2070
2071
        if (!isset($snippetObject['properties'])) {
2072
            $snippetObject['properties'] = '';
2073
        }
2074
        $default_params = $this->parseProperties($snippetObject['properties'], $this->currentSnippet, 'snippet');
2075
        $params = array_merge($default_params, $params);
2076
2077
        $value = $this->evalSnippet($snippetObject['content'], $params);
2078
        $this->currentSnippet = '';
2079
        if ($modifiers !== false) {
2080
            $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...
2081
        }
2082
2083
        if ($this->dumpSnippets) {
2084
            $eventtime = $this->getMicroTime() - $eventtime;
2085
            $eventtime = sprintf('%2.2f ms', $eventtime * 1000);
2086
            $code = str_replace("\t", '  ', $this->htmlspecialchars($value));
2087
            $piece = str_replace("\t", '  ', $this->htmlspecialchars($piece));
2088
            $print_r_params = str_replace("\t", '  ', $this->htmlspecialchars('$modx->event->params = ' . print_r($params, true)));
2089
            $this->snippetsCode .= sprintf('<fieldset style="margin:1em;"><legend><b>%s</b>(%s)</legend><pre style="white-space: pre-wrap;background-color:#fff;width:90%%;">[[%s]]</pre><pre style="white-space: pre-wrap;background-color:#fff;width:90%%;">%s</pre><pre style="white-space: pre-wrap;background-color:#fff;width:90%%;">%s</pre></fieldset>', $snippetObject['name'], $eventtime, $piece, $print_r_params, $code);
2090
            $this->snippetsTime[] = array('sname' => $key, 'time' => $eventtime);
2091
        }
2092
        return $value;
2093
    }
2094
2095
    /**
2096
     * @param string $string
2097
     * @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...
2098
     */
2099
    public function getParamsFromString($string = '')
2100
    {
2101
        if (empty($string)) {
2102
            return array();
2103
        }
2104
2105
        if (strpos($string, '&_PHX_INTERNAL_') !== false) {
2106
            $string = str_replace(array('&_PHX_INTERNAL_091_&', '&_PHX_INTERNAL_093_&'), array('[', ']'), $string);
2107
        }
2108
2109
        $_ = $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...
2110
        $this->documentOutput = $string;
2111
        $this->invokeEvent('OnBeforeParseParams');
2112
        $string = $this->documentOutput;
2113
        $this->documentOutput = $_;
2114
2115
        $_tmp = $string;
2116
        $_tmp = ltrim($_tmp, '?&');
2117
        $temp_params = array();
2118
        $key = '';
2119
        $value = null;
2120
        while ($_tmp !== '') {
2121
            $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...
2122
            $char = substr($_tmp, 0, 1);
2123
            $_tmp = substr($_tmp, 1);
2124
2125
            if ($char === '=') {
2126
                $_tmp = trim($_tmp);
2127
                $delim = substr($_tmp, 0, 1);
2128
                if (in_array($delim, array('"', "'", '`'))) {
2129
                    $null = null;
2130
                    //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...
2131
                    list($null, $value, $_tmp) = explode($delim, $_tmp, 3);
2132
                    unset($null);
2133
2134
                    if (substr(trim($_tmp), 0, 2) === '//') {
2135
                        $_tmp = strstr(trim($_tmp), "\n");
2136
                    }
2137
                    $i = 0;
2138
                    while ($delim === '`' && substr(trim($_tmp), 0, 1) !== '&' && 1 < substr_count($_tmp, '`')) {
2139
                        list($inner, $outer, $_tmp) = explode('`', $_tmp, 3);
2140
                        $value .= "`{$inner}`{$outer}";
2141
                        $i++;
2142
                        if (100 < $i) {
2143
                            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...
2144
                        }
2145
                    }
2146
                    if ($i && $delim === '`') {
2147
                        $value = rtrim($value, '`');
2148
                    }
2149
                } elseif (strpos($_tmp, '&') !== false) {
2150
                    list($value, $_tmp) = explode('&', $_tmp, 2);
2151
                    $value = trim($value);
2152
                } else {
2153
                    $value = $_tmp;
2154
                    $_tmp = '';
2155
                }
2156
            } elseif ($char === '&') {
2157
                if (trim($key) !== '') {
2158
                    $value = '1';
2159
                } else {
2160
                    continue;
2161
                }
2162
            } elseif ($_tmp === '') {
2163
                $key .= $char;
2164
                $value = '1';
2165
            } elseif ($key !== '' || trim($char) !== '') {
2166
                $key .= $char;
2167
            }
2168
2169
            if (isset($value) && !is_null($value)) {
2170
                if (strpos($key, 'amp;') !== false) {
2171
                    $key = str_replace('amp;', '', $key);
2172
                }
2173
                $key = trim($key);
2174 View Code Duplication
                if (strpos($value, '[!') !== false) {
2175
                    $value = str_replace(array('[!', '!]'), array('[[', ']]'), $value);
2176
                }
2177
                $value = $this->mergeDocumentContent($value);
2178
                $value = $this->mergeSettingsContent($value);
2179
                $value = $this->mergeChunkContent($value);
2180
                $value = $this->evalSnippets($value);
2181
                if (substr($value, 0, 6) !== '@CODE:') {
2182
                    $value = $this->mergePlaceholderContent($value);
2183
                }
2184
2185
                $temp_params[][$key] = $value;
2186
2187
                $key = '';
2188
                $value = null;
2189
2190
                $_tmp = ltrim($_tmp, " ,\t");
2191
                if (substr($_tmp, 0, 2) === '//') {
2192
                    $_tmp = strstr($_tmp, "\n");
2193
                }
2194
            }
2195
2196
            if ($_tmp === $bt) {
2197
                $key = trim($key);
2198
                if ($key !== '') {
2199
                    $temp_params[][$key] = '';
2200
                }
2201
                break;
2202
            }
2203
        }
2204
2205
        foreach ($temp_params as $p) {
2206
            $k = key($p);
2207
            if (substr($k, -2) === '[]') {
2208
                $k = substr($k, 0, -2);
2209
                $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...
2210
            } elseif (strpos($k, '[') !== false && substr($k, -1) === ']') {
2211
                list($k, $subk) = explode('[', $k, 2);
2212
                $subk = substr($subk, 0, -1);
2213
                $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...
2214
            } else {
2215
                $params[$k] = current($p);
2216
            }
2217
        }
2218
        return $params;
2219
    }
2220
2221
    /**
2222
     * @param $str
2223
     * @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...
2224
     */
2225
    public function _getSplitPosition($str)
2226
    {
2227
        $closeOpt = false;
2228
        $maybePos = false;
2229
        $inFilter = false;
2230
        $total = strlen($str);
2231
        for ($i = 0; $i < $total; $i++) {
2232
            $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...
2233
            $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...
2234
            if (!$inFilter) {
2235
                if ($c === ':') {
2236
                    $inFilter = true;
2237
                } elseif ($c === '?') {
2238
                    $pos = $i;
2239
                } elseif ($c === ' ') {
2240
                    $maybePos = $i;
2241
                } 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...
2242
                    $pos = $maybePos;
2243
                } elseif ($c === "\n") {
2244
                    $pos = $i;
2245
                } else {
2246
                    $pos = false;
2247
                }
2248
            } else {
2249
                if ($cc == $closeOpt) {
2250
                    $closeOpt = false;
2251
                } elseif ($c == $closeOpt) {
2252
                    $closeOpt = false;
2253
                } 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...
2254
                    continue;
2255
                } elseif ($cc === "('") {
2256
                    $closeOpt = "')";
2257
                } elseif ($cc === '("') {
2258
                    $closeOpt = '")';
2259
                } elseif ($cc === '(`') {
2260
                    $closeOpt = '`)';
2261
                } elseif ($c === '(') {
2262
                    $closeOpt = ')';
2263
                } elseif ($c === '?') {
2264
                    $pos = $i;
2265
                } elseif ($c === ' ' && strpos($str, '?') === false) {
2266
                    $pos = $i;
2267
                } else {
2268
                    $pos = false;
2269
                }
2270
            }
2271
            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...
2272
                break;
2273
            }
2274
        }
2275
        return $pos;
2276
    }
2277
2278
    /**
2279
     * @param $call
2280
     * @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...
2281
     */
2282
    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...
2283
    {
2284
        $spacer = md5('dummy');
2285 View Code Duplication
        if (strpos($call, ']]>') !== false) {
2286
            $call = str_replace(']]>', "]{$spacer}]>", $call);
2287
        }
2288
2289
        $splitPosition = $this->_getSplitPosition($call);
2290
2291
        if ($splitPosition !== false) {
2292
            $name = substr($call, 0, $splitPosition);
2293
            $params = substr($call, $splitPosition + 1);
2294
        } else {
2295
            $name = $call;
2296
            $params = '';
2297
        }
2298
2299
        $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...
2300 View Code Duplication
        if (strpos($params, $spacer) !== false) {
2301
            $params = str_replace("]{$spacer}]>", ']]>', $params);
2302
        }
2303
        $snip['params'] = ltrim($params, "?& \t\n");
2304
2305
        return $snip;
2306
    }
2307
2308
    /**
2309
     * @param $snip_name
2310
     * @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...
2311
     */
2312
    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...
2313
    {
2314
        if (isset($this->snippetCache[$snip_name])) {
2315
            $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...
2316
            $snippetObject['content'] = $this->snippetCache[$snip_name];
2317
            if (isset($this->snippetCache["{$snip_name}Props"])) {
2318
                if (!isset($this->snippetCache["{$snip_name}Props"])) {
2319
                    $this->snippetCache["{$snip_name}Props"] = '';
2320
                }
2321
                $snippetObject['properties'] = $this->snippetCache["{$snip_name}Props"];
2322
            }
2323
        } elseif (substr($snip_name, 0, 1) === '@' && isset($this->pluginEvent[trim($snip_name, '@')])) {
2324
            $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...
2325
            $snippetObject['content'] = sprintf('$rs=$this->invokeEvent("%s",$params);echo trim(implode("",$rs));', trim($snip_name, '@'));
2326
            $snippetObject['properties'] = '';
2327
        } else {
2328
            $where = sprintf("name='%s' AND disabled=0", $this->db->escape($snip_name));
2329
            $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...
2330
            $count = $this->db->getRecordCount($rs);
2331
            if (1 < $count) {
2332
                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...
2333
            }
2334
            if ($count) {
2335
                $row = $this->db->getRow($rs);
2336
                $snip_content = $row['snippet'];
2337
                $snip_prop = $row['properties'];
2338
            } else {
2339
                $snip_content = null;
2340
                $snip_prop = '';
2341
            }
2342
            $snippetObject['name'] = $snip_name;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$snippetObject was never initialized. Although not strictly required by PHP, it is generally a good practice to add $snippetObject = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
2343
            $snippetObject['content'] = $snip_content;
2344
            $snippetObject['properties'] = $snip_prop;
2345
            $this->snippetCache[$snip_name] = $snip_content;
2346
            $this->snippetCache["{$snip_name}Props"] = $snip_prop;
2347
        }
2348
        return $snippetObject;
2349
    }
2350
2351
    /**
2352
     * @param $text
2353
     * @return mixed
2354
     */
2355
    public function toAlias($text)
2356
    {
2357
        $suff = $this->config['friendly_url_suffix'];
2358
        return str_replace(array('.xml' . $suff, '.rss' . $suff, '.js' . $suff, '.css' . $suff, '.txt' . $suff, '.json' . $suff, '.pdf' . $suff), array('.xml', '.rss', '.js', '.css', '.txt', '.json', '.pdf'), $text);
2359
    }
2360
2361
    /**
2362
     * makeFriendlyURL
2363
     *
2364
     * @desc Create an URL.
2365
     *
2366
     * @param $pre {string} - Friendly URL Prefix. @required
2367
     * @param $suff {string} - Friendly URL Suffix. @required
2368
     * @param $alias {string} - Full document path. @required
2369
     * @param int $isfolder {0; 1}
2370
     * - Is it a folder? Default: 0.
2371
     * @param int $id {integer}
2372
     * - Document id. Default: 0.
2373
     * @return mixed|string {string} - Result URL.
2374
     * - Result URL.
2375
     */
2376
    public function makeFriendlyURL($pre, $suff, $alias, $isfolder = 0, $id = 0)
2377
    {
2378
        if ($id == $this->config['site_start'] && $this->config['seostrict'] === '1') {
2379
            $url = $this->config['base_url'];
2380
        } else {
2381
            $Alias = explode('/', $alias);
2382
            $alias = array_pop($Alias);
2383
            $dir = implode('/', $Alias);
2384
            unset($Alias);
2385
2386
            if ($this->config['make_folders'] === '1' && $isfolder == 1) {
2387
                $suff = '/';
2388
            }
2389
2390
            $url = ($dir != '' ? $dir . '/' : '') . $pre . $alias . $suff;
2391
        }
2392
2393
        $evtOut = $this->invokeEvent('OnMakeDocUrl', array(
2394
            'id' => $id,
2395
            'url' => $url
2396
        ));
2397
2398
        if (is_array($evtOut) && count($evtOut) > 0) {
2399
            $url = array_pop($evtOut);
2400
        }
2401
2402
        return $url;
2403
    }
2404
2405
    /**
2406
     * Convert URL tags [~...~] to URLs
2407
     *
2408
     * @param string $documentSource
2409
     * @return string
2410
     */
2411
    public function rewriteUrls($documentSource)
2412
    {
2413
        // rewrite the urls
2414
        if ($this->config['friendly_urls'] == 1) {
2415
            $aliases = array();
2416
            if (is_array($this->documentListing)) {
2417
                foreach ($this->documentListing as $path => $docid) { // This is big Loop on large site!
2418
                    $aliases[$docid] = $path;
2419
                    $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...
2420
                }
2421
            }
2422
2423
            if ($this->config['aliaslistingfolder'] == 1) {
2424
                preg_match_all('!\[\~([0-9]+)\~\]!ise', $documentSource, $match);
2425
                $ids = implode(',', array_unique($match['1']));
2426
                if ($ids) {
2427
                    $res = $this->db->select("id,alias,isfolder,parent,alias_visible", $this->getFullTableName('site_content'), "id IN (" . $ids . ") AND isfolder = '0'");
2428
                    while ($row = $this->db->getRow($res)) {
2429
                        if ($this->config['use_alias_path'] == '1' && $row['parent'] != 0) {
2430
                            $parent = $row['parent'];
2431
                            $path = $aliases[$parent];
2432
2433
                            while (isset($this->aliasListing[$parent]) && $this->aliasListing[$parent]['alias_visible'] == 0) {
2434
                                $path = $this->aliasListing[$parent]['path'];
2435
                                $parent = $this->aliasListing[$parent]['parent'];
2436
                            }
2437
2438
                            $aliases[$row['id']] = $path . '/' . $row['alias'];
2439
                        } else {
2440
                            $aliases[$row['id']] = $row['alias'];
2441
                        }
2442
                        $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...
2443
                    }
2444
                }
2445
            }
2446
            $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...
2447
            $isfriendly = ($this->config['friendly_alias_urls'] == 1 ? 1 : 0);
2448
            $pref = $this->config['friendly_url_prefix'];
2449
            $suff = $this->config['friendly_url_suffix'];
2450
            $documentSource = preg_replace_callback($in, function ($m) use ($aliases, $isfolder, $isfriendly, $pref, $suff) {
0 ignored issues
show
Bug introduced by
The variable $isfolder does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Comprehensibility introduced by
Avoid variables with short names like $m. Configured minimum length is 3.

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

Loading history...
2451
                global $modx;
2452
                $thealias = $aliases[$m[1]];
2453
                $thefolder = $isfolder[$m[1]];
2454
                if ($isfriendly && isset($thealias)) {
2455
                    //found friendly url
2456
                    $out = ($modx->config['seostrict'] == '1' ? $modx->toAlias($modx->makeFriendlyURL($pref, $suff, $thealias, $thefolder, $m[1])) : $modx->makeFriendlyURL($pref, $suff, $thealias, $thefolder, $m[1]));
2457
                } else {
2458
                    //not found friendly url
2459
                    $out = $modx->makeFriendlyURL($pref, $suff, $m[1]);
2460
                }
2461
                return $out;
2462
            }, $documentSource);
2463
2464
        } else {
2465
            $in = '!\[\~([0-9]+)\~\]!is';
2466
            $out = "index.php?id=" . '\1';
2467
            $documentSource = preg_replace($in, $out, $documentSource);
2468
        }
2469
2470
        return $documentSource;
2471
    }
2472
2473
    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...
2474
    {
2475
        $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...
2476
        // FIX URLs
2477
        if (empty($this->documentIdentifier) || $this->config['seostrict'] == '0' || $this->config['friendly_urls'] == '0') {
2478
            return;
2479
        }
2480
        if ($this->config['site_status'] == 0) {
2481
            return;
2482
        }
2483
2484
        $scheme = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https' : 'http';
2485
        $len_base_url = strlen($this->config['base_url']);
2486
2487
        $url_path = $q;//LANG
2488
2489 View Code Duplication
        if (substr($url_path, 0, $len_base_url) === $this->config['base_url']) {
2490
            $url_path = substr($url_path, $len_base_url);
2491
        }
2492
2493
        $strictURL = $this->toAlias($this->makeUrl($this->documentIdentifier));
2494
2495 View Code Duplication
        if (substr($strictURL, 0, $len_base_url) === $this->config['base_url']) {
2496
            $strictURL = substr($strictURL, $len_base_url);
2497
        }
2498
        $http_host = $_SERVER['HTTP_HOST'];
2499
        $requestedURL = "{$scheme}://{$http_host}" . '/' . $q; //LANG
2500
2501
        $site_url = $this->config['site_url'];
2502
2503
        if ($this->documentIdentifier == $this->config['site_start']) {
2504
            if ($requestedURL != $this->config['site_url']) {
2505
                // Force redirect of site start
2506
                // $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...
2507
                $qstring = isset($url_query_string) ? preg_replace("#(^|&)(q|id)=[^&]+#", '', $url_query_string) : ''; // Strip conflicting id/q from query string
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...
2508
                if ($qstring) {
2509
                    $url = "{$site_url}?{$qstring}";
2510
                } else {
2511
                    $url = $site_url;
2512
                }
2513
                if ($this->config['base_url'] != $_SERVER['REQUEST_URI']) {
2514
                    if (empty($_POST)) {
2515
                        if (($this->config['base_url'] . '?' . $qstring) != $_SERVER['REQUEST_URI']) {
2516
                            $this->sendRedirect($url, 0, 'REDIRECT_HEADER', 'HTTP/1.0 301 Moved Permanently');
2517
                            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...
2518
                        }
2519
                    }
2520
                }
2521
            }
2522
        } elseif ($url_path != $strictURL && $this->documentIdentifier != $this->config['error_page']) {
2523
            // Force page redirect
2524
            //$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...
2525
2526
            if (!empty($url_query_string)) {
2527
                $qstring = preg_replace("#(^|&)(q|id)=[^&]+#", '', $url_query_string);
2528
            }  // Strip conflicting id/q from query string
2529
            if (!empty($qstring)) {
2530
                $url = "{$site_url}{$strictURL}?{$qstring}";
2531
            } else {
2532
                $url = "{$site_url}{$strictURL}";
2533
            }
2534
            $this->sendRedirect($url, 0, 'REDIRECT_HEADER', 'HTTP/1.0 301 Moved Permanently');
2535
            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...
2536
        }
2537
        return;
2538
    }
2539
2540
    /**
2541
     * Get all db fields and TVs for a document/resource
2542
     *
2543
     * @param string $method
2544
     * @param mixed $identifier
2545
     * @param bool $isPrepareResponse
2546
     * @return array
2547
     */
2548
    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...
2549
    {
2550
2551
        $cacheKey = md5(print_r(func_get_args(), true));
2552
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
2553
            return $this->tmpCache[__FUNCTION__][$cacheKey];
2554
        }
2555
2556
        $tblsc = $this->getFullTableName("site_content");
2557
        $tbldg = $this->getFullTableName("document_groups");
2558
2559
        // allow alias to be full path
2560
        if ($method == 'alias') {
2561
            $identifier = $this->cleanDocumentIdentifier($identifier);
2562
            $method = $this->documentMethod;
2563
        }
2564
        if ($method == 'alias' && $this->config['use_alias_path'] && array_key_exists($identifier, $this->documentListing)) {
2565
            $method = 'id';
2566
            $identifier = $this->documentListing[$identifier];
2567
        }
2568
2569
        $out = $this->invokeEvent('OnBeforeLoadDocumentObject', compact('method', 'identifier'));
2570
        if (is_array($out) && is_array($out[0])) {
2571
            $documentObject = $out[0];
2572
        } else {
2573
            // get document groups for current user
2574
            if ($docgrp = $this->getUserDocGroups()) {
2575
                $docgrp = implode(",", $docgrp);
2576
            }
2577
            // get document
2578
            $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
2579
            $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...
2580
                LEFT JOIN {$tbldg} dg ON dg.document = sc.id", "sc.{$method} = '{$identifier}' AND ({$access})", "", 1);
2581
            if ($this->db->getRecordCount($rs) < 1) {
2582
                $seclimit = 0;
2583
                if ($this->config['unauthorized_page']) {
2584
                    // method may still be alias, while identifier is not full path alias, e.g. id not found above
2585
                    if ($method === 'alias') {
2586
                        $secrs = $this->db->select('count(dg.id)', "{$tbldg} as dg, {$tblsc} as sc", "dg.document = sc.id AND sc.alias = '{$identifier}'", '', 1);
2587
                    } else {
2588
                        $secrs = $this->db->select('count(id)', $tbldg, "document = '{$identifier}'", '', 1);
2589
                    }
2590
                    // check if file is not public
2591
                    $seclimit = $this->db->getValue($secrs);
2592
                }
2593
                if ($seclimit > 0) {
2594
                    // match found but not publicly accessible, send the visitor to the unauthorized_page
2595
                    $this->sendUnauthorizedPage();
2596
                    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...
2597
                } else {
2598
                    $this->sendErrorPage();
2599
                    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...
2600
                }
2601
            }
2602
            # 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...
2603
            $documentObject = $this->db->getRow($rs);
2604
2605
            if ($isPrepareResponse === 'prepareResponse') {
2606
                $this->documentObject = &$documentObject;
2607
            }
2608
            $out = $this->invokeEvent('OnLoadDocumentObject', compact('method', 'identifier', 'documentObject'));
2609
            if (is_array($out) && is_array($out[0])) {
2610
                $documentObject = $out[0];
2611
            }
2612
            if ($documentObject['template']) {
2613
                // load TVs and merge with document - Orig by Apodigm - Docvars
2614
                $rs = $this->db->select("tv.*, IF(tvc.value!='',tvc.value,tv.default_text) as value", $this->getFullTableName("site_tmplvars") . " tv
2615
                INNER JOIN " . $this->getFullTableName("site_tmplvar_templates") . " tvtpl ON tvtpl.tmplvarid = tv.id
2616
                LEFT JOIN " . $this->getFullTableName("site_tmplvar_contentvalues") . " tvc ON tvc.tmplvarid=tv.id AND tvc.contentid = '{$documentObject['id']}'", "tvtpl.templateid = '{$documentObject['template']}'");
2617
                $tmplvars = array();
2618
                while ($row = $this->db->getRow($rs)) {
2619
                    $tmplvars[$row['name']] = array(
2620
                        $row['name'],
2621
                        $row['value'],
2622
                        $row['display'],
2623
                        $row['display_params'],
2624
                        $row['type']
2625
                    );
2626
                }
2627
                $documentObject = array_merge($documentObject, $tmplvars);
2628
            }
2629
            $out = $this->invokeEvent('OnAfterLoadDocumentObject', compact('method', 'identifier', 'documentObject'));
2630
            if (is_array($out) && array_key_exists(0, $out) !== false && is_array($out[0])) {
2631
                $documentObject = $out[0];
2632
            }
2633
        }
2634
2635
        $this->tmpCache[__FUNCTION__][$cacheKey] = $documentObject;
2636
2637
        return $documentObject;
2638
    }
2639
2640
    /**
2641
     * Parse a source string.
2642
     *
2643
     * Handles most MODX tags. Exceptions include:
2644
     *   - uncached snippet tags [!...!]
2645
     *   - URL tags [~...~]
2646
     *
2647
     * @param string $source
2648
     * @return string
2649
     */
2650
    public function parseDocumentSource($source)
2651
    {
2652
        // set the number of times we are to parse the document source
2653
        $this->minParserPasses = empty ($this->minParserPasses) ? 2 : $this->minParserPasses;
2654
        $this->maxParserPasses = empty ($this->maxParserPasses) ? 10 : $this->maxParserPasses;
2655
        $passes = $this->minParserPasses;
2656
        for ($i = 0; $i < $passes; $i++) {
2657
            // get source length if this is the final pass
2658
            if ($i == ($passes - 1)) {
2659
                $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...
2660
            }
2661
            if ($this->dumpSnippets == 1) {
2662
                $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>";
2663
            }
2664
2665
            // invoke OnParseDocument event
2666
            $this->documentOutput = $source; // store source code so plugins can
2667
            $this->invokeEvent("OnParseDocument"); // work on it via $modx->documentOutput
2668
            $source = $this->documentOutput;
2669
2670
            if ($this->config['enable_at_syntax']) {
2671
                $source = $this->ignoreCommentedTagsContent($source);
2672
                $source = $this->mergeConditionalTagsContent($source);
2673
            }
2674
2675
            $source = $this->mergeSettingsContent($source);
2676
            $source = $this->mergeDocumentContent($source);
2677
            $source = $this->mergeChunkContent($source);
2678
            $source = $this->evalSnippets($source);
2679
            $source = $this->mergePlaceholderContent($source);
2680
2681
            if ($this->dumpSnippets == 1) {
2682
                $this->snippetsCode .= "</fieldset><br />";
2683
            }
2684
            if ($i == ($passes - 1) && $i < ($this->maxParserPasses - 1)) {
2685
                // check if source content was changed
2686
                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...
2687
                    $passes++;
2688
                } // if content change then increase passes because
2689
            } // we have not yet reached maxParserPasses
2690
        }
2691
        return $source;
2692
    }
2693
2694
    /**
2695
     * Starts the parsing operations.
2696
     *
2697
     * - connects to the db
2698
     * - gets the settings (including system_settings)
2699
     * - gets the document/resource identifier as in the query string
2700
     * - finally calls prepareResponse()
2701
     */
2702
    public function executeParser()
2703
    {
2704
        if(MODX_CLI) {
2705
            throw new RuntimeException('Call DocumentParser::executeParser on CLI mode');
2706
        }
2707
2708
        //error_reporting(0);
2709
        set_error_handler(array(
2710
            & $this,
2711
            "phpError"
2712
        ), E_ALL);
2713
        $this->db->connect();
2714
2715
        // get the settings
2716
        if (empty ($this->config)) {
2717
            $this->getSettings();
2718
        }
2719
2720
        $this->_IIS_furl_fix(); // IIS friendly url fix
2721
2722
        // check site settings
2723
        if ($this->checkSiteStatus()) {
2724
            // make sure the cache doesn't need updating
2725
            $this->updatePubStatus();
2726
2727
            // find out which document we need to display
2728
            $this->documentMethod = filter_input(INPUT_GET, 'q') ? 'alias' : 'id';
2729
            $this->documentIdentifier = $this->getDocumentIdentifier($this->documentMethod);
2730
        } else {
2731
            header('HTTP/1.0 503 Service Unavailable');
2732
            $this->systemCacheKey = 'unavailable';
2733
            if (!$this->config['site_unavailable_page']) {
2734
                // display offline message
2735
                $this->documentContent = $this->config['site_unavailable_message'];
2736
                $this->outputContent();
2737
                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...
2738
            } else {
2739
                // setup offline page document settings
2740
                $this->documentMethod = 'id';
2741
                $this->documentIdentifier = $this->config['site_unavailable_page'];
2742
            }
2743
        }
2744
2745
        if ($this->documentMethod == "alias") {
2746
            $this->documentIdentifier = $this->cleanDocumentIdentifier($this->documentIdentifier);
2747
2748
            // Check use_alias_path and check if $this->virtualDir is set to anything, then parse the path
2749
            if ($this->config['use_alias_path'] == 1) {
2750
                $alias = (strlen($this->virtualDir) > 0 ? $this->virtualDir . '/' : '') . $this->documentIdentifier;
2751
                if (isset($this->documentListing[$alias])) {
2752
                    $this->documentIdentifier = $this->documentListing[$alias];
2753
                } else {
2754
                    //@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...
2755
                    if ($this->config['aliaslistingfolder'] == 1) {
2756
                        $tbl_site_content = $this->getFullTableName('site_content');
2757
2758
                        $parentId = $this->getIdFromAlias($this->virtualDir);
2759
                        $parentId = ($parentId > 0) ? $parentId : '0';
2760
2761
                        $docAlias = $this->db->escape($this->documentIdentifier);
2762
2763
                        $rs = $this->db->select('id', $tbl_site_content, "deleted=0 and parent='{$parentId}' and alias='{$docAlias}'");
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rs. Configured minimum length is 3.

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

Loading history...
2764
                        if ($this->db->getRecordCount($rs) == 0) {
2765
                            $this->sendErrorPage();
2766
                        }
2767
                        $docId = $this->db->getValue($rs);
2768
2769
                        if (!$docId) {
2770
                            $alias = $this->q;
2771
                            if (!empty($this->config['friendly_url_suffix'])) {
2772
                                $pos = strrpos($alias, $this->config['friendly_url_suffix']);
2773
2774
                                if ($pos !== false) {
2775
                                    $alias = substr($alias, 0, $pos);
2776
                                }
2777
                            }
2778
                            $docId = $this->getIdFromAlias($alias);
2779
                        }
2780
2781
                        if ($docId > 0) {
2782
                            $this->documentIdentifier = $docId;
2783
                        } else {
2784
                            /*
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...
2785
                            $rs  = $this->db->select('id', $tbl_site_content, "deleted=0 and alias='{$docAlias}'");
2786
                            if($this->db->getRecordCount($rs)==0)
2787
                            {
2788
                                $rs  = $this->db->select('id', $tbl_site_content, "deleted=0 and id='{$docAlias}'");
2789
                            }
2790
                            $docId = $this->db->getValue($rs);
2791
2792
                            if ($docId > 0)
2793
                            {
2794
                                $this->documentIdentifier = $docId;
2795
2796
                            }else{
2797
                            */
2798
                            $this->sendErrorPage();
2799
                            //}
2800
                        }
2801
                    } else {
2802
                        $this->sendErrorPage();
2803
                    }
2804
                }
2805
            } else {
2806
                if (isset($this->documentListing[$this->documentIdentifier])) {
2807
                    $this->documentIdentifier = $this->documentListing[$this->documentIdentifier];
2808
                } else {
2809
                    $docAlias = $this->db->escape($this->documentIdentifier);
2810
                    $rs = $this->db->select('id', $this->getFullTableName('site_content'), "deleted=0 and alias='{$docAlias}'");
2811
                    $this->documentIdentifier = (int)$this->db->getValue($rs);
2812
                }
2813
            }
2814
            $this->documentMethod = 'id';
2815
        }
2816
2817
        //$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...
2818
        // invoke OnWebPageInit event
2819
        $this->invokeEvent("OnWebPageInit");
2820
        if ($this->config['seostrict'] === '1') {
2821
            $this->sendStrictURI();
2822
        }
2823
        $this->prepareResponse();
2824
    }
2825
2826
    /**
2827
     * @param $path
2828
     * @param null $suffix
2829
     * @return mixed
2830
     */
2831
    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...
2832
    {
2833
        $exp = explode('/', $path);
2834
        return str_replace($suffix, '', end($exp));
2835
    }
2836
2837
    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...
2838
    {
2839
        if ($this->config['friendly_urls'] != 1) {
2840
            return;
2841
        }
2842
2843
        if (strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') === false) {
2844
            return;
2845
        }
2846
2847
        $url = $_SERVER['QUERY_STRING'];
2848
        $err = substr($url, 0, 3);
2849
        if ($err !== '404' && $err !== '405') {
2850
            return;
2851
        }
2852
2853
        $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...
2854
        unset ($_GET[$k[0]]);
2855
        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...
2856
        $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...
2857
        $_SERVER['QUERY_STRING'] = $qp['query'];
2858
        if (!empty ($qp['query'])) {
2859
            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...
2860
            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...
2861
                $_REQUEST[$n] = $_GET[$n] = $v;
2862
            }
2863
        }
2864
        $_SERVER['PHP_SELF'] = $this->config['base_url'] . $qp['path'];
2865
        $this->q = $qp['path'];
2866
        return $qp['path'];
2867
    }
2868
2869
    /**
2870
     * The next step called at the end of executeParser()
2871
     *
2872
     * - checks cache
2873
     * - checks if document/resource is deleted/unpublished
2874
     * - checks if resource is a weblink and redirects if so
2875
     * - gets template and parses it
2876
     * - ensures that postProcess is called when PHP is finished
2877
     */
2878
    public function prepareResponse()
2879
    {
2880
        // we now know the method and identifier, let's check the cache
2881
2882
        if ($this->config['enable_cache'] == 2 && $this->isLoggedIn()) {
2883
            $this->config['enable_cache'] = 0;
2884
        }
2885
2886
        if ($this->config['enable_cache']) {
2887
            $this->documentContent = $this->getDocumentObjectFromCache($this->documentIdentifier, true);
2888
        } else {
2889
            $this->documentContent = '';
2890
        }
2891
2892
        if ($this->documentContent == '') {
2893
            // get document object from DB
2894
            $this->documentObject = $this->getDocumentObject($this->documentMethod, $this->documentIdentifier, 'prepareResponse');
2895
2896
            // write the documentName to the object
2897
            $this->documentName = &$this->documentObject['pagetitle'];
2898
2899
            // check if we should not hit this document
2900
            if ($this->documentObject['donthit'] == 1) {
2901
                $this->config['track_visitors'] = 0;
2902
            }
2903
2904
            if ($this->documentObject['deleted'] == 1) {
2905
                $this->sendErrorPage();
2906
            } // validation routines
2907
            elseif ($this->documentObject['published'] == 0) {
2908
                $this->_sendErrorForUnpubPage();
2909
            } elseif ($this->documentObject['type'] == 'reference') {
2910
                $this->_sendRedirectForRefPage($this->documentObject['content']);
2911
            }
2912
2913
            // get the template and start parsing!
2914
            if (!$this->documentObject['template']) {
2915
                $templateCode = '[*content*]';
2916
            } // use blank template
2917
            else {
2918
                $templateCode = $this->_getTemplateCodeFromDB($this->documentObject['template']);
2919
            }
2920
2921
            if (substr($templateCode, 0, 8) === '@INCLUDE') {
2922
                $templateCode = $this->atBindInclude($templateCode);
2923
            }
2924
2925
2926
            $this->documentContent = &$templateCode;
2927
2928
            // invoke OnLoadWebDocument event
2929
            $this->invokeEvent('OnLoadWebDocument');
2930
2931
            // Parse document source
2932
            $this->documentContent = $this->parseDocumentSource($templateCode);
2933
2934
            $this->documentGenerated = 1;
2935
        } else {
2936
            $this->documentGenerated = 0;
2937
        }
2938
2939
        if ($this->config['error_page'] == $this->documentIdentifier && $this->config['error_page'] != $this->config['site_start']) {
2940
            header('HTTP/1.0 404 Not Found');
2941
        }
2942
2943
        register_shutdown_function(array(
2944
            &$this,
2945
            'postProcess'
2946
        )); // tell PHP to call postProcess when it shuts down
2947
        $this->outputContent();
2948
        //$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...
2949
    }
2950
2951
    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...
2952
    {
2953
        // Can't view unpublished pages !$this->checkPreview()
2954
        if (!$this->hasPermission('view_unpublished')) {
2955
            $this->sendErrorPage();
2956
        } else {
2957
            // Inculde the necessary files to check document permissions
2958
            include_once(MODX_MANAGER_PATH . 'processors/user_documents_permissions.class.php');
2959
            $udperms = new udperms();
2960
            $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...
2961
            $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...
2962
            $udperms->role = $_SESSION['mgrRole'];
2963
            // Doesn't have access to this document
2964
            if (!$udperms->checkPermissions()) {
2965
                $this->sendErrorPage();
2966
            }
2967
        }
2968
    }
2969
2970
    /**
2971
     * @param $url
2972
     */
2973
    public function _sendRedirectForRefPage($url)
2974
    {
2975
        // check whether it's a reference
2976
        if (preg_match('@^[1-9][0-9]*$@', $url)) {
2977
            $url = $this->makeUrl($url); // if it's a bare document id
2978
        } elseif (strpos($url, '[~') !== false) {
2979
            $url = $this->rewriteUrls($url); // if it's an internal docid tag, process it
2980
        }
2981
        $this->sendRedirect($url, 0, '', 'HTTP/1.0 301 Moved Permanently');
2982
        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...
2983
    }
2984
2985
    /**
2986
     * @param $templateID
2987
     * @return mixed
2988
     */
2989
    public function _getTemplateCodeFromDB($templateID)
2990
    {
2991
        $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...
2992
        if ($this->db->getRecordCount($rs) == 1) {
2993
            return $this->db->getValue($rs);
2994
        } else {
2995
            $this->messageQuit('Incorrect number of templates returned from database');
2996
        }
2997
    }
2998
2999
    /**
3000
     * Returns an array of all parent record IDs for the id passed.
3001
     *
3002
     * @param int $id Docid to get parents for.
3003
     * @param int $height The maximum number of levels to go up, default 10.
3004
     * @return array
3005
     */
3006
    public function getParentIds($id, $height = 10)
3007
    {
3008
        $parents = array();
3009
        while ($id && $height--) {
3010
            $thisid = $id;
3011
            if ($this->config['aliaslistingfolder'] == 1) {
3012
                $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");
3013
                if (!$id || $id == '0') {
3014
                    break;
3015
                }
3016
            } else {
3017
                $id = $this->aliasListing[$id]['parent'];
3018
                if (!$id) {
3019
                    break;
3020
                }
3021
            }
3022
            $parents[$thisid] = $id;
3023
        }
3024
        return $parents;
3025
    }
3026
3027
    /**
3028
     * @param $id
3029
     * @param int $top
3030
     * @return mixed
3031
     */
3032
    public function getUltimateParentId($id, $top = 0)
3033
    {
3034
        $i = 0;
3035
        while ($id && $i < 20) {
3036
            if ($top == $this->aliasListing[$id]['parent']) {
3037
                break;
3038
            }
3039
            $id = $this->aliasListing[$id]['parent'];
3040
            $i++;
3041
        }
3042
        return $id;
3043
    }
3044
3045
    /**
3046
     * Returns an array of child IDs belonging to the specified parent.
3047
     *
3048
     * @param int $id The parent resource/document to start from
3049
     * @param int $depth How many levels deep to search for children, default: 10
3050
     * @param array $children Optional array of docids to merge with the result.
3051
     * @return array Contains the document Listing (tree) like the sitemap
3052
     */
3053
    public function getChildIds($id, $depth = 10, $children = array())
3054
    {
3055
3056
        $cacheKey = md5(print_r(func_get_args(), true));
3057
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3058
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3059
        }
3060
3061
        if ($this->config['aliaslistingfolder'] == 1) {
3062
3063
            $res = $this->db->select("id,alias,isfolder,parent", $this->getFullTableName('site_content'), "parent IN (" . $id . ") AND deleted = '0'");
3064
            $idx = array();
3065
            while ($row = $this->db->getRow($res)) {
3066
                $pAlias = '';
3067
                if (isset($this->aliasListing[$row['parent']])) {
3068
                    $pAlias .= !empty($this->aliasListing[$row['parent']]['path']) ? $this->aliasListing[$row['parent']]['path'] . '/' : '';
3069
                    $pAlias .= !empty($this->aliasListing[$row['parent']]['alias']) ? $this->aliasListing[$row['parent']]['alias'] . '/' : '';
3070
                };
3071
                $children[$pAlias . $row['alias']] = $row['id'];
3072
                if ($row['isfolder'] == 1) {
3073
                    $idx[] = $row['id'];
3074
                }
3075
            }
3076
            $depth--;
3077
            $idx = implode(',', $idx);
3078
            if (!empty($idx)) {
3079
                if ($depth) {
3080
                    $children = $this->getChildIds($idx, $depth, $children);
3081
                }
3082
            }
3083
            $this->tmpCache[__FUNCTION__][$cacheKey] = $children;
3084
            return $children;
3085
3086
        } else {
3087
3088
            // Initialise a static array to index parents->children
3089
            static $documentMap_cache = array();
3090
            if (!count($documentMap_cache)) {
3091
                foreach ($this->documentMap as $document) {
3092
                    foreach ($document as $p => $c) {
3093
                        $documentMap_cache[$p][] = $c;
3094
                    }
3095
                }
3096
            }
3097
3098
            // Get all the children for this parent node
3099
            if (isset($documentMap_cache[$id])) {
3100
                $depth--;
3101
3102
                foreach ($documentMap_cache[$id] as $childId) {
3103
                    $pkey = (strlen($this->aliasListing[$childId]['path']) ? "{$this->aliasListing[$childId]['path']}/" : '') . $this->aliasListing[$childId]['alias'];
3104
                    if (!strlen($pkey)) {
3105
                        $pkey = "{$childId}";
3106
                    }
3107
                    $children[$pkey] = $childId;
3108
3109
                    if ($depth && isset($documentMap_cache[$childId])) {
3110
                        $children += $this->getChildIds($childId, $depth);
3111
                    }
3112
                }
3113
            }
3114
            $this->tmpCache[__FUNCTION__][$cacheKey] = $children;
3115
            return $children;
3116
3117
        }
3118
    }
3119
3120
    /**
3121
     * Displays a javascript alert message in the web browser and quit
3122
     *
3123
     * @param string $msg Message to show
3124
     * @param string $url URL to redirect to
3125
     */
3126
    public function webAlertAndQuit($msg, $url = "")
3127
    {
3128
        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...
3129
        if (substr(strtolower($url), 0, 11) == "javascript:") {
3130
            $fnc = substr($url, 11);
3131
        } elseif ($url) {
3132
            $fnc = "window.location.href='" . addslashes($url) . "';";
3133
        } else {
3134
            $fnc = "history.back(-1);";
3135
        }
3136
        echo "<html><head>
3137
            <title>MODX :: Alert</title>
3138
            <meta http-equiv=\"Content-Type\" content=\"text/html; charset={$modx_manager_charset};\">
3139
            <script>
3140
                function __alertQuit() {
3141
                    alert('" . addslashes($msg) . "');
3142
                    {$fnc}
3143
                }
3144
                window.setTimeout('__alertQuit();',100);
3145
            </script>
3146
            </head><body>
3147
            <p>{$msg}</p>
3148
            </body></html>";
3149
        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...
3150
    }
3151
3152
    /**
3153
     * Returns 1 if user has the currect permission
3154
     *
3155
     * @param string $pm Permission name
3156
     * @return int Why not bool?
3157
     */
3158
    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...
3159
    {
3160
        $state = 0;
3161
        $pms = $_SESSION['mgrPermissions'];
3162
        if ($pms) {
3163
            $state = ((bool)$pms[$pm] === true);
3164
        }
3165
        return (int)$state;
3166
    }
3167
3168
    /**
3169
     * Returns true if element is locked
3170
     *
3171
     * @param int $type Types: 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3172
     * @param int $id Element- / Resource-id
3173
     * @param bool $includeThisUser true = Return also info about actual user
3174
     * @return array lock-details or null
3175
     */
3176
    public function elementIsLocked($type, $id, $includeThisUser = false)
3177
    {
3178
        $id = (int)$id;
3179
        $type = (int)$type;
3180
        if (!$type || !$id) {
3181
            return null;
3182
        }
3183
3184
        // Build lockedElements-Cache at first call
3185
        $this->buildLockedElementsCache();
3186
3187
        if (!$includeThisUser && $this->lockedElements[$type][$id]['sid'] == $this->sid) {
3188
            return null;
3189
        }
3190
3191
        if (isset($this->lockedElements[$type][$id])) {
3192
            return $this->lockedElements[$type][$id];
3193
        } else {
3194
            return null;
3195
        }
3196
    }
3197
3198
    /**
3199
     * Returns Locked Elements as Array
3200
     *
3201
     * @param int $type Types: 0=all, 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3202
     * @param bool $minimumDetails true =
3203
     * @return array|mixed|null
3204
     */
3205
    public function getLockedElements($type = 0, $minimumDetails = false)
3206
    {
3207
        $this->buildLockedElementsCache();
3208
3209
        if (!$minimumDetails) {
3210
            $lockedElements = $this->lockedElements;
3211
        } else {
3212
            // Minimum details for HTML / Ajax-requests
3213
            $lockedElements = array();
3214
            foreach ($this->lockedElements as $elType => $elements) {
3215
                foreach ($elements as $elId => $el) {
3216
                    $lockedElements[$elType][$elId] = array(
3217
                        'username' => $el['username'],
3218
                        'lasthit_df' => $el['lasthit_df'],
3219
                        'state' => $this->determineLockState($el['internalKey'])
3220
                    );
3221
                }
3222
            }
3223
        }
3224
3225
        if ($type == 0) {
3226
            return $lockedElements;
3227
        }
3228
3229
        $type = (int)$type;
3230
        if (isset($lockedElements[$type])) {
3231
            return $lockedElements[$type];
3232
        } else {
3233
            return array();
3234
        }
3235
    }
3236
3237
    /**
3238
     * Builds the Locked Elements Cache once
3239
     */
3240
    public function buildLockedElementsCache()
3241
    {
3242
        if (is_null($this->lockedElements)) {
3243
            $this->lockedElements = array();
3244
            $this->cleanupExpiredLocks();
3245
3246
            $rs = $this->db->select('sid,internalKey,elementType,elementId,lasthit,username', $this->getFullTableName('active_user_locks') . " ul
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rs. Configured minimum length is 3.

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

Loading history...
3247
                LEFT JOIN {$this->getFullTableName('manager_users')} mu on ul.internalKey = mu.id");
3248
            while ($row = $this->db->getRow($rs)) {
3249
                $this->lockedElements[$row['elementType']][$row['elementId']] = array(
3250
                    'sid' => $row['sid'],
3251
                    'internalKey' => $row['internalKey'],
3252
                    'username' => $row['username'],
3253
                    'elementType' => $row['elementType'],
3254
                    'elementId' => $row['elementId'],
3255
                    'lasthit' => $row['lasthit'],
3256
                    'lasthit_df' => $this->toDateFormat($row['lasthit']),
3257
                    'state' => $this->determineLockState($row['sid'])
3258
                );
3259
            }
3260
        }
3261
    }
3262
3263
    /**
3264
     * Cleans up the active user locks table
3265
     */
3266
    public function cleanupExpiredLocks()
3267
    {
3268
        // Clean-up active_user_sessions first
3269
        $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
3270
        $validSessionTimeLimit = $this->time - $timeout;
3271
        $this->db->delete($this->getFullTableName('active_user_sessions'), "lasthit < {$validSessionTimeLimit}");
3272
3273
        // Clean-up active_user_locks
3274
        $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...
3275
        $count = $this->db->getRecordCount($rs);
3276
        if ($count) {
3277
            $rs = $this->db->makeArray($rs);
3278
            $userSids = array();
3279
            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...
3280
                $userSids[] = $row['sid'];
3281
            }
3282
            $userSids = "'" . implode("','", $userSids) . "'";
3283
            $this->db->delete($this->getFullTableName('active_user_locks'), "sid NOT IN({$userSids})");
3284
        } else {
3285
            $this->db->delete($this->getFullTableName('active_user_locks'));
3286
        }
3287
3288
    }
3289
3290
    /**
3291
     * Cleans up the active users table
3292
     */
3293
    public function cleanupMultipleActiveUsers()
3294
    {
3295
        $timeout = 20 * 60; // Delete multiple user-sessions after 20min
3296
        $validSessionTimeLimit = $this->time - $timeout;
3297
3298
        $activeUserSids = array();
3299
        $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...
3300
        $count = $this->db->getRecordCount($rs);
3301
        if ($count) {
3302
            $rs = $this->db->makeArray($rs);
3303
            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...
3304
                $activeUserSids[] = $row['sid'];
3305
            }
3306
        }
3307
3308
        $rs = $this->db->select("sid,internalKey,lasthit", "{$this->getFullTableName('active_users')}", "", "lasthit DESC");
3309
        if ($this->db->getRecordCount($rs)) {
3310
            $rs = $this->db->makeArray($rs);
3311
            $internalKeyCount = array();
3312
            $deleteSids = '';
3313
            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...
3314
                if (!isset($internalKeyCount[$row['internalKey']])) {
3315
                    $internalKeyCount[$row['internalKey']] = 0;
3316
                }
3317
                $internalKeyCount[$row['internalKey']]++;
3318
3319
                if ($internalKeyCount[$row['internalKey']] > 1 && !in_array($row['sid'], $activeUserSids) && $row['lasthit'] < $validSessionTimeLimit) {
3320
                    $deleteSids .= $deleteSids == '' ? '' : ' OR ';
3321
                    $deleteSids .= "sid='{$row['sid']}'";
3322
                };
3323
3324
            }
3325
            if ($deleteSids) {
3326
                $this->db->delete($this->getFullTableName('active_users'), $deleteSids);
3327
            }
3328
        }
3329
3330
    }
3331
3332
    /**
3333
     * Determines state of a locked element acc. to user-permissions
3334
     *
3335
     * @param $sid
3336
     * @return int $state States: 0=No display, 1=viewing this element, 2=locked, 3=show unlock-button
3337
     * @internal param int $internalKey : ID of User who locked actual element
3338
     */
3339
    public function determineLockState($sid)
3340
    {
3341
        $state = 0;
3342
        if ($this->hasPermission('display_locks')) {
3343
            if ($sid == $this->sid) {
3344
                $state = 1;
3345
            } else {
3346
                if ($this->hasPermission('remove_locks')) {
3347
                    $state = 3;
3348
                } else {
3349
                    $state = 2;
3350
                }
3351
            }
3352
        }
3353
        return $state;
3354
    }
3355
3356
    /**
3357
     * Locks an element
3358
     *
3359
     * @param int $type Types: 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3360
     * @param int $id Element- / Resource-id
3361
     * @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...
3362
     */
3363
    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...
3364
    {
3365
        $userId = $this->isBackend() && $_SESSION['mgrInternalKey'] ? $_SESSION['mgrInternalKey'] : 0;
3366
        $type = (int)$type;
3367
        $id = (int)$id;
3368
        if (!$type || !$id || !$userId) {
3369
            return false;
3370
        }
3371
3372
        $sql = sprintf('REPLACE INTO %s (internalKey, elementType, elementId, lasthit, sid)
3373
                VALUES (%d, %d, %d, %d, \'%s\')', $this->getFullTableName('active_user_locks'), $userId, $type, $id, $this->time, $this->sid);
3374
        $this->db->query($sql);
3375
    }
3376
3377
    /**
3378
     * Unlocks an element
3379
     *
3380
     * @param int $type Types: 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3381
     * @param int $id Element- / Resource-id
3382
     * @param bool $includeAllUsers true = Deletes not only own user-locks
3383
     * @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...
3384
     */
3385
    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...
3386
    {
3387
        $userId = $this->isBackend() && $_SESSION['mgrInternalKey'] ? $_SESSION['mgrInternalKey'] : 0;
3388
        $type = (int)$type;
3389
        $id = (int)$id;
3390
        if (!$type || !$id) {
3391
            return false;
3392
        }
3393
3394
        if (!$includeAllUsers) {
3395
            $sql = sprintf('DELETE FROM %s WHERE internalKey = %d AND elementType = %d AND elementId = %d;', $this->getFullTableName('active_user_locks'), $userId, $type, $id);
3396
        } else {
3397
            $sql = sprintf('DELETE FROM %s WHERE elementType = %d AND elementId = %d;', $this->getFullTableName('active_user_locks'), $type, $id);
3398
        }
3399
        $this->db->query($sql);
3400
    }
3401
3402
    /**
3403
     * Updates table "active_user_sessions" with userid, lasthit, IP
3404
     */
3405
    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...
3406
    {
3407
        if (!$this->sid) {
3408
            return;
3409
        }
3410
3411
        // web users are stored with negative keys
3412
        $userId = $this->getLoginUserType() == 'manager' ? $this->getLoginUserID() : -$this->getLoginUserID();
3413
3414
        // Get user IP
3415 View Code Duplication
        if ($cip = getenv("HTTP_CLIENT_IP")) {
3416
            $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...
3417
        } elseif ($cip = getenv("HTTP_X_FORWARDED_FOR")) {
3418
            $ip = $cip;
3419
        } elseif ($cip = getenv("REMOTE_ADDR")) {
3420
            $ip = $cip;
3421
        } else {
3422
            $ip = "UNKNOWN";
3423
        }
3424
        $_SESSION['ip'] = $ip;
3425
3426
        $sql = sprintf('REPLACE INTO %s (internalKey, lasthit, ip, sid)
3427
            VALUES (%d, %d, \'%s\', \'%s\')', $this->getFullTableName('active_user_sessions'), $userId, $this->time, $ip, $this->sid);
3428
        $this->db->query($sql);
3429
    }
3430
3431
    /**
3432
     * Add an a alert message to the system event log
3433
     *
3434
     * @param int $evtid Event ID
3435
     * @param int $type Types: 1 = information, 2 = warning, 3 = error
3436
     * @param string $msg Message to be logged
3437
     * @param string $source source of the event (module, snippet name, etc.)
3438
     *                       Default: Parser
3439
     */
3440
    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...
3441
    {
3442
        $msg = $this->db->escape($msg);
3443
        if (strpos($GLOBALS['database_connection_charset'], 'utf8') === 0 && extension_loaded('mbstring')) {
3444
            $esc_source = mb_substr($source, 0, 50, "UTF-8");
3445
        } else {
3446
            $esc_source = substr($source, 0, 50);
3447
        }
3448
        $esc_source = $this->db->escape($esc_source);
3449
3450
        $LoginUserID = $this->getLoginUserID();
3451
        if ($LoginUserID == '') {
3452
            $LoginUserID = 0;
3453
        }
3454
3455
        $usertype = $this->isFrontend() ? 1 : 0;
3456
        $evtid = (int)$evtid;
3457
        $type = (int)$type;
3458
3459
        // Types: 1 = information, 2 = warning, 3 = error
3460
        if ($type < 1) {
3461
            $type = 1;
3462
        } elseif ($type > 3) {
3463
            $type = 3;
3464
        }
3465
3466
        $this->db->insert(array(
3467
            'eventid' => $evtid,
3468
            'type' => $type,
3469
            'createdon' => $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'],
3470
            'source' => $esc_source,
3471
            'description' => $msg,
3472
            'user' => $LoginUserID,
3473
            'usertype' => $usertype
3474
        ), $this->getFullTableName("event_log"));
3475
3476
        if (isset($this->config['send_errormail']) && $this->config['send_errormail'] !== '0') {
3477
            if ($this->config['send_errormail'] <= $type) {
3478
                $this->sendmail(array(
3479
                    'subject' => 'MODX System Error on ' . $this->config['site_name'],
3480
                    'body' => 'Source: ' . $source . ' - The details of the error could be seen in the MODX system events log.',
3481
                    'type' => 'text'
3482
                ));
3483
            }
3484
        }
3485
    }
3486
3487
    /**
3488
     * @param array $params
3489
     * @param string $msg
3490
     * @param array $files
3491
     * @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...
3492
     */
3493
    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...
3494
    {
3495
        if (isset($params) && is_string($params)) {
3496
            if (strpos($params, '=') === false) {
3497
                if (strpos($params, '@') !== false) {
3498
                    $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...
3499
                } else {
3500
                    $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...
3501
                }
3502
            } else {
3503
                $params_array = explode(',', $params);
3504
                foreach ($params_array as $k => $v) {
3505
                    $k = trim($k);
3506
                    $v = trim($v);
3507
                    $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...
3508
                }
3509
            }
3510
        } else {
3511
            $p = $params;
3512
            unset($params);
3513
        }
3514
        if (isset($p['sendto'])) {
3515
            $p['to'] = $p['sendto'];
3516
        }
3517
3518
        if (isset($p['to']) && preg_match('@^[0-9]+$@', $p['to'])) {
3519
            $userinfo = $this->getUserInfo($p['to']);
3520
            $p['to'] = $userinfo['email'];
3521
        }
3522
        if (isset($p['from']) && preg_match('@^[0-9]+$@', $p['from'])) {
3523
            $userinfo = $this->getUserInfo($p['from']);
3524
            $p['from'] = $userinfo['email'];
3525
            $p['fromname'] = $userinfo['username'];
3526
        }
3527
        if ($msg === '' && !isset($p['body'])) {
3528
            $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...
3529
        } elseif (is_string($msg) && 0 < strlen($msg)) {
3530
            $p['body'] = $msg;
3531
        }
3532
3533
        $this->loadExtension('MODxMailer');
3534
        $sendto = (!isset($p['to'])) ? $this->config['emailsender'] : $p['to'];
3535
        $sendto = explode(',', $sendto);
3536
        foreach ($sendto as $address) {
3537
            list($name, $address) = $this->mail->address_split($address);
3538
            $this->mail->AddAddress($address, $name);
3539
        }
3540 View Code Duplication
        if (isset($p['cc'])) {
3541
            $p['cc'] = explode(',', $p['cc']);
3542
            foreach ($p['cc'] as $address) {
3543
                list($name, $address) = $this->mail->address_split($address);
3544
                $this->mail->AddCC($address, $name);
3545
            }
3546
        }
3547 View Code Duplication
        if (isset($p['bcc'])) {
3548
            $p['bcc'] = explode(',', $p['bcc']);
3549
            foreach ($p['bcc'] as $address) {
3550
                list($name, $address) = $this->mail->address_split($address);
3551
                $this->mail->AddBCC($address, $name);
3552
            }
3553
        }
3554
        if (isset($p['from']) && strpos($p['from'], '<') !== false && substr($p['from'], -1) === '>') {
3555
            list($p['fromname'], $p['from']) = $this->mail->address_split($p['from']);
3556
        }
3557
        $this->mail->From = (!isset($p['from'])) ? $this->config['emailsender'] : $p['from'];
3558
        $this->mail->FromName = (!isset($p['fromname'])) ? $this->config['site_name'] : $p['fromname'];
3559
        $this->mail->Subject = (!isset($p['subject'])) ? $this->config['emailsubject'] : $p['subject'];
3560
        $this->mail->Body = $p['body'];
3561
        if (isset($p['type']) && $p['type'] == 'text') {
3562
            $this->mail->IsHTML(false);
3563
        }
3564
        if (!is_array($files)) {
3565
            $files = array();
3566
        }
3567
        foreach ($files as $f) {
3568
            if (file_exists(MODX_BASE_PATH . $f) && is_file(MODX_BASE_PATH . $f) && is_readable(MODX_BASE_PATH . $f)) {
3569
                $this->mail->AddAttachment(MODX_BASE_PATH . $f);
3570
            }
3571
        }
3572
        $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...
3573
        return $rs;
3574
    }
3575
3576
    /**
3577
     * @param string $target
3578
     * @param int $limit
3579
     * @param int $trim
3580
     */
3581
    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...
3582
    {
3583
        if ($limit < $trim) {
3584
            $trim = $limit;
3585
        }
3586
3587
        $table_name = $this->getFullTableName($target);
3588
        $count = $this->db->getValue($this->db->select('COUNT(id)', $table_name));
3589
        $over = $count - $limit;
3590
        if (0 < $over) {
3591
            $trim = ($over + $trim);
3592
            $this->db->delete($table_name, '', '', $trim);
3593
        }
3594
        $this->db->optimize($table_name);
3595
    }
3596
3597
    /**
3598
     * Returns true if we are currently in the manager backend
3599
     *
3600
     * @return boolean
3601
     */
3602
    public function isBackend()
3603
    {
3604
        return (defined('IN_MANAGER_MODE') && IN_MANAGER_MODE === true);
3605
    }
3606
3607
    /**
3608
     * Returns true if we are currently in the frontend
3609
     *
3610
     * @return boolean
3611
     */
3612
    public function isFrontend()
3613
    {
3614
        return ! $this->isBackend();
3615
    }
3616
3617
    /**
3618
     * Gets all child documents of the specified document, including those which are unpublished or deleted.
3619
     *
3620
     * @param int $id The Document identifier to start with
3621
     * @param string $sort Sort field
3622
     *                     Default: menuindex
3623
     * @param string $dir Sort direction, ASC and DESC is possible
3624
     *                    Default: ASC
3625
     * @param string $fields Default: id, pagetitle, description, parent, alias, menutitle
3626
     * @return array
3627
     */
3628 View Code Duplication
    public function getAllChildren($id = 0, $sort = 'menuindex', $dir = 'ASC', $fields = 'id, pagetitle, description, parent, alias, menutitle')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
Coding Style introduced by
getAllChildren uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
3629
    {
3630
3631
        $cacheKey = md5(print_r(func_get_args(), true));
3632
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3633
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3634
        }
3635
3636
        $tblsc = $this->getFullTableName("site_content");
3637
        $tbldg = $this->getFullTableName("document_groups");
3638
        // modify field names to use sc. table reference
3639
        $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3640
        $sort = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
3641
        // get document groups for current user
3642
        if ($docgrp = $this->getUserDocGroups()) {
3643
            $docgrp = implode(",", $docgrp);
3644
        }
3645
        // build query
3646
        $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
3647
        $result = $this->db->select("DISTINCT {$fields}", "{$tblsc} sc
3648
                LEFT JOIN {$tbldg} dg on dg.document = sc.id", "sc.parent = '{$id}' AND ({$access}) GROUP BY sc.id", "{$sort} {$dir}");
3649
        $resourceArray = $this->db->makeArray($result);
3650
        $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
3651
        return $resourceArray;
3652
    }
3653
3654
    /**
3655
     * Gets all active child documents of the specified document, i.e. those which published and not deleted.
3656
     *
3657
     * @param int $id The Document identifier to start with
3658
     * @param string $sort Sort field
3659
     *                     Default: menuindex
3660
     * @param string $dir Sort direction, ASC and DESC is possible
3661
     *                    Default: ASC
3662
     * @param string $fields Default: id, pagetitle, description, parent, alias, menutitle
3663
     * @return array
3664
     */
3665 View Code Duplication
    public function getActiveChildren($id = 0, $sort = 'menuindex', $dir = 'ASC', $fields = 'id, pagetitle, description, parent, alias, menutitle')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
Coding Style introduced by
getActiveChildren uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
3666
    {
3667
        $cacheKey = md5(print_r(func_get_args(), true));
3668
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3669
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3670
        }
3671
3672
        $tblsc = $this->getFullTableName("site_content");
3673
        $tbldg = $this->getFullTableName("document_groups");
3674
3675
        // modify field names to use sc. table reference
3676
        $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3677
        $sort = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
3678
        // get document groups for current user
3679
        if ($docgrp = $this->getUserDocGroups()) {
3680
            $docgrp = implode(",", $docgrp);
3681
        }
3682
        // build query
3683
        $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
3684
        $result = $this->db->select("DISTINCT {$fields}", "{$tblsc} sc
3685
                LEFT JOIN {$tbldg} dg on dg.document = sc.id", "sc.parent = '{$id}' AND sc.published=1 AND sc.deleted=0 AND ({$access}) GROUP BY sc.id", "{$sort} {$dir}");
3686
        $resourceArray = $this->db->makeArray($result);
3687
3688
        $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
3689
3690
        return $resourceArray;
3691
    }
3692
3693
    /**
3694
     * getDocumentChildren
3695
     * @version 1.1.1 (2014-02-19)
3696
     *
3697
     * @desc Returns the children of the selected document/folder as an associative array.
3698
     *
3699
     * @param $parentid {integer} - The parent document identifier. Default: 0 (site root).
3700
     * @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.
3701
     * @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.
3702
     * @param $fields {comma separated string; '*'} - Comma separated list of document fields to get. Default: '*' (all fields).
3703
     * @param $where {string} - Where condition in SQL style. Should include a leading 'AND '. Default: ''.
3704
     * @param $sort {comma separated string} - Should be a comma-separated list of field names on which to sort. Default: 'menuindex'.
3705
     * @param $dir {'ASC'; 'DESC'} - Sort direction, ASC and DESC is possible. Default: 'ASC'.
3706
     * @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).
3707
     *
3708
     * @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...
3709
     */
3710
    public function getDocumentChildren($parentid = 0, $published = 1, $deleted = 0, $fields = '*', $where = '', $sort = 'menuindex', $dir = 'ASC', $limit = '')
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
Coding Style introduced by
getDocumentChildren uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
3711
    {
3712
3713
        $cacheKey = md5(print_r(func_get_args(), true));
3714
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3715
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3716
        }
3717
3718
        $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...
3719
        $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...
3720
3721
        if ($where != '') {
3722
            $where = 'AND ' . $where;
3723
        }
3724
3725
        // modify field names to use sc. table reference
3726
        $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3727
        $sort = ($sort == '') ? '' : 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
3728
3729
        // get document groups for current user
3730
        if ($docgrp = $this->getUserDocGroups()) {
3731
            $docgrp = implode(',', $docgrp);
3732
        }
3733
3734
        // build query
3735
        $access = ($this->isFrontend() ? 'sc.privateweb=0' : '1="' . $_SESSION['mgrRole'] . '" OR sc.privatemgr=0') . (!$docgrp ? '' : ' OR dg.document_group IN (' . $docgrp . ')');
3736
3737
        $tblsc = $this->getFullTableName('site_content');
3738
        $tbldg = $this->getFullTableName('document_groups');
3739
3740
        $result = $this->db->select("DISTINCT {$fields}", "{$tblsc} sc
3741
                LEFT JOIN {$tbldg} dg on dg.document = sc.id", "sc.parent = '{$parentid}' {$published} {$deleted} {$where} AND ({$access}) GROUP BY sc.id", ($sort ? "{$sort} {$dir}" : ""), $limit);
3742
3743
        $resourceArray = $this->db->makeArray($result);
3744
3745
        $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
3746
3747
        return $resourceArray;
3748
    }
3749
3750
    /**
3751
     * getDocuments
3752
     * @version 1.1.1 (2013-02-19)
3753
     *
3754
     * @desc Returns required documents (their fields).
3755
     *
3756
     * @param $ids {array; comma separated string} - Documents Ids to get. @required
3757
     * @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.
3758
     * @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.
3759
     * @param $fields {comma separated string; '*'} - Documents fields to get. Default: '*'.
3760
     * @param $where {string} - SQL WHERE clause. Default: ''.
3761
     * @param $sort {comma separated string} - A comma-separated list of field names to sort by. Default: 'menuindex'.
3762
     * @param $dir {'ASC'; 'DESC'} - Sorting direction. Default: 'ASC'.
3763
     * @param $limit {string} - SQL LIMIT (without 'LIMIT '). An empty string means no limit. Default: ''.
3764
     *
3765
     * @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...
3766
     */
3767
    public function getDocuments($ids = array(), $published = 1, $deleted = 0, $fields = '*', $where = '', $sort = 'menuindex', $dir = 'ASC', $limit = '')
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
Coding Style introduced by
getDocuments uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
3768
    {
3769
3770
        $cacheKey = md5(print_r(func_get_args(), true));
3771
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3772
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3773
        }
3774
3775
        if (is_string($ids)) {
3776
            if (strpos($ids, ',') !== false) {
3777
                $ids = array_filter(array_map('intval', explode(',', $ids)));
3778
            } else {
3779
                $ids = array($ids);
3780
            }
3781
        }
3782
        if (count($ids) == 0) {
3783
            $this->tmpCache[__FUNCTION__][$cacheKey] = false;
3784
            return false;
3785
        } else {
3786
            // modify field names to use sc. table reference
3787
            $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3788
            $sort = ($sort == '') ? '' : 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
3789
            if ($where != '') {
3790
                $where = 'AND ' . $where;
3791
            }
3792
3793
            $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...
3794
            $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...
3795
3796
            // get document groups for current user
3797
            if ($docgrp = $this->getUserDocGroups()) {
3798
                $docgrp = implode(',', $docgrp);
3799
            }
3800
3801
            $access = ($this->isFrontend() ? 'sc.privateweb=0' : '1="' . $_SESSION['mgrRole'] . '" OR sc.privatemgr=0') . (!$docgrp ? '' : ' OR dg.document_group IN (' . $docgrp . ')');
3802
3803
            $tblsc = $this->getFullTableName('site_content');
3804
            $tbldg = $this->getFullTableName('document_groups');
3805
3806
            $result = $this->db->select("DISTINCT {$fields}", "{$tblsc} sc
3807
                    LEFT JOIN {$tbldg} dg on dg.document = sc.id", "(sc.id IN (" . implode(',', $ids) . ") {$published} {$deleted} {$where}) AND ({$access}) GROUP BY sc.id", ($sort ? "{$sort} {$dir}" : ""), $limit);
3808
3809
            $resourceArray = $this->db->makeArray($result);
3810
3811
            $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
3812
3813
            return $resourceArray;
3814
        }
3815
    }
3816
3817
    /**
3818
     * getDocument
3819
     * @version 1.0.1 (2014-02-19)
3820
     *
3821
     * @desc Returns required fields of a document.
3822
     *
3823
     * @param int $id {integer}
3824
     * - Id of a document which data has to be gained. @required
3825
     * @param string $fields {comma separated string; '*'}
3826
     * - Comma separated list of document fields to get. Default: '*'.
3827
     * @param int $published {0; 1; 'all'}
3828
     * - 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.
3829
     * @param int $deleted {0; 1; 'all'}
3830
     * - 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.
3831
     * @return bool {array; false} - Result array with fields or false.
3832
     * - Result array with fields or false.
3833
     */
3834 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...
3835
    {
3836
        if ($id == 0) {
3837
            return false;
3838
        } else {
3839
            $docs = $this->getDocuments(array($id), $published, $deleted, $fields, '', '', '', 1);
3840
3841
            if ($docs != false) {
3842
                return $docs[0];
3843
            } else {
3844
                return false;
3845
            }
3846
        }
3847
    }
3848
3849
    /**
3850
     * @param string $field
3851
     * @param string $docid
3852
     * @return bool|mixed
3853
     */
3854
    public function getField($field = 'content', $docid = '')
3855
    {
3856
        if (empty($docid) && isset($this->documentIdentifier)) {
3857
            $docid = $this->documentIdentifier;
3858
        } elseif (!preg_match('@^[0-9]+$@', $docid)) {
3859
            $docid = $this->getIdFromAlias($docid);
3860
        }
3861
3862
        if (empty($docid)) {
3863
            return false;
3864
        }
3865
3866
        $cacheKey = md5(print_r(func_get_args(), true));
3867
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3868
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3869
        }
3870
3871
        $doc = $this->getDocumentObject('id', $docid);
3872
        if (is_array($doc[$field])) {
3873
            $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...
3874
            $content = $tvs[$field];
3875
        } else {
3876
            $content = $doc[$field];
3877
        }
3878
3879
        $this->tmpCache[__FUNCTION__][$cacheKey] = $content;
3880
3881
        return $content;
3882
    }
3883
3884
    /**
3885
     * Returns the page information as database row, the type of result is
3886
     * defined with the parameter $rowMode
3887
     *
3888
     * @param int $pageid The parent document identifier
3889
     *                    Default: -1 (no result)
3890
     * @param int $active Should we fetch only published and undeleted documents/resources?
3891
     *                     1 = yes, 0 = no
3892
     *                     Default: 1
3893
     * @param string $fields List of fields
3894
     *                       Default: id, pagetitle, description, alias
3895
     * @return boolean|array
3896
     */
3897
    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...
3898
    {
3899
3900
        $cacheKey = md5(print_r(func_get_args(), true));
3901
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3902
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3903
        }
3904
3905
        if ($pageid == 0) {
3906
            return false;
3907
        } else {
3908
            $tblsc = $this->getFullTableName("site_content");
3909
            $tbldg = $this->getFullTableName("document_groups");
3910
            $activeSql = $active == 1 ? "AND sc.published=1 AND sc.deleted=0" : "";
3911
            // modify field names to use sc. table reference
3912
            $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3913
            // get document groups for current user
3914
            if ($docgrp = $this->getUserDocGroups()) {
3915
                $docgrp = implode(",", $docgrp);
3916
            }
3917
            $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
3918
            $result = $this->db->select($fields, "{$tblsc} sc LEFT JOIN {$tbldg} dg on dg.document = sc.id", "(sc.id='{$pageid}' {$activeSql}) AND ({$access})", "", 1);
3919
            $pageInfo = $this->db->getRow($result);
3920
3921
            $this->tmpCache[__FUNCTION__][$cacheKey] = $pageInfo;
3922
3923
            return $pageInfo;
3924
        }
3925
    }
3926
3927
    /**
3928
     * Returns the parent document/resource of the given docid
3929
     *
3930
     * @param int $pid The parent docid. If -1, then fetch the current document/resource's parent
3931
     *                 Default: -1
3932
     * @param int $active Should we fetch only published and undeleted documents/resources?
3933
     *                     1 = yes, 0 = no
3934
     *                     Default: 1
3935
     * @param string $fields List of fields
3936
     *                       Default: id, pagetitle, description, alias
3937
     * @return boolean|array
3938
     */
3939
    public function getParent($pid = -1, $active = 1, $fields = 'id, pagetitle, description, alias, parent')
3940
    {
3941
        if ($pid == -1) {
3942
            $pid = $this->documentObject['parent'];
3943
            return ($pid == 0) ? false : $this->getPageInfo($pid, $active, $fields);
3944
        } else if ($pid == 0) {
3945
            return false;
3946
        } else {
3947
            // first get the child document
3948
            $child = $this->getPageInfo($pid, $active, "parent");
3949
            // now return the child's parent
3950
            $pid = ($child['parent']) ? $child['parent'] : 0;
3951
            return ($pid == 0) ? false : $this->getPageInfo($pid, $active, $fields);
3952
        }
3953
    }
3954
3955
    /**
3956
     * Returns the id of the current snippet.
3957
     *
3958
     * @return int
3959
     */
3960
    public function getSnippetId()
3961
    {
3962
        if ($this->currentSnippet) {
3963
            $tbl = $this->getFullTableName("site_snippets");
3964
            $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...
3965
            if ($snippetId = $this->db->getValue($rs)) {
3966
                return $snippetId;
3967
            }
3968
        }
3969
        return 0;
3970
    }
3971
3972
    /**
3973
     * Returns the name of the current snippet.
3974
     *
3975
     * @return string
3976
     */
3977
    public function getSnippetName()
3978
    {
3979
        return $this->currentSnippet;
3980
    }
3981
3982
    /**
3983
     * Clear the cache of MODX.
3984
     *
3985
     * @param string $type
3986
     * @param bool $report
3987
     * @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...
3988
     */
3989
    public function clearCache($type = '', $report = false)
3990
    {
3991
        $cache_dir = MODX_BASE_PATH . $this->getCacheFolder();
3992
        if (is_array($type)) {
3993
            foreach ($type as $_) {
3994
                $this->clearCache($_, $report);
3995
            }
3996
        } elseif ($type == 'full') {
3997
            include_once(MODX_MANAGER_PATH . 'processors/cache_sync.class.processor.php');
3998
            $sync = new synccache();
3999
            $sync->setCachepath($cache_dir);
4000
            $sync->setReport($report);
4001
            $sync->emptyCache();
4002
        } elseif (preg_match('@^[1-9][0-9]*$@', $type)) {
4003
            $key = ($this->config['cache_type'] == 2) ? $this->makePageCacheKey($type) : $type;
4004
            $file_name = "docid_" . $key . "_*.pageCache.php";
4005
            $cache_path = $cache_dir . $file_name;
4006
            $files = glob($cache_path);
4007
            $files[] = $cache_dir . "docid_" . $key . ".pageCache.php";
4008
            foreach ($files as $file) {
4009
                if (!is_file($file)) {
4010
                    continue;
4011
                }
4012
                unlink($file);
4013
            }
4014
        } else {
4015
            $files = glob($cache_dir . '*');
4016
            foreach ($files as $file) {
4017
                $name = basename($file);
4018
                if (strpos($name, '.pageCache.php') === false) {
4019
                    continue;
4020
                }
4021
                if (!is_file($file)) {
4022
                    continue;
4023
                }
4024
                unlink($file);
4025
            }
4026
        }
4027
    }
4028
4029
    /**
4030
     * makeUrl
4031
     *
4032
     * @desc Create an URL for the given document identifier. The url prefix and postfix are used, when “friendly_url” is active.
4033
     *
4034
     * @param $id {integer} - The document identifier. @required
4035
     * @param string $alias {string}
4036
     * - The alias name for the document. Default: ''.
4037
     * @param string $args {string}
4038
     * - The paramaters to add to the URL. Default: ''.
4039
     * @param string $scheme {string}
4040
     * - With full as valus, the site url configuration is used. Default: ''.
4041
     * @return mixed|string {string} - Result URL.
4042
     * - Result URL.
4043
     */
4044
    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...
4045
    {
4046
        $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...
4047
        $virtualDir = isset($this->config['virtual_dir']) ? $this->config['virtual_dir'] : '';
4048
        $f_url_prefix = $this->config['friendly_url_prefix'];
4049
        $f_url_suffix = $this->config['friendly_url_suffix'];
4050
4051
        if (!is_numeric($id)) {
4052
            $this->messageQuit("`{$id}` is not numeric and may not be passed to makeUrl()");
4053
        }
4054
4055
        if ($args !== '') {
4056
            // add ? or & to $args if missing
4057
            $args = ltrim($args, '?&');
4058
            $_ = 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...
4059
4060
            if ($_ === false && $this->config['friendly_urls'] == 1) {
4061
                $args = "?{$args}";
4062
            } else {
4063
                $args = "&{$args}";
4064
            }
4065
        }
4066
4067
        if ($id != $this->config['site_start']) {
4068
            if ($this->config['friendly_urls'] == 1 && $alias == '') {
4069
                $alias = $id;
4070
                $alPath = '';
4071
4072
                if ($this->config['friendly_alias_urls'] == 1) {
4073
4074
                    if ($this->config['aliaslistingfolder'] == 1) {
4075
                        $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...
4076
                    } else {
4077
                        $al = $this->aliasListing[$id];
4078
                    }
4079
4080
                    if ($al['isfolder'] === 1 && $this->config['make_folders'] === '1') {
4081
                        $f_url_suffix = '/';
4082
                    }
4083
4084
                    $alPath = !empty ($al['path']) ? $al['path'] . '/' : '';
4085
4086
                    if ($al && $al['alias']) {
4087
                        $alias = $al['alias'];
4088
                    }
4089
4090
                }
4091
4092
                $alias = $alPath . $f_url_prefix . $alias . $f_url_suffix;
4093
                $url = "{$alias}{$args}";
4094
            } else {
4095
                $url = "index.php?id={$id}{$args}";
4096
            }
4097
        } else {
4098
            $url = $args;
4099
        }
4100
4101
        $host = $this->config['base_url'];
4102
4103
        // check if scheme argument has been set
4104
        if ($scheme != '') {
4105
            // for backward compatibility - check if the desired scheme is different than the current scheme
4106
            if (is_numeric($scheme) && $scheme != $_SERVER['HTTPS']) {
4107
                $scheme = ($_SERVER['HTTPS'] ? 'http' : 'https');
4108
            }
4109
4110
            //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...
4111
            $host = $scheme == 'full' ? $this->config['site_url'] : $scheme . '://' . $_SERVER['HTTP_HOST'] . $host;
4112
        }
4113
4114
        //fix strictUrl by Bumkaka
4115
        if ($this->config['seostrict'] == '1') {
4116
            $url = $this->toAlias($url);
4117
        }
4118
4119
        if ($this->config['xhtml_urls']) {
4120
            $url = preg_replace("/&(?!amp;)/", "&amp;", $host . $virtualDir . $url);
4121
        } else {
4122
            $url = $host . $virtualDir . $url;
4123
        }
4124
4125
        $evtOut = $this->invokeEvent('OnMakeDocUrl', array(
4126
            'id' => $id,
4127
            'url' => $url
4128
        ));
4129
4130
        if (is_array($evtOut) && count($evtOut) > 0) {
4131
            $url = array_pop($evtOut);
4132
        }
4133
4134
        return $url;
4135
    }
4136
4137
    /**
4138
     * @param $id
4139
     * @return mixed
4140
     */
4141
    public function getAliasListing($id)
4142
    {
4143
        if (isset($this->aliasListing[$id])) {
4144
            $out = $this->aliasListing[$id];
4145
        } else {
4146
            $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...
4147
            if ($this->db->getRecordCount($q) == '1') {
4148
                $q = $this->db->getRow($q);
4149
                $this->aliasListing[$id] = array(
4150
                    'id' => (int)$q['id'],
4151
                    'alias' => $q['alias'] == '' ? $q['id'] : $q['alias'],
4152
                    'parent' => (int)$q['parent'],
4153
                    'isfolder' => (int)$q['isfolder'],
4154
                );
4155
                if ($this->aliasListing[$id]['parent'] > 0) {
4156
                    //fix alias_path_usage
4157
                    if ($this->config['use_alias_path'] == '1') {
4158
                        //&& $tmp['path'] != '' - fix error slash with epty path
4159
                        $tmp = $this->getAliasListing($this->aliasListing[$id]['parent']);
4160
                        $this->aliasListing[$id]['path'] = $tmp['path'] . ($tmp['alias_visible'] ? (($tmp['parent'] > 0 && $tmp['path'] != '') ? '/' : '') . $tmp['alias'] : '');
4161
                    } else {
4162
                        $this->aliasListing[$id]['path'] = '';
4163
                    }
4164
                }
4165
4166
                $out = $this->aliasListing[$id];
4167
            }
4168
        }
4169
        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...
4170
    }
4171
4172
    /**
4173
     * Returns an entry from the config
4174
     *
4175
     * Note: most code accesses the config array directly and we will continue to support this.
4176
     *
4177
     * @param string $name
4178
     * @return bool|string
4179
     */
4180
    public function getConfig($name = '')
4181
    {
4182
        if (!empty ($this->config[$name])) {
4183
            return $this->config[$name];
4184
        } else {
4185
            return false;
4186
        }
4187
    }
4188
4189
    /**
4190
     * Returns the MODX version information as version, branch, release date and full application name.
4191
     *
4192
     * @param null $data
4193
     * @return array
4194
     */
4195
4196
    public function getVersionData($data = null)
4197
    {
4198
        $out = array();
4199
        if (empty($this->version) || !is_array($this->version)) {
4200
            //include for compatibility modx version < 1.0.10
4201
            include MODX_MANAGER_PATH . "includes/version.inc.php";
4202
            $this->version = array();
4203
            $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...
4204
            $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...
4205
            $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...
4206
            $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...
4207
            $this->version['new_version'] = isset($this->config['newversiontext']) ? $this->config['newversiontext'] : '';
4208
        }
4209
        return (!is_null($data) && is_array($this->version) && isset($this->version[$data])) ? $this->version[$data] : $this->version;
4210
    }
4211
4212
    /**
4213
     * Executes a snippet.
4214
     *
4215
     * @param string $snippetName
4216
     * @param array $params Default: Empty array
4217
     * @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...
4218
     */
4219
    public function runSnippet($snippetName, $params = array())
4220
    {
4221
        if (isset ($this->snippetCache[$snippetName])) {
4222
            $snippet = $this->snippetCache[$snippetName];
4223
            $properties = !empty($this->snippetCache[$snippetName . "Props"]) ? $this->snippetCache[$snippetName . "Props"] : '';
4224
        } else { // not in cache so let's check the db
4225
            $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;";
4226
            $result = $this->db->query($sql);
4227
            if ($this->db->getRecordCount($result) == 1) {
4228
                $row = $this->db->getRow($result);
4229
                $snippet = $this->snippetCache[$snippetName] = $row['snippet'];
4230
                $mergedProperties = array_merge($this->parseProperties($row['properties']), $this->parseProperties($row['sharedproperties']));
4231
                $properties = $this->snippetCache[$snippetName . "Props"] = json_encode($mergedProperties);
4232
            } else {
4233
                $snippet = $this->snippetCache[$snippetName] = "return false;";
4234
                $properties = $this->snippetCache[$snippetName . "Props"] = '';
4235
            }
4236
        }
4237
        // load default params/properties
4238
        $parameters = $this->parseProperties($properties, $snippetName, 'snippet');
4239
        $parameters = array_merge($parameters, $params);
4240
4241
        // run snippet
4242
        return $this->evalSnippet($snippet, $parameters);
4243
    }
4244
4245
    /**
4246
     * Returns the chunk content for the given chunk name
4247
     *
4248
     * @param string $chunkName
4249
     * @return boolean|string
4250
     */
4251
    public function getChunk($chunkName)
4252
    {
4253
        $out = null;
4254
        if (empty($chunkName)) {
4255
            return $out;
4256
        }
4257
        if (isset ($this->chunkCache[$chunkName])) {
4258
            $out = $this->chunkCache[$chunkName];
4259
        } else if (stripos($chunkName, '@FILE') === 0) {
4260
            $out = $this->chunkCache[$chunkName] = $this->atBindFileContent($chunkName);
4261
        } else {
4262
            $where = sprintf("`name`='%s' AND disabled=0", $this->db->escape($chunkName));
4263
            $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...
4264
            if ($this->db->getRecordCount($rs) == 1) {
4265
                $row = $this->db->getRow($rs);
4266
                $out = $this->chunkCache[$chunkName] = $row['snippet'];
4267
            } else {
4268
                $out = $this->chunkCache[$chunkName] = null;
4269
            }
4270
        }
4271
        return $out;
4272
    }
4273
4274
    /**
4275
     * parseText
4276
     * @version 1.0 (2013-10-17)
4277
     *
4278
     * @desc Replaces placeholders in text with required values.
4279
     *
4280
     * @param string $tpl
4281
     * @param array $ph
4282
     * @param string $left
4283
     * @param string $right
4284
     * @param bool $execModifier
4285
     * @return string {string} - Parsed text.
4286
     * - Parsed text.
4287
     * @internal param $chunk {string} - String to parse. - String to parse. @required
4288
     * @internal param $chunkArr {array} - Array of values. Key — placeholder name, value — value. - Array of values. Key — placeholder name, value — value. @required
4289
     * @internal param $prefix {string} - Placeholders prefix. Default: '[+'. - Placeholders prefix. Default: '[+'.
4290
     * @internal param $suffix {string} - Placeholders suffix. Default: '+]'. - Placeholders suffix. Default: '+]'.
4291
     *
4292
     */
4293
    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...
4294
    {
4295
        if (empty($ph) || empty($tpl)) {
4296
            return $tpl;
4297
        }
4298
4299 View Code Duplication
        if ($this->config['enable_at_syntax']) {
4300
            if (stripos($tpl, '<@LITERAL>') !== false) {
4301
                $tpl = $this->escapeLiteralTagsContent($tpl);
4302
            }
4303
        }
4304
4305
        $matches = $this->getTagsFromContent($tpl, $left, $right);
4306
        if (empty($matches)) {
4307
            return $tpl;
4308
        }
4309
4310
        foreach ($matches[1] as $i => $key) {
4311
4312
            if (strpos($key, ':') !== false && $execModifier) {
4313
                list($key, $modifiers) = $this->splitKeyAndFilter($key);
4314
            } else {
4315
                $modifiers = false;
4316
            }
4317
4318
            //          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...
4319
            if (!array_key_exists($key, $ph)) {
4320
                continue;
4321
            } //NULL values must be saved in placeholders, if we got them from database string
4322
4323
            $value = $ph[$key];
4324
4325
            $s = &$matches[0][$i];
4326
            if ($modifiers !== false) {
4327
                if (strpos($modifiers, $left) !== false) {
4328
                    $modifiers = $this->parseText($modifiers, $ph, $left, $right);
4329
                }
4330
                $value = $this->applyFilter($value, $modifiers, $key);
4331
            }
4332 View Code Duplication
            if (strpos($tpl, $s) !== false) {
4333
                $tpl = str_replace($s, $value, $tpl);
4334
            } elseif($this->debug) {
4335
                $this->addLog('parseText parse error', $_SERVER['REQUEST_URI'] . $s, 2);
4336
            }
4337
        }
4338
4339
        return $tpl;
4340
    }
4341
4342
    /**
4343
     * parseChunk
4344
     * @version 1.1 (2013-10-17)
4345
     *
4346
     * @desc Replaces placeholders in a chunk with required values.
4347
     *
4348
     * @param $chunkName {string} - Name of chunk to parse. @required
4349
     * @param $chunkArr {array} - Array of values. Key — placeholder name, value — value. @required
4350
     * @param string $prefix {string}
4351
     * - Placeholders prefix. Default: '{'.
4352
     * @param string $suffix {string}
4353
     * - Placeholders suffix. Default: '}'.
4354
     * @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...
4355
     * - Parsed chunk or false if $chunkArr is not array.
4356
     */
4357
    public function parseChunk($chunkName, $chunkArr, $prefix = '{', $suffix = '}')
4358
    {
4359
        //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...
4360
        if (!is_array($chunkArr)) {
4361
            return false;
4362
        }
4363
4364
        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...
4365
    }
4366
4367
    /**
4368
     * getTpl
4369
     * get template for snippets
4370
     * @param $tpl {string}
4371
     * @return bool|string {string}
4372
     */
4373
    public function getTpl($tpl)
4374
    {
4375
        $template = $tpl;
4376
        if (preg_match("~^@([^:\s]+)[:\s]+(.+)$~", $tpl, $match)) {
4377
            $command = strtoupper($match[1]);
4378
            $template = $match[2];
4379
        }
4380
        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...
4381
            case 'CODE':
4382
                break;
4383
            case 'FILE':
4384
                $template = file_get_contents(MODX_BASE_PATH . $template);
4385
                break;
4386
            case 'CHUNK':
4387
                $template = $this->getChunk($template);
4388
                break;
4389
            case 'DOCUMENT':
4390
                $doc = $this->getDocument($template, 'content', 'all');
4391
                $template = $doc['content'];
4392
                break;
4393
            case 'SELECT':
4394
                $this->db->getValue($this->db->query("SELECT {$template}"));
4395
                break;
4396
            default:
4397
                if (!($template = $this->getChunk($tpl))) {
4398
                    $template = $tpl;
4399
                }
4400
        }
4401
        return $template;
4402
    }
4403
4404
    /**
4405
     * Returns the timestamp in the date format defined in $this->config['datetime_format']
4406
     *
4407
     * @param int $timestamp Default: 0
4408
     * @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.
4409
     * @return string
4410
     */
4411
    public function toDateFormat($timestamp = 0, $mode = '')
4412
    {
4413
        $timestamp = trim($timestamp);
4414
        if ($mode !== 'formatOnly' && empty($timestamp)) {
4415
            return '-';
4416
        }
4417
        $timestamp = (int)$timestamp;
4418
4419
        switch ($this->config['datetime_format']) {
4420
            case 'YYYY/mm/dd':
4421
                $dateFormat = '%Y/%m/%d';
4422
                break;
4423
            case 'dd-mm-YYYY':
4424
                $dateFormat = '%d-%m-%Y';
4425
                break;
4426
            case 'mm/dd/YYYY':
4427
                $dateFormat = '%m/%d/%Y';
4428
                break;
4429
            /*
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...
4430
            case 'dd-mmm-YYYY':
4431
                $dateFormat = '%e-%b-%Y';
4432
                break;
4433
            */
4434
        }
4435
4436
        if (empty($mode)) {
4437
            $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...
4438
        } elseif ($mode == 'dateOnly') {
4439
            $strTime = strftime($dateFormat, $timestamp);
4440
        } elseif ($mode == 'formatOnly') {
4441
            $strTime = $dateFormat;
4442
        }
4443
        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...
4444
    }
4445
4446
    /**
4447
     * Make a timestamp from a string corresponding to the format in $this->config['datetime_format']
4448
     *
4449
     * @param string $str
4450
     * @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...
4451
     */
4452
    public function toTimeStamp($str)
4453
    {
4454
        $str = trim($str);
4455
        if (empty($str)) {
4456
            return '';
4457
        }
4458
4459
        switch ($this->config['datetime_format']) {
4460 View Code Duplication
            case 'YYYY/mm/dd':
4461
                if (!preg_match('/^[0-9]{4}\/[0-9]{2}\/[0-9]{2}[0-9 :]*$/', $str)) {
4462
                    return '';
4463
                }
4464
                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...
4465
                break;
4466 View Code Duplication
            case 'dd-mm-YYYY':
4467
                if (!preg_match('/^[0-9]{2}-[0-9]{2}-[0-9]{4}[0-9 :]*$/', $str)) {
4468
                    return '';
4469
                }
4470
                list ($d, $m, $Y, $H, $M, $S) = sscanf($str, '%2d-%2d-%4d %2d:%2d:%2d');
4471
                break;
4472 View Code Duplication
            case 'mm/dd/YYYY':
4473
                if (!preg_match('/^[0-9]{2}\/[0-9]{2}\/[0-9]{4}[0-9 :]*$/', $str)) {
4474
                    return '';
4475
                }
4476
                list ($m, $d, $Y, $H, $M, $S) = sscanf($str, '%2d/%2d/%4d %2d:%2d:%2d');
4477
                break;
4478
            /*
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...
4479
            case 'dd-mmm-YYYY':
4480
                if (!preg_match('/^[0-9]{2}-[0-9a-z]+-[0-9]{4}[0-9 :]*$/i', $str)) {return '';}
4481
                list ($m, $d, $Y, $H, $M, $S) = sscanf($str, '%2d-%3s-%4d %2d:%2d:%2d');
4482
                break;
4483
            */
4484
        }
4485
        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...
4486
            $H = 0;
4487
            $M = 0;
4488
            $S = 0;
4489
        }
4490
        $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...
4491
        $timeStamp = (int)$timeStamp;
4492
        return $timeStamp;
4493
    }
4494
4495
    /**
4496
     * Get the TVs of a document's children. Returns an array where each element represents one child doc.
4497
     *
4498
     * Ignores deleted children. Gets all children - there is no where clause available.
4499
     *
4500
     * @param int $parentid The parent docid
4501
     *                 Default: 0 (site root)
4502
     * @param array $tvidnames . Which TVs to fetch - Can relate to the TV ids in the db (array elements should be numeric only)
4503
     *                                               or the TV names (array elements should be names only)
4504
     *                      Default: Empty array
4505
     * @param int $published Whether published or unpublished documents are in the result
4506
     *                      Default: 1
4507
     * @param string $docsort How to sort the result array (field)
4508
     *                      Default: menuindex
4509
     * @param ASC|string $docsortdir How to sort the result array (direction)
4510
     *                      Default: ASC
4511
     * @param string $tvfields Fields to fetch from site_tmplvars, default '*'
4512
     *                      Default: *
4513
     * @param string $tvsort How to sort each element of the result array i.e. how to sort the TVs (field)
4514
     *                      Default: rank
4515
     * @param string $tvsortdir How to sort each element of the result array i.e. how to sort the TVs (direction)
4516
     *                      Default: ASC
4517
     * @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...
4518
     */
4519
    public function getDocumentChildrenTVars($parentid = 0, $tvidnames = array(), $published = 1, $docsort = "menuindex", $docsortdir = "ASC", $tvfields = "*", $tvsort = "rank", $tvsortdir = "ASC")
4520
    {
4521
        $docs = $this->getDocumentChildren($parentid, $published, 0, '*', '', $docsort, $docsortdir);
4522
        if (!$docs) {
4523
            return false;
4524
        } else {
4525
            $result = array();
4526
            // get user defined template variables
4527
            if ($tvfields) {
4528
                $_ = 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...
4529
                foreach ($_ as $i => $v) {
4530
                    if ($v === 'value') {
4531
                        unset($_[$i]);
4532
                    } else {
4533
                        $_[$i] = 'tv.' . $v;
4534
                    }
4535
                }
4536
                $fields = implode(',', $_);
4537
            } else {
4538
                $fields = "tv.*";
4539
            }
4540
4541
            if ($tvsort != '') {
4542
                $tvsort = 'tv.' . implode(',tv.', array_filter(array_map('trim', explode(',', $tvsort))));
4543
            }
4544 View Code Duplication
            if ($tvidnames == "*") {
4545
                $query = "tv.id<>0";
4546
            } else {
4547
                $query = (is_numeric($tvidnames[0]) ? "tv.id" : "tv.name") . " IN ('" . implode("','", $tvidnames) . "')";
4548
            }
4549
4550
            $this->getUserDocGroups();
4551
4552
            foreach ($docs as $doc) {
4553
4554
                $docid = $doc['id'];
4555
4556
                $rs = $this->db->select("{$fields}, IF(tvc.value!='',tvc.value,tv.default_text) as value ", "[+prefix+]site_tmplvars tv
4557
                        INNER JOIN [+prefix+]site_tmplvar_templates tvtpl ON tvtpl.tmplvarid = tv.id
4558
                        LEFT JOIN [+prefix+]site_tmplvar_contentvalues tvc ON tvc.tmplvarid=tv.id AND tvc.contentid='{$docid}'", "{$query} AND tvtpl.templateid = '{$doc['template']}'", ($tvsort ? "{$tvsort} {$tvsortdir}" : ""));
4559
                $tvs = $this->db->makeArray($rs);
4560
4561
                // get default/built-in template variables
4562
                ksort($doc);
4563
                foreach ($doc as $key => $value) {
4564
                    if ($tvidnames == '*' || in_array($key, $tvidnames)) {
4565
                        $tvs[] = array('name' => $key, 'value' => $value);
4566
                    }
4567
                }
4568
                if (is_array($tvs) && count($tvs)) {
4569
                    $result[] = $tvs;
4570
                }
4571
            }
4572
            return $result;
4573
        }
4574
    }
4575
4576
    /**
4577
     * getDocumentChildrenTVarOutput
4578
     * @version 1.1 (2014-02-19)
4579
     *
4580
     * @desc Returns an array where each element represents one child doc and contains the result from getTemplateVarOutput().
4581
     *
4582
     * @param int $parentid {integer}
4583
     * - Id of parent document. Default: 0 (site root).
4584
     * @param array $tvidnames {array; '*'}
4585
     * - Which TVs to fetch. In the form expected by getTemplateVarOutput(). Default: array().
4586
     * @param int $published {0; 1; 'all'}
4587
     * - 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.
4588
     * @param string $sortBy {string}
4589
     * - How to sort the result array (field). Default: 'menuindex'.
4590
     * @param string $sortDir {'ASC'; 'DESC'}
4591
     * - How to sort the result array (direction). Default: 'ASC'.
4592
     * @param string $where {string}
4593
     * - SQL WHERE condition (use only document fields, not TV). Default: ''.
4594
     * @param string $resultKey {string; false}
4595
     * - Field, which values are keys into result array. Use the “false”, that result array keys just will be numbered. Default: 'id'.
4596
     * @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...
4597
     * - Result array, or false.
4598
     */
4599
    public function getDocumentChildrenTVarOutput($parentid = 0, $tvidnames = array(), $published = 1, $sortBy = 'menuindex', $sortDir = 'ASC', $where = '', $resultKey = 'id')
4600
    {
4601
        $docs = $this->getDocumentChildren($parentid, $published, 0, 'id', $where, $sortBy, $sortDir);
4602
4603
        if (!$docs) {
4604
            return false;
4605
        } else {
4606
            $result = array();
4607
4608
            $unsetResultKey = false;
4609
4610
            if ($resultKey !== false) {
4611
                if (is_array($tvidnames)) {
4612
                    if (count($tvidnames) != 0 && !in_array($resultKey, $tvidnames)) {
4613
                        $tvidnames[] = $resultKey;
4614
                        $unsetResultKey = true;
4615
                    }
4616
                } else if ($tvidnames != '*' && $tvidnames != $resultKey) {
4617
                    $tvidnames = array($tvidnames, $resultKey);
4618
                    $unsetResultKey = true;
4619
                }
4620
            }
4621
4622
            for ($i = 0; $i < count($docs); $i++) {
4623
                $tvs = $this->getTemplateVarOutput($tvidnames, $docs[$i]['id'], $published);
4624
4625
                if ($tvs) {
4626
                    if ($resultKey !== false && array_key_exists($resultKey, $tvs)) {
4627
                        $result[$tvs[$resultKey]] = $tvs;
4628
4629
                        if ($unsetResultKey) {
4630
                            unset($result[$tvs[$resultKey]][$resultKey]);
4631
                        }
4632
                    } else {
4633
                        $result[] = $tvs;
4634
                    }
4635
                }
4636
            }
4637
4638
            return $result;
4639
        }
4640
    }
4641
4642
    /**
4643
     * Modified by Raymond for TV - Orig Modified by Apodigm - DocVars
4644
     * Returns a single site_content field or TV record from the db.
4645
     *
4646
     * If a site content field the result is an associative array of 'name' and 'value'.
4647
     *
4648
     * If a TV the result is an array representing a db row including the fields specified in $fields.
4649
     *
4650
     * @param string $idname Can be a TV id or name
4651
     * @param string $fields Fields to fetch from site_tmplvars. Default: *
4652
     * @param string|type $docid Docid. Defaults to empty string which indicates the current document.
4653
     * @param int $published Whether published or unpublished documents are in the result
4654
     *                        Default: 1
4655
     * @return bool
4656
     */
4657 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...
4658
    {
4659
        if ($idname == "") {
4660
            return false;
4661
        } else {
4662
            $result = $this->getTemplateVars(array($idname), $fields, $docid, $published, "", ""); //remove sorting for speed
4663
            return ($result != false) ? $result[0] : false;
4664
        }
4665
    }
4666
4667
    /**
4668
     * getTemplateVars
4669
     * @version 1.0.1 (2014-02-19)
4670
     *
4671
     * @desc Returns an array of site_content field fields and/or TV records from the db.
4672
     * Elements representing a site content field consist of an associative array of 'name' and 'value'.
4673
     * Elements representing a TV consist of an array representing a db row including the fields specified in $fields.
4674
     *
4675
     * @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
4676
     * @param $fields {comma separated string; '*'} - Fields names in the TV table of MODx database. Default: '*'
4677
     * @param $docid {integer; ''} - Id of a document to get. Default: an empty string which indicates the current document.
4678
     * @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.
4679
     * @param $sort {comma separated string} - Fields of the TV table to sort by. Default: 'rank'.
4680
     * @param $dir {'ASC'; 'DESC'} - How to sort the result array (direction). Default: 'ASC'.
4681
     *
4682
     * @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...
4683
     */
4684
    public function getTemplateVars($idnames = array(), $fields = '*', $docid = '', $published = 1, $sort = 'rank', $dir = 'ASC')
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
4685
    {
4686
        $cacheKey = md5(print_r(func_get_args(), true));
4687
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
4688
            return $this->tmpCache[__FUNCTION__][$cacheKey];
4689
        }
4690
4691
        if (($idnames != '*' && !is_array($idnames)) || empty($idnames) ) {
4692
            return false;
4693
        } else {
4694
4695
            // get document record
4696
            if ($docid == '') {
4697
                $docid = $this->documentIdentifier;
4698
                $docRow = $this->documentObject;
4699
            } else {
4700
                $docRow = $this->getDocument($docid, '*', $published);
4701
4702
                if (!$docRow) {
4703
                    $this->tmpCache[__FUNCTION__][$cacheKey] = false;
4704
                    return false;
4705
                }
4706
            }
4707
4708
            // get user defined template variables
4709
            $fields = ($fields == '') ? 'tv.*' : 'tv.' . implode(',tv.', array_filter(array_map('trim', explode(',', $fields))));
4710
            $sort = ($sort == '') ? '' : 'tv.' . implode(',tv.', array_filter(array_map('trim', explode(',', $sort))));
4711
4712 View Code Duplication
            if ($idnames == '*') {
4713
                $query = 'tv.id<>0';
4714
            } else {
4715
                $query = (is_numeric($idnames[0]) ? 'tv.id' : 'tv.name') . " IN ('" . implode("','", $idnames) . "')";
4716
            }
4717
4718
            $rs = $this->db->select("{$fields}, IF(tvc.value != '', tvc.value, tv.default_text) as value", $this->getFullTableName('site_tmplvars') . " tv
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rs. Configured minimum length is 3.

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

Loading history...
4719
                    INNER JOIN " . $this->getFullTableName('site_tmplvar_templates') . " tvtpl ON tvtpl.tmplvarid = tv.id
4720
                    LEFT JOIN " . $this->getFullTableName('site_tmplvar_contentvalues') . " tvc ON tvc.tmplvarid=tv.id AND tvc.contentid = '{$docid}'", "{$query} AND tvtpl.templateid = '{$docRow['template']}'", ($sort ? "{$sort} {$dir}" : ""));
4721
4722
            $result = $this->db->makeArray($rs);
4723
4724
            // get default/built-in template variables
4725
            if(is_array($docRow)){
4726
                ksort($docRow);
4727
4728
                foreach ($docRow as $key => $value) {
4729
                    if ($idnames == '*' || in_array($key, $idnames)) {
4730
                        array_push($result, array(
4731
                            'name' => $key,
4732
                            'value' => $value
4733
                        ));
4734
                    }
4735
                }
4736
            }
4737
4738
            $this->tmpCache[__FUNCTION__][$cacheKey] = $result;
4739
4740
            return $result;
4741
        }
4742
    }
4743
4744
    /**
4745
     * getTemplateVarOutput
4746
     * @version 1.0.1 (2014-02-19)
4747
     *
4748
     * @desc Returns an associative array containing TV rendered output values.
4749
     *
4750
     * @param array $idnames {array; '*'}
4751
     * - 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
4752
     * @param string $docid {integer; ''}
4753
     * - Id of a document to get. Default: an empty string which indicates the current document.
4754
     * @param int $published {0; 1; 'all'}
4755
     * - 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.
4756
     * @param string $sep {string}
4757
     * - Separator that is used while concatenating in getTVDisplayFormat(). Default: ''.
4758
     * @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...
4759
     * - Result array, or false.
4760
     */
4761
    public function getTemplateVarOutput($idnames = array(), $docid = '', $published = 1, $sep = '')
4762
    {
4763
        if (is_array($idnames) && empty($idnames) ) {
4764
            return false;
4765
        } else {
4766
            $output = array();
4767
            $vars = ($idnames == '*' || is_array($idnames)) ? $idnames : array($idnames);
4768
4769
            $docid = (int)$docid > 0 ? (int)$docid : $this->documentIdentifier;
4770
            // remove sort for speed
4771
            $result = $this->getTemplateVars($vars, '*', $docid, $published, '', '');
4772
4773
            if ($result == false) {
4774
                return false;
4775
            } else {
4776
                $baspath = MODX_MANAGER_PATH . 'includes';
4777
                include_once $baspath . '/tmplvars.format.inc.php';
4778
                include_once $baspath . '/tmplvars.commands.inc.php';
4779
4780
                for ($i = 0; $i < count($result); $i++) {
4781
                    $row = $result[$i];
4782
4783
                    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...
4784
                        $output[$row['name']] = $row['value'];
4785
                    } else {
4786
                        $output[$row['name']] = getTVDisplayFormat($row['name'], $row['value'], $row['display'], $row['display_params'], $row['type'], $docid, $sep);
4787
                    }
4788
                }
4789
4790
                return $output;
4791
            }
4792
        }
4793
    }
4794
4795
    /**
4796
     * Returns the full table name based on db settings
4797
     *
4798
     * @param string $tbl Table name
4799
     * @return string Table name with prefix
4800
     */
4801
    public function getFullTableName($tbl)
4802
    {
4803
        return $this->db->config['dbase'] . ".`" . $this->db->config['table_prefix'] . $tbl . "`";
4804
    }
4805
4806
    /**
4807
     * Returns the placeholder value
4808
     *
4809
     * @param string $name Placeholder name
4810
     * @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...
4811
     */
4812
    public function getPlaceholder($name)
4813
    {
4814
        return isset($this->placeholders[$name]) ? $this->placeholders[$name] : null;
4815
    }
4816
4817
    /**
4818
     * Sets a value for a placeholder
4819
     *
4820
     * @param string $name The name of the placeholder
4821
     * @param string $value The value of the placeholder
4822
     */
4823
    public function setPlaceholder($name, $value)
4824
    {
4825
        $this->placeholders[$name] = $value;
4826
    }
4827
4828
    /**
4829
     * Set placeholders en masse via an array or object.
4830
     *
4831
     * @param object|array $subject
4832
     * @param string $prefix
4833
     */
4834
    public function toPlaceholders($subject, $prefix = '')
4835
    {
4836
        if (is_object($subject)) {
4837
            $subject = get_object_vars($subject);
4838
        }
4839
        if (is_array($subject)) {
4840
            foreach ($subject as $key => $value) {
4841
                $this->toPlaceholder($key, $value, $prefix);
4842
            }
4843
        }
4844
    }
4845
4846
    /**
4847
     * For use by toPlaceholders(); For setting an array or object element as placeholder.
4848
     *
4849
     * @param string $key
4850
     * @param object|array $value
4851
     * @param string $prefix
4852
     */
4853
    public function toPlaceholder($key, $value, $prefix = '')
4854
    {
4855
        if (is_array($value) || is_object($value)) {
4856
            $this->toPlaceholders($value, "{$prefix}{$key}.");
4857
        } else {
4858
            $this->setPlaceholder("{$prefix}{$key}", $value);
4859
        }
4860
    }
4861
4862
    /**
4863
     * Returns the manager relative URL/path with respect to the site root.
4864
     *
4865
     * @global string $base_url
4866
     * @return string The complete URL to the manager folder
4867
     */
4868
    public function getManagerPath()
4869
    {
4870
        return MODX_MANAGER_URL;
4871
    }
4872
4873
    /**
4874
     * Returns the cache relative URL/path with respect to the site root.
4875
     *
4876
     * @global string $base_url
4877
     * @return string The complete URL to the cache folder
4878
     */
4879
    public function getCachePath()
4880
    {
4881
        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...
4882
        $pth = $base_url . $this->getCacheFolder();
4883
        return $pth;
4884
    }
4885
4886
    /**
4887
     * Sends a message to a user's message box.
4888
     *
4889
     * @param string $type Type of the message
4890
     * @param string $to The recipient of the message
4891
     * @param string $from The sender of the message
4892
     * @param string $subject The subject of the message
4893
     * @param string $msg The message body
4894
     * @param int $private Whether it is a private message, or not
4895
     *                     Default : 0
4896
     */
4897
    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...
4898
    {
4899
        $private = ($private) ? 1 : 0;
4900 View Code Duplication
        if (!is_numeric($to)) {
4901
            // Query for the To ID
4902
            $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...
4903
            $to = $this->db->getValue($rs);
4904
        }
4905 View Code Duplication
        if (!is_numeric($from)) {
4906
            // Query for the From ID
4907
            $rs = $this->db->select('id', $this->getFullTableName("manager_users"), "username='{$from}'");
4908
            $from = $this->db->getValue($rs);
4909
        }
4910
        // insert a new message into user_messages
4911
        $this->db->insert(array(
4912
            'type' => $type,
4913
            'subject' => $subject,
4914
            'message' => $msg,
4915
            'sender' => $from,
4916
            'recipient' => $to,
4917
            'private' => $private,
4918
            'postdate' => $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'],
4919
            'messageread' => 0,
4920
        ), $this->getFullTableName('user_messages'));
4921
    }
4922
4923
    /**
4924
     * Returns current user id.
4925
     *
4926
     * @param string $context . Default is an empty string which indicates the method should automatically pick 'web (frontend) or 'mgr' (backend)
4927
     * @return string
4928
     */
4929 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...
4930
    {
4931
        $out = false;
4932
4933
        if (!empty($context)) {
4934
            if (is_scalar($context) && isset($_SESSION[$context . 'Validated'])) {
4935
                $out = $_SESSION[$context . 'InternalKey'];
4936
            }
4937
        } else {
4938
            switch (true) {
4939
                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...
4940
                    $out = $_SESSION['webInternalKey'];
4941
                    break;
4942
                }
4943
                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...
4944
                    $out = $_SESSION['mgrInternalKey'];
4945
                    break;
4946
                }
4947
            }
4948
        }
4949
        return $out;
4950
    }
4951
4952
    /**
4953
     * Returns current user name
4954
     *
4955
     * @param string $context . Default is an empty string which indicates the method should automatically pick 'web (frontend) or 'mgr' (backend)
4956
     * @return string
4957
     */
4958 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...
4959
    {
4960
        $out = false;
4961
4962
        if (!empty($context)) {
4963
            if (is_scalar($context) && isset($_SESSION[$context . 'Validated'])) {
4964
                $out = $_SESSION[$context . 'Shortname'];
4965
            }
4966
        } else {
4967
            switch (true) {
4968
                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...
4969
                    $out = $_SESSION['webShortname'];
4970
                    break;
4971
                }
4972
                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...
4973
                    $out = $_SESSION['mgrShortname'];
4974
                    break;
4975
                }
4976
            }
4977
        }
4978
        return $out;
4979
    }
4980
4981
    /**
4982
     * Returns current login user type - web or manager
4983
     *
4984
     * @return string
4985
     */
4986
    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...
4987
    {
4988
        if ($this->isFrontend() && isset ($_SESSION['webValidated'])) {
4989
            return 'web';
4990
        } elseif ($this->isBackend() && isset ($_SESSION['mgrValidated'])) {
4991
            return 'manager';
4992
        } else {
4993
            return '';
4994
        }
4995
    }
4996
4997
    /**
4998
     * Returns a user info record for the given manager user
4999
     *
5000
     * @param int $uid
5001
     * @return boolean|string
5002
     */
5003
    public function getUserInfo($uid)
5004
    {
5005
        if (isset($this->tmpCache[__FUNCTION__][$uid])) {
5006
            return $this->tmpCache[__FUNCTION__][$uid];
5007
        }
5008
5009
        $from = '[+prefix+]manager_users mu INNER JOIN [+prefix+]user_attributes mua ON mua.internalkey=mu.id';
5010
        $where = sprintf("mu.id='%s'", $this->db->escape($uid));
5011
        $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...
5012
5013
        if (!$this->db->getRecordCount($rs)) {
5014
            return $this->tmpCache[__FUNCTION__][$uid] = false;
5015
        }
5016
5017
        $row = $this->db->getRow($rs);
5018 View Code Duplication
        if (!isset($row['usertype']) || !$row['usertype']) {
5019
            $row['usertype'] = 'manager';
5020
        }
5021
5022
        $this->tmpCache[__FUNCTION__][$uid] = $row;
5023
5024
        return $row;
5025
    }
5026
5027
    /**
5028
     * Returns a record for the web user
5029
     *
5030
     * @param int $uid
5031
     * @return boolean|string
5032
     */
5033
    public function getWebUserInfo($uid)
5034
    {
5035
        $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...
5036
                INNER JOIN " . $this->getFullTableName("web_user_attributes") . " wua ON wua.internalkey=wu.id", "wu.id='{$uid}'");
5037
        if ($row = $this->db->getRow($rs)) {
5038 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...
5039
                $row["usertype"] = "web";
5040
            }
5041
            return $row;
5042
        }
5043
    }
5044
5045
    /**
5046
     * Returns an array of document groups that current user is assigned to.
5047
     * This function will first return the web user doc groups when running from
5048
     * frontend otherwise it will return manager user's docgroup.
5049
     *
5050
     * @param boolean $resolveIds Set to true to return the document group names
5051
     *                            Default: false
5052
     * @return string|array
5053
     */
5054
    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...
5055
    {
5056
        if ($this->isFrontend() && isset($_SESSION['webDocgroups']) && isset($_SESSION['webValidated'])) {
5057
            $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...
5058
            $dgn = isset($_SESSION['webDocgrpNames']) ? $_SESSION['webDocgrpNames'] : false;
5059
        } else if ($this->isBackend() && isset($_SESSION['mgrDocgroups']) && isset($_SESSION['mgrValidated'])) {
5060
            $dg = $_SESSION['mgrDocgroups'];
5061
            $dgn = isset($_SESSION['mgrDocgrpNames']) ? $_SESSION['mgrDocgrpNames'] : false;
5062
        } else {
5063
            $dg = '';
5064
        }
5065
        if (!$resolveIds) {
5066
            return $dg;
5067
        } else if (is_array($dgn)) {
5068
            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...
5069
        } else if (is_array($dg)) {
5070
            // resolve ids to names
5071
            $dgn = array();
5072
            $ds = $this->db->select('name', $this->getFullTableName("documentgroup_names"), "id IN (" . implode(",", $dg) . ")");
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ds. Configured minimum length is 3.

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

Loading history...
5073
            while ($row = $this->db->getRow($ds)) {
5074
                $dgn[] = $row['name'];
5075
            }
5076
            // cache docgroup names to session
5077
            if ($this->isFrontend()) {
5078
                $_SESSION['webDocgrpNames'] = $dgn;
5079
            } else {
5080
                $_SESSION['mgrDocgrpNames'] = $dgn;
5081
            }
5082
            return $dgn;
5083
        }
5084
    }
5085
5086
    /**
5087
     * Change current web user's password
5088
     *
5089
     * @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...
5090
     * @param string $oldPwd
5091
     * @param string $newPwd
5092
     * @return string|boolean Returns true if successful, oterhwise return error
5093
     *                        message
5094
     */
5095
    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...
5096
    {
5097
        $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...
5098
        if ($_SESSION["webValidated"] == 1) {
5099
            $tbl = $this->getFullTableName("web_users");
5100
            $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...
5101
            if ($row = $this->db->getRow($ds)) {
5102
                if ($row["password"] == md5($oldPwd)) {
5103
                    if (strlen($newPwd) < 6) {
5104
                        return "Password is too short!";
5105
                    } elseif ($newPwd == "") {
5106
                        return "You didn't specify a password for this user!";
5107
                    } else {
5108
                        $this->db->update(array(
5109
                            'password' => $this->db->escape($newPwd),
5110
                        ), $tbl, "id='" . $this->getLoginUserID() . "'");
5111
                        // invoke OnWebChangePassword event
5112
                        $this->invokeEvent("OnWebChangePassword", array(
5113
                            "userid" => $row["id"],
5114
                            "username" => $row["username"],
5115
                            "userpassword" => $newPwd
5116
                        ));
5117
                        return true;
5118
                    }
5119
                } else {
5120
                    return "Incorrect password.";
5121
                }
5122
            }
5123
        }
5124
        return $rt;
5125
    }
5126
5127
    /**
5128
     * Returns true if the current web user is a member the specified groups
5129
     *
5130
     * @param array $groupNames
5131
     * @return boolean
5132
     */
5133
    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...
5134
    {
5135
        if (!is_array($groupNames)) {
5136
            return false;
5137
        }
5138
        // check cache
5139
        $grpNames = isset ($_SESSION['webUserGroupNames']) ? $_SESSION['webUserGroupNames'] : false;
5140
        if (!is_array($grpNames)) {
5141
            $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...
5142
                    INNER JOIN " . $this->getFullTableName("web_groups") . " wg ON wg.webgroup=wgn.id AND wg.webuser='" . $this->getLoginUserID() . "'");
5143
            $grpNames = $this->db->getColumn("name", $rs);
5144
            // save to cache
5145
            $_SESSION['webUserGroupNames'] = $grpNames;
5146
        }
5147
        foreach ($groupNames as $k => $v) {
5148
            if (in_array(trim($v), $grpNames)) {
5149
                return true;
5150
            }
5151
        }
5152
        return false;
5153
    }
5154
5155
    /**
5156
     * Registers Client-side CSS scripts - these scripts are loaded at inside
5157
     * the <head> tag
5158
     *
5159
     * @param string $src
5160
     * @param string $media Default: Empty string
5161
     * @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...
5162
     */
5163
    public function regClientCSS($src, $media = '')
5164
    {
5165
        if (empty($src) || isset ($this->loadedjscripts[$src])) {
5166
            return '';
5167
        }
5168
        $nextpos = max(array_merge(array(0), array_keys($this->sjscripts))) + 1;
5169
        $this->loadedjscripts[$src]['startup'] = true;
5170
        $this->loadedjscripts[$src]['version'] = '0';
5171
        $this->loadedjscripts[$src]['pos'] = $nextpos;
5172
        if (strpos(strtolower($src), "<style") !== false || strpos(strtolower($src), "<link") !== false) {
5173
            $this->sjscripts[$nextpos] = $src;
5174
        } else {
5175
            $this->sjscripts[$nextpos] = "\t" . '<link rel="stylesheet" type="text/css" href="' . $src . '" ' . ($media ? 'media="' . $media . '" ' : '') . '/>';
5176
        }
5177
    }
5178
5179
    /**
5180
     * Registers Startup Client-side JavaScript - these scripts are loaded at inside the <head> tag
5181
     *
5182
     * @param string $src
5183
     * @param array $options Default: 'name'=>'', 'version'=>'0', 'plaintext'=>false
5184
     */
5185
    public function regClientStartupScript($src, $options = array('name' => '', 'version' => '0', 'plaintext' => false))
5186
    {
5187
        $this->regClientScript($src, $options, true);
5188
    }
5189
5190
    /**
5191
     * Registers Client-side JavaScript these scripts are loaded at the end of the page unless $startup is true
5192
     *
5193
     * @param string $src
5194
     * @param array $options Default: 'name'=>'', 'version'=>'0', 'plaintext'=>false
5195
     * @param boolean $startup Default: false
5196
     * @return string
5197
     */
5198
    public function regClientScript($src, $options = array('name' => '', 'version' => '0', 'plaintext' => false), $startup = false)
5199
    {
5200
        if (empty($src)) {
5201
            return '';
5202
        } // nothing to register
5203
        if (!is_array($options)) {
5204
            if (is_bool($options))  // backward compatibility with old plaintext parameter
5205
            {
5206
                $options = array('plaintext' => $options);
5207
            } elseif (is_string($options)) // Also allow script name as 2nd param
5208
            {
5209
                $options = array('name' => $options);
5210
            } else {
5211
                $options = array();
5212
            }
5213
        }
5214
        $name = isset($options['name']) ? strtolower($options['name']) : '';
5215
        $version = isset($options['version']) ? $options['version'] : '0';
5216
        $plaintext = isset($options['plaintext']) ? $options['plaintext'] : false;
5217
        $key = !empty($name) ? $name : $src;
5218
        unset($overwritepos); // probably unnecessary--just making sure
5219
5220
        $useThisVer = true;
5221
        if (isset($this->loadedjscripts[$key])) { // a matching script was found
5222
            // if existing script is a startup script, make sure the candidate is also a startup script
5223
            if ($this->loadedjscripts[$key]['startup']) {
5224
                $startup = true;
5225
            }
5226
5227
            if (empty($name)) {
5228
                $useThisVer = false; // if the match was based on identical source code, no need to replace the old one
5229
            } else {
5230
                $useThisVer = version_compare($this->loadedjscripts[$key]['version'], $version, '<');
5231
            }
5232
5233
            if ($useThisVer) {
5234
                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...
5235
                    // remove old script from the bottom of the page (new one will be at the top)
5236
                    unset($this->jscripts[$this->loadedjscripts[$key]['pos']]);
5237
                } else {
5238
                    // overwrite the old script (the position may be important for dependent scripts)
5239
                    $overwritepos = $this->loadedjscripts[$key]['pos'];
5240
                }
5241
            } else { // Use the original version
5242
                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...
5243
                    // need to move the exisiting script to the head
5244
                    $version = $this->loadedjscripts[$key][$version];
5245
                    $src = $this->jscripts[$this->loadedjscripts[$key]['pos']];
5246
                    unset($this->jscripts[$this->loadedjscripts[$key]['pos']]);
5247
                } else {
5248
                    return ''; // the script is already in the right place
5249
                }
5250
            }
5251
        }
5252
5253
        if ($useThisVer && $plaintext != true && (strpos(strtolower($src), "<script") === false)) {
5254
            $src = "\t" . '<script type="text/javascript" src="' . $src . '"></script>';
5255
        }
5256
        if ($startup) {
5257
            $pos = isset($overwritepos) ? $overwritepos : max(array_merge(array(0), array_keys($this->sjscripts))) + 1;
5258
            $this->sjscripts[$pos] = $src;
5259
        } else {
5260
            $pos = isset($overwritepos) ? $overwritepos : max(array_merge(array(0), array_keys($this->jscripts))) + 1;
5261
            $this->jscripts[$pos] = $src;
5262
        }
5263
        $this->loadedjscripts[$key]['version'] = $version;
5264
        $this->loadedjscripts[$key]['startup'] = $startup;
5265
        $this->loadedjscripts[$key]['pos'] = $pos;
5266
        return '';
5267
    }
5268
5269
    /**
5270
     * Returns all registered JavaScripts
5271
     *
5272
     * @return string
5273
     */
5274
    public function regClientStartupHTMLBlock($html)
5275
    {
5276
        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...
5277
    }
5278
5279
    /**
5280
     * Returns all registered startup scripts
5281
     *
5282
     * @return string
5283
     */
5284
    public function regClientHTMLBlock($html)
5285
    {
5286
        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...
5287
    }
5288
5289
    /**
5290
     * Remove unwanted html tags and snippet, settings and tags
5291
     *
5292
     * @param string $html
5293
     * @param string $allowed Default: Empty string
5294
     * @return string
5295
     */
5296
    public function stripTags($html, $allowed = "")
5297
    {
5298
        $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...
5299
        $t = preg_replace('~\[\*(.*?)\*\]~', "", $t); //tv
5300
        $t = preg_replace('~\[\[(.*?)\]\]~', "", $t); //snippet
5301
        $t = preg_replace('~\[\!(.*?)\!\]~', "", $t); //snippet
5302
        $t = preg_replace('~\[\((.*?)\)\]~', "", $t); //settings
5303
        $t = preg_replace('~\[\+(.*?)\+\]~', "", $t); //placeholders
5304
        $t = preg_replace('~{{(.*?)}}~', "", $t); //chunks
5305
        return $t;
5306
    }
5307
5308
    /**
5309
     * Add an event listener to a plugin - only for use within the current execution cycle
5310
     *
5311
     * @param string $evtName
5312
     * @param string $pluginName
5313
     * @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...
5314
     */
5315
    public function addEventListener($evtName, $pluginName)
5316
    {
5317
        if (!$evtName || !$pluginName) {
5318
            return false;
5319
        }
5320
        if (!array_key_exists($evtName, $this->pluginEvent)) {
5321
            $this->pluginEvent[$evtName] = array();
5322
        }
5323
        return array_push($this->pluginEvent[$evtName], $pluginName); // return array count
5324
    }
5325
5326
    /**
5327
     * Remove event listener - only for use within the current execution cycle
5328
     *
5329
     * @param string $evtName
5330
     * @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...
5331
     */
5332
    public function removeEventListener($evtName)
5333
    {
5334
        if (!$evtName) {
5335
            return false;
5336
        }
5337
        unset ($this->pluginEvent[$evtName]);
5338
    }
5339
5340
    /**
5341
     * Remove all event listeners - only for use within the current execution cycle
5342
     */
5343
    public function removeAllEventListener()
5344
    {
5345
        unset ($this->pluginEvent);
5346
        $this->pluginEvent = array();
5347
    }
5348
5349
    /**
5350
     * Invoke an event.
5351
     *
5352
     * @param string $evtName
5353
     * @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.
5354
     * @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...
5355
     */
5356
    public function invokeEvent($evtName, $extParams = array())
5357
    {
5358
        if (!$evtName) {
5359
            return false;
5360
        }
5361
        if (!isset ($this->pluginEvent[$evtName])) {
5362
            return false;
5363
        }
5364
5365
        $results = null;
5366
        foreach ($this->pluginEvent[$evtName] as $pluginName) { // start for loop
5367
            if ($this->dumpPlugins) {
5368
                $eventtime = $this->getMicroTime();
5369
            }
5370
            // reset event object
5371
            $e = &$this->event;
5372
            $e->_resetEventObject();
5373
            $e->name = $evtName;
5374
            $e->activePlugin = $pluginName;
5375
5376
            // get plugin code
5377
            $_ = $this->getPluginCode($pluginName);
5378
            $pluginCode = $_['code'];
5379
            $pluginProperties = $_['props'];
5380
5381
            // load default params/properties
5382
            $parameter = $this->parseProperties($pluginProperties);
5383
            if (!is_array($parameter)) {
5384
                $parameter = array();
5385
            }
5386
            if (!empty($extParams)) {
5387
                $parameter = array_merge($parameter, $extParams);
5388
            }
5389
5390
            // eval plugin
5391
            $this->evalPlugin($pluginCode, $parameter);
5392
5393
            if (class_exists('PHxParser')) {
5394
                $this->config['enable_filter'] = 0;
5395
            }
5396
5397
            if ($this->dumpPlugins) {
5398
                $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...
5399
                $this->pluginsCode .= sprintf('<fieldset><legend><b>%s / %s</b> (%2.2f ms)</legend>', $evtName, $pluginName, $eventtime * 1000);
5400
                foreach ($parameter as $k => $v) {
5401
                    $this->pluginsCode .= "{$k} => " . print_r($v, true) . '<br>';
5402
                }
5403
                $this->pluginsCode .= '</fieldset><br />';
5404
                $this->pluginsTime["{$evtName} / {$pluginName}"] += $eventtime;
5405
            }
5406
            if ($e->_output != '') {
5407
                $results[] = $e->_output;
5408
            }
5409
            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...
5410
                break;
5411
            }
5412
        }
5413
5414
        $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...
5415
        return $results;
5416
    }
5417
5418
    /**
5419
     * Returns plugin-code and properties
5420
     *
5421
     * @param string $pluginName
5422
     * @return array Associative array consisting of 'code' and 'props'
5423
     */
5424
    public function getPluginCode($pluginName)
5425
    {
5426
        $plugin = array();
5427
        if (isset ($this->pluginCache[$pluginName])) {
5428
            $pluginCode = $this->pluginCache[$pluginName];
5429
            $pluginProperties = isset($this->pluginCache[$pluginName . "Props"]) ? $this->pluginCache[$pluginName . "Props"] : '';
5430
        } else {
5431
            $pluginName = $this->db->escape($pluginName);
5432
            $result = $this->db->select('name, plugincode, properties', $this->getFullTableName("site_plugins"), "name='{$pluginName}' AND disabled=0");
5433
            if ($row = $this->db->getRow($result)) {
5434
                $pluginCode = $this->pluginCache[$row['name']] = $row['plugincode'];
5435
                $pluginProperties = $this->pluginCache[$row['name'] . "Props"] = $row['properties'];
5436
            } else {
5437
                $pluginCode = $this->pluginCache[$pluginName] = "return false;";
5438
                $pluginProperties = '';
5439
            }
5440
        }
5441
        $plugin['code'] = $pluginCode;
5442
        $plugin['props'] = $pluginProperties;
5443
5444
        return $plugin;
5445
    }
5446
5447
    /**
5448
     * Parses a resource property string and returns the result as an array
5449
     *
5450
     * @param string $propertyString
5451
     * @param string|null $elementName
5452
     * @param string|null $elementType
5453
     * @return array Associative array in the form property name => property value
5454
     */
5455
    public function parseProperties($propertyString, $elementName = null, $elementType = null)
5456
    {
5457
        $propertyString = trim($propertyString);
5458
        $propertyString = str_replace('{}', '', $propertyString);
5459
        $propertyString = str_replace('} {', ',', $propertyString);
5460
        if (empty($propertyString)) {
5461
            return array();
5462
        }
5463
        if ($propertyString == '{}') {
5464
            return array();
5465
        }
5466
5467
        $jsonFormat = $this->isJson($propertyString, true);
5468
        $property = array();
5469
        // old format
5470
        if ($jsonFormat === false) {
5471
            $props = explode('&', $propertyString);
5472
            foreach ($props as $prop) {
5473
5474
                if (empty($prop)) {
5475
                    continue;
5476
                } elseif (strpos($prop, '=') === false) {
5477
                    $property[trim($prop)] = '';
5478
                    continue;
5479
                }
5480
5481
                $_ = explode('=', $prop, 2);
5482
                $key = trim($_[0]);
5483
                $p = explode(';', trim($_[1]));
5484
                switch ($p[1]) {
5485
                    case 'list':
5486
                    case 'list-multi':
5487
                    case 'checkbox':
5488
                    case 'radio':
5489
                        $value = !isset($p[3]) ? '' : $p[3];
5490
                        break;
5491
                    default:
5492
                        $value = !isset($p[2]) ? '' : $p[2];
5493
                }
5494
                if (!empty($key)) {
5495
                    $property[$key] = $value;
5496
                }
5497
            }
5498
            // new json-format
5499
        } else if (!empty($jsonFormat)) {
5500
            foreach ($jsonFormat as $key => $row) {
5501
                if (!empty($key)) {
5502
                    if (is_array($row)) {
5503
                        if (isset($row[0]['value'])) {
5504
                            $value = $row[0]['value'];
5505
                        }
5506
                    } else {
5507
                        $value = $row;
5508
                    }
5509
                    if (isset($value) && $value !== '') {
5510
                        $property[$key] = $value;
5511
                    }
5512
                }
5513
            }
5514
        }
5515
        if (!empty($elementName) && !empty($elementType)) {
5516
            $out = $this->invokeEvent('OnParseProperties', array(
5517
                'element' => $elementName,
5518
                'type' => $elementType,
5519
                'args' => $property
5520
            ));
5521
            if (is_array($out)) {
5522
                $out = array_pop($out);
5523
            }
5524
            if (is_array($out)) {
5525
                $property = $out;
5526
            }
5527
        }
5528
        return $property;
5529
    }
5530
5531
    /**
5532
     * Parses docBlock from a file and returns the result as an array
5533
     *
5534
     * @param string $element_dir
5535
     * @param string $filename
5536
     * @param boolean $escapeValues
5537
     * @return array Associative array in the form property name => property value
5538
     */
5539
    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...
5540
    {
5541
        $params = array();
5542
        $fullpath = $element_dir . '/' . $filename;
5543
        if (is_readable($fullpath)) {
5544
            $tpl = @fopen($fullpath, "r");
5545
            if ($tpl) {
5546
                $params['filename'] = $filename;
5547
                $docblock_start_found = false;
5548
                $name_found = false;
5549
                $description_found = false;
5550
                $docblock_end_found = false;
5551
                $arrayParams = array('author', 'documentation', 'reportissues', 'link');
5552
5553
                while (!feof($tpl)) {
5554
                    $line = fgets($tpl);
5555
                    $r = $this->parseDocBlockLine($line, $docblock_start_found, $name_found, $description_found, $docblock_end_found);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $r. Configured minimum length is 3.

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

Loading history...
5556
                    $docblock_start_found = $r['docblock_start_found'];
5557
                    $name_found = $r['name_found'];
5558
                    $description_found = $r['description_found'];
5559
                    $docblock_end_found = $r['docblock_end_found'];
5560
                    $param = $r['param'];
5561
                    $val = $r['val'];
5562
                    if (!$docblock_end_found) {
5563
                        break;
5564
                    }
5565
                    if (!$docblock_start_found || !$name_found || !$description_found || empty($param)) {
5566
                        continue;
5567
                    }
5568 View Code Duplication
                    if (!empty($param)) {
5569
                        if (in_array($param, $arrayParams)) {
5570
                            if (!isset($params[$param])) {
5571
                                $params[$param] = array();
5572
                            }
5573
                            $params[$param][] = $escapeValues ? $this->db->escape($val) : $val;
5574
                        } else {
5575
                            $params[$param] = $escapeValues ? $this->db->escape($val) : $val;
5576
                        }
5577
                    }
5578
                }
5579
                @fclose($tpl);
5580
            }
5581
        }
5582
        return $params;
5583
    }
5584
5585
    /**
5586
     * Parses docBlock from string and returns the result as an array
5587
     *
5588
     * @param string $string
5589
     * @param boolean $escapeValues
5590
     * @return array Associative array in the form property name => property value
5591
     */
5592
    public function parseDocBlockFromString($string, $escapeValues = false)
5593
    {
5594
        $params = array();
5595
        if (!empty($string)) {
5596
            $string = str_replace('\r\n', '\n', $string);
5597
            $exp = explode('\n', $string);
5598
            $docblock_start_found = false;
5599
            $name_found = false;
5600
            $description_found = false;
5601
            $docblock_end_found = false;
5602
            $arrayParams = array('author', 'documentation', 'reportissues', 'link');
5603
5604
            foreach ($exp as $line) {
5605
                $r = $this->parseDocBlockLine($line, $docblock_start_found, $name_found, $description_found, $docblock_end_found);
5606
                $docblock_start_found = $r['docblock_start_found'];
5607
                $name_found = $r['name_found'];
5608
                $description_found = $r['description_found'];
5609
                $docblock_end_found = $r['docblock_end_found'];
5610
                $param = $r['param'];
5611
                $val = $r['val'];
5612
                if (!$docblock_start_found) {
5613
                    continue;
5614
                }
5615
                if ($docblock_end_found) {
5616
                    break;
5617
                }
5618 View Code Duplication
                if (!empty($param)) {
5619
                    if (in_array($param, $arrayParams)) {
5620
                        if (!isset($params[$param])) {
5621
                            $params[$param] = array();
5622
                        }
5623
                        $params[$param][] = $escapeValues ? $this->db->escape($val) : $val;
5624
                    } else {
5625
                        $params[$param] = $escapeValues ? $this->db->escape($val) : $val;
5626
                    }
5627
                }
5628
            }
5629
        }
5630
        return $params;
5631
    }
5632
5633
    /**
5634
     * Parses docBlock of a component´s source-code and returns the result as an array
5635
     * (modified parseDocBlock() from modules/stores/setup.info.php by Bumkaka & Dmi3yy)
5636
     *
5637
     * @param string $line
5638
     * @param boolean $docblock_start_found
5639
     * @param boolean $name_found
5640
     * @param boolean $description_found
5641
     * @param boolean $docblock_end_found
5642
     * @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...
5643
     */
5644
    public function parseDocBlockLine($line, $docblock_start_found, $name_found, $description_found, $docblock_end_found)
0 ignored issues
show
Coding Style Naming introduced by
The parameter $docblock_start_found is not named in camelCase.

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

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

Loading history...
Coding Style Naming introduced by
The parameter $name_found is not named in camelCase.

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

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

Loading history...
Coding Style Naming introduced by
The parameter $description_found is not named in camelCase.

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

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

Loading history...
Coding Style Naming introduced by
The parameter $docblock_end_found is not named in camelCase.

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

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

Loading history...
5645
    {
5646
        $param = '';
5647
        $val = '';
5648
        $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...
5649
        if (!$docblock_start_found) {
5650
            // find docblock start
5651
            if (strpos($line, '/**') !== false) {
5652
                $docblock_start_found = true;
5653
            }
5654 View Code Duplication
        } elseif (!$name_found) {
5655
            // find name
5656
            if (preg_match("/^\s+\*\s+(.+)/", $line, $ma)) {
5657
                $param = 'name';
5658
                $val = trim($ma[1]);
5659
                $name_found = !empty($val);
5660
            }
5661
        } elseif (!$description_found) {
5662
            // find description
5663
            if (preg_match("/^\s+\*\s+(.+)/", $line, $ma)) {
5664
                $param = 'description';
5665
                $val = trim($ma[1]);
5666
                $description_found = !empty($val);
5667
            }
5668
        } else {
5669
            if (preg_match("/^\s+\*\s+\@([^\s]+)\s+(.+)/", $line, $ma)) {
5670
                $param = trim($ma[1]);
5671
                $val = trim($ma[2]);
5672 View Code Duplication
                if (!empty($param) && !empty($val)) {
5673
                    if ($param == 'internal') {
5674
                        $ma = null;
5675
                        if (preg_match("/\@([^\s]+)\s+(.+)/", $val, $ma)) {
5676
                            $param = trim($ma[1]);
5677
                            $val = trim($ma[2]);
5678
                        }
5679
                    }
5680
                }
5681
            } elseif (preg_match("/^\s*\*\/\s*$/", $line)) {
5682
                $docblock_end_found = true;
5683
            }
5684
        }
5685
        return array(
5686
            'docblock_start_found' => $docblock_start_found,
5687
            'name_found' => $name_found,
5688
            'description_found' => $description_found,
5689
            'docblock_end_found' => $docblock_end_found,
5690
            'param' => $param,
5691
            'val' => $val
5692
        );
5693
    }
5694
5695
    /**
5696
     * Renders docBlock-parameters into human readable list
5697
     *
5698
     * @param array $parsed
5699
     * @return string List in HTML-format
5700
     */
5701
    public function convertDocBlockIntoList($parsed)
5702
    {
5703
        global $_lang;
5704
5705
        // Replace special placeholders & make URLs + Emails clickable
5706
        $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...
5707
        $regexUrl = "/((http|https|ftp|ftps)\:\/\/[^\/]+(\/[^\s]+[^,.?!:;\s])?)/";
5708
        $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';
5709
        $emailSubject = isset($parsed['name']) ? '?subject=' . $parsed['name'] : '';
5710
        $emailSubject .= isset($parsed['version']) ? ' v' . $parsed['version'] : '';
5711
        foreach ($parsed as $key => $val) {
5712
            if (is_array($val)) {
5713
                foreach ($val as $key2 => $val2) {
5714
                    $val2 = $this->parseText($val2, $ph);
5715 View Code Duplication
                    if (preg_match($regexUrl, $val2, $url)) {
5716
                        $val2 = preg_replace($regexUrl, "<a href=\"{$url[0]}\" target=\"_blank\">{$url[0]}</a> ", $val2);
5717
                    }
5718 View Code Duplication
                    if (preg_match($regexEmail, $val2, $url)) {
5719
                        $val2 = preg_replace($regexEmail, '<a href="mailto:\\1' . $emailSubject . '">\\1</a>', $val2);
5720
                    }
5721
                    $parsed[$key][$key2] = $val2;
5722
                }
5723
            } else {
5724
                $val = $this->parseText($val, $ph);
5725 View Code Duplication
                if (preg_match($regexUrl, $val, $url)) {
5726
                    $val = preg_replace($regexUrl, "<a href=\"{$url[0]}\" target=\"_blank\">{$url[0]}</a> ", $val);
5727
                }
5728 View Code Duplication
                if (preg_match($regexEmail, $val, $url)) {
5729
                    $val = preg_replace($regexEmail, '<a href="mailto:\\1' . $emailSubject . '">\\1</a>', $val);
5730
                }
5731
                $parsed[$key] = $val;
5732
            }
5733
        }
5734
5735
        $arrayParams = array(
5736
            'documentation' => $_lang['documentation'],
5737
            'reportissues' => $_lang['report_issues'],
5738
            'link' => $_lang['further_info'],
5739
            'author' => $_lang['author_infos']
5740
        );
5741
5742
        $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...
5743
        $list = isset($parsed['logo']) ? '<img src="' . $this->config['base_url'] . ltrim($parsed['logo'], "/") . '" style="float:right;max-width:100px;height:auto;" />' . $nl : '';
5744
        $list .= '<p>' . $nl;
5745
        $list .= isset($parsed['name']) ? '<strong>' . $parsed['name'] . '</strong><br/>' . $nl : '';
5746
        $list .= isset($parsed['description']) ? $parsed['description'] . $nl : '';
5747
        $list .= '</p><br/>' . $nl;
5748
        $list .= isset($parsed['version']) ? '<p><strong>' . $_lang['version'] . ':</strong> ' . $parsed['version'] . '</p>' . $nl : '';
5749
        $list .= isset($parsed['license']) ? '<p><strong>' . $_lang['license'] . ':</strong> ' . $parsed['license'] . '</p>' . $nl : '';
5750
        $list .= isset($parsed['lastupdate']) ? '<p><strong>' . $_lang['last_update'] . ':</strong> ' . $parsed['lastupdate'] . '</p>' . $nl : '';
5751
        $list .= '<br/>' . $nl;
5752
        $first = true;
5753
        foreach ($arrayParams as $param => $label) {
5754
            if (isset($parsed[$param])) {
5755
                if ($first) {
5756
                    $list .= '<p><strong>' . $_lang['references'] . '</strong></p>' . $nl;
5757
                    $list .= '<ul class="docBlockList">' . $nl;
5758
                    $first = false;
5759
                }
5760
                $list .= '    <li><strong>' . $label . '</strong>' . $nl;
5761
                $list .= '        <ul>' . $nl;
5762
                foreach ($parsed[$param] as $val) {
5763
                    $list .= '            <li>' . $val . '</li>' . $nl;
5764
                }
5765
                $list .= '        </ul></li>' . $nl;
5766
            }
5767
        }
5768
        $list .= !$first ? '</ul>' . $nl : '';
5769
5770
        return $list;
5771
    }
5772
5773
    /**
5774
     * @param string $string
5775
     * @return string
5776
     */
5777
    public function removeSanitizeSeed($string = '')
5778
    {
5779
        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...
5780
5781
        if (!$string || strpos($string, $sanitize_seed) === false) {
5782
            return $string;
5783
        }
5784
5785
        return str_replace($sanitize_seed, '', $string);
5786
    }
5787
5788
    /**
5789
     * @param string $content
5790
     * @return string
5791
     */
5792
    public function cleanUpMODXTags($content = '')
5793
    {
5794
        if ($this->minParserPasses < 1) {
5795
            return $content;
5796
        }
5797
5798
        $enable_filter = $this->config['enable_filter'];
5799
        $this->config['enable_filter'] = 1;
5800
        $_ = 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...
5801
        foreach ($_ as $brackets) {
5802
            list($left, $right) = explode(' ', $brackets);
5803
            if (strpos($content, $left) !== false) {
5804
                if ($left === '[*') {
5805
                    $content = $this->mergeDocumentContent($content);
5806
                } elseif ($left === '[(') {
5807
                    $content = $this->mergeSettingsContent($content);
5808
                } elseif ($left === '{{') {
5809
                    $content = $this->mergeChunkContent($content);
5810
                } elseif ($left === '[[') {
5811
                    $content = $this->evalSnippets($content);
5812
                }
5813
            }
5814
        }
5815
        foreach ($_ as $brackets) {
5816
            list($left, $right) = explode(' ', $brackets);
5817
            if (strpos($content, $left) !== false) {
5818
                $matches = $this->getTagsFromContent($content, $left, $right);
5819
                $content = str_replace($matches[0], '', $content);
5820
            }
5821
        }
5822
        $this->config['enable_filter'] = $enable_filter;
5823
        return $content;
5824
    }
5825
5826
    /**
5827
     * @param string $str
5828
     * @param string $allowable_tags
5829
     * @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...
5830
     */
5831
    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...
5832
    {
5833
        $str = strip_tags($str, $allowable_tags);
5834
        modx_sanitize_gpc($str);
5835
        return $str;
5836
    }
5837
5838
    /**
5839
     * @param string $name
5840
     * @param string $phpCode
5841
     */
5842
    public function addSnippet($name, $phpCode)
5843
    {
5844
        $this->snippetCache['#' . $name] = $phpCode;
5845
    }
5846
5847
    /**
5848
     * @param string $name
5849
     * @param string $text
5850
     */
5851
    public function addChunk($name, $text)
5852
    {
5853
        $this->chunkCache['#' . $name] = $text;
5854
    }
5855
5856
    /**
5857
     * @param string $phpcode
5858
     * @param string $evalmode
5859
     * @param string $safe_functions
5860
     * @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...
5861
     */
5862
    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...
5863
    {
5864
        if ($evalmode == '') {
5865
            $evalmode = $this->config['allow_eval'];
5866
        }
5867
        if ($safe_functions == '') {
5868
            $safe_functions = $this->config['safe_functions_at_eval'];
5869
        }
5870
5871
        modx_sanitize_gpc($phpcode);
5872
5873
        switch ($evalmode) {
5874
            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...
5875
                $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...
5876
                break;
5877
            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...
5878
                $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...
5879
                break;
5880
            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...
5881
                $isSafe = true;
5882
                break; // Should debug only
5883
            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...
5884
            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...
5885
                return $phpcode;
5886
        }
5887
5888
        if (!$isSafe) {
5889
            $msg = $phpcode . "\n" . $this->currentSnippet . "\n" . print_r($_SERVER, true);
5890
            $title = sprintf('Unknown eval was executed (%s)', $this->htmlspecialchars(substr(trim($phpcode), 0, 50)));
5891
            $this->messageQuit($title, '', true, '', '', 'Parser', $msg);
5892
            return;
5893
        }
5894
5895
        ob_start();
5896
        $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...
5897
        $echo = ob_get_clean();
5898
5899
        if (is_array($return)) {
5900
            return 'array()';
5901
        }
5902
5903
        $output = $echo . $return;
5904
        modx_sanitize_gpc($output);
5905
        return $this->htmlspecialchars($output); // Maybe, all html tags are dangerous
5906
    }
5907
5908
    /**
5909
     * @param string $phpcode
5910
     * @param string $safe_functions
5911
     * @return bool
5912
     */
5913
    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...
5914
    { // 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...
5915
        if ($safe_functions == '') {
5916
            return false;
5917
        }
5918
5919
        $safe = explode(',', $safe_functions);
5920
5921
        $phpcode = rtrim($phpcode, ';') . ';';
5922
        $tokens = token_get_all('<?php ' . $phpcode);
5923
        foreach ($tokens as $i => $token) {
5924
            if (!is_array($token)) {
5925
                continue;
5926
            }
5927
            $tokens[$i]['token_name'] = token_name($token[0]);
5928
        }
5929
        foreach ($tokens as $token) {
5930
            if (!is_array($token)) {
5931
                continue;
5932
            }
5933
            switch ($token['token_name']) {
5934
                case 'T_STRING':
5935
                    if (!in_array($token[1], $safe)) {
5936
                        return false;
5937
                    }
5938
                    break;
5939
                case 'T_VARIABLE':
5940
                    if ($token[1] == '$GLOBALS') {
5941
                        return false;
5942
                    }
5943
                    break;
5944
                case 'T_EVAL':
5945
                    return false;
5946
            }
5947
        }
5948
        return true;
5949
    }
5950
5951
    /**
5952
     * @param string $str
5953
     * @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...
5954
     */
5955
    public function atBindFileContent($str = '')
5956
    {
5957
5958
        $search_path = array('assets/tvs/', 'assets/chunks/', 'assets/templates/', $this->config['rb_base_url'] . 'files/', '');
5959
5960
        if (stripos($str, '@FILE') !== 0) {
5961
            return $str;
5962
        }
5963 View Code Duplication
        if (strpos($str, "\n") !== false) {
5964
            $str = substr($str, 0, strpos("\n", $str));
5965
        }
5966
5967
        if ($this->getExtFromFilename($str) === '.php') {
5968
            return 'Could not retrieve PHP file.';
5969
        }
5970
5971
        $str = substr($str, 6);
5972
        $str = trim($str);
5973
        if (strpos($str, '\\') !== false) {
5974
            $str = str_replace('\\', '/', $str);
5975
        }
5976
        $str = ltrim($str, '/');
5977
5978
        $errorMsg = sprintf("Could not retrieve string '%s'.", $str);
5979
5980
        foreach ($search_path as $path) {
5981
            $file_path = MODX_BASE_PATH . $path . $str;
5982
            if (strpos($file_path, MODX_MANAGER_PATH) === 0) {
5983
                return $errorMsg;
5984
            } elseif (is_file($file_path)) {
5985
                break;
5986
            } else {
5987
                $file_path = false;
5988
            }
5989
        }
5990
5991
        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...
5992
            return $errorMsg;
5993
        }
5994
5995
        $content = (string)file_get_contents($file_path);
5996
        if ($content === false) {
5997
            return $errorMsg;
5998
        }
5999
6000
        return $content;
6001
    }
6002
6003
    /**
6004
     * @param $str
6005
     * @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...
6006
     */
6007
    public function getExtFromFilename($str)
6008
    {
6009
        $str = strtolower(trim($str));
6010
        $pos = strrpos($str, '.');
6011
        if ($pos === false) {
6012
            return false;
6013
        } else {
6014
            return substr($str, $pos);
6015
        }
6016
    }
6017
    /***************************************************************************************/
6018
    /* End of API functions                                       */
6019
    /***************************************************************************************/
6020
6021
    /**
6022
     * PHP error handler set by http://www.php.net/manual/en/function.set-error-handler.php
6023
     *
6024
     * Checks the PHP error and calls messageQuit() unless:
6025
     *  - error_reporting() returns 0, or
6026
     *  - the PHP error level is 0, or
6027
     *  - the PHP error level is 8 (E_NOTICE) and stopOnNotice is false
6028
     *
6029
     * @param int $nr The PHP error level as per http://www.php.net/manual/en/errorfunc.constants.php
6030
     * @param string $text Error message
6031
     * @param string $file File where the error was detected
6032
     * @param string $line Line number within $file
6033
     * @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...
6034
     */
6035
    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...
6036
    {
6037
        if (error_reporting() == 0 || $nr == 0) {
6038
            return true;
6039
        }
6040
        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...
6041
            switch ($nr) {
6042
                case E_NOTICE:
6043
                    if ($this->error_reporting <= 2) {
6044
                        return true;
6045
                    }
6046
                    $isError = false;
6047
                    $msg = 'PHP Minor Problem (this message show logged in only)';
6048
                    break;
6049
                case E_STRICT:
6050 View Code Duplication
                case E_DEPRECATED:
6051
                    if ($this->error_reporting <= 1) {
6052
                        return true;
6053
                    }
6054
                    $isError = true;
6055
                    $msg = 'PHP Strict Standards Problem';
6056
                    break;
6057 View Code Duplication
                default:
6058
                    if ($this->error_reporting === 0) {
6059
                        return true;
6060
                    }
6061
                    $isError = true;
6062
                    $msg = 'PHP Parse Error';
6063
            }
6064
        }
6065
        if (is_readable($file)) {
6066
            $source = file($file);
6067
            $source = $this->htmlspecialchars($source[$line - 1]);
6068
        } else {
6069
            $source = "";
6070
        } //Error $nr in $file at $line: <div><code>$source</code></div>
6071
6072
        $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...
6073
    }
6074
6075
    /**
6076
     * @param string $msg
6077
     * @param string $query
6078
     * @param bool $is_error
6079
     * @param string $nr
6080
     * @param string $file
6081
     * @param string $source
6082
     * @param string $text
6083
     * @param string $line
6084
     * @param string $output
6085
     * @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...
6086
     */
6087
    public function messageQuit($msg = 'unspecified error', $query = '', $is_error = true, $nr = '', $file = '', $source = '', $text = '', $line = '', $output = '')
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $nr. Configured minimum length is 3.

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

Loading history...
Coding Style introduced by
messageQuit uses the super-global variable $GLOBALS which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
messageQuit uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
messageQuit uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style Naming introduced by
The parameter $is_error is not named in camelCase.

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

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

Loading history...
6088
    {
6089
6090
        if (0 < $this->messageQuitCount) {
6091
            return;
6092
        }
6093
        $this->messageQuitCount++;
6094
6095
        if (!class_exists('makeTable')) {
6096
            include_once('extenders/maketable.class.php');
6097
        }
6098
        $MakeTable = new MakeTable();
6099
        $MakeTable->setTableClass('grid');
6100
        $MakeTable->setRowRegularClass('gridItem');
6101
        $MakeTable->setRowAlternateClass('gridAltItem');
6102
        $MakeTable->setColumnWidths(array('100px'));
6103
6104
        $table = array();
6105
6106
        $version = isset ($GLOBALS['modx_version']) ? $GLOBALS['modx_version'] : '';
6107
        $release_date = isset ($GLOBALS['release_date']) ? $GLOBALS['release_date'] : '';
6108
        $request_uri = "http://" . $_SERVER['HTTP_HOST'] . ($_SERVER["SERVER_PORT"] == 80 ? "" : (":" . $_SERVER["SERVER_PORT"])) . $_SERVER['REQUEST_URI'];
6109
        $request_uri = $this->htmlspecialchars($request_uri, ENT_QUOTES, $this->config['modx_charset']);
6110
        $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...
6111
        $referer = $this->htmlspecialchars($_SERVER['HTTP_REFERER'], ENT_QUOTES, $this->config['modx_charset']);
6112
        if ($is_error) {
6113
            $str = '<h2 style="color:red">&laquo; Evo Parse Error &raquo;</h2>';
6114
            if ($msg != 'PHP Parse Error') {
6115
                $str .= '<h3 style="color:red">' . $msg . '</h3>';
6116
            }
6117
        } else {
6118
            $str = '<h2 style="color:#003399">&laquo; Evo Debug/ stop message &raquo;</h2>';
6119
            $str .= '<h3 style="color:#003399">' . $msg . '</h3>';
6120
        }
6121
6122
        if (!empty ($query)) {
6123
            $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>';
6124
        }
6125
6126
        $errortype = array(
6127
            E_ERROR => "ERROR",
6128
            E_WARNING => "WARNING",
6129
            E_PARSE => "PARSING ERROR",
6130
            E_NOTICE => "NOTICE",
6131
            E_CORE_ERROR => "CORE ERROR",
6132
            E_CORE_WARNING => "CORE WARNING",
6133
            E_COMPILE_ERROR => "COMPILE ERROR",
6134
            E_COMPILE_WARNING => "COMPILE WARNING",
6135
            E_USER_ERROR => "USER ERROR",
6136
            E_USER_WARNING => "USER WARNING",
6137
            E_USER_NOTICE => "USER NOTICE",
6138
            E_STRICT => "STRICT NOTICE",
6139
            E_RECOVERABLE_ERROR => "RECOVERABLE ERROR",
6140
            E_DEPRECATED => "DEPRECATED",
6141
            E_USER_DEPRECATED => "USER DEPRECATED"
6142
        );
6143
6144
        if (!empty($nr) || !empty($file)) {
6145
            if ($text != '') {
6146
                $str .= '<div style="font-weight:bold;border:1px solid #ccc;padding:8px;color:#333;background-color:#ffffcd;margin-bottom:15px;">Error : ' . $text . '</div>';
6147
            }
6148
            if ($output != '') {
6149
                $str .= '<div style="font-weight:bold;border:1px solid #ccc;padding:8px;color:#333;background-color:#ffffcd;margin-bottom:15px;">' . $output . '</div>';
6150
            }
6151
            if ($nr !== '') {
6152
                $table[] = array('ErrorType[num]', $errortype [$nr] . "[" . $nr . "]");
6153
            }
6154
            if ($file) {
6155
                $table[] = array('File', $file);
6156
            }
6157
            if ($line) {
6158
                $table[] = array('Line', $line);
6159
            }
6160
6161
        }
6162
6163
        if ($source != '') {
6164
            $table[] = array("Source", $source);
6165
        }
6166
6167
        if (!empty($this->currentSnippet)) {
6168
            $table[] = array('Current Snippet', $this->currentSnippet);
6169
        }
6170
6171
        if (!empty($this->event->activePlugin)) {
6172
            $table[] = array('Current Plugin', $this->event->activePlugin . '(' . $this->event->name . ')');
6173
        }
6174
6175
        $str .= $MakeTable->create($table, array('Error information', ''));
6176
        $str .= "<br />";
6177
6178
        $table = array();
6179
        $table[] = array('REQUEST_URI', $request_uri);
6180
6181
        if ($this->manager->action) {
6182
            include_once(MODX_MANAGER_PATH . 'includes/actionlist.inc.php');
6183
            global $action_list;
6184
            $actionName = (isset($action_list[$this->manager->action])) ? " - {$action_list[$this->manager->action]}" : '';
6185
6186
            $table[] = array('Manager action', $this->manager->action . $actionName);
6187
        }
6188
6189
        if (preg_match('@^[0-9]+@', $this->documentIdentifier)) {
6190
            $resource = $this->getDocumentObject('id', $this->documentIdentifier);
6191
            $url = $this->makeUrl($this->documentIdentifier, '', '', 'full');
6192
            $table[] = array('Resource', '[' . $this->documentIdentifier . '] <a href="' . $url . '" target="_blank">' . $resource['pagetitle'] . '</a>');
6193
        }
6194
        $table[] = array('Referer', $referer);
6195
        $table[] = array('User Agent', $ua);
6196
        $table[] = array('IP', $_SERVER['REMOTE_ADDR']);
6197
        $table[] = array('Current time', date("Y-m-d H:i:s", $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time']));
6198
        $str .= $MakeTable->create($table, array('Basic info', ''));
6199
        $str .= "<br />";
6200
6201
        $table = array();
6202
        $table[] = array('MySQL', '[^qt^] ([^q^] Requests)');
6203
        $table[] = array('PHP', '[^p^]');
6204
        $table[] = array('Total', '[^t^]');
6205
        $table[] = array('Memory', '[^m^]');
6206
        $str .= $MakeTable->create($table, array('Benchmarks', ''));
6207
        $str .= "<br />";
6208
6209
        $totalTime = ($this->getMicroTime() - $this->tstart);
6210
6211
        $mem = memory_get_peak_usage(true);
6212
        $total_mem = $mem - $this->mstart;
6213
        $total_mem = ($total_mem / 1024 / 1024) . ' mb';
6214
6215
        $queryTime = $this->queryTime;
6216
        $phpTime = $totalTime - $queryTime;
6217
        $queries = isset ($this->executedQueries) ? $this->executedQueries : 0;
6218
        $queryTime = sprintf("%2.4f s", $queryTime);
6219
        $totalTime = sprintf("%2.4f s", $totalTime);
6220
        $phpTime = sprintf("%2.4f s", $phpTime);
6221
6222
        $str = str_replace('[^q^]', $queries, $str);
6223
        $str = str_replace('[^qt^]', $queryTime, $str);
6224
        $str = str_replace('[^p^]', $phpTime, $str);
6225
        $str = str_replace('[^t^]', $totalTime, $str);
6226
        $str = str_replace('[^m^]', $total_mem, $str);
6227
6228
        if (isset($php_errormsg) && !empty($php_errormsg)) {
6229
            $str = "<b>{$php_errormsg}</b><br />\n{$str}";
6230
        }
6231
        $str .= $this->get_backtrace(debug_backtrace());
6232
        // Log error
6233
        if (!empty($this->currentSnippet)) {
6234
            $source = 'Snippet - ' . $this->currentSnippet;
6235
        } elseif (!empty($this->event->activePlugin)) {
6236
            $source = 'Plugin - ' . $this->event->activePlugin;
6237
        } elseif ($source !== '') {
6238
            $source = 'Parser - ' . $source;
6239
        } elseif ($query !== '') {
6240
            $source = 'SQL Query';
6241
        } else {
6242
            $source = 'Parser';
6243
        }
6244
        if ($msg) {
6245
            $source .= ' / ' . $msg;
6246
        }
6247
        if (isset($actionName) && !empty($actionName)) {
6248
            $source .= $actionName;
6249
        }
6250 View Code Duplication
        switch ($nr) {
6251
            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...
6252
            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...
6253
            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...
6254
            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...
6255
            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...
6256
                $error_level = 2;
6257
                break;
6258
            default:
6259
                $error_level = 3;
6260
        }
6261
        $this->logEvent(0, $error_level, $str, $source);
6262
6263
        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...
6264
            return true;
6265
        }
6266
        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...
6267
            return true;
6268
        }
6269
6270
        // Set 500 response header
6271
        if ($error_level !== 2) {
6272
            header('HTTP/1.1 500 Internal Server Error');
6273
        }
6274
6275
        // Display error
6276
        if (isset($_SESSION['mgrValidated'])) {
6277
            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>
6278
                 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
6279
                 <link rel="stylesheet" type="text/css" href="' . $this->config['site_manager_url'] . 'media/style/' . $this->config['manager_theme'] . '/style.css" />
6280
                 <style type="text/css">body { padding:10px; } td {font:inherit;}</style>
6281
                 </head><body>
6282
                 ' . $str . '</body></html>';
6283
6284
        } else {
6285
            echo 'Error';
6286
        }
6287
        ob_end_flush();
6288
        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...
6289
    }
6290
6291
    /**
6292
     * @param $backtrace
6293
     * @return string
6294
     */
6295
    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...
6296
    {
6297
        if (!class_exists('makeTable')) {
6298
            include_once('extenders/maketable.class.php');
6299
        }
6300
        $MakeTable = new MakeTable();
6301
        $MakeTable->setTableClass('grid');
6302
        $MakeTable->setRowRegularClass('gridItem');
6303
        $MakeTable->setRowAlternateClass('gridAltItem');
6304
        $table = array();
6305
        $backtrace = array_reverse($backtrace);
6306
        foreach ($backtrace as $key => $val) {
6307
            $key++;
6308
            if (substr($val['function'], 0, 11) === 'messageQuit') {
6309
                break;
6310
            } elseif (substr($val['function'], 0, 8) === 'phpError') {
6311
                break;
6312
            }
6313
            $path = str_replace('\\', '/', $val['file']);
6314
            if (strpos($path, MODX_BASE_PATH) === 0) {
6315
                $path = substr($path, strlen(MODX_BASE_PATH));
6316
            }
6317
            switch ($val['type']) {
6318
                case '->':
6319
                case '::':
6320
                    $functionName = $val['function'] = $val['class'] . $val['type'] . $val['function'];
6321
                    break;
6322
                default:
6323
                    $functionName = $val['function'];
6324
            }
6325
            $tmp = 1;
6326
            $_ = (!empty($val['args'])) ? count($val['args']) : 0;
6327
            $args = array_pad(array(), $_, '$var');
6328
            $args = implode(", ", $args);
6329
            $modx = &$this;
6330
            $args = preg_replace_callback('/\$var/', function () use ($modx, &$tmp, $val) {
6331
                $arg = $val['args'][$tmp - 1];
6332
                switch (true) {
6333
                    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...
6334
                        $out = 'NULL';
6335
                        break;
6336
                    }
6337
                    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...
6338
                        $out = $arg;
6339
                        break;
6340
                    }
6341
                    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...
6342
                        $out = strlen($arg) > 20 ? 'string $var' . $tmp : ("'" . $this->htmlspecialchars(str_replace("'", "\\'", $arg)) . "'");
6343
                        break;
6344
                    }
6345
                    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...
6346
                        $out = $arg ? 'TRUE' : 'FALSE';
6347
                        break;
6348
                    }
6349
                    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...
6350
                        $out = 'array $var' . $tmp;
6351
                        break;
6352
                    }
6353
                    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...
6354
                        $out = get_class($arg) . ' $var' . $tmp;
6355
                        break;
6356
                    }
6357
                    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...
6358
                        $out = '$var' . $tmp;
6359
                    }
6360
                }
6361
                $tmp++;
6362
                return $out;
6363
            }, $args);
6364
            $line = array(
6365
                "<strong>" . $functionName . "</strong>(" . $args . ")",
6366
                $path . " on line " . $val['line']
6367
            );
6368
            $table[] = array(implode("<br />", $line));
6369
        }
6370
        return $MakeTable->create($table, array('Backtrace'));
6371
    }
6372
6373
    /**
6374
     * @return string
6375
     */
6376
    public function getRegisteredClientScripts()
6377
    {
6378
        return implode("\n", $this->jscripts);
6379
    }
6380
6381
    /**
6382
     * @return string
6383
     */
6384
    public function getRegisteredClientStartupScripts()
6385
    {
6386
        return implode("\n", $this->sjscripts);
6387
    }
6388
6389
    /**
6390
     * Format alias to be URL-safe. Strip invalid characters.
6391
     *
6392
     * @param string $alias Alias to be formatted
6393
     * @return string Safe alias
6394
     */
6395
    public function stripAlias($alias)
6396
    {
6397
        // let add-ons overwrite the default behavior
6398
        $results = $this->invokeEvent('OnStripAlias', array('alias' => $alias));
6399
        if (!empty($results)) {
6400
            // if multiple plugins are registered, only the last one is used
6401
            return end($results);
6402
        } else {
6403
            // default behavior: strip invalid characters and replace spaces with dashes.
6404
            $alias = strip_tags($alias); // strip HTML
6405
            $alias = preg_replace('/[^\.A-Za-z0-9 _-]/', '', $alias); // strip non-alphanumeric characters
6406
            $alias = preg_replace('/\s+/', '-', $alias); // convert white-space to dash
6407
            $alias = preg_replace('/-+/', '-', $alias);  // convert multiple dashes to one
6408
            $alias = trim($alias, '-'); // trim excess
6409
            return $alias;
6410
        }
6411
    }
6412
6413
    /**
6414
     * @param $size
6415
     * @return string
6416
     */
6417
    public function nicesize($size)
6418
    {
6419
        $sizes = array('Tb' => 1099511627776, 'Gb' => 1073741824, 'Mb' => 1048576, 'Kb' => 1024, 'b' => 1);
6420
        $precisions = count($sizes) - 1;
6421
        foreach ($sizes as $unit => $bytes) {
6422
            if ($size >= $bytes) {
6423
                return number_format($size / $bytes, $precisions) . ' ' . $unit;
6424
            }
6425
            $precisions--;
6426
        }
6427
        return '0 b';
6428
    }
6429
6430
    /**
6431
     * @param $parentid
6432
     * @param $alias
6433
     * @return bool
6434
     */
6435
    public function getHiddenIdFromAlias($parentid, $alias)
6436
    {
6437
        $table = $this->getFullTableName('site_content');
6438
        $query = $this->db->query("SELECT sc.id, children.id AS child_id, children.alias, COUNT(children2.id) AS children_count
6439
            FROM {$table} sc
6440
            JOIN {$table} children ON children.parent = sc.id
6441
            LEFT JOIN {$table} children2 ON children2.parent = children.id
6442
            WHERE sc.parent = {$parentid} AND sc.alias_visible = '0' GROUP BY children.id;");
6443
6444
        while ($child = $this->db->getRow($query)) {
6445
            if ($child['alias'] == $alias || $child['child_id'] == $alias) {
6446
                return $child['child_id'];
6447
            }
6448
6449
            if ($child['children_count'] > 0) {
6450
                $id = $this->getHiddenIdFromAlias($child['id'], $alias);
6451
                if ($id) {
6452
                    return $id;
6453
                }
6454
            }
6455
        }
6456
6457
        return false;
6458
    }
6459
6460
    /**
6461
     * @param $alias
6462
     * @return bool|int
6463
     */
6464
    public function getIdFromAlias($alias)
6465
    {
6466
        if (isset($this->documentListing[$alias])) {
6467
            return $this->documentListing[$alias];
6468
        }
6469
6470
        $tbl_site_content = $this->getFullTableName('site_content');
6471
        if ($this->config['use_alias_path'] == 1) {
6472
            if ($alias == '.') {
6473
                return 0;
6474
            }
6475
6476
            if (strpos($alias, '/') !== false) {
6477
                $_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...
6478
            } else {
6479
                $_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...
6480
            }
6481
            $id = 0;
6482
6483
            foreach ($_a as $alias) {
6484
                if ($id === false) {
6485
                    break;
6486
                }
6487
                $alias = $this->db->escape($alias);
6488
                $rs = $this->db->select('id', $tbl_site_content, "deleted=0 and parent='{$id}' and alias='{$alias}'");
6489
                if ($this->db->getRecordCount($rs) == 0) {
6490
                    $rs = $this->db->select('id', $tbl_site_content, "deleted=0 and parent='{$id}' and id='{$alias}'");
6491
                }
6492
                $next = $this->db->getValue($rs);
6493
                $id = !$next ? $this->getHiddenIdFromAlias($id, $alias) : $next;
6494
            }
6495
        } else {
6496
            $rs = $this->db->select('id', $tbl_site_content, "deleted=0 and alias='{$alias}'", 'parent, menuindex');
6497
            $id = $this->db->getValue($rs);
6498
            if (!$id) {
6499
                $id = false;
6500
            }
6501
        }
6502
        return $id;
6503
    }
6504
6505
    /**
6506
     * @param string $str
6507
     * @return bool|mixed|string
6508
     */
6509
    public function atBindInclude($str = '')
6510
    {
6511
        if (strpos($str, '@INCLUDE') !== 0) {
6512
            return $str;
6513
        }
6514 View Code Duplication
        if (strpos($str, "\n") !== false) {
6515
            $str = substr($str, 0, strpos("\n", $str));
6516
        }
6517
6518
        $str = substr($str, 9);
6519
        $str = trim($str);
6520
        $str = str_replace('\\', '/', $str);
6521
        $str = ltrim($str, '/');
6522
6523
        $tpl_dir = 'assets/templates/';
6524
6525
        if (strpos($str, MODX_MANAGER_PATH) === 0) {
6526
            return false;
6527
        } elseif (is_file(MODX_BASE_PATH . $str)) {
6528
            $file_path = MODX_BASE_PATH . $str;
6529
        } elseif (is_file(MODX_BASE_PATH . "{$tpl_dir}{$str}")) {
6530
            $file_path = MODX_BASE_PATH . $tpl_dir . $str;
6531
        } else {
6532
            return false;
6533
        }
6534
6535
        if (!$file_path || !is_file($file_path)) {
6536
            return false;
6537
        }
6538
6539
        ob_start();
6540
        $modx = &$this;
6541
        $result = include($file_path);
6542
        if ($result === 1) {
6543
            $result = '';
6544
        }
6545
        $content = ob_get_clean();
6546
        if (!$content && $result) {
6547
            $content = $result;
6548
        }
6549
        return $content;
6550
    }
6551
6552
    // php compat
6553
6554
    /**
6555
     * @param $str
6556
     * @param int $flags
6557
     * @param string $encode
6558
     * @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...
6559
     */
6560
    public function htmlspecialchars($str, $flags = ENT_COMPAT, $encode = '')
6561
    {
6562
        $this->loadExtension('PHPCOMPAT');
6563
        return $this->phpcompat->htmlspecialchars($str, $flags, $encode);
6564
    }
6565
6566
    /**
6567
     * @param $string
6568
     * @param bool $returnData
6569
     * @return bool|mixed
6570
     */
6571
    public function isJson($string, $returnData = false)
6572
    {
6573
        $data = json_decode($string, true);
6574
        return (json_last_error() == JSON_ERROR_NONE) ? ($returnData ? $data : true) : false;
6575
    }
6576
6577
    /**
6578
     * @param $key
6579
     * @return array
6580
     */
6581
    public function splitKeyAndFilter($key)
6582
    {
6583
        if ($this->config['enable_filter'] == 1 && strpos($key, ':') !== false && stripos($key, '@FILE') !== 0) {
6584
            list($key, $modifiers) = explode(':', $key, 2);
6585
        } else {
6586
            $modifiers = false;
6587
        }
6588
6589
        $key = trim($key);
6590
        if ($modifiers !== false) {
6591
            $modifiers = trim($modifiers);
6592
        }
6593
6594
        return array($key, $modifiers);
6595
    }
6596
6597
    /**
6598
     * @param string $value
6599
     * @param bool $modifiers
6600
     * @param string $key
6601
     * @return string
6602
     */
6603
    public function applyFilter($value = '', $modifiers = false, $key = '')
6604
    {
6605
        if ($modifiers === false || $modifiers == 'raw') {
6606
            return $value;
6607
        }
6608
        if ($modifiers !== false) {
6609
            $modifiers = trim($modifiers);
6610
        }
6611
6612
        $this->loadExtension('MODIFIERS');
6613
        return $this->filter->phxFilter($key, $value, $modifiers);
0 ignored issues
show
Bug introduced by
It seems like $modifiers defined by parameter $modifiers on line 6603 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...
6614
    }
6615
6616
    // End of class.
6617
6618
6619
    /**
6620
     * Get Clean Query String
6621
     *
6622
     * Fixes the issue where passing an array into the q get variable causes errors
6623
     *
6624
     */
6625
    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...
6626
    {
6627
        $q = MODX_CLI ? null : $_GET['q'];
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $q. Configured minimum length is 3.

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

Loading history...
6628
6629
        //Return null if the query doesn't exist
6630
        if (empty($q)) {
6631
            return null;
6632
        }
6633
6634
        //If we have a string, return it
6635
        if (is_string($q)) {
6636
            return $q;
6637
        }
6638
6639
        //If we have an array, return the first element
6640
        if (is_array($q)) {
6641
            return $q[0];
6642
        }
6643
    }
6644
6645
    /**
6646
     * @param string $title
6647
     * @param string $msg
6648
     * @param int $type
6649
     */
6650
    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...
6651
    {
6652
        if ($title === '') {
6653
            $title = 'no title';
6654
        }
6655
        if (is_array($msg)) {
6656
            $msg = '<pre>' . print_r($msg, true) . '</pre>';
6657
        } elseif ($msg === '') {
6658
            $msg = $_SERVER['REQUEST_URI'];
6659
        }
6660
        $this->logEvent(0, $type, $msg, $title);
6661
    }
6662
6663
}
6664
6665
/**
6666
 * System Event Class
6667
 */
6668
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...
6669
{
6670
    public $name = '';
6671
    public $_propagate = true;
6672
    public $_output = '';
6673
    public $activated = false;
6674
    public $activePlugin = '';
6675
    public $params = array();
6676
6677
    /**
6678
     * @param string $name Name of the event
6679
     */
6680
    public function __construct($name = "")
6681
    {
6682
        $this->_resetEventObject();
6683
        $this->name = $name;
6684
    }
6685
6686
    /**
6687
     * Display a message to the user
6688
     *
6689
     * @global array $SystemAlertMsgQueque
6690
     * @param string $msg The message
6691
     */
6692
    public function alert($msg)
6693
    {
6694
        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...
6695
        if ($msg == "") {
6696
            return;
6697
        }
6698
        if (is_array($SystemAlertMsgQueque)) {
6699
            $title = '';
6700
            if ($this->name && $this->activePlugin) {
6701
                $title = "<div><b>" . $this->activePlugin . "</b> - <span style='color:maroon;'>" . $this->name . "</span></div>";
6702
            }
6703
            $SystemAlertMsgQueque[] = "$title<div style='margin-left:10px;margin-top:3px;'>$msg</div>";
6704
        }
6705
    }
6706
6707
    /**
6708
     * Output
6709
     *
6710
     * @param string $msg
6711
     */
6712
    public function output($msg)
6713
    {
6714
        $this->_output .= $msg;
6715
    }
6716
6717
    /**
6718
     * Stop event propogation
6719
     */
6720
    public function stopPropagation()
6721
    {
6722
        $this->_propagate = false;
6723
    }
6724
6725
    public function _resetEventObject()
6726
    {
6727
        unset ($this->returnedValues);
6728
        $this->name = "";
6729
        $this->_output = "";
6730
        $this->_propagate = true;
6731
        $this->activated = false;
6732
    }
6733
}
6734