Completed
Pull Request — develop (#545)
by Agel_Nash
05:36
created

DocumentParser::getInstance()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 0
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 *    MODX Document Parser
4
 *    Function: This class contains the main document parsing functions
5
 *
6
 */
7
if (!defined('E_DEPRECATED')) {
8
    define('E_DEPRECATED', 8192);
9
}
10
if (!defined('E_USER_DEPRECATED')) {
11
    define('E_USER_DEPRECATED', 16384);
12
}
13
14
class DocumentParser
0 ignored issues
show
Coding Style introduced by
The property $table_prefix is not named in camelCase.

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

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

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

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

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

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

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

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

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

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

namespace YourVendor;

class YourClass { }

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

Loading history...
15
{
16
    /**
17
     * This is New evolution
18
     * @var string
19
     */
20
    public $apiVersion = '1.0.0';
21
22
    /**
23
     * db object
24
     * @var DBAPI
25
     * @see /manager/includes/extenders/ex_dbapi.inc.php
26
     * @example $this->loadExtension('DBAPI')
27
     */
28
    public $db;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $db. Configured minimum length is 3.

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

Loading history...
29
30
    /**
31
     * @var MODxMailer
32
     * @see /manager/includes/extenders/ex_modxmailer.inc.php
33
     * @example $this->loadExtension('MODxMailer');
34
     */
35
    public $mail;
36
37
    /**
38
     * @var PHPCOMPAT
39
     * @see /manager/includes/extenders/ex_phpcompat.inc.php
40
     * @example $this->loadExtension('PHPCOMPAT');
41
     */
42
    public $phpcompat;
43
44
    /**
45
     * @var MODIFIERS
46
     * @see /manager/includes/extenders/ex_modifiers.inc.php
47
     * @example $this->loadExtension('MODIFIERS');
48
     */
49
    public $filter;
50
51
    /**
52
     * @var EXPORT_SITE
53
     * @see /manager/includes/extenders/ex_export_site.inc.php
54
     * @example $this->loadExtension('EXPORT_SITE');
55
     */
56
    public $export;
57
58
    /**
59
     * @var MakeTable
60
     * @see /manager/includes/extenders/ex_maketable.inc.php
61
     * @example $this->loadExtension('makeTable');
62
     */
63
    public $table;
64
65
    /**
66
     * @var ManagerAPI
67
     * @see /manager/includes/extenders/ex_managerapi.inc.php
68
     * @example $this->loadExtension('ManagerAPI');
69
     */
70
    public $manager;
71
72
    /**
73
     * @var PasswordHash
74
     * @see manager/includes/extenders/ex_phpass.inc.php
75
     * @example $this->loadExtension('phpass');
76
     */
77
    public $phpass;
78
79
    /**
80
     * event object
81
     * @var SystemEvent
82
     */
83
84
    public $event;
85
    /**
86
     * event object
87
     * @var SystemEvent
88
     */
89
    public $Event;
90
91
    /**
92
     * @var array
93
     */
94
    public $pluginEvent = array();
95
96
    /**
97
     * @var array
98
     */
99
    public $config = array();
100
    /**
101
     * @var array
102
     */
103
    public $dbConfig = array();
104
    public $configGlobal = null; // contains backup of settings overwritten by user-settings
105
    public $rs;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rs. Configured minimum length is 3.

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

Loading history...
106
    public $result;
107
    public $sql;
108
    public $table_prefix;
109
    public $debug = false;
110
    public $documentIdentifier;
111
    public $documentMethod;
112
    public $documentGenerated;
113
    public $documentContent;
114
    public $documentOutput;
115
    public $tstart;
116
    public $mstart;
117
    public $minParserPasses;
118
    public $maxParserPasses;
119
    public $documentObject;
120
    public $templateObject;
121
    public $snippetObjects;
122
    public $stopOnNotice = false;
123
    public $executedQueries;
124
    public $queryTime;
125
    public $currentSnippet;
126
    public $documentName;
127
    public $aliases;
128
    public $visitor;
129
    public $entrypage;
130
    public $documentListing;
131
    /**
132
     * feed the parser the execution start time
133
     * @var bool
134
     */
135
    public $dumpSnippets = false;
136
    public $snippetsCode;
137
    public $snippetsTime = array();
138
    public $chunkCache;
139
    public $snippetCache;
140
    public $contentTypes;
141
    public $dumpSQL = false;
142
    public $queryCode;
143
    public $virtualDir;
144
    public $placeholders;
145
    public $sjscripts = array();
146
    public $jscripts = array();
147
    public $loadedjscripts = array();
148
    public $documentMap;
149
    public $forwards = 3;
150
    public $error_reporting = 1;
151
    public $dumpPlugins = false;
152
    public $pluginsCode;
153
    public $pluginsTime = array();
154
    public $pluginCache = array();
155
    public $aliasListing;
156
    public $lockedElements = null;
157
    public $tmpCache = array();
158
    private $version = array();
159
    public $extensions = array();
160
    public $cacheKey = null;
161
    public $recentUpdate = 0;
162
    public $useConditional = false;
163
    protected $systemCacheKey = null;
164
    public $snipLapCount = 0;
165
    public $messageQuitCount;
166
    public $time;
167
    public $sid;
168
    private $q;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $q. Configured minimum length is 3.

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

Loading history...
169
    public $decoded_request_uri;
170
    /**
171
     * @var OldFunctions
172
     */
173
    public $old;
174
175
    /**
176
     * Hold the class instance.
177
     * @var DocumentParser
178
     */
179
    private static $instance = null;
180
181
    /**
182
     * Document constructor
183
     * PUBLIC access for backward compatibility !!!!
184
     *
185
     * @return DocumentParser
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

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

Please refer to the PHP core documentation on constructors.

Loading history...
186
     */
187
    final public function __construct()
0 ignored issues
show
Coding Style introduced by
__construct uses the super-global variable $_SERVER which is generally not recommended.

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

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

// Better
class Router
{
    private $host;

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

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

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

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
188
    {
189
        if ($this->isLoggedIn()) {
190
            ini_set('display_errors', 1);
191
        }
192
        global $database_server;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
193
        if (substr(PHP_OS, 0, 3) === 'WIN' && $database_server === 'localhost') {
194
            $database_server = '127.0.0.1';
195
        }
196
        $this->loadExtension('DBAPI') or die('Could not load DBAPI class.'); // load DBAPI class
0 ignored issues
show
Coding Style Compatibility introduced by
The method __construct() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

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

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

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