Completed
Push — develop ( d568f8...c061fc )
by Agel_Nash
06:25
created

DocumentParser::atBindFileContent()   D

Complexity

Conditions 10
Paths 43

Size

Total Lines 46
Code Lines 28

Duplication

Lines 3
Ratio 6.52 %

Importance

Changes 0
Metric Value
cc 10
eloc 28
nc 43
nop 1
dl 3
loc 46
rs 4.983
c 0
b 0
f 0

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

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

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

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

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

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

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

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

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

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

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

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

namespace YourVendor;

class YourClass { }

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

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

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

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

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

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

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

Loading history...
169
    public $decoded_request_uri;
170
    /**
171
     * @var OldFunctions
172
     */
173
    public $old;
174
175
    /**
176
     * Hold the class instance.
177
     * @var DocumentParser
178
     */
179
    private static $instance = null;
180
181
    /**
182
     * Document constructor
183
     *
184
     * @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...
185
     */
186
    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...
187
    {
188
        if ($this->isLoggedIn()) {
189
            ini_set('display_errors', 1);
190
        }
191
        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...
192
        if (substr(PHP_OS, 0, 3) === 'WIN' && $database_server === 'localhost') {
193
            $database_server = '127.0.0.1';
194
        }
195
        $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...
196
        $this->dbConfig = &$this->db->config; // alias for backward compatibility
197
        // events
198
        $this->event = new SystemEvent();
199
        $this->Event = &$this->event; //alias for backward compatibility
200
        // set track_errors ini variable
201
        @ ini_set("track_errors", "1"); // enable error tracking in $php_errormsg
202
        $this->time = $_SERVER['REQUEST_TIME']; // for having global timestamp
203
204
        $this->q = self::_getCleanQueryString();
205
    }
206
207
    final private function __clone()
208
    {
209
    }
210
    /**
211
     * @return DocumentParser
212
     */
213
    public static function getInstance()
214
    {
215
        if (self::$instance === null) {
216
            self::$instance = new DocumentParser();
217
        }
218
        return self::$instance;
219
    }
220
221
    /**
222
     * @param $method_name
223
     * @param $arguments
224
     * @return mixed
225
     */
226
    public function __call($method_name, $arguments)
0 ignored issues
show
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...
227
    {
228
        include_once(MODX_MANAGER_PATH . 'includes/extenders/deprecated.functions.inc.php');
229
        if (method_exists($this->old, $method_name)) {
230
            $error_type = 1;
231
        } else {
232
            $error_type = 3;
233
        }
234
235
        if (!isset($this->config['error_reporting']) || 1 < $this->config['error_reporting']) {
236
            if ($error_type == 1) {
237
                $title = 'Call deprecated method';
238
                $msg = $this->htmlspecialchars("\$modx->{$method_name}() is deprecated function");
239
            } else {
240
                $title = 'Call undefined method';
241
                $msg = $this->htmlspecialchars("\$modx->{$method_name}() is undefined function");
242
            }
243
            $info = debug_backtrace();
244
            $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...
245
            if (!empty($this->currentSnippet)) {
246
                $m[] = 'Snippet - ' . $this->currentSnippet;
247
            } elseif (!empty($this->event->activePlugin)) {
248
                $m[] = 'Plugin - ' . $this->event->activePlugin;
249
            }
250
            $m[] = $this->decoded_request_uri;
251
            $m[] = str_replace('\\', '/', $info[0]['file']) . '(line:' . $info[0]['line'] . ')';
252
            $msg = implode('<br />', $m);
253
            $this->logEvent(0, $error_type, $msg, $title);
254
        }
255
        if (method_exists($this->old, $method_name)) {
256
            return call_user_func_array(array($this->old, $method_name), $arguments);
257
        }
258
    }
259
260
    /**
261
     * @param string $connector
262
     * @return bool
263
     */
264
    public function checkSQLconnect($connector = 'db')
265
    {
266
        $flag = false;
267
        if (is_scalar($connector) && !empty($connector) && isset($this->{$connector}) && $this->{$connector} instanceof DBAPI) {
268
            $flag = (bool)$this->{$connector}->conn;
269
        }
270
        return $flag;
271
    }
272
273
    /**
274
     * Loads an extension from the extenders folder.
275
     * You can load any extension creating a boot file:
276
     * MODX_MANAGER_PATH."includes/extenders/ex_{$extname}.inc.php"
277
     * $extname - extension name in lowercase
278
     *
279
     * @param $extname
280
     * @param bool $reload
281
     * @return bool
282
     */
283
    public function loadExtension($extname, $reload = true)
284
    {
285
        $out = false;
286
        $flag = ($reload || !in_array($extname, $this->extensions));
287
        if ($this->checkSQLconnect('db') && $flag) {
288
            $evtOut = $this->invokeEvent('OnBeforeLoadExtension', array('name' => $extname, 'reload' => $reload));
289
            if (is_array($evtOut) && count($evtOut) > 0) {
290
                $out = array_pop($evtOut);
291
            }
292
        }
293
        if (!$out && $flag) {
294
            $extname = trim(str_replace(array('..', '/', '\\'), '', strtolower($extname)));
295
            $filename = MODX_MANAGER_PATH . "includes/extenders/ex_{$extname}.inc.php";
296
            $out = is_file($filename) ? include $filename : false;
297
        }
298
        if ($out && !in_array($extname, $this->extensions)) {
299
            $this->extensions[] = $extname;
300
        }
301
        return $out;
302
    }
303
304
    /**
305
     * Returns the current micro time
306
     *
307
     * @return float
308
     */
309
    public function getMicroTime()
310
    {
311
        list($usec, $sec) = explode(' ', microtime());
312
        return ((float)$usec + (float)$sec);
313
    }
314
315
    /**
316
     * Redirect
317
     *
318
     * @param string $url
319
     * @param int $count_attempts
320
     * @param string $type $type
321
     * @param string $responseCode
322
     * @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...
323
     * @global string $base_url
324
     * @global string $site_url
325
     */
326
    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...
327
    {
328
        $header = '';
329
        if (empty($url)) {
330
            return false;
331
        }
332
        if ($count_attempts == 1) {
333
            // append the redirect count string to the url
334
            $currentNumberOfRedirects = isset($_REQUEST['err']) ? $_REQUEST['err'] : 0;
335
            if ($currentNumberOfRedirects > 3) {
336
                $this->messageQuit('Redirection attempt failed - please ensure the document you\'re trying to redirect to exists. <p>Redirection URL: <i>' . $url . '</i></p>');
337
            } else {
338
                $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...
339
                if (strpos($url, "?") > 0) {
340
                    $url .= "&err=$currentNumberOfRedirects";
341
                } else {
342
                    $url .= "?err=$currentNumberOfRedirects";
343
                }
344
            }
345
        }
346
        if ($type == 'REDIRECT_REFRESH') {
347
            $header = 'Refresh: 0;URL=' . $url;
348
        } elseif ($type == 'REDIRECT_META') {
349
            $header = '<META HTTP-EQUIV="Refresh" CONTENT="0; URL=' . $url . '" />';
350
            echo $header;
351
            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...
352
        } elseif ($type == 'REDIRECT_HEADER' || empty($type)) {
353
            // check if url has /$base_url
354
            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...
355
            if (substr($url, 0, strlen($base_url)) == $base_url) {
356
                // append $site_url to make it work with Location:
357
                $url = $site_url . substr($url, strlen($base_url));
358
            }
359
            if (strpos($url, "\n") === false) {
360
                $header = 'Location: ' . $url;
361
            } else {
362
                $this->messageQuit('No newline allowed in redirect url.');
363
            }
364
        }
365
        if ($responseCode && (strpos($responseCode, '30') !== false)) {
366
            header($responseCode);
367
        }
368
369
        if (!empty($header)) {
370
            header($header);
371
        }
372
373
        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...
374
    }
375
376
    /**
377
     * Forward to another page
378
     *
379
     * @param int|string $id
380
     * @param string $responseCode
381
     */
382
    public function sendForward($id, $responseCode = '')
383
    {
384
        if ($this->forwards > 0) {
385
            $this->forwards = $this->forwards - 1;
386
            $this->documentIdentifier = $id;
387
            $this->documentMethod = 'id';
388
            if ($responseCode) {
389
                header($responseCode);
390
            }
391
            $this->prepareResponse();
392
            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...
393
        } else {
394
            $this->messageQuit("Internal Server Error id={$id}");
395
            header('HTTP/1.0 500 Internal Server Error');
396
            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...
397
        }
398
    }
399
400
    /**
401
     * Redirect to the error page, by calling sendForward(). This is called for example when the page was not found.
402
     * @param bool $noEvent
403
     */
404
    public function sendErrorPage($noEvent = false)
405
    {
406
        $this->systemCacheKey = 'notfound';
407
        if (!$noEvent) {
408
            // invoke OnPageNotFound event
409
            $this->invokeEvent('OnPageNotFound');
410
        }
411
        $url = $this->config['error_page'] ? $this->config['error_page'] : $this->config['site_start'];
412
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 (MODX_CLI || (isset($_SESSION[$_]) && !empty($_SESSION[$_]))) {
0 ignored issues
show
Coding Style introduced by
The if-else statement can be simplified to return (bool) (MODX_CLI ...!empty($_SESSION[$_]));.
Loading history...
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
    {
770
        $hash = $id;
771
        $tmp = null;
772
        $params = array();
773
        if (!empty($this->systemCacheKey)) {
774
            $hash = $this->systemCacheKey;
775
        } else {
776
            if (!empty($_GET)) {
777
                // Sort GET parameters so that the order of parameters on the HTTP request don't affect the generated cache ID.
778
                $params = $_GET;
779
                ksort($params);
780
                $hash .= '_' . md5(http_build_query($params));
781
            }
782
        }
783
        $evtOut = $this->invokeEvent("OnMakePageCacheKey", array("hash" => $hash, "id" => $id, 'params' => $params));
784
        if (is_array($evtOut) && count($evtOut) > 0) {
785
            $tmp = array_pop($evtOut);
786
        }
787
        return empty($tmp) ? $hash : $tmp;
788
    }
789
790
    /**
791
     * @param $id
792
     * @param bool $loading
793
     * @return string
794
     */
795
    public function checkCache($id, $loading = false)
796
    {
797
        return $this->getDocumentObjectFromCache($id, $loading);
798
    }
799
800
    /**
801
     * Check the cache for a specific document/resource
802
     *
803
     * @param int $id
804
     * @param bool $loading
805
     * @return string
806
     */
807
    public function getDocumentObjectFromCache($id, $loading = false)
808
    {
809
        $key = ($this->config['cache_type'] == 2) ? $this->makePageCacheKey($id) : $id;
810
        if ($loading) {
811
            $this->cacheKey = $key;
812
        }
813
814
        $cache_path = $this->getHashFile($key);
815
816
        if (!is_file($cache_path)) {
817
            $this->documentGenerated = 1;
818
            return '';
819
        }
820
        $content = file_get_contents($cache_path, false);
821
        if (substr($content, 0, 5) === '<?php') {
822
            $content = substr($content, strpos($content, '?>') + 2);
823
        } // remove php header
824
        $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...
825
        if (count($a) == 1) {
826
            $result = $a[0];
827
        } // return only document content
828
        else {
829
            $docObj = unserialize($a[0]); // rebuild document object
830
            // check page security
831
            if ($docObj['privateweb'] && isset($docObj['__MODxDocGroups__'])) {
832
                $pass = false;
833
                $usrGrps = $this->getUserDocGroups();
834
                $docGrps = explode(',', $docObj['__MODxDocGroups__']);
835
                // check is user has access to doc groups
836
                if (is_array($usrGrps)) {
837
                    foreach ($usrGrps as $k => $v) {
838
                        if (!in_array($v, $docGrps)) {
839
                            continue;
840
                        }
841
                        $pass = true;
842
                        break;
843
                    }
844
                }
845
                // diplay error pages if user has no access to cached doc
846
                if (!$pass) {
847
                    if ($this->config['unauthorized_page']) {
848
                        // check if file is not public
849
                        $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...
850
                        $total = $this->db->getValue($rs);
851
                    } else {
852
                        $total = 0;
853
                    }
854
855
                    if ($total > 0) {
856
                        $this->sendUnauthorizedPage();
857
                    } else {
858
                        $this->sendErrorPage();
859
                    }
860
861
                    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...
862
                }
863
            }
864
            // Grab the Scripts
865
            if (isset($docObj['__MODxSJScripts__'])) {
866
                $this->sjscripts = $docObj['__MODxSJScripts__'];
867
            }
868
            if (isset($docObj['__MODxJScripts__'])) {
869
                $this->jscripts = $docObj['__MODxJScripts__'];
870
            }
871
872
            // Remove intermediate variables
873
            unset($docObj['__MODxDocGroups__'], $docObj['__MODxSJScripts__'], $docObj['__MODxJScripts__']);
874
875
            $this->documentObject = $docObj;
876
877
            $result = $a[1]; // return document content
878
        }
879
880
        $this->documentGenerated = 0;
881
        // invoke OnLoadWebPageCache  event
882
        $this->documentContent = $result;
883
        $this->invokeEvent('OnLoadWebPageCache');
884
        return $result;
885
    }
886
887
    /**
888
     * Final processing and output of the document/resource.
889
     *
890
     * - runs uncached snippets
891
     * - add javascript to <head>
892
     * - removes unused placeholders
893
     * - converts URL tags [~...~] to URLs
894
     *
895
     * @param boolean $noEvent Default: false
896
     */
897
    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...
898
    {
899
        $this->documentOutput = $this->documentContent;
900
901
        if ($this->documentGenerated == 1 && $this->documentObject['cacheable'] == 1 && $this->documentObject['type'] == 'document' && $this->documentObject['published'] == 1) {
902
            if (!empty($this->sjscripts)) {
903
                $this->documentObject['__MODxSJScripts__'] = $this->sjscripts;
904
            }
905
            if (!empty($this->jscripts)) {
906
                $this->documentObject['__MODxJScripts__'] = $this->jscripts;
907
            }
908
        }
909
910
        // check for non-cached snippet output
911
        if (strpos($this->documentOutput, '[!') > -1) {
912
            $this->recentUpdate = $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'];
913
914
            $this->documentOutput = str_replace('[!', '[[', $this->documentOutput);
915
            $this->documentOutput = str_replace('!]', ']]', $this->documentOutput);
916
917
            // Parse document source
918
            $this->documentOutput = $this->parseDocumentSource($this->documentOutput);
919
        }
920
921
        // Moved from prepareResponse() by sirlancelot
922
        // Insert Startup jscripts & CSS scripts into template - template must have a <head> tag
923
        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...
924
            // change to just before closing </head>
925
            // $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...
926
            $this->documentOutput = preg_replace("/(<\/head>)/i", $js . "\n\\1", $this->documentOutput);
927
        }
928
929
        // Insert jscripts & html block into template - template must have a </body> tag
930
        if ($js = $this->getRegisteredClientScripts()) {
931
            $this->documentOutput = preg_replace("/(<\/body>)/i", $js . "\n\\1", $this->documentOutput);
932
        }
933
        // End fix by sirlancelot
934
935
        $this->documentOutput = $this->cleanUpMODXTags($this->documentOutput);
936
937
        $this->documentOutput = $this->rewriteUrls($this->documentOutput);
938
939
        // send out content-type and content-disposition headers
940
        if (IN_PARSER_MODE == "true") {
941
            $type = !empty($this->contentTypes[$this->documentIdentifier]) ? $this->contentTypes[$this->documentIdentifier] : "text/html";
942
            header('Content-Type: ' . $type . '; charset=' . $this->config['modx_charset']);
943
            //            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...
944
            //                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...
945
            if (!$this->checkPreview() && $this->documentObject['content_dispo'] == 1) {
946
                if ($this->documentObject['alias']) {
947
                    $name = $this->documentObject['alias'];
948
                } else {
949
                    // strip title of special characters
950
                    $name = $this->documentObject['pagetitle'];
951
                    $name = strip_tags($name);
952
                    $name = $this->cleanUpMODXTags($name);
953
                    $name = strtolower($name);
954
                    $name = preg_replace('/&.+?;/', '', $name); // kill entities
955
                    $name = preg_replace('/[^\.%a-z0-9 _-]/', '', $name);
956
                    $name = preg_replace('/\s+/', '-', $name);
957
                    $name = preg_replace('|-+|', '-', $name);
958
                    $name = trim($name, '-');
959
                }
960
                $header = 'Content-Disposition: attachment; filename=' . $name;
961
                header($header);
962
            }
963
        }
964
        $this->setConditional();
965
966
        $stats = $this->getTimerStats($this->tstart);
967
968
        $out =& $this->documentOutput;
969
        $out = str_replace("[^q^]", $stats['queries'], $out);
970
        $out = str_replace("[^qt^]", $stats['queryTime'], $out);
971
        $out = str_replace("[^p^]", $stats['phpTime'], $out);
972
        $out = str_replace("[^t^]", $stats['totalTime'], $out);
973
        $out = str_replace("[^s^]", $stats['source'], $out);
974
        $out = str_replace("[^m^]", $stats['phpMemory'], $out);
975
        //$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...
976
977
        // invoke OnWebPagePrerender event
978
        if (!$noEvent) {
979
            $evtOut = $this->invokeEvent('OnWebPagePrerender', array('documentOutput' => $this->documentOutput));
980
            if (is_array($evtOut) && count($evtOut) > 0) {
981
                $this->documentOutput = $evtOut['0'];
982
            }
983
        }
984
985
        $this->documentOutput = $this->removeSanitizeSeed($this->documentOutput);
986
987
        if (strpos($this->documentOutput, '\{') !== false) {
988
            $this->documentOutput = $this->RecoveryEscapedTags($this->documentOutput);
989
        } elseif (strpos($this->documentOutput, '\[') !== false) {
990
            $this->documentOutput = $this->RecoveryEscapedTags($this->documentOutput);
991
        }
992
993
        echo $this->documentOutput;
994
995
        if ($this->dumpSQL) {
996
            echo $this->queryCode;
997
        }
998
        if ($this->dumpSnippets) {
999
            $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...
1000
            $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...
1001
            foreach ($this->snippetsTime as $s => $v) {
1002
                $t = $v['time'];
1003
                $sname = $v['sname'];
1004
                $sc .= sprintf("%s. %s (%s)<br>", $s, $sname, sprintf("%2.2f ms", $t)); // currentSnippet
1005
                $tt += $t;
1006
            }
1007
            echo "<fieldset><legend><b>Snippets</b> (" . count($this->snippetsTime) . " / " . sprintf("%2.2f ms", $tt) . ")</legend>{$sc}</fieldset><br />";
1008
            echo $this->snippetsCode;
1009
        }
1010
        if ($this->dumpPlugins) {
1011
            $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...
1012
            $tt = 0;
1013
            foreach ($this->pluginsTime as $s => $t) {
1014
                $ps .= "$s (" . sprintf("%2.2f ms", $t * 1000) . ")<br>";
1015
                $tt += $t;
1016
            }
1017
            echo "<fieldset><legend><b>Plugins</b> (" . count($this->pluginsTime) . " / " . sprintf("%2.2f ms", $tt * 1000) . ")</legend>{$ps}</fieldset><br />";
1018
            echo $this->pluginsCode;
1019
        }
1020
1021
        ob_end_flush();
1022
    }
1023
1024
    /**
1025
     * @param $contents
1026
     * @return mixed
1027
     */
1028
    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...
1029
    {
1030
        list($sTags, $rTags) = $this->getTagsForEscape();
1031
        return str_replace($rTags, $sTags, $contents);
1032
    }
1033
1034
    /**
1035
     * @param string $tags
1036
     * @return array[]
1037
     */
1038
    public function getTagsForEscape($tags = '{{,}},[[,]],[!,!],[*,*],[(,)],[+,+],[~,~],[^,^]')
1039
    {
1040
        $srcTags = explode(',', $tags);
1041
        $repTags = array();
1042
        foreach ($srcTags as $tag) {
1043
            $repTags[] = '\\' . $tag[0] . '\\' . $tag[1];
1044
        }
1045
        return array($srcTags, $repTags);
1046
    }
1047
1048
    /**
1049
     * @param $tstart
1050
     * @return array
1051
     */
1052
    public function getTimerStats($tstart)
1053
    {
1054
        $stats = array();
1055
1056
        $stats['totalTime'] = ($this->getMicroTime() - $tstart);
1057
        $stats['queryTime'] = $this->queryTime;
1058
        $stats['phpTime'] = $stats['totalTime'] - $stats['queryTime'];
1059
1060
        $stats['queryTime'] = sprintf("%2.4f s", $stats['queryTime']);
1061
        $stats['totalTime'] = sprintf("%2.4f s", $stats['totalTime']);
1062
        $stats['phpTime'] = sprintf("%2.4f s", $stats['phpTime']);
1063
        $stats['source'] = $this->documentGenerated == 1 ? "database" : "cache";
1064
        $stats['queries'] = isset($this->executedQueries) ? $this->executedQueries : 0;
1065
        $stats['phpMemory'] = (memory_get_peak_usage(true) / 1024 / 1024) . " mb";
1066
1067
        return $stats;
1068
    }
1069
1070
    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...
1071
    {
1072
        if (!empty($_POST) || (defined('MODX_API_MODE') && MODX_API_MODE) || $this->getLoginUserID('mgr') || !$this->useConditional || empty($this->recentUpdate)) {
1073
            return;
1074
        }
1075
        $last_modified = gmdate('D, d M Y H:i:s T', $this->recentUpdate);
1076
        $etag = md5($last_modified);
1077
        $HTTP_IF_MODIFIED_SINCE = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? $_SERVER['HTTP_IF_MODIFIED_SINCE'] : false;
1078
        $HTTP_IF_NONE_MATCH = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? $_SERVER['HTTP_IF_NONE_MATCH'] : false;
1079
        header('Pragma: no-cache');
1080
1081
        if ($HTTP_IF_MODIFIED_SINCE == $last_modified || strpos($HTTP_IF_NONE_MATCH, $etag) !== false) {
1082
            header('HTTP/1.1 304 Not Modified');
1083
            header('Content-Length: 0');
1084
            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...
1085
        } else {
1086
            header("Last-Modified: {$last_modified}");
1087
            header("ETag: '{$etag}'");
1088
        }
1089
    }
1090
1091
    /**
1092
     * Checks the publish state of page
1093
     */
1094
    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...
1095
    {
1096
        $cacheRefreshTime = 0;
1097
        $recent_update = 0;
1098
        @include(MODX_BASE_PATH . $this->getCacheFolder() . 'sitePublishing.idx.php');
1099
        $this->recentUpdate = $recent_update;
1100
1101
        $timeNow = $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'];
1102
        if ($timeNow < $cacheRefreshTime || $cacheRefreshTime == 0) {
1103
            return;
1104
        }
1105
1106
        // now, check for documents that need publishing
1107
        $field = array('published' => 1, 'publishedon' => $timeNow);
1108
        $where = "pub_date <= {$timeNow} AND pub_date!=0 AND published=0";
1109
        $result_pub = $this->db->select('id', '[+prefix+]site_content', $where);
1110
        $this->db->update($field, '[+prefix+]site_content', $where);
1111 View Code Duplication
        if ($this->db->getRecordCount($result_pub) >= 1) { //Event unPublished doc
1112
            while ($row_pub = $this->db->getRow($result_pub)) {
1113
                $this->invokeEvent("OnDocUnPublished", array(
1114
                    "docid" => $row_pub['id']
1115
                ));
1116
            }
1117
        }
1118
1119
        // now, check for documents that need un-publishing
1120
        $field = array('published' => 0, 'publishedon' => 0);
1121
        $where = "unpub_date <= {$timeNow} AND unpub_date!=0 AND published=1";
1122
        $result_unpub = $this->db->select('id', '[+prefix+]site_content', $where);
1123
        $this->db->update($field, '[+prefix+]site_content', $where);
1124 View Code Duplication
        if ($this->db->getRecordCount($result_unpub) >= 1) { //Event unPublished doc
1125
            while ($row_unpub = $this->db->getRow($result_unpub)) {
1126
                $this->invokeEvent("OnDocUnPublished", array(
1127
                    "docid" => $row_unpub['id']
1128
                ));
1129
            }
1130
        }
1131
1132
        $this->recentUpdate = $timeNow;
1133
1134
        // clear the cache
1135
        $this->clearCache('full');
1136
    }
1137
1138
    public function checkPublishStatus()
1139
    {
1140
        $this->updatePubStatus();
1141
    }
1142
1143
    /**
1144
     * Final jobs.
1145
     *
1146
     * - cache page
1147
     */
1148
    public function postProcess()
1149
    {
1150
        // if the current document was generated, cache it!
1151
        $cacheable = ($this->config['enable_cache'] && $this->documentObject['cacheable']) ? 1 : 0;
1152
        if ($cacheable && $this->documentGenerated && $this->documentObject['type'] == 'document' && $this->documentObject['published']) {
1153
            // invoke OnBeforeSaveWebPageCache event
1154
            $this->invokeEvent("OnBeforeSaveWebPageCache");
1155
1156
            if (!empty($this->cacheKey) && is_scalar($this->cacheKey)) {
1157
                // get and store document groups inside document object. Document groups will be used to check security on cache pages
1158
                $where = "document='{$this->documentIdentifier}'";
1159
                $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...
1160
                $docGroups = $this->db->getColumn('document_group', $rs);
1161
1162
                // Attach Document Groups and Scripts
1163
                if (is_array($docGroups)) {
1164
                    $this->documentObject['__MODxDocGroups__'] = implode(",", $docGroups);
1165
                }
1166
1167
                $docObjSerial = serialize($this->documentObject);
1168
                $cacheContent = $docObjSerial . "<!--__MODxCacheSpliter__-->" . $this->documentContent;
1169
                $page_cache_path = MODX_BASE_PATH . $this->getHashFile($this->cacheKey);
1170
                file_put_contents($page_cache_path, "<?php die('Unauthorized access.'); ?>$cacheContent");
1171
            }
1172
        }
1173
1174
        // Useful for example to external page counters/stats packages
1175
        $this->invokeEvent('OnWebPageComplete');
1176
1177
        // end post processing
1178
    }
1179
1180
    /**
1181
     * @param $content
1182
     * @param string $left
1183
     * @param string $right
1184
     * @return array
1185
     */
1186
    public function getTagsFromContent($content, $left = '[+', $right = '+]')
1187
    {
1188
        $_ = $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...
1189
        if (empty($_)) {
1190
            return array();
1191
        }
1192
        foreach ($_ as $v) {
1193
            $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...
1194
            $tags[1][] = $v;
1195
        }
1196
        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...
1197
    }
1198
1199
    /**
1200
     * @param $content
1201
     * @param string $left
1202
     * @param string $right
1203
     * @return array
1204
     */
1205
    public function _getTagsFromContent($content, $left = '[+', $right = '+]')
1206
    {
1207
        if (strpos($content, $left) === false) {
1208
            return array();
1209
        }
1210
        $spacer = md5('<<<EVO>>>');
1211
        if ($left==='{{' && strpos($content, ';}}')!==false) {
1212
            $content = str_replace(';}}', sprintf(';}%s}', $spacer), $content);
1213
        }
1214
        if ($left==='{{' && strpos($content, '{{}}')!==false) {
1215
            $content = str_replace('{{}}', sprintf('{%$1s{}%$1s}', $spacer), $content);
1216
        }
1217
        if ($left==='[[' && strpos($content, ']]]]')!==false) {
1218
            $content = str_replace(']]]]', sprintf(']]%s]]', $spacer), $content);
1219
        }
1220
        if ($left==='[[' && strpos($content, ']]]')!==false) {
1221
            $content = str_replace(']]]', sprintf(']%s]]', $spacer), $content);
1222
        }
1223
1224
        $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...
1225
        $pos[']]>'] = strpos($content, ']]>');
1226
1227
        if ($pos['<![CDATA['] !== false && $pos[']]>'] !== false) {
1228
            $content = substr($content, 0, $pos['<![CDATA[']) . substr($content, $pos[']]>'] + 3);
1229
        }
1230
1231
        $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...
1232
        $piece = array();
1233
        foreach ($lp as $lc => $lv) {
1234
            if ($lc !== 0) {
1235
                $piece[] = $left;
1236
            }
1237
            if (strpos($lv, $right) === false) {
1238
                $piece[] = $lv;
1239
            } else {
1240
                $rp = explode($right, $lv);
1241
                foreach ($rp as $rc => $rv) {
1242
                    if ($rc !== 0) {
1243
                        $piece[] = $right;
1244
                    }
1245
                    $piece[] = $rv;
1246
                }
1247
            }
1248
        }
1249
        $lc = 0;
1250
        $rc = 0;
1251
        $fetch = '';
1252
        $tags = array();
1253
        foreach ($piece as $v) {
1254
            if ($v === $left) {
1255
                if (0 < $lc) {
1256
                    $fetch .= $left;
1257
                }
1258
                $lc++;
1259
            } elseif ($v === $right) {
1260
                if ($lc === 0) {
1261
                    continue;
1262
                }
1263
                $rc++;
1264
                if ($lc === $rc) {
1265
                    // #1200 Enable modifiers in Wayfinder - add nested placeholders to $tags like for $fetch = "phx:input=`[+wf.linktext+]`:test"
1266
                    if (strpos($fetch, $left) !== false) {
1267
                        $nested = $this->_getTagsFromContent($fetch, $left, $right);
1268
                        foreach ($nested as $tag) {
1269
                            if (!in_array($tag, $tags)) {
1270
                                $tags[] = $tag;
1271
                            }
1272
                        }
1273
                    }
1274
1275
                    if (!in_array($fetch, $tags)) {  // Avoid double Matches
1276
                        $tags[] = $fetch; // Fetch
1277
                    };
1278
                    $fetch = ''; // and reset
1279
                    $lc = 0;
1280
                    $rc = 0;
1281
                } else {
1282
                    $fetch .= $right;
1283
                }
1284
            } else {
1285
                if (0 < $lc) {
1286
                    $fetch .= $v;
1287
                } else {
1288
                    continue;
1289
                }
1290
            }
1291
        }
1292
        foreach ($tags as $i=>$tag) {
1293
            if (strpos($tag, $spacer)!==false) {
1294
                $tags[$i] = str_replace($spacer, '', $tag);
1295
            }
1296
        }
1297
        return $tags;
1298
    }
1299
1300
    /**
1301
     * Merge content fields and TVs
1302
     *
1303
     * @param $content
1304
     * @param bool $ph
1305
     * @return string
1306
     * @internal param string $template
1307
     */
1308
    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...
1309
    {
1310 View Code Duplication
        if ($this->config['enable_at_syntax']) {
1311
            if (stripos($content, '<@LITERAL>') !== false) {
1312
                $content = $this->escapeLiteralTagsContent($content);
1313
            }
1314
        }
1315
        if (strpos($content, '[*') === false) {
1316
            return $content;
1317
        }
1318
        if (!isset($this->documentIdentifier)) {
1319
            return $content;
1320
        }
1321
        if (!isset($this->documentObject) || empty($this->documentObject)) {
1322
            return $content;
1323
        }
1324
1325
        if (!$ph) {
1326
            $ph = $this->documentObject;
1327
        }
1328
1329
        $matches = $this->getTagsFromContent($content, '[*', '*]');
1330
        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...
1331
            return $content;
1332
        }
1333
1334
        foreach ($matches[1] as $i => $key) {
1335
            if (strpos($key, '[+')!==false) {
1336
                continue;
1337
            } // 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...
1338
            if (substr($key, 0, 1) == '#') {
1339
                $key = substr($key, 1);
1340
            } // remove # for QuickEdit format
1341
1342
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1343
            if (strpos($key, '@') !== false) {
1344
                list($key, $context) = explode('@', $key, 2);
1345
            } else {
1346
                $context = false;
1347
            }
1348
1349
            // 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...
1350
            if ($context) {
1351
                $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...
1352
            } else {
1353
                $value = isset($ph[$key]) ? $ph[$key] : '';
1354
            }
1355
1356
            if (is_array($value)) {
1357
                include_once(MODX_MANAGER_PATH . 'includes/tmplvars.format.inc.php');
1358
                include_once(MODX_MANAGER_PATH . 'includes/tmplvars.commands.inc.php');
1359
                $value = getTVDisplayFormat($value[0], $value[1], $value[2], $value[3], $value[4]);
1360
            }
1361
1362
            $s = &$matches[0][$i];
1363
            if ($modifiers !== false) {
1364
                $value = $this->applyFilter($value, $modifiers, $key);
1365
            }
1366
1367 View Code Duplication
            if (strpos($content, $s) !== false) {
1368
                $content = str_replace($s, $value, $content);
1369
            } elseif ($this->debug) {
1370
                $this->addLog('mergeDocumentContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1371
            }
1372
        }
1373
1374
        return $content;
1375
    }
1376
1377
    /**
1378
     * @param $key
1379
     * @param bool|int $parent
1380
     * @return bool|mixed|string
1381
     */
1382
    public function _contextValue($key, $parent = false)
1383
    {
1384
        if (preg_match('/@\d+\/u/', $key)) {
1385
            $key = str_replace(array('@', '/u'), array('@u(', ')'), $key);
1386
        }
1387
        list($key, $str) = explode('@', $key, 2);
1388
1389
        if (strpos($str, '(')) {
1390
            list($context, $option) = explode('(', $str, 2);
1391
        } else {
1392
            list($context, $option) = array($str, false);
1393
        }
1394
1395
        if ($option) {
1396
            $option = trim($option, ')(\'"`');
1397
        }
1398
1399
        switch (strtolower($context)) {
1400
            case 'site_start':
1401
                $docid = $this->config['site_start'];
1402
                break;
1403
            case 'parent':
1404
            case 'p':
1405
                $docid = $parent;
1406
                if ($docid == 0) {
1407
                    $docid = $this->config['site_start'];
1408
                }
1409
                break;
1410
            case 'ultimateparent':
1411
            case 'uparent':
1412
            case 'up':
1413
            case 'u':
1414 View Code Duplication
                if (strpos($str, '(') !== false) {
1415
                    $top = substr($str, strpos($str, '('));
1416
                    $top = trim($top, '()"\'');
1417
                } else {
1418
                    $top = 0;
1419
                }
1420
                $docid = $this->getUltimateParentId($this->documentIdentifier, $top);
1421
                break;
1422
            case 'alias':
1423
                $str = substr($str, strpos($str, '('));
1424
                $str = trim($str, '()"\'');
1425
                $docid = $this->getIdFromAlias($str);
1426
                break;
1427 View Code Duplication
            case 'prev':
1428
                if (!$option) {
1429
                    $option = 'menuindex,ASC';
1430
                } elseif (strpos($option, ',') === false) {
1431
                    $option .= ',ASC';
1432
                }
1433
                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...
1434
                $children = $this->getActiveChildren($parent, $by, $dir);
1435
                $find = false;
1436
                $prev = false;
1437
                foreach ($children as $row) {
1438
                    if ($row['id'] == $this->documentIdentifier) {
1439
                        $find = true;
1440
                        break;
1441
                    }
1442
                    $prev = $row;
1443
                }
1444
                if ($find) {
1445
                    if (isset($prev[$key])) {
1446
                        return $prev[$key];
1447
                    } else {
1448
                        $docid = $prev['id'];
1449
                    }
1450
                } else {
1451
                    $docid = '';
1452
                }
1453
                break;
1454 View Code Duplication
            case 'next':
1455
                if (!$option) {
1456
                    $option = 'menuindex,ASC';
1457
                } elseif (strpos($option, ',') === false) {
1458
                    $option .= ',ASC';
1459
                }
1460
                list($by, $dir) = explode(',', $option, 2);
1461
                $children = $this->getActiveChildren($parent, $by, $dir);
1462
                $find = false;
1463
                $next = false;
1464
                foreach ($children as $row) {
1465
                    if ($find) {
1466
                        $next = $row;
1467
                        break;
1468
                    }
1469
                    if ($row['id'] == $this->documentIdentifier) {
1470
                        $find = true;
1471
                    }
1472
                }
1473
                if ($find) {
1474
                    if (isset($next[$key])) {
1475
                        return $next[$key];
1476
                    } else {
1477
                        $docid = $next['id'];
1478
                    }
1479
                } else {
1480
                    $docid = '';
1481
                }
1482
                break;
1483
            default:
1484
                $docid = $str;
1485
        }
1486
        if (preg_match('@^[1-9][0-9]*$@', $docid)) {
1487
            $value = $this->getField($key, $docid);
1488
        } else {
1489
            $value = '';
1490
        }
1491
        return $value;
1492
    }
1493
1494
    /**
1495
     * Merge system settings
1496
     *
1497
     * @param $content
1498
     * @param bool|array $ph
1499
     * @return string
1500
     * @internal param string $template
1501
     */
1502
    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...
1503
    {
1504 View Code Duplication
        if ($this->config['enable_at_syntax']) {
1505
            if (stripos($content, '<@LITERAL>') !== false) {
1506
                $content = $this->escapeLiteralTagsContent($content);
1507
            }
1508
        }
1509
        if (strpos($content, '[(') === false) {
1510
            return $content;
1511
        }
1512
1513
        if (empty($ph)) {
1514
            $ph = $this->config;
1515
        }
1516
1517
        $matches = $this->getTagsFromContent($content, '[(', ')]');
1518
        if (empty($matches)) {
1519
            return $content;
1520
        }
1521
1522
        foreach ($matches[1] as $i => $key) {
1523
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1524
1525
            if (isset($ph[$key])) {
1526
                $value = $ph[$key];
1527
            } else {
1528
                continue;
1529
            }
1530
1531
            if ($modifiers !== false) {
1532
                $value = $this->applyFilter($value, $modifiers, $key);
1533
            }
1534
            $s = &$matches[0][$i];
1535 View Code Duplication
            if (strpos($content, $s) !== false) {
1536
                $content = str_replace($s, $value, $content);
1537
            } elseif ($this->debug) {
1538
                $this->addLog('mergeSettingsContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1539
            }
1540
        }
1541
        return $content;
1542
    }
1543
1544
    /**
1545
     * Merge chunks
1546
     *
1547
     * @param string $content
1548
     * @param bool|array $ph
1549
     * @return string
1550
     */
1551
    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...
1552
    {
1553
        if ($this->config['enable_at_syntax']) {
1554
            if (strpos($content, '{{ ') !== false) {
1555
                $content = str_replace(array('{{ ', ' }}'), array('\{\{ ', ' \}\}'), $content);
1556
            }
1557
            if (stripos($content, '<@LITERAL>') !== false) {
1558
                $content = $this->escapeLiteralTagsContent($content);
1559
            }
1560
        }
1561
        if (strpos($content, '{{') === false) {
1562
            return $content;
1563
        }
1564
1565
        if (empty($ph)) {
1566
            $ph = $this->chunkCache;
1567
        }
1568
1569
        $matches = $this->getTagsFromContent($content, '{{', '}}');
1570
        if (empty($matches)) {
1571
            return $content;
1572
        }
1573
1574
        foreach ($matches[1] as $i => $key) {
1575
            $snip_call = $this->_split_snip_call($key);
1576
            $key = $snip_call['name'];
1577
            $params = $this->getParamsFromString($snip_call['params']);
1578
1579
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1580
1581
            if (!isset($ph[$key])) {
1582
                $ph[$key] = $this->getChunk($key);
1583
            }
1584
            $value = $ph[$key];
1585
1586
            if (is_null($value)) {
1587
                continue;
1588
            }
1589
1590
            $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 1577 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...
1591
            $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 1577 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...
1592
            if ($this->config['enable_at_syntax']) {
1593
                $value = $this->mergeConditionalTagsContent($value);
1594
            }
1595
            $value = $this->mergeDocumentContent($value);
1596
            $value = $this->mergeSettingsContent($value);
1597
            $value = $this->mergeChunkContent($value);
1598
1599
            if ($modifiers !== false) {
1600
                $value = $this->applyFilter($value, $modifiers, $key);
1601
            }
1602
1603
            $s = &$matches[0][$i];
1604 View Code Duplication
            if (strpos($content, $s) !== false) {
1605
                $content = str_replace($s, $value, $content);
1606
            } elseif ($this->debug) {
1607
                $this->addLog('mergeChunkContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1608
            }
1609
        }
1610
        return $content;
1611
    }
1612
1613
    /**
1614
     * Merge placeholder values
1615
     *
1616
     * @param string $content
1617
     * @param bool|array $ph
1618
     * @return string
1619
     */
1620
    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...
1621
    {
1622 View Code Duplication
        if ($this->config['enable_at_syntax']) {
1623
            if (stripos($content, '<@LITERAL>') !== false) {
1624
                $content = $this->escapeLiteralTagsContent($content);
1625
            }
1626
        }
1627
        if (strpos($content, '[+') === false) {
1628
            return $content;
1629
        }
1630
1631
        if (empty($ph)) {
1632
            $ph = $this->placeholders;
1633
        }
1634
1635
        if ($this->config['enable_at_syntax']) {
1636
            $content = $this->mergeConditionalTagsContent($content);
1637
        }
1638
1639
        $content = $this->mergeDocumentContent($content);
1640
        $content = $this->mergeSettingsContent($content);
1641
        $matches = $this->getTagsFromContent($content, '[+', '+]');
1642
        if (empty($matches)) {
1643
            return $content;
1644
        }
1645
        foreach ($matches[1] as $i => $key) {
1646
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1647
1648
            if (isset($ph[$key])) {
1649
                $value = $ph[$key];
1650
            } elseif ($key === 'phx') {
1651
                $value = '';
1652
            } else {
1653
                continue;
1654
            }
1655
1656
            if ($modifiers !== false) {
1657
                $modifiers = $this->mergePlaceholderContent($modifiers);
1658
                $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...
1659
            }
1660
            $s = &$matches[0][$i];
1661 View Code Duplication
            if (strpos($content, $s) !== false) {
1662
                $content = str_replace($s, $value, $content);
1663
            } elseif ($this->debug) {
1664
                $this->addLog('mergePlaceholderContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1665
            }
1666
        }
1667
        return $content;
1668
    }
1669
1670
    /**
1671
     * @param $content
1672
     * @param string $iftag
1673
     * @param string $elseiftag
1674
     * @param string $elsetag
1675
     * @param string $endiftag
1676
     * @return mixed|string
1677
     */
1678
    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...
1679
    {
1680
        if (strpos($content, '@IF') !== false) {
1681
            $content = $this->_prepareCTag($content, $iftag, $elseiftag, $elsetag, $endiftag);
1682
        }
1683
1684
        if (strpos($content, $iftag) === false) {
1685
            return $content;
1686
        }
1687
1688
        $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...
1689
        $content = str_replace(array('<?php', '<?=', '<?', '?>'), array("{$sp}b", "{$sp}p", "{$sp}s", "{$sp}e"), $content);
1690
1691
        $pieces = explode('<@IF:', $content);
1692 View Code Duplication
        foreach ($pieces as $i => $split) {
1693
            if ($i === 0) {
1694
                $content = $split;
1695
                continue;
1696
            }
1697
            list($cmd, $text) = explode('>', $split, 2);
1698
            $cmd = str_replace("'", "\'", $cmd);
1699
            $content .= "<?php if(\$this->_parseCTagCMD('" . $cmd . "')): ?>";
1700
            $content .= $text;
1701
        }
1702
        $pieces = explode('<@ELSEIF:', $content);
1703 View Code Duplication
        foreach ($pieces as $i => $split) {
1704
            if ($i === 0) {
1705
                $content = $split;
1706
                continue;
1707
            }
1708
            list($cmd, $text) = explode('>', $split, 2);
1709
            $cmd = str_replace("'", "\'", $cmd);
1710
            $content .= "<?php elseif(\$this->_parseCTagCMD('" . $cmd . "')): ?>";
1711
            $content .= $text;
1712
        }
1713
1714
        $content = str_replace(array('<@ELSE>', '<@ENDIF>'), array('<?php else:?>', '<?php endif;?>'), $content);
1715
        ob_start();
1716
        $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...
1717
        $content = ob_get_clean();
1718
        $content = str_replace(array("{$sp}b", "{$sp}p", "{$sp}s", "{$sp}e"), array('<?php', '<?=', '<?', '?>'), $content);
1719
1720
        return $content;
1721
    }
1722
1723
    /**
1724
     * @param $content
1725
     * @param string $iftag
1726
     * @param string $elseiftag
1727
     * @param string $elsetag
1728
     * @param string $endiftag
1729
     * @return mixed
1730
     */
1731
    private function _prepareCTag($content, $iftag = '<@IF:', $elseiftag = '<@ELSEIF:', $elsetag = '<@ELSE>', $endiftag = '<@ENDIF>')
1732
    {
1733
        if (strpos($content, '<!--@IF ') !== false) {
1734
            $content = str_replace('<!--@IF ', $iftag, $content);
1735
        } // for jp
1736
        if (strpos($content, '<!--@IF:') !== false) {
1737
            $content = str_replace('<!--@IF:', $iftag, $content);
1738
        }
1739
        if (strpos($content, $iftag) === false) {
1740
            return $content;
1741
        }
1742
        if (strpos($content, '<!--@ELSEIF:') !== false) {
1743
            $content = str_replace('<!--@ELSEIF:', $elseiftag, $content);
1744
        } // for jp
1745
        if (strpos($content, '<!--@ELSE-->') !== false) {
1746
            $content = str_replace('<!--@ELSE-->', $elsetag, $content);
1747
        }  // for jp
1748
        if (strpos($content, '<!--@ENDIF-->') !== false) {
1749
            $content = str_replace('<!--@ENDIF-->', $endiftag, $content);
1750
        }    // for jp
1751
        if (strpos($content, '<@ENDIF-->') !== false) {
1752
            $content = str_replace('<@ENDIF-->', $endiftag, $content);
1753
        }
1754
        $tags = array($iftag, $elseiftag, $elsetag, $endiftag);
1755
        $content = str_ireplace($tags, $tags, $content); // Change to capital letters
1756
        return $content;
1757
    }
1758
1759
    /**
1760
     * @param $cmd
1761
     * @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...
1762
     */
1763
    private function _parseCTagCMD($cmd)
1764
    {
1765
        $cmd = trim($cmd);
1766
        $reverse = substr($cmd, 0, 1) === '!' ? true : false;
1767
        if ($reverse) {
1768
            $cmd = ltrim($cmd, '!');
1769
        }
1770
        if (strpos($cmd, '[!') !== false) {
1771
            $cmd = str_replace(array('[!', '!]'), array('[[', ']]'), $cmd);
1772
        }
1773
        $safe = 0;
1774
        while ($safe < 20) {
1775
            $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...
1776
            if (strpos($cmd, '[*') !== false) {
1777
                $cmd = $this->mergeDocumentContent($cmd);
1778
            }
1779
            if (strpos($cmd, '[(') !== false) {
1780
                $cmd = $this->mergeSettingsContent($cmd);
1781
            }
1782
            if (strpos($cmd, '{{') !== false) {
1783
                $cmd = $this->mergeChunkContent($cmd);
1784
            }
1785
            if (strpos($cmd, '[[') !== false) {
1786
                $cmd = $this->evalSnippets($cmd);
1787
            }
1788
            if (strpos($cmd, '[+') !== false && strpos($cmd, '[[') === false) {
1789
                $cmd = $this->mergePlaceholderContent($cmd);
1790
            }
1791
            if ($bt === md5($cmd)) {
1792
                break;
1793
            }
1794
            $safe++;
1795
        }
1796
        $cmd = ltrim($cmd);
1797
        $cmd = rtrim($cmd, '-');
1798
        $cmd = str_ireplace(array(' and ', ' or '), array('&&', '||'), $cmd);
1799
1800
        if (!preg_match('@^[0-9]*$@', $cmd) && preg_match('@^[0-9<= \-\+\*/\(\)%!&|]*$@', $cmd)) {
1801
            $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...
1802
        } else {
1803
            $_ = 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...
1804
            foreach ($_ as $left) {
1805
                if (strpos($cmd, $left) !== false) {
1806
                    $cmd = 0;
1807
                    break;
1808
                }
1809
            }
1810
        }
1811
        $cmd = trim($cmd);
1812
        if (!preg_match('@^[0-9]+$@', $cmd)) {
1813
            $cmd = empty($cmd) ? 0 : 1;
1814
        } elseif ($cmd <= 0) {
1815
            $cmd = 0;
1816
        }
1817
1818
        if ($reverse) {
1819
            $cmd = !$cmd;
1820
        }
1821
1822
        return $cmd;
1823
    }
1824
1825
    /**
1826
     * Remove Comment-Tags from output like <!--@- Comment -@-->
1827
     * @param $content
1828
     * @param string $left
1829
     * @param string $right
1830
     * @return mixed
1831
     */
1832
    public function ignoreCommentedTagsContent($content, $left = '<!--@-', $right = '-@-->')
1833
    {
1834
        if (strpos($content, $left) === false) {
1835
            return $content;
1836
        }
1837
1838
        $matches = $this->getTagsFromContent($content, $left, $right);
1839
        if (!empty($matches)) {
1840
            foreach ($matches[0] as $i => $v) {
1841
                $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...
1842
            }
1843
            $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...
1844
            if (strpos($content, $left) !== false) {
1845
                $content = str_replace($matches[0], '', $content);
1846
            }
1847
        }
1848
        return $content;
1849
    }
1850
1851
    /**
1852
     * @param $content
1853
     * @param string $left
1854
     * @param string $right
1855
     * @return mixed
1856
     */
1857
    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...
1858
    {
1859
        if (stripos($content, $left) === false) {
1860
            return $content;
1861
        }
1862
1863
        $matches = $this->getTagsFromContent($content, $left, $right);
1864
        if (empty($matches)) {
1865
            return $content;
1866
        }
1867
1868
        list($sTags, $rTags) = $this->getTagsForEscape();
1869
        foreach ($matches[1] as $i => $v) {
1870
            $v = str_ireplace($sTags, $rTags, $v);
1871
            $s = &$matches[0][$i];
1872 View Code Duplication
            if (strpos($content, $s) !== false) {
1873
                $content = str_replace($s, $v, $content);
1874
            } elseif ($this->debug) {
1875
                $this->addLog('ignoreCommentedTagsContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1876
            }
1877
        }
1878
        return $content;
1879
    }
1880
1881
    /**
1882
     * Detect PHP error according to MODX error level
1883
     *
1884
     * @param integer $error PHP error level
1885
     * @return boolean Error detected
1886
     */
1887
1888
    public function detectError($error)
1889
    {
1890
        $detected = false;
1891
        if ($this->config['error_reporting'] == 99 && $error) {
1892
            $detected = true;
1893
        } elseif ($this->config['error_reporting'] == 2 && ($error & ~E_NOTICE)) {
1894
            $detected = true;
1895
        } elseif ($this->config['error_reporting'] == 1 && ($error & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT)) {
1896
            $detected = true;
1897
        }
1898
        return $detected;
1899
    }
1900
1901
    /**
1902
     * Run a plugin
1903
     *
1904
     * @param string $pluginCode Code to run
1905
     * @param array $params
1906
     */
1907
    public function evalPlugin($pluginCode, $params)
1908
    {
1909
        $modx = &$this;
1910
        $modx->event->params = &$params; // store params inside event object
1911
        if (is_array($params)) {
1912
            extract($params, EXTR_SKIP);
1913
        }
1914
        /* 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...
1915
        // 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.
1916
        // Related to https://github.com/modxcms/evolution/issues/1130
1917
        $lock_file_path = MODX_BASE_PATH . 'assets/cache/lock_' . str_replace(' ','-',strtolower($this->event->activePlugin)) . '.pageCache.php';
1918
        if($this->isBackend()) {
1919
            if(is_file($lock_file_path)) {
1920
                $msg = sprintf("Plugin parse error, Temporarily disabled '%s'.", $this->event->activePlugin);
1921
                $this->logEvent(0, 3, $msg, $msg);
1922
                return;
1923
            }
1924
            elseif(stripos($this->event->activePlugin,'ElementsInTree')===false) touch($lock_file_path);
1925
        }*/
1926
        ob_start();
1927
        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...
1928
        $msg = ob_get_contents();
1929
        ob_end_clean();
1930
        // When reached here, no fatal error occured so the lock should be removed.
1931
        /*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...
1932
1933 View Code Duplication
        if ((0 < $this->config['error_reporting']) && $msg && isset($php_errormsg)) {
1934
            $error_info = error_get_last();
1935
            if ($this->detectError($error_info['type'])) {
1936
                $msg = ($msg === false) ? 'ob_get_contents() error' : $msg;
1937
                $this->messageQuit('PHP Parse Error', '', true, $error_info['type'], $error_info['file'], 'Plugin', $error_info['message'], $error_info['line'], $msg);
1938
                if ($this->isBackend()) {
1939
                    $this->event->alert('An error occurred while loading. Please see the event log for more information.<p>' . $msg . '</p>');
1940
                }
1941
            }
1942
        } else {
1943
            echo $msg;
1944
        }
1945
        unset($modx->event->params);
1946
    }
1947
1948
    /**
1949
     * Run a snippet
1950
     *
1951
     * @param $phpcode
1952
     * @param array $params
1953
     * @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...
1954
     * @internal param string $snippet Code to run
1955
     */
1956
    public function evalSnippet($phpcode, $params)
1957
    {
1958
        $modx = &$this;
1959
        /*
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...
1960
        if(isset($params) && is_array($params)) {
1961
            foreach($params as $k=>$v) {
1962
                $v = strtolower($v);
1963
                if($v==='false')    $params[$k] = false;
1964
                elseif($v==='true') $params[$k] = true;
1965
            }
1966
        }*/
1967
        $modx->event->params = &$params; // store params inside event object
1968
        if (is_array($params)) {
1969
            extract($params, EXTR_SKIP);
1970
        }
1971
        ob_start();
1972
        if (strpos($phpcode, ';') !== false) {
1973
            $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...
1974
        } else {
1975
            $return = call_user_func_array($phpcode, array($params));
1976
        }
1977
        $echo = ob_get_contents();
1978
        ob_end_clean();
1979 View Code Duplication
        if ((0 < $this->config['error_reporting']) && isset($php_errormsg)) {
1980
            $error_info = error_get_last();
1981
            if ($this->detectError($error_info['type'])) {
1982
                $echo = ($echo === false) ? 'ob_get_contents() error' : $echo;
1983
                $this->messageQuit('PHP Parse Error', '', true, $error_info['type'], $error_info['file'], 'Snippet', $error_info['message'], $error_info['line'], $echo);
1984
                if ($this->isBackend()) {
1985
                    $this->event->alert('An error occurred while loading. Please see the event log for more information<p>' . $echo . $return . '</p>');
1986
                }
1987
            }
1988
        }
1989
        unset($modx->event->params);
1990
        if (is_array($return) || is_object($return)) {
1991
            return $return;
1992
        } else {
1993
            return $echo . $return;
1994
        }
1995
    }
1996
1997
    /**
1998
     * Run snippets as per the tags in $documentSource and replace the tags with the returned values.
1999
     *
2000
     * @param $content
2001
     * @return string
2002
     * @internal param string $documentSource
2003
     */
2004
    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...
2005
    {
2006
        if (strpos($content, '[[') === false) {
2007
            return $content;
2008
        }
2009
2010
        $matches = $this->getTagsFromContent($content, '[[', ']]');
2011
2012
        if (empty($matches)) {
2013
            return $content;
2014
        }
2015
2016
        $this->snipLapCount++;
2017
        if ($this->dumpSnippets) {
2018
            $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);
2019
        }
2020
2021
        foreach ($matches[1] as $i => $call) {
2022
            $s = &$matches[0][$i];
2023
            if (substr($call, 0, 2) === '$_') {
2024
                if (strpos($content, '_PHX_INTERNAL_') === false) {
2025
                    $value = $this->_getSGVar($call);
2026
                } else {
2027
                    $value = $s;
2028
                }
2029 View Code Duplication
                if (strpos($content, $s) !== false) {
2030
                    $content = str_replace($s, $value, $content);
2031
                } elseif ($this->debug) {
2032
                    $this->addLog('evalSnippetsSGVar parse error', $_SERVER['REQUEST_URI'] . $s, 2);
2033
                }
2034
                continue;
2035
            }
2036
            $value = $this->_get_snip_result($call);
2037
            if (is_null($value)) {
2038
                continue;
2039
            }
2040
2041 View Code Duplication
            if (strpos($content, $s) !== false) {
2042
                $content = str_replace($s, $value, $content);
2043
            } elseif ($this->debug) {
2044
                $this->addLog('evalSnippets parse error', $_SERVER['REQUEST_URI'] . $s, 2);
2045
            }
2046
        }
2047
2048
        if ($this->dumpSnippets) {
2049
            $this->snippetsCode .= '</fieldset><br />';
2050
        }
2051
2052
        return $content;
2053
    }
2054
2055
    /**
2056
     * @param $value
2057
     * @return mixed|string
2058
     */
2059
    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...
2060
    { // Get super globals
2061
        $key = $value;
2062
        $_ = $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...
2063
        $this->config['enable_filter'] = 1;
2064
        list($key, $modifiers) = $this->splitKeyAndFilter($key);
2065
        $this->config['enable_filter'] = $_;
2066
        $key = str_replace(array('(', ')'), array("['", "']"), $key);
2067
        $key = rtrim($key, ';');
2068
        if (strpos($key, '$_SESSION') !== false) {
2069
            $_ = $_SESSION;
2070
            $key = str_replace('$_SESSION', '$_', $key);
2071
            if (isset($_['mgrFormValues'])) {
2072
                unset($_['mgrFormValues']);
2073
            }
2074
            if (isset($_['token'])) {
2075
                unset($_['token']);
2076
            }
2077
        }
2078
        if (strpos($key, '[') !== false) {
2079
            $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...
2080
        } 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...
2081
            $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...
2082
        } else {
2083
            $value = '';
2084
        }
2085
        if ($modifiers !== false) {
2086
            $value = $this->applyFilter($value, $modifiers, $key);
2087
        }
2088
        return $value;
2089
    }
2090
2091
    /**
2092
     * @param $piece
2093
     * @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...
2094
     */
2095
    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...
2096
    {
2097
        if (ltrim($piece) !== $piece) {
2098
            return '';
2099
        }
2100
2101
        $eventtime = $this->dumpSnippets ? $this->getMicroTime() : 0;
2102
2103
        $snip_call = $this->_split_snip_call($piece);
2104
        $key = $snip_call['name'];
2105
2106
        list($key, $modifiers) = $this->splitKeyAndFilter($key);
2107
        $snip_call['name'] = $key;
2108
        $snippetObject = $this->_getSnippetObject($key);
2109
        if (is_null($snippetObject['content'])) {
2110
            return null;
2111
        }
2112
2113
        $this->currentSnippet = $snippetObject['name'];
2114
2115
        // current params
2116
        $params = $this->getParamsFromString($snip_call['params']);
2117
2118
        if (!isset($snippetObject['properties'])) {
2119
            $snippetObject['properties'] = '';
2120
        }
2121
        $default_params = $this->parseProperties($snippetObject['properties'], $this->currentSnippet, 'snippet');
2122
        $params = array_merge($default_params, $params);
2123
2124
        $value = $this->evalSnippet($snippetObject['content'], $params);
2125
        $this->currentSnippet = '';
2126
        if ($modifiers !== false) {
2127
            $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...
2128
        }
2129
2130
        if ($this->dumpSnippets) {
2131
            $eventtime = $this->getMicroTime() - $eventtime;
2132
            $eventtime = sprintf('%2.2f ms', $eventtime * 1000);
2133
            $code = str_replace("\t", '  ', $this->htmlspecialchars($value));
2134
            $piece = str_replace("\t", '  ', $this->htmlspecialchars($piece));
2135
            $print_r_params = str_replace("\t", '  ', $this->htmlspecialchars('$modx->event->params = ' . print_r($params, true)));
2136
            $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);
2137
            $this->snippetsTime[] = array('sname' => $key, 'time' => $eventtime);
2138
        }
2139
        return $value;
2140
    }
2141
2142
    /**
2143
     * @param string $string
2144
     * @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...
2145
     */
2146
    public function getParamsFromString($string = '')
2147
    {
2148
        if (empty($string)) {
2149
            return array();
2150
        }
2151
2152
        if (strpos($string, '&_PHX_INTERNAL_') !== false) {
2153
            $string = str_replace(array('&_PHX_INTERNAL_091_&', '&_PHX_INTERNAL_093_&'), array('[', ']'), $string);
2154
        }
2155
2156
        $_ = $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...
2157
        $this->documentOutput = $string;
2158
        $this->invokeEvent('OnBeforeParseParams');
2159
        $string = $this->documentOutput;
2160
        $this->documentOutput = $_;
2161
2162
        $_tmp = $string;
2163
        $_tmp = ltrim($_tmp, '?&');
2164
        $temp_params = array();
2165
        $key = '';
2166
        $value = null;
2167
        while ($_tmp !== '') {
2168
            $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...
2169
            $char = substr($_tmp, 0, 1);
2170
            $_tmp = substr($_tmp, 1);
2171
2172
            if ($char === '=') {
2173
                $_tmp = trim($_tmp);
2174
                $delim = substr($_tmp, 0, 1);
2175
                if (in_array($delim, array('"', "'", '`'))) {
2176
                    $null = null;
2177
                    //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...
2178
                    list($null, $value, $_tmp) = explode($delim, $_tmp, 3);
2179
                    unset($null);
2180
2181
                    if (substr(trim($_tmp), 0, 2) === '//') {
2182
                        $_tmp = strstr(trim($_tmp), "\n");
2183
                    }
2184
                    $i = 0;
2185
                    while ($delim === '`' && substr(trim($_tmp), 0, 1) !== '&' && 1 < substr_count($_tmp, '`')) {
2186
                        list($inner, $outer, $_tmp) = explode('`', $_tmp, 3);
2187
                        $value .= "`{$inner}`{$outer}";
2188
                        $i++;
2189
                        if (100 < $i) {
2190
                            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...
2191
                        }
2192
                    }
2193
                    if ($i && $delim === '`') {
2194
                        $value = rtrim($value, '`');
2195
                    }
2196
                } elseif (strpos($_tmp, '&') !== false) {
2197
                    list($value, $_tmp) = explode('&', $_tmp, 2);
2198
                    $value = trim($value);
2199
                } else {
2200
                    $value = $_tmp;
2201
                    $_tmp = '';
2202
                }
2203
            } elseif ($char === '&') {
2204
                if (trim($key) !== '') {
2205
                    $value = '1';
2206
                } else {
2207
                    continue;
2208
                }
2209
            } elseif ($_tmp === '') {
2210
                $key .= $char;
2211
                $value = '1';
2212
            } elseif ($key !== '' || trim($char) !== '') {
2213
                $key .= $char;
2214
            }
2215
2216
            if (isset($value) && !is_null($value)) {
2217
                if (strpos($key, 'amp;') !== false) {
2218
                    $key = str_replace('amp;', '', $key);
2219
                }
2220
                $key = trim($key);
2221 View Code Duplication
                if (strpos($value, '[!') !== false) {
2222
                    $value = str_replace(array('[!', '!]'), array('[[', ']]'), $value);
2223
                }
2224
                $value = $this->mergeDocumentContent($value);
2225
                $value = $this->mergeSettingsContent($value);
2226
                $value = $this->mergeChunkContent($value);
2227
                $value = $this->evalSnippets($value);
2228
                if (substr($value, 0, 6) !== '@CODE:') {
2229
                    $value = $this->mergePlaceholderContent($value);
2230
                }
2231
2232
                $temp_params[][$key] = $value;
2233
2234
                $key = '';
2235
                $value = null;
2236
2237
                $_tmp = ltrim($_tmp, " ,\t");
2238
                if (substr($_tmp, 0, 2) === '//') {
2239
                    $_tmp = strstr($_tmp, "\n");
2240
                }
2241
            }
2242
2243
            if ($_tmp === $bt) {
2244
                $key = trim($key);
2245
                if ($key !== '') {
2246
                    $temp_params[][$key] = '';
2247
                }
2248
                break;
2249
            }
2250
        }
2251
2252
        foreach ($temp_params as $p) {
2253
            $k = key($p);
2254
            if (substr($k, -2) === '[]') {
2255
                $k = substr($k, 0, -2);
2256
                $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...
2257
            } elseif (strpos($k, '[') !== false && substr($k, -1) === ']') {
2258
                list($k, $subk) = explode('[', $k, 2);
2259
                $subk = substr($subk, 0, -1);
2260
                $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...
2261
            } else {
2262
                $params[$k] = current($p);
2263
            }
2264
        }
2265
        return $params;
2266
    }
2267
2268
    /**
2269
     * @param $str
2270
     * @return bool|int
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use false|integer.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
2271
     */
2272
    public function _getSplitPosition($str)
2273
    {
2274
        $closeOpt = false;
2275
        $maybePos = false;
2276
        $inFilter = false;
2277
        $pos = false;
2278
        $total = strlen($str);
2279
        for ($i = 0; $i < $total; $i++) {
2280
            $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...
2281
            $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...
2282
            if (!$inFilter) {
2283
                if ($c === ':') {
2284
                    $inFilter = true;
2285
                } elseif ($c === '?') {
2286
                    $pos = $i;
2287
                } elseif ($c === ' ') {
2288
                    $maybePos = $i;
2289
                } 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...
2290
                    $pos = $maybePos;
2291
                } elseif ($c === "\n") {
2292
                    $pos = $i;
2293
                } else {
2294
                    $pos = false;
2295
                }
2296
            } else {
2297
                if ($cc == $closeOpt) {
2298
                    $closeOpt = false;
2299
                } elseif ($c == $closeOpt) {
2300
                    $closeOpt = false;
2301
                } 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...
2302
                    continue;
2303
                } elseif ($cc === "('") {
2304
                    $closeOpt = "')";
2305
                } elseif ($cc === '("') {
2306
                    $closeOpt = '")';
2307
                } elseif ($cc === '(`') {
2308
                    $closeOpt = '`)';
2309
                } elseif ($c === '(') {
2310
                    $closeOpt = ')';
2311
                } elseif ($c === '?') {
2312
                    $pos = $i;
2313
                } elseif ($c === ' ' && strpos($str, '?') === false) {
2314
                    $pos = $i;
2315
                } else {
2316
                    $pos = false;
2317
                }
2318
            }
2319
            if ($pos) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $pos of type false|integer is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
2320
                break;
2321
            }
2322
        }
2323
        return $pos;
2324
    }
2325
2326
    /**
2327
     * @param $call
2328
     * @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...
2329
     */
2330
    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...
2331
    {
2332
        $spacer = md5('dummy');
2333 View Code Duplication
        if (strpos($call, ']]>') !== false) {
2334
            $call = str_replace(']]>', "]{$spacer}]>", $call);
2335
        }
2336
2337
        $splitPosition = $this->_getSplitPosition($call);
2338
2339
        if ($splitPosition !== false) {
2340
            $name = substr($call, 0, $splitPosition);
2341
            $params = substr($call, $splitPosition + 1);
2342
        } else {
2343
            $name = $call;
2344
            $params = '';
2345
        }
2346
2347
        $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...
2348 View Code Duplication
        if (strpos($params, $spacer) !== false) {
2349
            $params = str_replace("]{$spacer}]>", ']]>', $params);
2350
        }
2351
        $snip['params'] = ltrim($params, "?& \t\n");
2352
2353
        return $snip;
2354
    }
2355
2356
    /**
2357
     * @param $snip_name
2358
     * @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...
2359
     */
2360
    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...
2361
    {
2362
        if (isset($this->snippetCache[$snip_name])) {
2363
            $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...
2364
            $snippetObject['content'] = $this->snippetCache[$snip_name];
2365
            if (isset($this->snippetCache["{$snip_name}Props"])) {
2366
                if (!isset($this->snippetCache["{$snip_name}Props"])) {
2367
                    $this->snippetCache["{$snip_name}Props"] = '';
2368
                }
2369
                $snippetObject['properties'] = $this->snippetCache["{$snip_name}Props"];
2370
            }
2371
        } elseif (substr($snip_name, 0, 1) === '@' && isset($this->pluginEvent[trim($snip_name, '@')])) {
2372
            $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...
2373
            $snippetObject['content'] = sprintf('$rs=$this->invokeEvent("%s",$params);echo trim(implode("",$rs));', trim($snip_name, '@'));
2374
            $snippetObject['properties'] = '';
2375
        } else {
2376
            $where = sprintf("name='%s' AND disabled=0", $this->db->escape($snip_name));
2377
            $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...
2378
            $count = $this->db->getRecordCount($rs);
2379
            if (1 < $count) {
2380
                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...
2381
            }
2382
            if ($count) {
2383
                $row = $this->db->getRow($rs);
2384
                $snip_content = $row['snippet'];
2385
                $snip_prop = $row['properties'];
2386
            } else {
2387
                $snip_content = null;
2388
                $snip_prop = '';
2389
            }
2390
            $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...
2391
            $snippetObject['content'] = $snip_content;
2392
            $snippetObject['properties'] = $snip_prop;
2393
            $this->snippetCache[$snip_name] = $snip_content;
2394
            $this->snippetCache["{$snip_name}Props"] = $snip_prop;
2395
        }
2396
        return $snippetObject;
2397
    }
2398
2399
    /**
2400
     * @param $text
2401
     * @return mixed
2402
     */
2403
    public function toAlias($text)
2404
    {
2405
        $suff = $this->config['friendly_url_suffix'];
2406
        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);
2407
    }
2408
2409
    /**
2410
     * makeFriendlyURL
2411
     *
2412
     * @desc Create an URL.
2413
     *
2414
     * @param $pre {string} - Friendly URL Prefix. @required
2415
     * @param $suff {string} - Friendly URL Suffix. @required
2416
     * @param $alias {string} - Full document path. @required
2417
     * @param int $isfolder {0; 1}
2418
     * - Is it a folder? Default: 0.
2419
     * @param int $id {integer}
2420
     * - Document id. Default: 0.
2421
     * @return mixed|string {string} - Result URL.
2422
     * - Result URL.
2423
     */
2424
    public function makeFriendlyURL($pre, $suff, $alias, $isfolder = 0, $id = 0)
2425
    {
2426
        if ($id == $this->config['site_start'] && $this->config['seostrict'] === '1') {
2427
            $url = $this->config['base_url'];
2428
        } else {
2429
            $Alias = explode('/', $alias);
2430
            $alias = array_pop($Alias);
2431
            $dir = implode('/', $Alias);
2432
            unset($Alias);
2433
2434
            if ($this->config['make_folders'] === '1' && $isfolder == 1) {
2435
                $suff = '/';
2436
            }
2437
2438
            $url = ($dir != '' ? $dir . '/' : '') . $pre . $alias . $suff;
2439
        }
2440
2441
        $evtOut = $this->invokeEvent('OnMakeDocUrl', array(
2442
            'id' => $id,
2443
            'url' => $url
2444
        ));
2445
2446
        if (is_array($evtOut) && count($evtOut) > 0) {
2447
            $url = array_pop($evtOut);
2448
        }
2449
2450
        return $url;
2451
    }
2452
2453
    /**
2454
     * Convert URL tags [~...~] to URLs
2455
     *
2456
     * @param string $documentSource
2457
     * @return string
2458
     */
2459
    public function rewriteUrls($documentSource)
2460
    {
2461
        // rewrite the urls
2462
        if ($this->config['friendly_urls'] == 1) {
2463
            $aliases = array();
2464
            if (is_array($this->documentListing)) {
2465
                foreach ($this->documentListing as $path => $docid) { // This is big Loop on large site!
2466
                    $aliases[$docid] = $path;
2467
                    $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...
2468
                }
2469
            }
2470
2471
            if ($this->config['aliaslistingfolder'] == 1) {
2472
                preg_match_all('!\[\~([0-9]+)\~\]!ise', $documentSource, $match);
2473
                $ids = implode(',', array_unique($match['1']));
2474
                if ($ids) {
2475
                    $res = $this->db->select("id,alias,isfolder,parent,alias_visible", $this->getFullTableName('site_content'), "id IN (" . $ids . ") AND isfolder = '0'");
2476
                    while ($row = $this->db->getRow($res)) {
2477
                        if ($this->config['use_alias_path'] == '1' && $row['parent'] != 0) {
2478
                            $parent = $row['parent'];
2479
                            $path = $aliases[$parent];
2480
2481
                            while (isset($this->aliasListing[$parent]) && $this->aliasListing[$parent]['alias_visible'] == 0) {
2482
                                $path = $this->aliasListing[$parent]['path'];
2483
                                $parent = $this->aliasListing[$parent]['parent'];
2484
                            }
2485
2486
                            $aliases[$row['id']] = $path . '/' . $row['alias'];
2487
                        } else {
2488
                            $aliases[$row['id']] = $row['alias'];
2489
                        }
2490
                        $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...
2491
                    }
2492
                }
2493
            }
2494
            $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...
2495
            $isfriendly = ($this->config['friendly_alias_urls'] == 1 ? 1 : 0);
2496
            $pref = $this->config['friendly_url_prefix'];
2497
            $suff = $this->config['friendly_url_suffix'];
2498
            $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...
2499
                global $modx;
2500
                $thealias = $aliases[$m[1]];
2501
                $thefolder = $isfolder[$m[1]];
2502
                if ($isfriendly && isset($thealias)) {
2503
                    //found friendly url
2504
                    $out = ($modx->config['seostrict'] == '1' ? $modx->toAlias($modx->makeFriendlyURL($pref, $suff, $thealias, $thefolder, $m[1])) : $modx->makeFriendlyURL($pref, $suff, $thealias, $thefolder, $m[1]));
2505
                } else {
2506
                    //not found friendly url
2507
                    $out = $modx->makeFriendlyURL($pref, $suff, $m[1]);
2508
                }
2509
                return $out;
2510
            }, $documentSource);
2511
        } else {
2512
            $in = '!\[\~([0-9]+)\~\]!is';
2513
            $out = "index.php?id=" . '\1';
2514
            $documentSource = preg_replace($in, $out, $documentSource);
2515
        }
2516
2517
        return $documentSource;
2518
    }
2519
2520
    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...
2521
    {
2522
        $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...
2523
        // FIX URLs
2524
        if (empty($this->documentIdentifier) || $this->config['seostrict'] == '0' || $this->config['friendly_urls'] == '0') {
2525
            return;
2526
        }
2527
        if ($this->config['site_status'] == 0) {
2528
            return;
2529
        }
2530
2531
        $scheme = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https' : 'http';
2532
        $len_base_url = strlen($this->config['base_url']);
2533
2534
        $url_path = $q;//LANG
2535
2536 View Code Duplication
        if (substr($url_path, 0, $len_base_url) === $this->config['base_url']) {
2537
            $url_path = substr($url_path, $len_base_url);
2538
        }
2539
2540
        $strictURL = $this->toAlias($this->makeUrl($this->documentIdentifier));
2541
2542 View Code Duplication
        if (substr($strictURL, 0, $len_base_url) === $this->config['base_url']) {
2543
            $strictURL = substr($strictURL, $len_base_url);
2544
        }
2545
        $http_host = $_SERVER['HTTP_HOST'];
2546
        $requestedURL = "{$scheme}://{$http_host}" . '/' . $q; //LANG
2547
2548
        $site_url = $this->config['site_url'];
2549
        $url_query_string = explode('?', $_SERVER['REQUEST_URI']);
2550
        // Strip conflicting id/q from query string
2551
        $qstring = !empty($url_query_string[1]) ? preg_replace("#(^|&)(q|id)=[^&]+#", '', $url_query_string[1]) : '';
2552
2553
        if ($this->documentIdentifier == $this->config['site_start']) {
2554
            if ($requestedURL != $this->config['site_url']) {
2555
                // Force redirect of site start
2556
                // $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...
2557
                if ($qstring) {
2558
                    $url = "{$site_url}?{$qstring}";
2559
                } else {
2560
                    $url = $site_url;
2561
                }
2562
                if ($this->config['base_url'] != $_SERVER['REQUEST_URI']) {
2563
                    if (empty($_POST)) {
2564
                        if (($this->config['base_url'] . '?' . $qstring) != $_SERVER['REQUEST_URI']) {
2565
                            $this->sendRedirect($url, 0, 'REDIRECT_HEADER', 'HTTP/1.0 301 Moved Permanently');
2566
                            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...
2567
                        }
2568
                    }
2569
                }
2570
            }
2571
        } elseif ($url_path != $strictURL && $this->documentIdentifier != $this->config['error_page']) {
2572
            // Force page redirect
2573
            //$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...
2574
            if (!empty($qstring)) {
2575
                $url = "{$site_url}{$strictURL}?{$qstring}";
2576
            } else {
2577
                $url = "{$site_url}{$strictURL}";
2578
            }
2579
            $this->sendRedirect($url, 0, 'REDIRECT_HEADER', 'HTTP/1.0 301 Moved Permanently');
2580
            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...
2581
        }
2582
        return;
2583
    }
2584
2585
    /**
2586
     * Get all db fields and TVs for a document/resource
2587
     *
2588
     * @param string $method
2589
     * @param mixed $identifier
2590
     * @param bool $isPrepareResponse
2591
     * @return array
2592
     */
2593
    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...
2594
    {
2595
        $cacheKey = md5(print_r(func_get_args(), true));
2596
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
2597
            return $this->tmpCache[__FUNCTION__][$cacheKey];
2598
        }
2599
2600
        $tblsc = $this->getFullTableName("site_content");
2601
        $tbldg = $this->getFullTableName("document_groups");
2602
2603
        // allow alias to be full path
2604
        if ($method == 'alias') {
2605
            $identifier = $this->cleanDocumentIdentifier($identifier);
2606
            $method = $this->documentMethod;
2607
        }
2608
        if ($method == 'alias' && $this->config['use_alias_path'] && array_key_exists($identifier, $this->documentListing)) {
2609
            $method = 'id';
2610
            $identifier = $this->documentListing[$identifier];
2611
        }
2612
2613
        $out = $this->invokeEvent('OnBeforeLoadDocumentObject', compact('method', 'identifier'));
2614
        if (is_array($out) && is_array($out[0])) {
2615
            $documentObject = $out[0];
2616
        } else {
2617
            // get document groups for current user
2618
            if ($docgrp = $this->getUserDocGroups()) {
2619
                $docgrp = implode(",", $docgrp);
2620
            }
2621
            // get document
2622
            $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
2623
            $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...
2624
                LEFT JOIN {$tbldg} dg ON dg.document = sc.id", "sc.{$method} = '{$identifier}' AND ({$access})", "", 1);
2625
            if ($this->db->getRecordCount($rs) < 1) {
2626
                $seclimit = 0;
2627
                if ($this->config['unauthorized_page']) {
2628
                    // method may still be alias, while identifier is not full path alias, e.g. id not found above
2629
                    if ($method === 'alias') {
2630
                        $secrs = $this->db->select('count(dg.id)', "{$tbldg} as dg, {$tblsc} as sc", "dg.document = sc.id AND sc.alias = '{$identifier}'", '', 1);
2631
                    } else {
2632
                        $secrs = $this->db->select('count(id)', $tbldg, "document = '{$identifier}'", '', 1);
2633
                    }
2634
                    // check if file is not public
2635
                    $seclimit = $this->db->getValue($secrs);
2636
                }
2637
                if ($seclimit > 0) {
2638
                    // match found but not publicly accessible, send the visitor to the unauthorized_page
2639
                    $this->sendUnauthorizedPage();
2640
                    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...
2641
                } else {
2642
                    $this->sendErrorPage();
2643
                    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...
2644
                }
2645
            }
2646
            # 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...
2647
            $documentObject = $this->db->getRow($rs);
2648
2649
            if ($isPrepareResponse === 'prepareResponse') {
2650
                $this->documentObject = &$documentObject;
2651
            }
2652
            $out = $this->invokeEvent('OnLoadDocumentObject', compact('method', 'identifier', 'documentObject'));
2653
            if (is_array($out) && is_array($out[0])) {
2654
                $documentObject = $out[0];
2655
            }
2656
            if ($documentObject['template']) {
2657
                // load TVs and merge with document - Orig by Apodigm - Docvars
2658
                $rs = $this->db->select("tv.*, IF(tvc.value!='',tvc.value,tv.default_text) as value", $this->getFullTableName("site_tmplvars") . " tv
2659
                INNER JOIN " . $this->getFullTableName("site_tmplvar_templates") . " tvtpl ON tvtpl.tmplvarid = tv.id
2660
                LEFT JOIN " . $this->getFullTableName("site_tmplvar_contentvalues") . " tvc ON tvc.tmplvarid=tv.id AND tvc.contentid = '{$documentObject['id']}'", "tvtpl.templateid = '{$documentObject['template']}'");
2661
                $tmplvars = array();
2662
                while ($row = $this->db->getRow($rs)) {
2663
                    $tmplvars[$row['name']] = array(
2664
                        $row['name'],
2665
                        $row['value'],
2666
                        $row['display'],
2667
                        $row['display_params'],
2668
                        $row['type']
2669
                    );
2670
                }
2671
                $documentObject = array_merge($documentObject, $tmplvars);
2672
            }
2673
            $out = $this->invokeEvent('OnAfterLoadDocumentObject', compact('method', 'identifier', 'documentObject'));
2674
            if (is_array($out) && array_key_exists(0, $out) !== false && is_array($out[0])) {
2675
                $documentObject = $out[0];
2676
            }
2677
        }
2678
2679
        $this->tmpCache[__FUNCTION__][$cacheKey] = $documentObject;
2680
2681
        return $documentObject;
2682
    }
2683
2684
    /**
2685
     * Parse a source string.
2686
     *
2687
     * Handles most MODX tags. Exceptions include:
2688
     *   - uncached snippet tags [!...!]
2689
     *   - URL tags [~...~]
2690
     *
2691
     * @param string $source
2692
     * @return string
2693
     */
2694
    public function parseDocumentSource($source)
2695
    {
2696
        // set the number of times we are to parse the document source
2697
        $this->minParserPasses = empty($this->minParserPasses) ? 2 : $this->minParserPasses;
2698
        $this->maxParserPasses = empty($this->maxParserPasses) ? 10 : $this->maxParserPasses;
2699
        $passes = $this->minParserPasses;
2700
        for ($i = 0; $i < $passes; $i++) {
2701
            // get source length if this is the final pass
2702
            if ($i == ($passes - 1)) {
2703
                $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...
2704
            }
2705
            if ($this->dumpSnippets == 1) {
2706
                $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>";
2707
            }
2708
2709
            // invoke OnParseDocument event
2710
            $this->documentOutput = $source; // store source code so plugins can
2711
            $this->invokeEvent("OnParseDocument"); // work on it via $modx->documentOutput
2712
            $source = $this->documentOutput;
2713
2714
            if ($this->config['enable_at_syntax']) {
2715
                $source = $this->ignoreCommentedTagsContent($source);
2716
                $source = $this->mergeConditionalTagsContent($source);
2717
            }
2718
2719
            $source = $this->mergeSettingsContent($source);
2720
            $source = $this->mergeDocumentContent($source);
2721
            $source = $this->mergeChunkContent($source);
2722
            $source = $this->evalSnippets($source);
2723
            $source = $this->mergePlaceholderContent($source);
2724
2725
            if ($this->dumpSnippets == 1) {
2726
                $this->snippetsCode .= "</fieldset><br />";
2727
            }
2728
            if ($i == ($passes - 1) && $i < ($this->maxParserPasses - 1)) {
2729
                // check if source content was changed
2730
                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...
2731
                    $passes++;
2732
                } // if content change then increase passes because
2733
            } // we have not yet reached maxParserPasses
2734
        }
2735
        return $source;
2736
    }
2737
2738
    /**
2739
     * Starts the parsing operations.
2740
     *
2741
     * - connects to the db
2742
     * - gets the settings (including system_settings)
2743
     * - gets the document/resource identifier as in the query string
2744
     * - finally calls prepareResponse()
2745
     */
2746
    public function executeParser()
2747
    {
2748
        if (MODX_CLI) {
2749
            throw new RuntimeException('Call DocumentParser::executeParser on CLI mode');
2750
        }
2751
2752
        //error_reporting(0);
2753
        set_error_handler(array(
2754
            & $this,
2755
            "phpError"
2756
        ), E_ALL);
2757
        $this->db->connect();
2758
2759
        // get the settings
2760
        if (empty($this->config)) {
2761
            $this->getSettings();
2762
        }
2763
2764
        $this->_IIS_furl_fix(); // IIS friendly url fix
2765
2766
        // check site settings
2767
        if ($this->checkSiteStatus()) {
2768
            // make sure the cache doesn't need updating
2769
            $this->updatePubStatus();
2770
2771
            // find out which document we need to display
2772
            $this->documentMethod = filter_input(INPUT_GET, 'q') ? 'alias' : 'id';
2773
            $this->documentIdentifier = $this->getDocumentIdentifier($this->documentMethod);
2774
        } else {
2775
            header('HTTP/1.0 503 Service Unavailable');
2776
            $this->systemCacheKey = 'unavailable';
2777
            if (!$this->config['site_unavailable_page']) {
2778
                // display offline message
2779
                $this->documentContent = $this->config['site_unavailable_message'];
2780
                $this->outputContent();
2781
                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...
2782
            } else {
2783
                // setup offline page document settings
2784
                $this->documentMethod = 'id';
2785
                $this->documentIdentifier = $this->config['site_unavailable_page'];
2786
            }
2787
        }
2788
2789
        if ($this->documentMethod == "alias") {
2790
            $this->documentIdentifier = $this->cleanDocumentIdentifier($this->documentIdentifier);
2791
2792
            // Check use_alias_path and check if $this->virtualDir is set to anything, then parse the path
2793
            if ($this->config['use_alias_path'] == 1) {
2794
                $alias = (strlen($this->virtualDir) > 0 ? $this->virtualDir . '/' : '') . $this->documentIdentifier;
2795
                if (isset($this->documentListing[$alias])) {
2796
                    $this->documentIdentifier = $this->documentListing[$alias];
2797
                } else {
2798
                    //@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...
2799
                    if ($this->config['aliaslistingfolder'] == 1) {
2800
                        $tbl_site_content = $this->getFullTableName('site_content');
2801
2802
                        $parentId = $this->getIdFromAlias($this->virtualDir);
2803
                        $parentId = ($parentId > 0) ? $parentId : '0';
2804
2805
                        $docAlias = $this->db->escape($this->documentIdentifier);
2806
2807
                        $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...
2808
                        if ($this->db->getRecordCount($rs) == 0) {
2809
                            $this->sendErrorPage();
2810
                        }
2811
                        $docId = $this->db->getValue($rs);
2812
2813
                        if (!$docId) {
2814
                            $alias = $this->q;
2815
                            if (!empty($this->config['friendly_url_suffix'])) {
2816
                                $pos = strrpos($alias, $this->config['friendly_url_suffix']);
2817
2818
                                if ($pos !== false) {
2819
                                    $alias = substr($alias, 0, $pos);
2820
                                }
2821
                            }
2822
                            $docId = $this->getIdFromAlias($alias);
2823
                        }
2824
2825
                        if ($docId > 0) {
2826
                            $this->documentIdentifier = $docId;
2827
                        } else {
2828
                            /*
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...
2829
                            $rs  = $this->db->select('id', $tbl_site_content, "deleted=0 and alias='{$docAlias}'");
2830
                            if($this->db->getRecordCount($rs)==0)
2831
                            {
2832
                                $rs  = $this->db->select('id', $tbl_site_content, "deleted=0 and id='{$docAlias}'");
2833
                            }
2834
                            $docId = $this->db->getValue($rs);
2835
2836
                            if ($docId > 0)
2837
                            {
2838
                                $this->documentIdentifier = $docId;
2839
2840
                            }else{
2841
                            */
2842
                            $this->sendErrorPage();
2843
                            //}
2844
                        }
2845
                    } else {
2846
                        $this->sendErrorPage();
2847
                    }
2848
                }
2849
            } else {
2850
                if (isset($this->documentListing[$this->documentIdentifier])) {
2851
                    $this->documentIdentifier = $this->documentListing[$this->documentIdentifier];
2852
                } else {
2853
                    $docAlias = $this->db->escape($this->documentIdentifier);
2854
                    $rs = $this->db->select('id', $this->getFullTableName('site_content'), "deleted=0 and alias='{$docAlias}'");
2855
                    $this->documentIdentifier = (int)$this->db->getValue($rs);
2856
                }
2857
            }
2858
            $this->documentMethod = 'id';
2859
        }
2860
2861
        //$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...
2862
        // invoke OnWebPageInit event
2863
        $this->invokeEvent("OnWebPageInit");
2864
        // invoke OnLogPageView event
2865
        if ($this->config['track_visitors'] == 1) {
2866
            $this->invokeEvent("OnLogPageHit");
2867
        }
2868
        if ($this->config['seostrict'] === '1') {
2869
            $this->sendStrictURI();
2870
        }
2871
        $this->prepareResponse();
2872
    }
2873
2874
    /**
2875
     * @param $path
2876
     * @param null $suffix
2877
     * @return mixed
2878
     */
2879
    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...
2880
    {
2881
        $exp = explode('/', $path);
2882
        return str_replace($suffix, '', end($exp));
2883
    }
2884
2885
    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...
2886
    {
2887
        if ($this->config['friendly_urls'] != 1) {
2888
            return;
2889
        }
2890
2891
        if (strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') === false) {
2892
            return;
2893
        }
2894
2895
        $url = $_SERVER['QUERY_STRING'];
2896
        $err = substr($url, 0, 3);
2897
        if ($err !== '404' && $err !== '405') {
2898
            return;
2899
        }
2900
2901
        $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...
2902
        unset($_GET[$k[0]]);
2903
        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...
2904
        $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...
2905
        $_SERVER['QUERY_STRING'] = $qp['query'];
2906
        if (!empty($qp['query'])) {
2907
            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...
2908
            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...
2909
                $_REQUEST[$n] = $_GET[$n] = $v;
2910
            }
2911
        }
2912
        $_SERVER['PHP_SELF'] = $this->config['base_url'] . $qp['path'];
2913
        $this->q = $qp['path'];
2914
        return $qp['path'];
2915
    }
2916
2917
    /**
2918
     * The next step called at the end of executeParser()
2919
     *
2920
     * - checks cache
2921
     * - checks if document/resource is deleted/unpublished
2922
     * - checks if resource is a weblink and redirects if so
2923
     * - gets template and parses it
2924
     * - ensures that postProcess is called when PHP is finished
2925
     */
2926
    public function prepareResponse()
2927
    {
2928
        // we now know the method and identifier, let's check the cache
2929
2930
        if ($this->config['enable_cache'] == 2 && $this->isLoggedIn()) {
2931
            $this->config['enable_cache'] = 0;
2932
        }
2933
2934
        if ($this->config['enable_cache']) {
2935
            $this->documentContent = $this->getDocumentObjectFromCache($this->documentIdentifier, true);
2936
        } else {
2937
            $this->documentContent = '';
2938
        }
2939
2940
        if ($this->documentContent == '') {
2941
            // get document object from DB
2942
            $this->documentObject = $this->getDocumentObject($this->documentMethod, $this->documentIdentifier, 'prepareResponse');
2943
2944
            // write the documentName to the object
2945
            $this->documentName = &$this->documentObject['pagetitle'];
2946
2947
            // check if we should not hit this document
2948
            if ($this->documentObject['donthit'] == 1) {
2949
                $this->config['track_visitors'] = 0;
2950
            }
2951
2952
            if ($this->documentObject['deleted'] == 1) {
2953
                $this->sendErrorPage();
2954
            } // validation routines
2955
            elseif ($this->documentObject['published'] == 0) {
2956
                $this->_sendErrorForUnpubPage();
2957
            } elseif ($this->documentObject['type'] == 'reference') {
2958
                $this->_sendRedirectForRefPage($this->documentObject['content']);
2959
            }
2960
2961
            // get the template and start parsing!
2962
            if (!$this->documentObject['template']) {
2963
                $templateCode = '[*content*]';
2964
            } // use blank template
2965
            else {
2966
                $templateCode = $this->_getTemplateCodeFromDB($this->documentObject['template']);
2967
            }
2968
2969
            if (substr($templateCode, 0, 8) === '@INCLUDE') {
2970
                $templateCode = $this->atBindInclude($templateCode);
2971
            }
2972
2973
2974
            $this->documentContent = &$templateCode;
2975
2976
            // invoke OnLoadWebDocument event
2977
            $this->invokeEvent('OnLoadWebDocument');
2978
2979
            // Parse document source
2980
            $this->documentContent = $this->parseDocumentSource($templateCode);
2981
2982
            $this->documentGenerated = 1;
2983
        } else {
2984
            $this->documentGenerated = 0;
2985
        }
2986
2987
        if ($this->config['error_page'] == $this->documentIdentifier && $this->config['error_page'] != $this->config['site_start']) {
2988
            header('HTTP/1.0 404 Not Found');
2989
        }
2990
2991
        register_shutdown_function(array(
2992
            &$this,
2993
            'postProcess'
2994
        )); // tell PHP to call postProcess when it shuts down
2995
        $this->outputContent();
2996
        //$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...
2997
    }
2998
2999
    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...
3000
    {
3001
        // Can't view unpublished pages !$this->checkPreview()
3002
        if (!$this->hasPermission('view_unpublished')) {
3003
            $this->sendErrorPage();
3004
        } else {
3005
            // Inculde the necessary files to check document permissions
3006
            include_once(MODX_MANAGER_PATH . 'processors/user_documents_permissions.class.php');
3007
            $udperms = new udperms();
3008
            $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...
3009
            $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...
3010
            $udperms->role = $_SESSION['mgrRole'];
3011
            // Doesn't have access to this document
3012
            if (!$udperms->checkPermissions()) {
3013
                $this->sendErrorPage();
3014
            }
3015
        }
3016
    }
3017
3018
    /**
3019
     * @param $url
3020
     */
3021
    public function _sendRedirectForRefPage($url)
3022
    {
3023
        // check whether it's a reference
3024
        if (preg_match('@^[1-9][0-9]*$@', $url)) {
3025
            $url = $this->makeUrl($url); // if it's a bare document id
3026
        } elseif (strpos($url, '[~') !== false) {
3027
            $url = $this->rewriteUrls($url); // if it's an internal docid tag, process it
3028
        }
3029
        $this->sendRedirect($url, 0, '', 'HTTP/1.0 301 Moved Permanently');
3030
        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...
3031
    }
3032
3033
    /**
3034
     * @param $templateID
3035
     * @return mixed
3036
     */
3037
    public function _getTemplateCodeFromDB($templateID)
3038
    {
3039
        $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...
3040
        if ($this->db->getRecordCount($rs) == 1) {
3041
            return $this->db->getValue($rs);
3042
        } else {
3043
            $this->messageQuit('Incorrect number of templates returned from database');
3044
        }
3045
    }
3046
3047
    /**
3048
     * Returns an array of all parent record IDs for the id passed.
3049
     *
3050
     * @param int $id Docid to get parents for.
3051
     * @param int $height The maximum number of levels to go up, default 10.
3052
     * @return array
3053
     */
3054
    public function getParentIds($id, $height = 10)
3055
    {
3056
        $parents = array();
3057
        while ($id && $height--) {
3058
            $thisid = $id;
3059
            if ($this->config['aliaslistingfolder'] == 1) {
3060
                $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");
3061
                if (!$id || $id == '0') {
3062
                    break;
3063
                }
3064
            } else {
3065
                $id = $this->aliasListing[$id]['parent'];
3066
                if (!$id) {
3067
                    break;
3068
                }
3069
            }
3070
            $parents[$thisid] = $id;
3071
        }
3072
        return $parents;
3073
    }
3074
3075
    /**
3076
     * @param $id
3077
     * @param int $top
3078
     * @return mixed
3079
     */
3080
    public function getUltimateParentId($id, $top = 0)
3081
    {
3082
        $i = 0;
3083
        while ($id && $i < 20) {
3084
            if ($top == $this->aliasListing[$id]['parent']) {
3085
                break;
3086
            }
3087
            $id = $this->aliasListing[$id]['parent'];
3088
            $i++;
3089
        }
3090
        return $id;
3091
    }
3092
3093
    /**
3094
     * Returns an array of child IDs belonging to the specified parent.
3095
     *
3096
     * @param int $id The parent resource/document to start from
3097
     * @param int $depth How many levels deep to search for children, default: 10
3098
     * @param array $children Optional array of docids to merge with the result.
3099
     * @return array Contains the document Listing (tree) like the sitemap
3100
     */
3101
    public function getChildIds($id, $depth = 10, $children = array())
3102
    {
3103
        $cacheKey = md5(print_r(func_get_args(), true));
3104
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3105
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3106
        }
3107
3108
        if ($this->config['aliaslistingfolder'] == 1) {
3109
            $res = $this->db->select("id,alias,isfolder,parent", $this->getFullTableName('site_content'), "parent IN (" . $id . ") AND deleted = '0'");
3110
            $idx = array();
3111
            while ($row = $this->db->getRow($res)) {
3112
                $pAlias = '';
3113
                if (isset($this->aliasListing[$row['parent']])) {
3114
                    $pAlias .= !empty($this->aliasListing[$row['parent']]['path']) ? $this->aliasListing[$row['parent']]['path'] . '/' : '';
3115
                    $pAlias .= !empty($this->aliasListing[$row['parent']]['alias']) ? $this->aliasListing[$row['parent']]['alias'] . '/' : '';
3116
                };
3117
                $children[$pAlias . $row['alias']] = $row['id'];
3118
                if ($row['isfolder'] == 1) {
3119
                    $idx[] = $row['id'];
3120
                }
3121
            }
3122
            $depth--;
3123
            $idx = implode(',', $idx);
3124
            if (!empty($idx)) {
3125
                if ($depth) {
3126
                    $children = $this->getChildIds($idx, $depth, $children);
3127
                }
3128
            }
3129
            $this->tmpCache[__FUNCTION__][$cacheKey] = $children;
3130
            return $children;
3131
        } else {
3132
3133
            // Initialise a static array to index parents->children
3134
            static $documentMap_cache = array();
3135
            if (!count($documentMap_cache)) {
3136
                foreach ($this->documentMap as $document) {
3137
                    foreach ($document as $p => $c) {
3138
                        $documentMap_cache[$p][] = $c;
3139
                    }
3140
                }
3141
            }
3142
3143
            // Get all the children for this parent node
3144
            if (isset($documentMap_cache[$id])) {
3145
                $depth--;
3146
3147
                foreach ($documentMap_cache[$id] as $childId) {
3148
                    $pkey = (strlen($this->aliasListing[$childId]['path']) ? "{$this->aliasListing[$childId]['path']}/" : '') . $this->aliasListing[$childId]['alias'];
3149
                    if (!strlen($pkey)) {
3150
                        $pkey = "{$childId}";
3151
                    }
3152
                    $children[$pkey] = $childId;
3153
3154
                    if ($depth && isset($documentMap_cache[$childId])) {
3155
                        $children += $this->getChildIds($childId, $depth);
3156
                    }
3157
                }
3158
            }
3159
            $this->tmpCache[__FUNCTION__][$cacheKey] = $children;
3160
            return $children;
3161
        }
3162
    }
3163
3164
    /**
3165
     * Displays a javascript alert message in the web browser and quit
3166
     *
3167
     * @param string $msg Message to show
3168
     * @param string $url URL to redirect to
3169
     */
3170
    public function webAlertAndQuit($msg, $url = "")
3171
    {
3172
        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...
3173
        if (substr(strtolower($url), 0, 11) == "javascript:") {
3174
            $fnc = substr($url, 11);
3175
        } elseif ($url) {
3176
            $fnc = "window.location.href='" . addslashes($url) . "';";
3177
        } else {
3178
            $fnc = "history.back(-1);";
3179
        }
3180
        echo "<html><head>
3181
            <title>MODX :: Alert</title>
3182
            <meta http-equiv=\"Content-Type\" content=\"text/html; charset={$modx_manager_charset};\">
3183
            <script>
3184
                function __alertQuit() {
3185
                    alert('" . addslashes($msg) . "');
3186
                    {$fnc}
3187
                }
3188
                window.setTimeout('__alertQuit();',100);
3189
            </script>
3190
            </head><body>
3191
            <p>{$msg}</p>
3192
            </body></html>";
3193
        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...
3194
    }
3195
3196
    /**
3197
     * Returns 1 if user has the currect permission
3198
     *
3199
     * @param string $pm Permission name
3200
     * @return int Why not bool?
3201
     */
3202
    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...
3203
    {
3204
        $state = 0;
3205
        $pms = $_SESSION['mgrPermissions'];
3206
        if ($pms) {
3207
            $state = ((bool)$pms[$pm] === true);
3208
        }
3209
        return (int)$state;
3210
    }
3211
3212
    /**
3213
     * Returns true if element is locked
3214
     *
3215
     * @param int $type Types: 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3216
     * @param int $id Element- / Resource-id
3217
     * @param bool $includeThisUser true = Return also info about actual user
3218
     * @return array lock-details or null
3219
     */
3220
    public function elementIsLocked($type, $id, $includeThisUser = false)
3221
    {
3222
        $id = (int)$id;
3223
        $type = (int)$type;
3224
        if (!$type || !$id) {
3225
            return null;
3226
        }
3227
3228
        // Build lockedElements-Cache at first call
3229
        $this->buildLockedElementsCache();
3230
3231
        if (!$includeThisUser && $this->lockedElements[$type][$id]['sid'] == $this->sid) {
3232
            return null;
3233
        }
3234
3235
        if (isset($this->lockedElements[$type][$id])) {
3236
            return $this->lockedElements[$type][$id];
3237
        } else {
3238
            return null;
3239
        }
3240
    }
3241
3242
    /**
3243
     * Returns Locked Elements as Array
3244
     *
3245
     * @param int $type Types: 0=all, 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3246
     * @param bool $minimumDetails true =
3247
     * @return array|mixed|null
3248
     */
3249
    public function getLockedElements($type = 0, $minimumDetails = false)
3250
    {
3251
        $this->buildLockedElementsCache();
3252
3253
        if (!$minimumDetails) {
3254
            $lockedElements = $this->lockedElements;
3255
        } else {
3256
            // Minimum details for HTML / Ajax-requests
3257
            $lockedElements = array();
3258
            foreach ($this->lockedElements as $elType => $elements) {
3259
                foreach ($elements as $elId => $el) {
3260
                    $lockedElements[$elType][$elId] = array(
3261
                        'username' => $el['username'],
3262
                        'lasthit_df' => $el['lasthit_df'],
3263
                        'state' => $this->determineLockState($el['internalKey'])
3264
                    );
3265
                }
3266
            }
3267
        }
3268
3269
        if ($type == 0) {
3270
            return $lockedElements;
3271
        }
3272
3273
        $type = (int)$type;
3274
        if (isset($lockedElements[$type])) {
3275
            return $lockedElements[$type];
3276
        } else {
3277
            return array();
3278
        }
3279
    }
3280
3281
    /**
3282
     * Builds the Locked Elements Cache once
3283
     */
3284
    public function buildLockedElementsCache()
3285
    {
3286
        if (is_null($this->lockedElements)) {
3287
            $this->lockedElements = array();
3288
            $this->cleanupExpiredLocks();
3289
3290
            $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...
3291
                LEFT JOIN {$this->getFullTableName('manager_users')} mu on ul.internalKey = mu.id");
3292
            while ($row = $this->db->getRow($rs)) {
3293
                $this->lockedElements[$row['elementType']][$row['elementId']] = array(
3294
                    'sid' => $row['sid'],
3295
                    'internalKey' => $row['internalKey'],
3296
                    'username' => $row['username'],
3297
                    'elementType' => $row['elementType'],
3298
                    'elementId' => $row['elementId'],
3299
                    'lasthit' => $row['lasthit'],
3300
                    'lasthit_df' => $this->toDateFormat($row['lasthit']),
3301
                    'state' => $this->determineLockState($row['sid'])
3302
                );
3303
            }
3304
        }
3305
    }
3306
3307
    /**
3308
     * Cleans up the active user locks table
3309
     */
3310
    public function cleanupExpiredLocks()
3311
    {
3312
        // Clean-up active_user_sessions first
3313
        $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
3314
        $validSessionTimeLimit = $this->time - $timeout;
3315
        $this->db->delete($this->getFullTableName('active_user_sessions'), "lasthit < {$validSessionTimeLimit}");
3316
3317
        // Clean-up active_user_locks
3318
        $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...
3319
        $count = $this->db->getRecordCount($rs);
3320
        if ($count) {
3321
            $rs = $this->db->makeArray($rs);
3322
            $userSids = array();
3323
            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...
3324
                $userSids[] = $row['sid'];
3325
            }
3326
            $userSids = "'" . implode("','", $userSids) . "'";
3327
            $this->db->delete($this->getFullTableName('active_user_locks'), "sid NOT IN({$userSids})");
3328
        } else {
3329
            $this->db->delete($this->getFullTableName('active_user_locks'));
3330
        }
3331
    }
3332
3333
    /**
3334
     * Cleans up the active users table
3335
     */
3336
    public function cleanupMultipleActiveUsers()
3337
    {
3338
        $timeout = 20 * 60; // Delete multiple user-sessions after 20min
3339
        $validSessionTimeLimit = $this->time - $timeout;
3340
3341
        $activeUserSids = array();
3342
        $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...
3343
        $count = $this->db->getRecordCount($rs);
3344
        if ($count) {
3345
            $rs = $this->db->makeArray($rs);
3346
            foreach ($rs as $row) {
0 ignored issues
show
Bug introduced by
The expression $rs of type false|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
3347
                $activeUserSids[] = $row['sid'];
3348
            }
3349
        }
3350
3351
        $rs = $this->db->select("sid,internalKey,lasthit", "{$this->getFullTableName('active_users')}", "", "lasthit DESC");
3352
        if ($this->db->getRecordCount($rs)) {
3353
            $rs = $this->db->makeArray($rs);
3354
            $internalKeyCount = array();
3355
            $deleteSids = '';
3356
            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...
3357
                if (!isset($internalKeyCount[$row['internalKey']])) {
3358
                    $internalKeyCount[$row['internalKey']] = 0;
3359
                }
3360
                $internalKeyCount[$row['internalKey']]++;
3361
3362
                if ($internalKeyCount[$row['internalKey']] > 1 && !in_array($row['sid'], $activeUserSids) && $row['lasthit'] < $validSessionTimeLimit) {
3363
                    $deleteSids .= $deleteSids == '' ? '' : ' OR ';
3364
                    $deleteSids .= "sid='{$row['sid']}'";
3365
                };
3366
            }
3367
            if ($deleteSids) {
3368
                $this->db->delete($this->getFullTableName('active_users'), $deleteSids);
3369
            }
3370
        }
3371
    }
3372
3373
    /**
3374
     * Determines state of a locked element acc. to user-permissions
3375
     *
3376
     * @param $sid
3377
     * @return int $state States: 0=No display, 1=viewing this element, 2=locked, 3=show unlock-button
3378
     * @internal param int $internalKey : ID of User who locked actual element
3379
     */
3380
    public function determineLockState($sid)
3381
    {
3382
        $state = 0;
3383
        if ($this->hasPermission('display_locks')) {
3384
            if ($sid == $this->sid) {
3385
                $state = 1;
3386
            } else {
3387
                if ($this->hasPermission('remove_locks')) {
3388
                    $state = 3;
3389
                } else {
3390
                    $state = 2;
3391
                }
3392
            }
3393
        }
3394
        return $state;
3395
    }
3396
3397
    /**
3398
     * Locks an element
3399
     *
3400
     * @param int $type Types: 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3401
     * @param int $id Element- / Resource-id
3402
     * @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...
3403
     */
3404
    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...
3405
    {
3406
        $userId = $this->isBackend() && $_SESSION['mgrInternalKey'] ? $_SESSION['mgrInternalKey'] : 0;
3407
        $type = (int)$type;
3408
        $id = (int)$id;
3409
        if (!$type || !$id || !$userId) {
3410
            return false;
3411
        }
3412
3413
        $sql = sprintf('REPLACE INTO %s (internalKey, elementType, elementId, lasthit, sid)
3414
                VALUES (%d, %d, %d, %d, \'%s\')', $this->getFullTableName('active_user_locks'), $userId, $type, $id, $this->time, $this->sid);
3415
        $this->db->query($sql);
3416
    }
3417
3418
    /**
3419
     * Unlocks an element
3420
     *
3421
     * @param int $type Types: 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3422
     * @param int $id Element- / Resource-id
3423
     * @param bool $includeAllUsers true = Deletes not only own user-locks
3424
     * @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...
3425
     */
3426
    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...
3427
    {
3428
        $userId = $this->isBackend() && $_SESSION['mgrInternalKey'] ? $_SESSION['mgrInternalKey'] : 0;
3429
        $type = (int)$type;
3430
        $id = (int)$id;
3431
        if (!$type || !$id) {
3432
            return false;
3433
        }
3434
3435
        if (!$includeAllUsers) {
3436
            $sql = sprintf('DELETE FROM %s WHERE internalKey = %d AND elementType = %d AND elementId = %d;', $this->getFullTableName('active_user_locks'), $userId, $type, $id);
3437
        } else {
3438
            $sql = sprintf('DELETE FROM %s WHERE elementType = %d AND elementId = %d;', $this->getFullTableName('active_user_locks'), $type, $id);
3439
        }
3440
        $this->db->query($sql);
3441
    }
3442
3443
    /**
3444
     * Updates table "active_user_sessions" with userid, lasthit, IP
3445
     */
3446
    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...
3447
    {
3448
        if (!$this->sid) {
3449
            return;
3450
        }
3451
3452
        // web users are stored with negative keys
3453
        $userId = $this->getLoginUserType() == 'manager' ? $this->getLoginUserID() : -$this->getLoginUserID();
3454
3455
        // Get user IP
3456 View Code Duplication
        if ($cip = getenv("HTTP_CLIENT_IP")) {
3457
            $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...
3458
        } elseif ($cip = getenv("HTTP_X_FORWARDED_FOR")) {
3459
            $ip = $cip;
3460
        } elseif ($cip = getenv("REMOTE_ADDR")) {
3461
            $ip = $cip;
3462
        } else {
3463
            $ip = "UNKNOWN";
3464
        }
3465
        $_SESSION['ip'] = $ip;
3466
3467
        $sql = sprintf('REPLACE INTO %s (internalKey, lasthit, ip, sid)
3468
            VALUES (%d, %d, \'%s\', \'%s\')', $this->getFullTableName('active_user_sessions'), $userId, $this->time, $ip, $this->sid);
3469
        $this->db->query($sql);
3470
    }
3471
3472
    /**
3473
     * Add an a alert message to the system event log
3474
     *
3475
     * @param int $evtid Event ID
3476
     * @param int $type Types: 1 = information, 2 = warning, 3 = error
3477
     * @param string $msg Message to be logged
3478
     * @param string $source source of the event (module, snippet name, etc.)
3479
     *                       Default: Parser
3480
     */
3481
    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...
3482
    {
3483
        $msg = $this->db->escape($msg);
3484
        if (strpos($GLOBALS['database_connection_charset'], 'utf8') === 0 && extension_loaded('mbstring')) {
3485
            $esc_source = mb_substr($source, 0, 50, "UTF-8");
3486
        } else {
3487
            $esc_source = substr($source, 0, 50);
3488
        }
3489
        $esc_source = $this->db->escape($esc_source);
3490
3491
        $LoginUserID = $this->getLoginUserID();
3492
        if ($LoginUserID == '') {
3493
            $LoginUserID = 0;
3494
        }
3495
3496
        $usertype = $this->isFrontend() ? 1 : 0;
3497
        $evtid = (int)$evtid;
3498
        $type = (int)$type;
3499
3500
        // Types: 1 = information, 2 = warning, 3 = error
3501
        if ($type < 1) {
3502
            $type = 1;
3503
        } elseif ($type > 3) {
3504
            $type = 3;
3505
        }
3506
3507
        $this->db->insert(array(
3508
            'eventid' => $evtid,
3509
            'type' => $type,
3510
            'createdon' => $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'],
3511
            'source' => $esc_source,
3512
            'description' => $msg,
3513
            'user' => $LoginUserID,
3514
            'usertype' => $usertype
3515
        ), $this->getFullTableName("event_log"));
3516
3517
        if (isset($this->config['send_errormail']) && $this->config['send_errormail'] !== '0') {
3518
            if ($this->config['send_errormail'] <= $type) {
3519
                $this->sendmail(array(
3520
                    'subject' => 'MODX System Error on ' . $this->config['site_name'],
3521
                    'body' => 'Source: ' . $source . ' - The details of the error could be seen in the MODX system events log.',
3522
                    'type' => 'text'
3523
                ));
3524
            }
3525
        }
3526
    }
3527
3528
    /**
3529
     * @param array $params
3530
     * @param string $msg
3531
     * @param array $files
3532
     * @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...
3533
     */
3534
    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...
3535
    {
3536
        if (isset($params) && is_string($params)) {
3537
            if (strpos($params, '=') === false) {
3538
                if (strpos($params, '@') !== false) {
3539
                    $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...
3540
                } else {
3541
                    $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...
3542
                }
3543
            } else {
3544
                $params_array = explode(',', $params);
3545
                foreach ($params_array as $k => $v) {
3546
                    $k = trim($k);
3547
                    $v = trim($v);
3548
                    $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...
3549
                }
3550
            }
3551
        } else {
3552
            $p = $params;
3553
            unset($params);
3554
        }
3555
        if (isset($p['sendto'])) {
3556
            $p['to'] = $p['sendto'];
3557
        }
3558
3559
        if (isset($p['to']) && preg_match('@^[0-9]+$@', $p['to'])) {
3560
            $userinfo = $this->getUserInfo($p['to']);
3561
            $p['to'] = $userinfo['email'];
3562
        }
3563
        if (isset($p['from']) && preg_match('@^[0-9]+$@', $p['from'])) {
3564
            $userinfo = $this->getUserInfo($p['from']);
3565
            $p['from'] = $userinfo['email'];
3566
            $p['fromname'] = $userinfo['username'];
3567
        }
3568
        if ($msg === '' && !isset($p['body'])) {
3569
            $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...
3570
        } elseif (is_string($msg) && 0 < strlen($msg)) {
3571
            $p['body'] = $msg;
3572
        }
3573
3574
        $this->loadExtension('MODxMailer');
3575
        $sendto = (!isset($p['to'])) ? $this->config['emailsender'] : $p['to'];
3576
        $sendto = explode(',', $sendto);
3577
        foreach ($sendto as $address) {
3578
            list($name, $address) = $this->mail->address_split($address);
3579
            $this->mail->AddAddress($address, $name);
3580
        }
3581 View Code Duplication
        if (isset($p['cc'])) {
3582
            $p['cc'] = explode(',', $p['cc']);
3583
            foreach ($p['cc'] as $address) {
3584
                list($name, $address) = $this->mail->address_split($address);
3585
                $this->mail->AddCC($address, $name);
3586
            }
3587
        }
3588 View Code Duplication
        if (isset($p['bcc'])) {
3589
            $p['bcc'] = explode(',', $p['bcc']);
3590
            foreach ($p['bcc'] as $address) {
3591
                list($name, $address) = $this->mail->address_split($address);
3592
                $this->mail->AddBCC($address, $name);
3593
            }
3594
        }
3595
        if (isset($p['from']) && strpos($p['from'], '<') !== false && substr($p['from'], -1) === '>') {
3596
            list($p['fromname'], $p['from']) = $this->mail->address_split($p['from']);
3597
        }
3598
        $this->mail->From = (!isset($p['from'])) ? $this->config['emailsender'] : $p['from'];
3599
        $this->mail->FromName = (!isset($p['fromname'])) ? $this->config['site_name'] : $p['fromname'];
3600
        $this->mail->Subject = (!isset($p['subject'])) ? $this->config['emailsubject'] : $p['subject'];
3601
        $this->mail->Body = $p['body'];
3602
        if (isset($p['type']) && $p['type'] == 'text') {
3603
            $this->mail->IsHTML(false);
3604
        }
3605
        if (!is_array($files)) {
3606
            $files = array();
3607
        }
3608
        foreach ($files as $f) {
3609
            if (file_exists(MODX_BASE_PATH . $f) && is_file(MODX_BASE_PATH . $f) && is_readable(MODX_BASE_PATH . $f)) {
3610
                $this->mail->AddAttachment(MODX_BASE_PATH . $f);
3611
            }
3612
        }
3613
        $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...
3614
        return $rs;
3615
    }
3616
3617
    /**
3618
     * @param string $target
3619
     * @param int $limit
3620
     * @param int $trim
3621
     */
3622
    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...
3623
    {
3624
        if ($limit < $trim) {
3625
            $trim = $limit;
3626
        }
3627
3628
        $table_name = $this->getFullTableName($target);
3629
        $count = $this->db->getValue($this->db->select('COUNT(id)', $table_name));
3630
        $over = $count - $limit;
3631
        if (0 < $over) {
3632
            $trim = ($over + $trim);
3633
            $this->db->delete($table_name, '', '', $trim);
3634
        }
3635
        $this->db->optimize($table_name);
3636
    }
3637
3638
    /**
3639
     * Returns true if we are currently in the manager backend
3640
     *
3641
     * @return boolean
3642
     */
3643
    public function isBackend()
3644
    {
3645
        return (defined('IN_MANAGER_MODE') && IN_MANAGER_MODE === true);
3646
    }
3647
3648
    /**
3649
     * Returns true if we are currently in the frontend
3650
     *
3651
     * @return boolean
3652
     */
3653
    public function isFrontend()
3654
    {
3655
        return ! $this->isBackend();
3656
    }
3657
3658
    /**
3659
     * Gets all child documents of the specified document, including those which are unpublished or deleted.
3660
     *
3661
     * @param int $id The Document identifier to start with
3662
     * @param string $sort Sort field
3663
     *                     Default: menuindex
3664
     * @param string $dir Sort direction, ASC and DESC is possible
3665
     *                    Default: ASC
3666
     * @param string $fields Default: id, pagetitle, description, parent, alias, menutitle
3667
     * @return array
3668
     */
3669 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...
3670
    {
3671
        $cacheKey = md5(print_r(func_get_args(), true));
3672
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3673
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3674
        }
3675
3676
        $tblsc = $this->getFullTableName("site_content");
3677
        $tbldg = $this->getFullTableName("document_groups");
3678
        // modify field names to use sc. table reference
3679
        $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3680
        $sort = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
3681
        // get document groups for current user
3682
        if ($docgrp = $this->getUserDocGroups()) {
3683
            $docgrp = implode(",", $docgrp);
3684
        }
3685
        // build query
3686
        $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
3687
        $result = $this->db->select("DISTINCT {$fields}", "{$tblsc} sc
3688
                LEFT JOIN {$tbldg} dg on dg.document = sc.id", "sc.parent = '{$id}' AND ({$access}) GROUP BY sc.id", "{$sort} {$dir}");
3689
        $resourceArray = $this->db->makeArray($result);
3690
        $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
3691
        return $resourceArray;
3692
    }
3693
3694
    /**
3695
     * Gets all active child documents of the specified document, i.e. those which published and not deleted.
3696
     *
3697
     * @param int $id The Document identifier to start with
3698
     * @param string $sort Sort field
3699
     *                     Default: menuindex
3700
     * @param string $dir Sort direction, ASC and DESC is possible
3701
     *                    Default: ASC
3702
     * @param string $fields Default: id, pagetitle, description, parent, alias, menutitle
3703
     * @return array
3704
     */
3705 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...
3706
    {
3707
        $cacheKey = md5(print_r(func_get_args(), true));
3708
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3709
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3710
        }
3711
3712
        $tblsc = $this->getFullTableName("site_content");
3713
        $tbldg = $this->getFullTableName("document_groups");
3714
3715
        // modify field names to use sc. table reference
3716
        $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3717
        $sort = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
3718
        // get document groups for current user
3719
        if ($docgrp = $this->getUserDocGroups()) {
3720
            $docgrp = implode(",", $docgrp);
3721
        }
3722
        // build query
3723
        $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
3724
        $result = $this->db->select("DISTINCT {$fields}", "{$tblsc} sc
3725
                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}");
3726
        $resourceArray = $this->db->makeArray($result);
3727
3728
        $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
3729
3730
        return $resourceArray;
3731
    }
3732
3733
    /**
3734
     * getDocumentChildren
3735
     * @version 1.1.1 (2014-02-19)
3736
     *
3737
     * @desc Returns the children of the selected document/folder as an associative array.
3738
     *
3739
     * @param $parentid {integer} - The parent document identifier. Default: 0 (site root).
3740
     * @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.
3741
     * @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.
3742
     * @param $fields {comma separated string; '*'} - Comma separated list of document fields to get. Default: '*' (all fields).
3743
     * @param $where {string} - Where condition in SQL style. Should include a leading 'AND '. Default: ''.
3744
     * @param $sort {comma separated string} - Should be a comma-separated list of field names on which to sort. Default: 'menuindex'.
3745
     * @param $dir {'ASC'; 'DESC'} - Sort direction, ASC and DESC is possible. Default: 'ASC'.
3746
     * @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).
3747
     *
3748
     * @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...
3749
     */
3750
    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...
3751
    {
3752
        $cacheKey = md5(print_r(func_get_args(), true));
3753
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3754
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3755
        }
3756
3757
        $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...
3758
        $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...
3759
3760
        if ($where != '') {
3761
            $where = 'AND ' . $where;
3762
        }
3763
3764
        // modify field names to use sc. table reference
3765
        $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3766
        $sort = ($sort == '') ? '' : 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
3767
3768
        // get document groups for current user
3769
        if ($docgrp = $this->getUserDocGroups()) {
3770
            $docgrp = implode(',', $docgrp);
3771
        }
3772
3773
        // build query
3774
        $access = ($this->isFrontend() ? 'sc.privateweb=0' : '1="' . $_SESSION['mgrRole'] . '" OR sc.privatemgr=0') . (!$docgrp ? '' : ' OR dg.document_group IN (' . $docgrp . ')');
3775
3776
        $tblsc = $this->getFullTableName('site_content');
3777
        $tbldg = $this->getFullTableName('document_groups');
3778
3779
        $result = $this->db->select("DISTINCT {$fields}", "{$tblsc} sc
3780
                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);
3781
3782
        $resourceArray = $this->db->makeArray($result);
3783
3784
        $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
3785
3786
        return $resourceArray;
3787
    }
3788
3789
    /**
3790
     * getDocuments
3791
     * @version 1.1.1 (2013-02-19)
3792
     *
3793
     * @desc Returns required documents (their fields).
3794
     *
3795
     * @param $ids {array; comma separated string} - Documents Ids to get. @required
3796
     * @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.
3797
     * @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.
3798
     * @param $fields {comma separated string; '*'} - Documents fields to get. Default: '*'.
3799
     * @param $where {string} - SQL WHERE clause. Default: ''.
3800
     * @param $sort {comma separated string} - A comma-separated list of field names to sort by. Default: 'menuindex'.
3801
     * @param $dir {'ASC'; 'DESC'} - Sorting direction. Default: 'ASC'.
3802
     * @param $limit {string} - SQL LIMIT (without 'LIMIT '). An empty string means no limit. Default: ''.
3803
     *
3804
     * @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...
3805
     */
3806
    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...
3807
    {
3808
        $cacheKey = md5(print_r(func_get_args(), true));
3809
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3810
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3811
        }
3812
3813
        if (is_string($ids)) {
3814
            if (strpos($ids, ',') !== false) {
3815
                $ids = array_filter(array_map('intval', explode(',', $ids)));
3816
            } else {
3817
                $ids = array($ids);
3818
            }
3819
        }
3820
        if (count($ids) == 0) {
3821
            $this->tmpCache[__FUNCTION__][$cacheKey] = false;
3822
            return false;
3823
        } else {
3824
            // modify field names to use sc. table reference
3825
            $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3826
            $sort = ($sort == '') ? '' : 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
3827
            if ($where != '') {
3828
                $where = 'AND ' . $where;
3829
            }
3830
3831
            $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...
3832
            $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...
3833
3834
            // get document groups for current user
3835
            if ($docgrp = $this->getUserDocGroups()) {
3836
                $docgrp = implode(',', $docgrp);
3837
            }
3838
3839
            $access = ($this->isFrontend() ? 'sc.privateweb=0' : '1="' . $_SESSION['mgrRole'] . '" OR sc.privatemgr=0') . (!$docgrp ? '' : ' OR dg.document_group IN (' . $docgrp . ')');
3840
3841
            $tblsc = $this->getFullTableName('site_content');
3842
            $tbldg = $this->getFullTableName('document_groups');
3843
3844
            $result = $this->db->select("DISTINCT {$fields}", "{$tblsc} sc
3845
                    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);
3846
3847
            $resourceArray = $this->db->makeArray($result);
3848
3849
            $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
3850
3851
            return $resourceArray;
3852
        }
3853
    }
3854
3855
    /**
3856
     * getDocument
3857
     * @version 1.0.1 (2014-02-19)
3858
     *
3859
     * @desc Returns required fields of a document.
3860
     *
3861
     * @param int $id {integer}
3862
     * - Id of a document which data has to be gained. @required
3863
     * @param string $fields {comma separated string; '*'}
3864
     * - Comma separated list of document fields to get. Default: '*'.
3865
     * @param int $published {0; 1; 'all'}
3866
     * - 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.
3867
     * @param int $deleted {0; 1; 'all'}
3868
     * - 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.
3869
     * @return bool {array; false} - Result array with fields or false.
3870
     * - Result array with fields or false.
3871
     */
3872 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...
3873
    {
3874
        if ($id == 0) {
3875
            return false;
3876
        } else {
3877
            $docs = $this->getDocuments(array($id), $published, $deleted, $fields, '', '', '', 1);
3878
3879
            if ($docs != false) {
3880
                return $docs[0];
3881
            } else {
3882
                return false;
3883
            }
3884
        }
3885
    }
3886
3887
    /**
3888
     * @param string $field
3889
     * @param string $docid
3890
     * @return bool|mixed
3891
     */
3892
    public function getField($field = 'content', $docid = '')
3893
    {
3894
        if (empty($docid) && isset($this->documentIdentifier)) {
3895
            $docid = $this->documentIdentifier;
3896
        } elseif (!preg_match('@^[0-9]+$@', $docid)) {
3897
            $docid = $this->getIdFromAlias($docid);
3898
        }
3899
3900
        if (empty($docid)) {
3901
            return false;
3902
        }
3903
3904
        $cacheKey = md5(print_r(func_get_args(), true));
3905
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3906
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3907
        }
3908
3909
        $doc = $this->getDocumentObject('id', $docid);
3910
        if (is_array($doc[$field])) {
3911
            $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...
3912
            $content = $tvs[$field];
3913
        } else {
3914
            $content = $doc[$field];
3915
        }
3916
3917
        $this->tmpCache[__FUNCTION__][$cacheKey] = $content;
3918
3919
        return $content;
3920
    }
3921
3922
    /**
3923
     * Returns the page information as database row, the type of result is
3924
     * defined with the parameter $rowMode
3925
     *
3926
     * @param int $pageid The parent document identifier
3927
     *                    Default: -1 (no result)
3928
     * @param int $active Should we fetch only published and undeleted documents/resources?
3929
     *                     1 = yes, 0 = no
3930
     *                     Default: 1
3931
     * @param string $fields List of fields
3932
     *                       Default: id, pagetitle, description, alias
3933
     * @return boolean|array
3934
     */
3935
    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...
3936
    {
3937
        $cacheKey = md5(print_r(func_get_args(), true));
3938
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3939
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3940
        }
3941
3942
        if ($pageid == 0) {
3943
            return false;
3944
        } else {
3945
            $tblsc = $this->getFullTableName("site_content");
3946
            $tbldg = $this->getFullTableName("document_groups");
3947
            $activeSql = $active == 1 ? "AND sc.published=1 AND sc.deleted=0" : "";
3948
            // modify field names to use sc. table reference
3949
            $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3950
            // get document groups for current user
3951
            if ($docgrp = $this->getUserDocGroups()) {
3952
                $docgrp = implode(",", $docgrp);
3953
            }
3954
            $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
3955
            $result = $this->db->select($fields, "{$tblsc} sc LEFT JOIN {$tbldg} dg on dg.document = sc.id", "(sc.id='{$pageid}' {$activeSql}) AND ({$access})", "", 1);
3956
            $pageInfo = $this->db->getRow($result);
3957
3958
            $this->tmpCache[__FUNCTION__][$cacheKey] = $pageInfo;
3959
3960
            return $pageInfo;
3961
        }
3962
    }
3963
3964
    /**
3965
     * Returns the parent document/resource of the given docid
3966
     *
3967
     * @param int $pid The parent docid. If -1, then fetch the current document/resource's parent
3968
     *                 Default: -1
3969
     * @param int $active Should we fetch only published and undeleted documents/resources?
3970
     *                     1 = yes, 0 = no
3971
     *                     Default: 1
3972
     * @param string $fields List of fields
3973
     *                       Default: id, pagetitle, description, alias
3974
     * @return boolean|array
3975
     */
3976
    public function getParent($pid = -1, $active = 1, $fields = 'id, pagetitle, description, alias, parent')
3977
    {
3978
        if ($pid == -1) {
3979
            $pid = $this->documentObject['parent'];
3980
            return ($pid == 0) ? false : $this->getPageInfo($pid, $active, $fields);
3981
        } elseif ($pid == 0) {
3982
            return false;
3983
        } else {
3984
            // first get the child document
3985
            $child = $this->getPageInfo($pid, $active, "parent");
3986
            // now return the child's parent
3987
            $pid = ($child['parent']) ? $child['parent'] : 0;
3988
            return ($pid == 0) ? false : $this->getPageInfo($pid, $active, $fields);
3989
        }
3990
    }
3991
3992
    /**
3993
     * Returns the id of the current snippet.
3994
     *
3995
     * @return int
3996
     */
3997
    public function getSnippetId()
3998
    {
3999
        if ($this->currentSnippet) {
4000
            $tbl = $this->getFullTableName("site_snippets");
4001
            $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...
4002
            if ($snippetId = $this->db->getValue($rs)) {
4003
                return $snippetId;
4004
            }
4005
        }
4006
        return 0;
4007
    }
4008
4009
    /**
4010
     * Returns the name of the current snippet.
4011
     *
4012
     * @return string
4013
     */
4014
    public function getSnippetName()
4015
    {
4016
        return $this->currentSnippet;
4017
    }
4018
4019
    /**
4020
     * Clear the cache of MODX.
4021
     *
4022
     * @param string $type
4023
     * @param bool $report
4024
     * @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...
4025
     */
4026
    public function clearCache($type = '', $report = false)
4027
    {
4028
        $cache_dir = MODX_BASE_PATH . $this->getCacheFolder();
4029
        if (is_array($type)) {
4030
            foreach ($type as $_) {
4031
                $this->clearCache($_, $report);
4032
            }
4033
        } elseif ($type == 'full') {
4034
            include_once(MODX_MANAGER_PATH . 'processors/cache_sync.class.processor.php');
4035
            $sync = new synccache();
4036
            $sync->setCachepath($cache_dir);
4037
            $sync->setReport($report);
4038
            $sync->emptyCache();
4039
        } elseif (preg_match('@^[1-9][0-9]*$@', $type)) {
4040
            $key = ($this->config['cache_type'] == 2) ? $this->makePageCacheKey($type) : $type;
4041
            $file_name = "docid_" . $key . "_*.pageCache.php";
4042
            $cache_path = $cache_dir . $file_name;
4043
            $files = glob($cache_path);
4044
            $files[] = $cache_dir . "docid_" . $key . ".pageCache.php";
4045
            foreach ($files as $file) {
4046
                if (!is_file($file)) {
4047
                    continue;
4048
                }
4049
                unlink($file);
4050
            }
4051
        } else {
4052
            $files = glob($cache_dir . '*');
4053
            foreach ($files as $file) {
4054
                $name = basename($file);
4055
                if (strpos($name, '.pageCache.php') === false) {
4056
                    continue;
4057
                }
4058
                if (!is_file($file)) {
4059
                    continue;
4060
                }
4061
                unlink($file);
4062
            }
4063
        }
4064
    }
4065
4066
    /**
4067
     * makeUrl
4068
     *
4069
     * @desc Create an URL for the given document identifier. The url prefix and postfix are used, when “friendly_url” is active.
4070
     *
4071
     * @param $id {integer} - The document identifier. @required
4072
     * @param string $alias {string}
4073
     * - The alias name for the document. Default: ''.
4074
     * @param string $args {string}
4075
     * - The paramaters to add to the URL. Default: ''.
4076
     * @param string $scheme {string}
4077
     * - With full as valus, the site url configuration is used. Default: ''.
4078
     * @return mixed|string {string} - Result URL.
4079
     * - Result URL.
4080
     */
4081
    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...
4082
    {
4083
        $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...
4084
        $virtualDir = isset($this->config['virtual_dir']) ? $this->config['virtual_dir'] : '';
4085
        $f_url_prefix = $this->config['friendly_url_prefix'];
4086
        $f_url_suffix = $this->config['friendly_url_suffix'];
4087
4088
        if (!is_numeric($id)) {
4089
            $this->messageQuit("`{$id}` is not numeric and may not be passed to makeUrl()");
4090
        }
4091
4092
        if ($args !== '') {
4093
            // add ? or & to $args if missing
4094
            $args = ltrim($args, '?&');
4095
            $_ = 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...
4096
4097
            if ($_ === false && $this->config['friendly_urls'] == 1) {
4098
                $args = "?{$args}";
4099
            } else {
4100
                $args = "&{$args}";
4101
            }
4102
        }
4103
4104
        if ($id != $this->config['site_start']) {
4105
            if ($this->config['friendly_urls'] == 1 && $alias == '') {
4106
                $alias = $id;
4107
                $alPath = '';
4108
4109
                if ($this->config['friendly_alias_urls'] == 1) {
4110
                    if ($this->config['aliaslistingfolder'] == 1) {
4111
                        $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...
4112
                    } else {
4113
                        $al = $this->aliasListing[$id];
4114
                    }
4115
4116
                    if ($al['isfolder'] === 1 && $this->config['make_folders'] === '1') {
4117
                        $f_url_suffix = '/';
4118
                    }
4119
4120
                    $alPath = !empty($al['path']) ? $al['path'] . '/' : '';
4121
4122
                    if ($al && $al['alias']) {
4123
                        $alias = $al['alias'];
4124
                    }
4125
                }
4126
4127
                $alias = $alPath . $f_url_prefix . $alias . $f_url_suffix;
4128
                $url = "{$alias}{$args}";
4129
            } else {
4130
                $url = "index.php?id={$id}{$args}";
4131
            }
4132
        } else {
4133
            $url = $args;
4134
        }
4135
4136
        $host = $this->config['base_url'];
4137
4138
        // check if scheme argument has been set
4139
        if ($scheme != '') {
4140
            // for backward compatibility - check if the desired scheme is different than the current scheme
4141
            if (is_numeric($scheme) && $scheme != $_SERVER['HTTPS']) {
4142
                $scheme = ($_SERVER['HTTPS'] ? 'http' : 'https');
4143
            }
4144
4145
            //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...
4146
            $host = $scheme == 'full' ? $this->config['site_url'] : $scheme . '://' . $_SERVER['HTTP_HOST'] . $host;
4147
        }
4148
4149
        //fix strictUrl by Bumkaka
4150
        if ($this->config['seostrict'] == '1') {
4151
            $url = $this->toAlias($url);
4152
        }
4153
4154
        if ($this->config['xhtml_urls']) {
4155
            $url = preg_replace("/&(?!amp;)/", "&amp;", $host . $virtualDir . $url);
4156
        } else {
4157
            $url = $host . $virtualDir . $url;
4158
        }
4159
4160
        $evtOut = $this->invokeEvent('OnMakeDocUrl', array(
4161
            'id' => $id,
4162
            'url' => $url
4163
        ));
4164
4165
        if (is_array($evtOut) && count($evtOut) > 0) {
4166
            $url = array_pop($evtOut);
4167
        }
4168
4169
        return $url;
4170
    }
4171
4172
    /**
4173
     * @param $id
4174
     * @return mixed
4175
     */
4176
    public function getAliasListing($id)
4177
    {
4178
        if (isset($this->aliasListing[$id])) {
4179
            $out = $this->aliasListing[$id];
4180
        } else {
4181
            $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...
4182
            if ($this->db->getRecordCount($q) == '1') {
4183
                $q = $this->db->getRow($q);
4184
                $this->aliasListing[$id] = array(
4185
                    'id' => (int)$q['id'],
4186
                    'alias' => $q['alias'] == '' ? $q['id'] : $q['alias'],
4187
                    'parent' => (int)$q['parent'],
4188
                    'isfolder' => (int)$q['isfolder'],
4189
                );
4190
                if ($this->aliasListing[$id]['parent'] > 0) {
4191
                    //fix alias_path_usage
4192
                    if ($this->config['use_alias_path'] == '1') {
4193
                        //&& $tmp['path'] != '' - fix error slash with epty path
4194
                        $tmp = $this->getAliasListing($this->aliasListing[$id]['parent']);
4195
                        $this->aliasListing[$id]['path'] = $tmp['path'] . ($tmp['alias_visible'] ? (($tmp['parent'] > 0 && $tmp['path'] != '') ? '/' : '') . $tmp['alias'] : '');
4196
                    } else {
4197
                        $this->aliasListing[$id]['path'] = '';
4198
                    }
4199
                }
4200
4201
                $out = $this->aliasListing[$id];
4202
            }
4203
        }
4204
        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...
4205
    }
4206
4207
    /**
4208
     * Returns an entry from the config
4209
     *
4210
     * Note: most code accesses the config array directly and we will continue to support this.
4211
     *
4212
     * @param string $name
4213
     * @return bool|string
4214
     */
4215
    public function getConfig($name = '')
4216
    {
4217
        if (!empty($this->config[$name])) {
4218
            return $this->config[$name];
4219
        } else {
4220
            return false;
4221
        }
4222
    }
4223
4224
    /**
4225
     * Returns the MODX version information as version, branch, release date and full application name.
4226
     *
4227
     * @param null $data
4228
     * @return array
4229
     */
4230
4231
    public function getVersionData($data = null)
4232
    {
4233
        $out = array();
4234
        if (empty($this->version) || !is_array($this->version)) {
4235
            //include for compatibility modx version < 1.0.10
4236
            include MODX_MANAGER_PATH . "includes/version.inc.php";
4237
            $this->version = array();
4238
            $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...
4239
            $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...
4240
            $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...
4241
            $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...
4242
            $this->version['new_version'] = isset($this->config['newversiontext']) ? $this->config['newversiontext'] : '';
4243
        }
4244
        return (!is_null($data) && is_array($this->version) && isset($this->version[$data])) ? $this->version[$data] : $this->version;
4245
    }
4246
4247
    /**
4248
     * Executes a snippet.
4249
     *
4250
     * @param string $snippetName
4251
     * @param array $params Default: Empty array
4252
     * @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...
4253
     */
4254
    public function runSnippet($snippetName, $params = array())
4255
    {
4256
        if (isset($this->snippetCache[$snippetName])) {
4257
            $snippet = $this->snippetCache[$snippetName];
4258
            $properties = !empty($this->snippetCache[$snippetName . "Props"]) ? $this->snippetCache[$snippetName . "Props"] : '';
4259
        } else { // not in cache so let's check the db
4260
            $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;";
4261
            $result = $this->db->query($sql);
4262
            if ($this->db->getRecordCount($result) == 1) {
4263
                $row = $this->db->getRow($result);
4264
                $snippet = $this->snippetCache[$snippetName] = $row['snippet'];
4265
                $mergedProperties = array_merge($this->parseProperties($row['properties']), $this->parseProperties($row['sharedproperties']));
4266
                $properties = $this->snippetCache[$snippetName . "Props"] = json_encode($mergedProperties);
4267
            } else {
4268
                $snippet = $this->snippetCache[$snippetName] = "return false;";
4269
                $properties = $this->snippetCache[$snippetName . "Props"] = '';
4270
            }
4271
        }
4272
        // load default params/properties
4273
        $parameters = $this->parseProperties($properties, $snippetName, 'snippet');
4274
        $parameters = array_merge($parameters, $params);
4275
4276
        // run snippet
4277
        return $this->evalSnippet($snippet, $parameters);
4278
    }
4279
4280
    /**
4281
     * Returns the chunk content for the given chunk name
4282
     *
4283
     * @param string $chunkName
4284
     * @return boolean|string
4285
     */
4286
    public function getChunk($chunkName)
4287
    {
4288
        $out = null;
4289
        if (empty($chunkName)) {
4290
            return $out;
4291
        }
4292
        if (isset($this->chunkCache[$chunkName])) {
4293
            $out = $this->chunkCache[$chunkName];
4294
        } elseif (stripos($chunkName, '@FILE') === 0) {
4295
            $out = $this->chunkCache[$chunkName] = $this->atBindFileContent($chunkName);
4296
        } else {
4297
            $where = sprintf("`name`='%s' AND disabled=0", $this->db->escape($chunkName));
4298
            $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...
4299
            if ($this->db->getRecordCount($rs) == 1) {
4300
                $row = $this->db->getRow($rs);
4301
                $out = $this->chunkCache[$chunkName] = $row['snippet'];
4302
            } else {
4303
                $out = $this->chunkCache[$chunkName] = null;
4304
            }
4305
        }
4306
        return $out;
4307
    }
4308
4309
    /**
4310
     * parseText
4311
     * @version 1.0 (2013-10-17)
4312
     *
4313
     * @desc Replaces placeholders in text with required values.
4314
     *
4315
     * @param string $tpl
4316
     * @param array $ph
4317
     * @param string $left
4318
     * @param string $right
4319
     * @param bool $execModifier
4320
     * @return string {string} - Parsed text.
4321
     * - Parsed text.
4322
     * @internal param $chunk {string} - String to parse. - String to parse. @required
4323
     * @internal param $chunkArr {array} - Array of values. Key — placeholder name, value — value. - Array of values. Key — placeholder name, value — value. @required
4324
     * @internal param $prefix {string} - Placeholders prefix. Default: '[+'. - Placeholders prefix. Default: '[+'.
4325
     * @internal param $suffix {string} - Placeholders suffix. Default: '+]'. - Placeholders suffix. Default: '+]'.
4326
     *
4327
     */
4328
    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...
4329
    {
4330
        if (empty($ph) || empty($tpl)) {
4331
            return $tpl;
4332
        }
4333
4334 View Code Duplication
        if ($this->config['enable_at_syntax']) {
4335
            if (stripos($tpl, '<@LITERAL>') !== false) {
4336
                $tpl = $this->escapeLiteralTagsContent($tpl);
4337
            }
4338
        }
4339
4340
        $matches = $this->getTagsFromContent($tpl, $left, $right);
4341
        if (empty($matches)) {
4342
            return $tpl;
4343
        }
4344
4345
        foreach ($matches[1] as $i => $key) {
4346
            if (strpos($key, ':') !== false && $execModifier) {
4347
                list($key, $modifiers) = $this->splitKeyAndFilter($key);
4348
            } else {
4349
                $modifiers = false;
4350
            }
4351
4352
            //          if(!isset($ph[$key])) continue;
0 ignored issues
show
Unused Code Comprehensibility introduced by
87% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
4353
            if (!array_key_exists($key, $ph)) {
4354
                continue;
4355
            } //NULL values must be saved in placeholders, if we got them from database string
4356
4357
            $value = $ph[$key];
4358
4359
            $s = &$matches[0][$i];
4360
            if ($modifiers !== false) {
4361
                if (strpos($modifiers, $left) !== false) {
4362
                    $modifiers = $this->parseText($modifiers, $ph, $left, $right);
4363
                }
4364
                $value = $this->applyFilter($value, $modifiers, $key);
4365
            }
4366 View Code Duplication
            if (strpos($tpl, $s) !== false) {
4367
                $tpl = str_replace($s, $value, $tpl);
4368
            } elseif ($this->debug) {
4369
                $this->addLog('parseText parse error', $_SERVER['REQUEST_URI'] . $s, 2);
4370
            }
4371
        }
4372
4373
        return $tpl;
4374
    }
4375
4376
    /**
4377
     * parseChunk
4378
     * @version 1.1 (2013-10-17)
4379
     *
4380
     * @desc Replaces placeholders in a chunk with required values.
4381
     *
4382
     * @param $chunkName {string} - Name of chunk to parse. @required
4383
     * @param $chunkArr {array} - Array of values. Key — placeholder name, value — value. @required
4384
     * @param string $prefix {string}
4385
     * - Placeholders prefix. Default: '{'.
4386
     * @param string $suffix {string}
4387
     * - Placeholders suffix. Default: '}'.
4388
     * @return bool|mixed|string {string; false} - Parsed chunk or false if $chunkArr is not array.
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use false|string.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
4389
     * - Parsed chunk or false if $chunkArr is not array.
4390
     */
4391
    public function parseChunk($chunkName, $chunkArr, $prefix = '{', $suffix = '}')
4392
    {
4393
        //TODO: Wouldn't it be more practical to return the contents of a chunk instead of false?
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
4394
        if (!is_array($chunkArr)) {
4395
            return false;
4396
        }
4397
4398
        return $this->parseText($this->getChunk($chunkName), $chunkArr, $prefix, $suffix);
0 ignored issues
show
Bug introduced by
It seems like $this->getChunk($chunkName) targeting 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...
4399
    }
4400
4401
    /**
4402
     * getTpl
4403
     * get template for snippets
4404
     * @param $tpl {string}
4405
     * @return bool|string {string}
4406
     */
4407
    public function getTpl($tpl)
4408
    {
4409
        $template = $tpl;
4410
        if (preg_match("/^@([^:\s]+)[:\s]+(.+)$/s", trim($tpl), $match)) {
4411
            $command = strtoupper($match[1]);
4412
            $template = $match[2];
4413
        }
4414
        switch ($command) {
0 ignored issues
show
Bug introduced by
The variable $command does not seem to be defined for all execution paths leading up to this point.

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

Let’s take a look at an example:

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

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

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

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

Available Fixes

  1. Check for existence of the variable explicitly:

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

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

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
4415
            case 'CODE':
4416
                break;
4417
            case 'FILE':
4418
                $template = file_get_contents(MODX_BASE_PATH . $template);
4419
                break;
4420
            case 'CHUNK':
4421
                $template = $this->getChunk($template);
4422
                break;
4423
            case 'DOCUMENT':
4424
                $doc = $this->getDocument($template, 'content', 'all');
4425
                $template = $doc['content'];
4426
                break;
4427
            case 'SELECT':
4428
                $this->db->getValue($this->db->query("SELECT {$template}"));
4429
                break;
4430
            default:
4431
                if (!($template = $this->getChunk($tpl))) {
4432
                    $template = $tpl;
4433
                }
4434
        }
4435
        return $template;
4436
    }
4437
4438
    /**
4439
     * Returns the timestamp in the date format defined in $this->config['datetime_format']
4440
     *
4441
     * @param int $timestamp Default: 0
4442
     * @param string $mode Default: Empty string (adds the time as below). Can also be 'dateOnly' for no time or 'formatOnly' to get the datetime_format string.
4443
     * @return string
4444
     */
4445
    public function toDateFormat($timestamp = 0, $mode = '')
4446
    {
4447
        $timestamp = trim($timestamp);
4448
        if ($mode !== 'formatOnly' && empty($timestamp)) {
4449
            return '-';
4450
        }
4451
        $timestamp = (int)$timestamp;
4452
4453
        switch ($this->config['datetime_format']) {
4454
            case 'YYYY/mm/dd':
4455
                $dateFormat = '%Y/%m/%d';
4456
                break;
4457
            case 'dd-mm-YYYY':
4458
                $dateFormat = '%d-%m-%Y';
4459
                break;
4460
            case 'mm/dd/YYYY':
4461
                $dateFormat = '%m/%d/%Y';
4462
                break;
4463
            /*
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
4464
            case 'dd-mmm-YYYY':
4465
                $dateFormat = '%e-%b-%Y';
4466
                break;
4467
            */
4468
        }
4469
4470
        if (empty($mode)) {
4471
            $strTime = strftime($dateFormat . " %H:%M:%S", $timestamp);
0 ignored issues
show
Bug introduced by
The variable $dateFormat does not seem to be defined for all execution paths leading up to this point.

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

Let’s take a look at an example:

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

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

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

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

Available Fixes

  1. Check for existence of the variable explicitly:

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

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

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
4472
        } elseif ($mode == 'dateOnly') {
4473
            $strTime = strftime($dateFormat, $timestamp);
4474
        } elseif ($mode == 'formatOnly') {
4475
            $strTime = $dateFormat;
4476
        }
4477
        return $strTime;
0 ignored issues
show
Bug introduced by
The variable $strTime does not seem to be defined for all execution paths leading up to this point.

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

Let’s take a look at an example:

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

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

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

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

Available Fixes

  1. Check for existence of the variable explicitly:

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

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

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
4478
    }
4479
4480
    /**
4481
     * Make a timestamp from a string corresponding to the format in $this->config['datetime_format']
4482
     *
4483
     * @param string $str
4484
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|integer?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
4485
     */
4486
    public function toTimeStamp($str)
4487
    {
4488
        $str = trim($str);
4489
        if (empty($str)) {
4490
            return '';
4491
        }
4492
4493
        switch ($this->config['datetime_format']) {
4494 View Code Duplication
            case 'YYYY/mm/dd':
4495
                if (!preg_match('/^[0-9]{4}\/[0-9]{2}\/[0-9]{2}[0-9 :]*$/', $str)) {
4496
                    return '';
4497
                }
4498
                list($Y, $m, $d, $H, $M, $S) = sscanf($str, '%4d/%2d/%2d %2d:%2d:%2d');
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $Y. Configured minimum length is 3.

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

Loading history...
Comprehensibility introduced by
Avoid variables with short names like $m. Configured minimum length is 3.

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

Loading history...
Comprehensibility introduced by
Avoid variables with short names like $d. Configured minimum length is 3.

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

Loading history...
Comprehensibility introduced by
Avoid variables with short names like $H. Configured minimum length is 3.

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

Loading history...
Comprehensibility introduced by
Avoid variables with short names like $M. Configured minimum length is 3.

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

Loading history...
Comprehensibility introduced by
Avoid variables with short names like $S. Configured minimum length is 3.

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

Loading history...
4499
                break;
4500 View Code Duplication
            case 'dd-mm-YYYY':
4501
                if (!preg_match('/^[0-9]{2}-[0-9]{2}-[0-9]{4}[0-9 :]*$/', $str)) {
4502
                    return '';
4503
                }
4504
                list($d, $m, $Y, $H, $M, $S) = sscanf($str, '%2d-%2d-%4d %2d:%2d:%2d');
4505
                break;
4506 View Code Duplication
            case 'mm/dd/YYYY':
4507
                if (!preg_match('/^[0-9]{2}\/[0-9]{2}\/[0-9]{4}[0-9 :]*$/', $str)) {
4508
                    return '';
4509
                }
4510
                list($m, $d, $Y, $H, $M, $S) = sscanf($str, '%2d/%2d/%4d %2d:%2d:%2d');
4511
                break;
4512
            /*
0 ignored issues
show
Unused Code Comprehensibility introduced by
65% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
4513
            case 'dd-mmm-YYYY':
4514
                if (!preg_match('/^[0-9]{2}-[0-9a-z]+-[0-9]{4}[0-9 :]*$/i', $str)) {return '';}
4515
                list ($m, $d, $Y, $H, $M, $S) = sscanf($str, '%2d-%3s-%4d %2d:%2d:%2d');
4516
                break;
4517
            */
4518
        }
4519
        if (!$H && !$M && !$S) {
0 ignored issues
show
Bug introduced by
The variable $H does not seem to be defined for all execution paths leading up to this point.

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

Let’s take a look at an example:

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

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

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

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

Available Fixes

  1. Check for existence of the variable explicitly:

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

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

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $M does not seem to be defined for all execution paths leading up to this point.

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

Let’s take a look at an example:

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

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

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

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

Available Fixes

  1. Check for existence of the variable explicitly:

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

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

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $S does not seem to be defined for all execution paths leading up to this point.

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

Let’s take a look at an example:

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

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

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

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

Available Fixes

  1. Check for existence of the variable explicitly:

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

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

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
4520
            $H = 0;
4521
            $M = 0;
4522
            $S = 0;
4523
        }
4524
        $timeStamp = mktime($H, $M, $S, $m, $d, $Y);
0 ignored issues
show
Bug introduced by
The variable $m does not seem to be defined for all execution paths leading up to this point.

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

Let’s take a look at an example:

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

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

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

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

Available Fixes

  1. Check for existence of the variable explicitly:

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

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

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $d does not seem to be defined for all execution paths leading up to this point.

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

Let’s take a look at an example:

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

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

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

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

Available Fixes

  1. Check for existence of the variable explicitly:

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

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

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $Y does not seem to be defined for all execution paths leading up to this point.

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

Let’s take a look at an example:

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

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

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

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

Available Fixes

  1. Check for existence of the variable explicitly:

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

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

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
4525
        $timeStamp = (int)$timeStamp;
4526
        return $timeStamp;
4527
    }
4528
4529
    /**
4530
     * Get the TVs of a document's children. Returns an array where each element represents one child doc.
4531
     *
4532
     * Ignores deleted children. Gets all children - there is no where clause available.
4533
     *
4534
     * @param int $parentid The parent docid
4535
     *                 Default: 0 (site root)
4536
     * @param array $tvidnames . Which TVs to fetch - Can relate to the TV ids in the db (array elements should be numeric only)
4537
     *                                               or the TV names (array elements should be names only)
4538
     *                      Default: Empty array
4539
     * @param int $published Whether published or unpublished documents are in the result
4540
     *                      Default: 1
4541
     * @param string $docsort How to sort the result array (field)
4542
     *                      Default: menuindex
4543
     * @param ASC|string $docsortdir How to sort the result array (direction)
4544
     *                      Default: ASC
4545
     * @param string $tvfields Fields to fetch from site_tmplvars, default '*'
4546
     *                      Default: *
4547
     * @param string $tvsort How to sort each element of the result array i.e. how to sort the TVs (field)
4548
     *                      Default: rank
4549
     * @param string $tvsortdir How to sort each element of the result array i.e. how to sort the TVs (direction)
4550
     *                      Default: ASC
4551
     * @return array|bool
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use false|array.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
4552
     */
4553
    public function getDocumentChildrenTVars($parentid = 0, $tvidnames = array(), $published = 1, $docsort = "menuindex", $docsortdir = "ASC", $tvfields = "*", $tvsort = "rank", $tvsortdir = "ASC")
4554
    {
4555
        $docs = $this->getDocumentChildren($parentid, $published, 0, '*', '', $docsort, $docsortdir);
4556
        if (!$docs) {
4557
            return false;
4558
        } else {
4559
            $result = array();
4560
            // get user defined template variables
4561
            if ($tvfields) {
4562
                $_ = array_filter(array_map('trim', explode(',', $tvfields)));
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $_. Configured minimum length is 3.

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

Loading history...
4563
                foreach ($_ as $i => $v) {
4564
                    if ($v === 'value') {
4565
                        unset($_[$i]);
4566
                    } else {
4567
                        $_[$i] = 'tv.' . $v;
4568
                    }
4569
                }
4570
                $fields = implode(',', $_);
4571
            } else {
4572
                $fields = "tv.*";
4573
            }
4574
4575
            if ($tvsort != '') {
4576
                $tvsort = 'tv.' . implode(',tv.', array_filter(array_map('trim', explode(',', $tvsort))));
4577
            }
4578 View Code Duplication
            if ($tvidnames == "*") {
4579
                $query = "tv.id<>0";
4580
            } else {
4581
                $query = (is_numeric($tvidnames[0]) ? "tv.id" : "tv.name") . " IN ('" . implode("','", $tvidnames) . "')";
4582
            }
4583
4584
            $this->getUserDocGroups();
4585
4586
            foreach ($docs as $doc) {
4587
                $docid = $doc['id'];
4588
4589
                $rs = $this->db->select("{$fields}, IF(tvc.value!='',tvc.value,tv.default_text) as value ", "[+prefix+]site_tmplvars tv
4590
                        INNER JOIN [+prefix+]site_tmplvar_templates tvtpl ON tvtpl.tmplvarid = tv.id
4591
                        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}" : ""));
4592
                $tvs = $this->db->makeArray($rs);
4593
4594
                // get default/built-in template variables
4595
                ksort($doc);
4596
                foreach ($doc as $key => $value) {
4597
                    if ($tvidnames == '*' || in_array($key, $tvidnames)) {
4598
                        $tvs[] = array('name' => $key, 'value' => $value);
4599
                    }
4600
                }
4601
                if (is_array($tvs) && count($tvs)) {
4602
                    $result[] = $tvs;
4603
                }
4604
            }
4605
            return $result;
4606
        }
4607
    }
4608
4609
    /**
4610
     * getDocumentChildrenTVarOutput
4611
     * @version 1.1 (2014-02-19)
4612
     *
4613
     * @desc Returns an array where each element represents one child doc and contains the result from getTemplateVarOutput().
4614
     *
4615
     * @param int $parentid {integer}
4616
     * - Id of parent document. Default: 0 (site root).
4617
     * @param array $tvidnames {array; '*'}
4618
     * - Which TVs to fetch. In the form expected by getTemplateVarOutput(). Default: array().
4619
     * @param int $published {0; 1; 'all'}
4620
     * - 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.
4621
     * @param string $sortBy {string}
4622
     * - How to sort the result array (field). Default: 'menuindex'.
4623
     * @param string $sortDir {'ASC'; 'DESC'}
4624
     * - How to sort the result array (direction). Default: 'ASC'.
4625
     * @param string $where {string}
4626
     * - SQL WHERE condition (use only document fields, not TV). Default: ''.
4627
     * @param string $resultKey {string; false}
4628
     * - Field, which values are keys into result array. Use the “false”, that result array keys just will be numbered. Default: 'id'.
4629
     * @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...
4630
     * - Result array, or false.
4631
     */
4632
    public function getDocumentChildrenTVarOutput($parentid = 0, $tvidnames = array(), $published = 1, $sortBy = 'menuindex', $sortDir = 'ASC', $where = '', $resultKey = 'id')
4633
    {
4634
        $docs = $this->getDocumentChildren($parentid, $published, 0, 'id', $where, $sortBy, $sortDir);
4635
4636
        if (!$docs) {
4637
            return false;
4638
        } else {
4639
            $result = array();
4640
4641
            $unsetResultKey = false;
4642
4643
            if ($resultKey !== false) {
4644
                if (is_array($tvidnames)) {
4645
                    if (count($tvidnames) != 0 && !in_array($resultKey, $tvidnames)) {
4646
                        $tvidnames[] = $resultKey;
4647
                        $unsetResultKey = true;
4648
                    }
4649
                } elseif ($tvidnames != '*' && $tvidnames != $resultKey) {
4650
                    $tvidnames = array($tvidnames, $resultKey);
4651
                    $unsetResultKey = true;
4652
                }
4653
            }
4654
4655
            for ($i = 0; $i < count($docs); $i++) {
4656
                $tvs = $this->getTemplateVarOutput($tvidnames, $docs[$i]['id'], $published);
4657
4658
                if ($tvs) {
4659
                    if ($resultKey !== false && array_key_exists($resultKey, $tvs)) {
4660
                        $result[$tvs[$resultKey]] = $tvs;
4661
4662
                        if ($unsetResultKey) {
4663
                            unset($result[$tvs[$resultKey]][$resultKey]);
4664
                        }
4665
                    } else {
4666
                        $result[] = $tvs;
4667
                    }
4668
                }
4669
            }
4670
4671
            return $result;
4672
        }
4673
    }
4674
4675
    /**
4676
     * Modified by Raymond for TV - Orig Modified by Apodigm - DocVars
4677
     * Returns a single site_content field or TV record from the db.
4678
     *
4679
     * If a site content field the result is an associative array of 'name' and 'value'.
4680
     *
4681
     * If a TV the result is an array representing a db row including the fields specified in $fields.
4682
     *
4683
     * @param string $idname Can be a TV id or name
4684
     * @param string $fields Fields to fetch from site_tmplvars. Default: *
4685
     * @param string|type $docid Docid. Defaults to empty string which indicates the current document.
4686
     * @param int $published Whether published or unpublished documents are in the result
4687
     *                        Default: 1
4688
     * @return bool
4689
     */
4690 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...
4691
    {
4692
        if ($idname == "") {
4693
            return false;
4694
        } else {
4695
            $result = $this->getTemplateVars(array($idname), $fields, $docid, $published, "", ""); //remove sorting for speed
4696
            return ($result != false) ? $result[0] : false;
4697
        }
4698
    }
4699
4700
    /**
4701
     * getTemplateVars
4702
     * @version 1.0.1 (2014-02-19)
4703
     *
4704
     * @desc Returns an array of site_content field fields and/or TV records from the db.
4705
     * Elements representing a site content field consist of an associative array of 'name' and 'value'.
4706
     * Elements representing a TV consist of an array representing a db row including the fields specified in $fields.
4707
     *
4708
     * @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
4709
     * @param $fields {comma separated string; '*'} - Fields names in the TV table of MODx database. Default: '*'
4710
     * @param $docid {integer; ''} - Id of a document to get. Default: an empty string which indicates the current document.
4711
     * @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.
4712
     * @param $sort {comma separated string} - Fields of the TV table to sort by. Default: 'rank'.
4713
     * @param $dir {'ASC'; 'DESC'} - How to sort the result array (direction). Default: 'ASC'.
4714
     *
4715
     * @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...
4716
     */
4717
    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...
4718
    {
4719
        $cacheKey = md5(print_r(func_get_args(), true));
4720
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
4721
            return $this->tmpCache[__FUNCTION__][$cacheKey];
4722
        }
4723
4724
        if (($idnames != '*' && !is_array($idnames)) || empty($idnames)) {
4725
            return false;
4726
        } else {
4727
4728
            // get document record
4729
            if ($docid == '') {
4730
                $docid = $this->documentIdentifier;
4731
                $docRow = $this->documentObject;
4732
            } else {
4733
                $docRow = $this->getDocument($docid, '*', $published);
4734
4735
                if (!$docRow) {
4736
                    $this->tmpCache[__FUNCTION__][$cacheKey] = false;
4737
                    return false;
4738
                }
4739
            }
4740
4741
            // get user defined template variables
4742
            $fields = ($fields == '') ? 'tv.*' : 'tv.' . implode(',tv.', array_filter(array_map('trim', explode(',', $fields))));
4743
            $sort = ($sort == '') ? '' : 'tv.' . implode(',tv.', array_filter(array_map('trim', explode(',', $sort))));
4744
4745 View Code Duplication
            if ($idnames == '*') {
4746
                $query = 'tv.id<>0';
4747
            } else {
4748
                $query = (is_numeric($idnames[0]) ? 'tv.id' : 'tv.name') . " IN ('" . implode("','", $idnames) . "')";
4749
            }
4750
4751
            $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...
4752
                    INNER JOIN " . $this->getFullTableName('site_tmplvar_templates') . " tvtpl ON tvtpl.tmplvarid = tv.id
4753
                    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}" : ""));
4754
4755
            $result = $this->db->makeArray($rs);
4756
4757
            // get default/built-in template variables
4758
            if (is_array($docRow)) {
4759
                ksort($docRow);
4760
4761
                foreach ($docRow as $key => $value) {
4762
                    if ($idnames == '*' || in_array($key, $idnames)) {
4763
                        array_push($result, array(
4764
                            'name' => $key,
4765
                            'value' => $value
4766
                        ));
4767
                    }
4768
                }
4769
            }
4770
4771
            $this->tmpCache[__FUNCTION__][$cacheKey] = $result;
4772
4773
            return $result;
4774
        }
4775
    }
4776
4777
    /**
4778
     * getTemplateVarOutput
4779
     * @version 1.0.1 (2014-02-19)
4780
     *
4781
     * @desc Returns an associative array containing TV rendered output values.
4782
     *
4783
     * @param array $idnames {array; '*'}
4784
     * - 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
4785
     * @param string $docid {integer; ''}
4786
     * - Id of a document to get. Default: an empty string which indicates the current document.
4787
     * @param int $published {0; 1; 'all'}
4788
     * - 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.
4789
     * @param string $sep {string}
4790
     * - Separator that is used while concatenating in getTVDisplayFormat(). Default: ''.
4791
     * @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...
4792
     * - Result array, or false.
4793
     */
4794
    public function getTemplateVarOutput($idnames = array(), $docid = '', $published = 1, $sep = '')
4795
    {
4796
        if (is_array($idnames) && empty($idnames)) {
4797
            return false;
4798
        } else {
4799
            $output = array();
4800
            $vars = ($idnames == '*' || is_array($idnames)) ? $idnames : array($idnames);
4801
4802
            $docid = (int)$docid > 0 ? (int)$docid : $this->documentIdentifier;
4803
            // remove sort for speed
4804
            $result = $this->getTemplateVars($vars, '*', $docid, $published, '', '');
4805
4806
            if ($result == false) {
4807
                return false;
4808
            } else {
4809
                $baspath = MODX_MANAGER_PATH . 'includes';
4810
                include_once $baspath . '/tmplvars.format.inc.php';
4811
                include_once $baspath . '/tmplvars.commands.inc.php';
4812
4813
                for ($i = 0; $i < count($result); $i++) {
4814
                    $row = $result[$i];
4815
4816
                    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...
4817
                        $output[$row['name']] = $row['value'];
4818
                    } else {
4819
                        $output[$row['name']] = getTVDisplayFormat($row['name'], $row['value'], $row['display'], $row['display_params'], $row['type'], $docid, $sep);
4820
                    }
4821
                }
4822
4823
                return $output;
4824
            }
4825
        }
4826
    }
4827
4828
    /**
4829
     * Returns the full table name based on db settings
4830
     *
4831
     * @param string $tbl Table name
4832
     * @return string Table name with prefix
4833
     */
4834
    public function getFullTableName($tbl)
4835
    {
4836
        return $this->db->config['dbase'] . ".`" . $this->db->config['table_prefix'] . $tbl . "`";
4837
    }
4838
4839
    /**
4840
     * Returns the placeholder value
4841
     *
4842
     * @param string $name Placeholder name
4843
     * @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...
4844
     */
4845
    public function getPlaceholder($name)
4846
    {
4847
        return isset($this->placeholders[$name]) ? $this->placeholders[$name] : null;
4848
    }
4849
4850
    /**
4851
     * Sets a value for a placeholder
4852
     *
4853
     * @param string $name The name of the placeholder
4854
     * @param string $value The value of the placeholder
4855
     */
4856
    public function setPlaceholder($name, $value)
4857
    {
4858
        $this->placeholders[$name] = $value;
4859
    }
4860
4861
    /**
4862
     * Set placeholders en masse via an array or object.
4863
     *
4864
     * @param object|array $subject
4865
     * @param string $prefix
4866
     */
4867
    public function toPlaceholders($subject, $prefix = '')
4868
    {
4869
        if (is_object($subject)) {
4870
            $subject = get_object_vars($subject);
4871
        }
4872
        if (is_array($subject)) {
4873
            foreach ($subject as $key => $value) {
4874
                $this->toPlaceholder($key, $value, $prefix);
4875
            }
4876
        }
4877
    }
4878
4879
    /**
4880
     * For use by toPlaceholders(); For setting an array or object element as placeholder.
4881
     *
4882
     * @param string $key
4883
     * @param object|array $value
4884
     * @param string $prefix
4885
     */
4886
    public function toPlaceholder($key, $value, $prefix = '')
4887
    {
4888
        if (is_array($value) || is_object($value)) {
4889
            $this->toPlaceholders($value, "{$prefix}{$key}.");
4890
        } else {
4891
            $this->setPlaceholder("{$prefix}{$key}", $value);
4892
        }
4893
    }
4894
4895
    /**
4896
     * Returns the manager relative URL/path with respect to the site root.
4897
     *
4898
     * @global string $base_url
4899
     * @return string The complete URL to the manager folder
4900
     */
4901
    public function getManagerPath()
4902
    {
4903
        return MODX_MANAGER_URL;
4904
    }
4905
4906
    /**
4907
     * Returns the cache relative URL/path with respect to the site root.
4908
     *
4909
     * @global string $base_url
4910
     * @return string The complete URL to the cache folder
4911
     */
4912
    public function getCachePath()
4913
    {
4914
        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...
4915
        $pth = $base_url . $this->getCacheFolder();
4916
        return $pth;
4917
    }
4918
4919
    /**
4920
     * Sends a message to a user's message box.
4921
     *
4922
     * @param string $type Type of the message
4923
     * @param string $to The recipient of the message
4924
     * @param string $from The sender of the message
4925
     * @param string $subject The subject of the message
4926
     * @param string $msg The message body
4927
     * @param int $private Whether it is a private message, or not
4928
     *                     Default : 0
4929
     */
4930
    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...
4931
    {
4932
        $private = ($private) ? 1 : 0;
4933 View Code Duplication
        if (!is_numeric($to)) {
4934
            // Query for the To ID
4935
            $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...
4936
            $to = $this->db->getValue($rs);
4937
        }
4938 View Code Duplication
        if (!is_numeric($from)) {
4939
            // Query for the From ID
4940
            $rs = $this->db->select('id', $this->getFullTableName("manager_users"), "username='{$from}'");
4941
            $from = $this->db->getValue($rs);
4942
        }
4943
        // insert a new message into user_messages
4944
        $this->db->insert(array(
4945
            'type' => $type,
4946
            'subject' => $subject,
4947
            'message' => $msg,
4948
            'sender' => $from,
4949
            'recipient' => $to,
4950
            'private' => $private,
4951
            'postdate' => $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'],
4952
            'messageread' => 0,
4953
        ), $this->getFullTableName('user_messages'));
4954
    }
4955
4956
    /**
4957
     * Returns current user id.
4958
     *
4959
     * @param string $context . Default is an empty string which indicates the method should automatically pick 'web (frontend) or 'mgr' (backend)
4960
     * @return string
4961
     */
4962 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...
4963
    {
4964
        $out = false;
4965
4966
        if (!empty($context)) {
4967
            if (is_scalar($context) && isset($_SESSION[$context . 'Validated'])) {
4968
                $out = $_SESSION[$context . 'InternalKey'];
4969
            }
4970
        } else {
4971
            switch (true) {
4972
                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...
4973
                    $out = $_SESSION['webInternalKey'];
4974
                    break;
4975
                }
4976
                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...
4977
                    $out = $_SESSION['mgrInternalKey'];
4978
                    break;
4979
                }
4980
            }
4981
        }
4982
        return $out;
4983
    }
4984
4985
    /**
4986
     * Returns current user name
4987
     *
4988
     * @param string $context . Default is an empty string which indicates the method should automatically pick 'web (frontend) or 'mgr' (backend)
4989
     * @return string
4990
     */
4991 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...
4992
    {
4993
        $out = false;
4994
4995
        if (!empty($context)) {
4996
            if (is_scalar($context) && isset($_SESSION[$context . 'Validated'])) {
4997
                $out = $_SESSION[$context . 'Shortname'];
4998
            }
4999
        } else {
5000
            switch (true) {
5001
                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...
5002
                    $out = $_SESSION['webShortname'];
5003
                    break;
5004
                }
5005
                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...
5006
                    $out = $_SESSION['mgrShortname'];
5007
                    break;
5008
                }
5009
            }
5010
        }
5011
        return $out;
5012
    }
5013
5014
    /**
5015
     * Returns current login user type - web or manager
5016
     *
5017
     * @return string
5018
     */
5019
    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...
5020
    {
5021
        if ($this->isFrontend() && isset($_SESSION['webValidated'])) {
5022
            return 'web';
5023
        } elseif ($this->isBackend() && isset($_SESSION['mgrValidated'])) {
5024
            return 'manager';
5025
        } else {
5026
            return '';
5027
        }
5028
    }
5029
5030
    /**
5031
     * Returns a user info record for the given manager user
5032
     *
5033
     * @param int $uid
5034
     * @return boolean|string
5035
     */
5036
    public function getUserInfo($uid)
5037
    {
5038
        if (isset($this->tmpCache[__FUNCTION__][$uid])) {
5039
            return $this->tmpCache[__FUNCTION__][$uid];
5040
        }
5041
5042
        $from = '[+prefix+]manager_users mu INNER JOIN [+prefix+]user_attributes mua ON mua.internalkey=mu.id';
5043
        $where = sprintf("mu.id='%s'", $this->db->escape($uid));
5044
        $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...
5045
5046
        if (!$this->db->getRecordCount($rs)) {
5047
            return $this->tmpCache[__FUNCTION__][$uid] = false;
5048
        }
5049
5050
        $row = $this->db->getRow($rs);
5051 View Code Duplication
        if (!isset($row['usertype']) || !$row['usertype']) {
5052
            $row['usertype'] = 'manager';
5053
        }
5054
5055
        $this->tmpCache[__FUNCTION__][$uid] = $row;
5056
5057
        return $row;
5058
    }
5059
5060
    /**
5061
     * Returns a record for the web user
5062
     *
5063
     * @param int $uid
5064
     * @return boolean|string
5065
     */
5066
    public function getWebUserInfo($uid)
5067
    {
5068
        $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...
5069
                INNER JOIN " . $this->getFullTableName("web_user_attributes") . " wua ON wua.internalkey=wu.id", "wu.id='{$uid}'");
5070
        if ($row = $this->db->getRow($rs)) {
5071 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...
5072
                $row["usertype"] = "web";
5073
            }
5074
            return $row;
5075
        }
5076
    }
5077
5078
    /**
5079
     * Returns an array of document groups that current user is assigned to.
5080
     * This function will first return the web user doc groups when running from
5081
     * frontend otherwise it will return manager user's docgroup.
5082
     *
5083
     * @param boolean $resolveIds Set to true to return the document group names
5084
     *                            Default: false
5085
     * @return string|array
5086
     */
5087
    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...
5088
    {
5089
        if ($this->isFrontend() && isset($_SESSION['webDocgroups']) && isset($_SESSION['webValidated'])) {
5090
            $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...
5091
            $dgn = isset($_SESSION['webDocgrpNames']) ? $_SESSION['webDocgrpNames'] : false;
5092
        } elseif ($this->isBackend() && isset($_SESSION['mgrDocgroups']) && isset($_SESSION['mgrValidated'])) {
5093
            $dg = $_SESSION['mgrDocgroups'];
5094
            $dgn = isset($_SESSION['mgrDocgrpNames']) ? $_SESSION['mgrDocgrpNames'] : false;
5095
        } else {
5096
            $dg = '';
5097
        }
5098
        if (!$resolveIds) {
5099
            return $dg;
5100
        } elseif (is_array($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...
5101
            return $dgn;
5102
        } elseif (is_array($dg)) {
5103
            // resolve ids to names
5104
            $dgn = array();
5105
            $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...
5106
            while ($row = $this->db->getRow($ds)) {
5107
                $dgn[] = $row['name'];
5108
            }
5109
            // cache docgroup names to session
5110
            if ($this->isFrontend()) {
5111
                $_SESSION['webDocgrpNames'] = $dgn;
5112
            } else {
5113
                $_SESSION['mgrDocgrpNames'] = $dgn;
5114
            }
5115
            return $dgn;
5116
        }
5117
    }
5118
5119
    /**
5120
     * Change current web user's password
5121
     *
5122
     * @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...
5123
     * @param string $oldPwd
5124
     * @param string $newPwd
5125
     * @return string|boolean Returns true if successful, oterhwise return error
5126
     *                        message
5127
     */
5128
    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...
5129
    {
5130
        $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...
5131
        if ($_SESSION["webValidated"] == 1) {
5132
            $tbl = $this->getFullTableName("web_users");
5133
            $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...
5134
            if ($row = $this->db->getRow($ds)) {
5135
                if ($row["password"] == md5($oldPwd)) {
5136
                    if (strlen($newPwd) < 6) {
5137
                        return "Password is too short!";
5138
                    } elseif ($newPwd == "") {
5139
                        return "You didn't specify a password for this user!";
5140
                    } else {
5141
                        $this->db->update(array(
5142
                            'password' => $this->db->escape($newPwd),
5143
                        ), $tbl, "id='" . $this->getLoginUserID() . "'");
5144
                        // invoke OnWebChangePassword event
5145
                        $this->invokeEvent("OnWebChangePassword", array(
5146
                            "userid" => $row["id"],
5147
                            "username" => $row["username"],
5148
                            "userpassword" => $newPwd
5149
                        ));
5150
                        return true;
5151
                    }
5152
                } else {
5153
                    return "Incorrect password.";
5154
                }
5155
            }
5156
        }
5157
        return $rt;
5158
    }
5159
5160
    /**
5161
     * Returns true if the current web user is a member the specified groups
5162
     *
5163
     * @param array $groupNames
5164
     * @return boolean
5165
     */
5166
    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...
5167
    {
5168
        if (!is_array($groupNames)) {
5169
            return false;
5170
        }
5171
        // check cache
5172
        $grpNames = isset($_SESSION['webUserGroupNames']) ? $_SESSION['webUserGroupNames'] : false;
5173
        if (!is_array($grpNames)) {
5174
            $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...
5175
                    INNER JOIN " . $this->getFullTableName("web_groups") . " wg ON wg.webgroup=wgn.id AND wg.webuser='" . $this->getLoginUserID() . "'");
5176
            $grpNames = $this->db->getColumn("name", $rs);
5177
            // save to cache
5178
            $_SESSION['webUserGroupNames'] = $grpNames;
5179
        }
5180
        foreach ($groupNames as $k => $v) {
5181
            if (in_array(trim($v), $grpNames)) {
5182
                return true;
5183
            }
5184
        }
5185
        return false;
5186
    }
5187
5188
    /**
5189
     * Registers Client-side CSS scripts - these scripts are loaded at inside
5190
     * the <head> tag
5191
     *
5192
     * @param string $src
5193
     * @param string $media Default: Empty string
5194
     * @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...
5195
     */
5196
    public function regClientCSS($src, $media = '')
5197
    {
5198
        if (empty($src) || isset($this->loadedjscripts[$src])) {
5199
            return '';
5200
        }
5201
        $nextpos = max(array_merge(array(0), array_keys($this->sjscripts))) + 1;
5202
        $this->loadedjscripts[$src]['startup'] = true;
5203
        $this->loadedjscripts[$src]['version'] = '0';
5204
        $this->loadedjscripts[$src]['pos'] = $nextpos;
5205
        if (strpos(strtolower($src), "<style") !== false || strpos(strtolower($src), "<link") !== false) {
5206
            $this->sjscripts[$nextpos] = $src;
5207
        } else {
5208
            $this->sjscripts[$nextpos] = "\t" . '<link rel="stylesheet" type="text/css" href="' . $src . '" ' . ($media ? 'media="' . $media . '" ' : '') . '/>';
5209
        }
5210
    }
5211
5212
    /**
5213
     * Registers Startup Client-side JavaScript - these scripts are loaded at inside the <head> tag
5214
     *
5215
     * @param string $src
5216
     * @param array $options Default: 'name'=>'', 'version'=>'0', 'plaintext'=>false
5217
     */
5218
    public function regClientStartupScript($src, $options = array('name' => '', 'version' => '0', 'plaintext' => false))
5219
    {
5220
        $this->regClientScript($src, $options, true);
5221
    }
5222
5223
    /**
5224
     * Registers Client-side JavaScript these scripts are loaded at the end of the page unless $startup is true
5225
     *
5226
     * @param string $src
5227
     * @param array $options Default: 'name'=>'', 'version'=>'0', 'plaintext'=>false
5228
     * @param boolean $startup Default: false
5229
     * @return string
5230
     */
5231
    public function regClientScript($src, $options = array('name' => '', 'version' => '0', 'plaintext' => false), $startup = false)
5232
    {
5233
        if (empty($src)) {
5234
            return '';
5235
        } // nothing to register
5236
        if (!is_array($options)) {
5237
            if (is_bool($options)) {  // backward compatibility with old plaintext parameter
5238
5239
                $options = array('plaintext' => $options);
5240
            } elseif (is_string($options)) { // Also allow script name as 2nd param
5241
5242
                $options = array('name' => $options);
5243
            } else {
5244
                $options = array();
5245
            }
5246
        }
5247
        $name = isset($options['name']) ? strtolower($options['name']) : '';
5248
        $version = isset($options['version']) ? $options['version'] : '0';
5249
        $plaintext = isset($options['plaintext']) ? $options['plaintext'] : false;
5250
        $key = !empty($name) ? $name : $src;
5251
        unset($overwritepos); // probably unnecessary--just making sure
5252
5253
        $useThisVer = true;
5254
        if (isset($this->loadedjscripts[$key])) { // a matching script was found
5255
            // if existing script is a startup script, make sure the candidate is also a startup script
5256
            if ($this->loadedjscripts[$key]['startup']) {
5257
                $startup = true;
5258
            }
5259
5260
            if (empty($name)) {
5261
                $useThisVer = false; // if the match was based on identical source code, no need to replace the old one
5262
            } else {
5263
                $useThisVer = version_compare($this->loadedjscripts[$key]['version'], $version, '<');
5264
            }
5265
5266
            if ($useThisVer) {
5267
                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...
5268
                    // remove old script from the bottom of the page (new one will be at the top)
5269
                    unset($this->jscripts[$this->loadedjscripts[$key]['pos']]);
5270
                } else {
5271
                    // overwrite the old script (the position may be important for dependent scripts)
5272
                    $overwritepos = $this->loadedjscripts[$key]['pos'];
5273
                }
5274
            } else { // Use the original version
5275
                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...
5276
                    // need to move the exisiting script to the head
5277
                    $version = $this->loadedjscripts[$key][$version];
5278
                    $src = $this->jscripts[$this->loadedjscripts[$key]['pos']];
5279
                    unset($this->jscripts[$this->loadedjscripts[$key]['pos']]);
5280
                } else {
5281
                    return ''; // the script is already in the right place
5282
                }
5283
            }
5284
        }
5285
5286
        if ($useThisVer && $plaintext != true && (strpos(strtolower($src), "<script") === false)) {
5287
            $src = "\t" . '<script type="text/javascript" src="' . $src . '"></script>';
5288
        }
5289
        if ($startup) {
5290
            $pos = isset($overwritepos) ? $overwritepos : max(array_merge(array(0), array_keys($this->sjscripts))) + 1;
5291
            $this->sjscripts[$pos] = $src;
5292
        } else {
5293
            $pos = isset($overwritepos) ? $overwritepos : max(array_merge(array(0), array_keys($this->jscripts))) + 1;
5294
            $this->jscripts[$pos] = $src;
5295
        }
5296
        $this->loadedjscripts[$key]['version'] = $version;
5297
        $this->loadedjscripts[$key]['startup'] = $startup;
5298
        $this->loadedjscripts[$key]['pos'] = $pos;
5299
        return '';
5300
    }
5301
5302
    /**
5303
     * Returns all registered JavaScripts
5304
     *
5305
     * @return string
5306
     */
5307
    public function regClientStartupHTMLBlock($html)
5308
    {
5309
        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...
5310
    }
5311
5312
    /**
5313
     * Returns all registered startup scripts
5314
     *
5315
     * @return string
5316
     */
5317
    public function regClientHTMLBlock($html)
5318
    {
5319
        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...
5320
    }
5321
5322
    /**
5323
     * Remove unwanted html tags and snippet, settings and tags
5324
     *
5325
     * @param string $html
5326
     * @param string $allowed Default: Empty string
5327
     * @return string
5328
     */
5329
    public function stripTags($html, $allowed = "")
5330
    {
5331
        $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...
5332
        $t = preg_replace('~\[\*(.*?)\*\]~', "", $t); //tv
5333
        $t = preg_replace('~\[\[(.*?)\]\]~', "", $t); //snippet
5334
        $t = preg_replace('~\[\!(.*?)\!\]~', "", $t); //snippet
5335
        $t = preg_replace('~\[\((.*?)\)\]~', "", $t); //settings
5336
        $t = preg_replace('~\[\+(.*?)\+\]~', "", $t); //placeholders
5337
        $t = preg_replace('~{{(.*?)}}~', "", $t); //chunks
5338
        return $t;
5339
    }
5340
5341
    /**
5342
     * Add an event listener to a plugin - only for use within the current execution cycle
5343
     *
5344
     * @param string $evtName
5345
     * @param string $pluginName
5346
     * @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...
5347
     */
5348
    public function addEventListener($evtName, $pluginName)
5349
    {
5350
        if (!$evtName || !$pluginName) {
5351
            return false;
5352
        }
5353
        if (!array_key_exists($evtName, $this->pluginEvent)) {
5354
            $this->pluginEvent[$evtName] = array();
5355
        }
5356
        return array_push($this->pluginEvent[$evtName], $pluginName); // return array count
5357
    }
5358
5359
    /**
5360
     * Remove event listener - only for use within the current execution cycle
5361
     *
5362
     * @param string $evtName
5363
     * @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...
5364
     */
5365
    public function removeEventListener($evtName)
5366
    {
5367
        if (!$evtName) {
5368
            return false;
5369
        }
5370
        unset($this->pluginEvent[$evtName]);
5371
    }
5372
5373
    /**
5374
     * Remove all event listeners - only for use within the current execution cycle
5375
     */
5376
    public function removeAllEventListener()
5377
    {
5378
        unset($this->pluginEvent);
5379
        $this->pluginEvent = array();
5380
    }
5381
5382
    /**
5383
     * Invoke an event.
5384
     *
5385
     * @param string $evtName
5386
     * @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.
5387
     * @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...
5388
     */
5389
    public function invokeEvent($evtName, $extParams = array())
5390
    {
5391
        if (!$evtName) {
5392
            return false;
5393
        }
5394
        if (!isset($this->pluginEvent[$evtName])) {
5395
            return false;
5396
        }
5397
5398
        $results = null;
5399
        foreach ($this->pluginEvent[$evtName] as $pluginName) { // start for loop
5400
            if ($this->dumpPlugins) {
5401
                $eventtime = $this->getMicroTime();
5402
            }
5403
            // reset event object
5404
            $e = &$this->event;
5405
            $e->_resetEventObject();
5406
            $e->name = $evtName;
5407
            $e->activePlugin = $pluginName;
5408
5409
            // get plugin code
5410
            $_ = $this->getPluginCode($pluginName);
5411
            $pluginCode = $_['code'];
5412
            $pluginProperties = $_['props'];
5413
5414
            // load default params/properties
5415
            $parameter = $this->parseProperties($pluginProperties);
5416
            if (!is_array($parameter)) {
5417
                $parameter = array();
5418
            }
5419
            if (!empty($extParams)) {
5420
                $parameter = array_merge($parameter, $extParams);
5421
            }
5422
5423
            // eval plugin
5424
            $this->evalPlugin($pluginCode, $parameter);
5425
5426
            if (class_exists('PHxParser')) {
5427
                $this->config['enable_filter'] = 0;
5428
            }
5429
5430
            if ($this->dumpPlugins) {
5431
                $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...
5432
                $this->pluginsCode .= sprintf('<fieldset><legend><b>%s / %s</b> (%2.2f ms)</legend>', $evtName, $pluginName, $eventtime * 1000);
5433
                foreach ($parameter as $k => $v) {
5434
                    $this->pluginsCode .= "{$k} => " . print_r($v, true) . '<br>';
5435
                }
5436
                $this->pluginsCode .= '</fieldset><br />';
5437
                $this->pluginsTime["{$evtName} / {$pluginName}"] += $eventtime;
5438
            }
5439
            if ($e->_output != '') {
5440
                $results[] = $e->_output;
5441
            }
5442
            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...
5443
                break;
5444
            }
5445
        }
5446
5447
        $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...
5448
        return $results;
5449
    }
5450
5451
    /**
5452
     * Returns plugin-code and properties
5453
     *
5454
     * @param string $pluginName
5455
     * @return array Associative array consisting of 'code' and 'props'
5456
     */
5457
    public function getPluginCode($pluginName)
5458
    {
5459
        $plugin = array();
5460
        if (isset($this->pluginCache[$pluginName])) {
5461
            $pluginCode = $this->pluginCache[$pluginName];
5462
            $pluginProperties = isset($this->pluginCache[$pluginName . "Props"]) ? $this->pluginCache[$pluginName . "Props"] : '';
5463
        } else {
5464
            $pluginName = $this->db->escape($pluginName);
5465
            $result = $this->db->select('name, plugincode, properties', $this->getFullTableName("site_plugins"), "name='{$pluginName}' AND disabled=0");
5466
            if ($row = $this->db->getRow($result)) {
5467
                $pluginCode = $this->pluginCache[$row['name']] = $row['plugincode'];
5468
                $pluginProperties = $this->pluginCache[$row['name'] . "Props"] = $row['properties'];
5469
            } else {
5470
                $pluginCode = $this->pluginCache[$pluginName] = "return false;";
5471
                $pluginProperties = '';
5472
            }
5473
        }
5474
        $plugin['code'] = $pluginCode;
5475
        $plugin['props'] = $pluginProperties;
5476
5477
        return $plugin;
5478
    }
5479
5480
    /**
5481
     * Parses a resource property string and returns the result as an array
5482
     *
5483
     * @param string $propertyString
5484
     * @param string|null $elementName
5485
     * @param string|null $elementType
5486
     * @return array Associative array in the form property name => property value
5487
     */
5488
    public function parseProperties($propertyString, $elementName = null, $elementType = null)
5489
    {
5490
        $propertyString = trim($propertyString);
5491
        $propertyString = str_replace('{}', '', $propertyString);
5492
        $propertyString = str_replace('} {', ',', $propertyString);
5493
        $property = array();
5494
        if (!empty($propertyString) && $propertyString != '{}') {
5495
            $jsonFormat = $this->isJson($propertyString, true);
5496
            // old format
5497
            if ($jsonFormat === false) {
5498
                $props = explode('&', $propertyString);
5499
                foreach ($props as $prop) {
5500
                    if (empty($prop)) {
5501
                        continue;
5502
                    } elseif (strpos($prop, '=') === false) {
5503
                        $property[trim($prop)] = '';
5504
                        continue;
5505
                    }
5506
5507
                    $_ = explode('=', $prop, 2);
5508
                    $key = trim($_[0]);
5509
                    $p = explode(';', trim($_[1]));
5510
                    switch ($p[1]) {
5511
                        case 'list':
5512
                        case 'list-multi':
5513
                        case 'checkbox':
5514
                        case 'radio':
5515
                            $value = !isset($p[3]) ? '' : $p[3];
5516
                            break;
5517
                        default:
5518
                            $value = !isset($p[2]) ? '' : $p[2];
5519
                    }
5520
                    if (!empty($key)) {
5521
                        $property[$key] = $value;
5522
                    }
5523
                }
5524
                // new json-format
5525
            } elseif (!empty($jsonFormat)) {
5526
                foreach ($jsonFormat as $key => $row) {
5527
                    if (!empty($key)) {
5528
                        if (is_array($row)) {
5529
                            if (isset($row[0]['value'])) {
5530
                                $value = $row[0]['value'];
5531
                            }
5532
                        } else {
5533
                            $value = $row;
5534
                        }
5535
                        if (isset($value) && $value !== '') {
5536
                            $property[$key] = $value;
5537
                        }
5538
                    }
5539
                }
5540
            }
5541
        }
5542
        if (!empty($elementName) && !empty($elementType)) {
5543
            $out = $this->invokeEvent('OnParseProperties', array(
5544
                'element' => $elementName,
5545
                'type' => $elementType,
5546
                'args' => $property
5547
            ));
5548
            if (is_array($out)) {
5549
                $out = array_pop($out);
5550
            }
5551
            if (is_array($out)) {
5552
                $property = $out;
5553
            }
5554
        }
5555
5556
        return $property;
5557
    }
5558
5559
    /**
5560
     * Parses docBlock from a file and returns the result as an array
5561
     *
5562
     * @param string $element_dir
5563
     * @param string $filename
5564
     * @param boolean $escapeValues
5565
     * @return array Associative array in the form property name => property value
5566
     */
5567
    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...
5568
    {
5569
        $params = array();
5570
        $fullpath = $element_dir . '/' . $filename;
5571
        if (is_readable($fullpath)) {
5572
            $tpl = @fopen($fullpath, "r");
5573
            if ($tpl) {
5574
                $params['filename'] = $filename;
5575
                $docblock_start_found = false;
5576
                $name_found = false;
5577
                $description_found = false;
5578
                $docblock_end_found = false;
5579
                $arrayParams = array('author', 'documentation', 'reportissues', 'link');
5580
5581
                while (!feof($tpl)) {
5582
                    $line = fgets($tpl);
5583
                    $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...
5584
                    $docblock_start_found = $r['docblock_start_found'];
5585
                    $name_found = $r['name_found'];
5586
                    $description_found = $r['description_found'];
5587
                    $docblock_end_found = $r['docblock_end_found'];
5588
                    $param = $r['param'];
5589
                    $val = $r['val'];
5590
                    if (!$docblock_end_found) {
5591
                        break;
5592
                    }
5593
                    if (!$docblock_start_found || !$name_found || !$description_found || empty($param)) {
5594
                        continue;
5595
                    }
5596 View Code Duplication
                    if (!empty($param)) {
5597
                        if (in_array($param, $arrayParams)) {
5598
                            if (!isset($params[$param])) {
5599
                                $params[$param] = array();
5600
                            }
5601
                            $params[$param][] = $escapeValues ? $this->db->escape($val) : $val;
5602
                        } else {
5603
                            $params[$param] = $escapeValues ? $this->db->escape($val) : $val;
5604
                        }
5605
                    }
5606
                }
5607
                @fclose($tpl);
5608
            }
5609
        }
5610
        return $params;
5611
    }
5612
5613
    /**
5614
     * Parses docBlock from string and returns the result as an array
5615
     *
5616
     * @param string $string
5617
     * @param boolean $escapeValues
5618
     * @return array Associative array in the form property name => property value
5619
     */
5620
    public function parseDocBlockFromString($string, $escapeValues = false)
5621
    {
5622
        $params = array();
5623
        if (!empty($string)) {
5624
            $string = str_replace('\r\n', '\n', $string);
5625
            $exp = explode('\n', $string);
5626
            $docblock_start_found = false;
5627
            $name_found = false;
5628
            $description_found = false;
5629
            $docblock_end_found = false;
5630
            $arrayParams = array('author', 'documentation', 'reportissues', 'link');
5631
5632
            foreach ($exp as $line) {
5633
                $r = $this->parseDocBlockLine($line, $docblock_start_found, $name_found, $description_found, $docblock_end_found);
5634
                $docblock_start_found = $r['docblock_start_found'];
5635
                $name_found = $r['name_found'];
5636
                $description_found = $r['description_found'];
5637
                $docblock_end_found = $r['docblock_end_found'];
5638
                $param = $r['param'];
5639
                $val = $r['val'];
5640
                if (!$docblock_start_found) {
5641
                    continue;
5642
                }
5643
                if ($docblock_end_found) {
5644
                    break;
5645
                }
5646 View Code Duplication
                if (!empty($param)) {
5647
                    if (in_array($param, $arrayParams)) {
5648
                        if (!isset($params[$param])) {
5649
                            $params[$param] = array();
5650
                        }
5651
                        $params[$param][] = $escapeValues ? $this->db->escape($val) : $val;
5652
                    } else {
5653
                        $params[$param] = $escapeValues ? $this->db->escape($val) : $val;
5654
                    }
5655
                }
5656
            }
5657
        }
5658
        return $params;
5659
    }
5660
5661
    /**
5662
     * Parses docBlock of a component´s source-code and returns the result as an array
5663
     * (modified parseDocBlock() from modules/stores/setup.info.php by Bumkaka & Dmi3yy)
5664
     *
5665
     * @param string $line
5666
     * @param boolean $docblock_start_found
5667
     * @param boolean $name_found
5668
     * @param boolean $description_found
5669
     * @param boolean $docblock_end_found
5670
     * @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...
5671
     */
5672
    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...
5673
    {
5674
        $param = '';
5675
        $val = '';
5676
        $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...
5677
        if (!$docblock_start_found) {
5678
            // find docblock start
5679
            if (strpos($line, '/**') !== false) {
5680
                $docblock_start_found = true;
5681
            }
5682
        } elseif (!$name_found) {
5683
            // find name
5684
            if (preg_match("/^\s+\*\s+(.+)/", $line, $ma)) {
5685
                $param = 'name';
5686
                $val = trim($ma[1]);
5687
                $name_found = !empty($val);
5688
            }
5689
        } elseif (!$description_found) {
5690
            // find description
5691
            if (preg_match("/^\s+\*\s+(.+)/", $line, $ma)) {
5692
                $param = 'description';
5693
                $val = trim($ma[1]);
5694
                $description_found = !empty($val);
5695
            }
5696
        } else {
5697
            if (preg_match("/^\s+\*\s+\@([^\s]+)\s+(.+)/", $line, $ma)) {
5698
                $param = trim($ma[1]);
5699
                $val = trim($ma[2]);
5700
                if (!empty($param) && !empty($val)) {
5701
                    if ($param == 'internal') {
5702
                        $ma = null;
5703
                        if (preg_match("/\@([^\s]+)\s+(.+)/", $val, $ma)) {
5704
                            $param = trim($ma[1]);
5705
                            $val = trim($ma[2]);
5706
                        }
5707
                    }
5708
                }
5709
            } elseif (preg_match("/^\s*\*\/\s*$/", $line)) {
5710
                $docblock_end_found = true;
5711
            }
5712
        }
5713
        return array(
5714
            'docblock_start_found' => $docblock_start_found,
5715
            'name_found' => $name_found,
5716
            'description_found' => $description_found,
5717
            'docblock_end_found' => $docblock_end_found,
5718
            'param' => $param,
5719
            'val' => $val
5720
        );
5721
    }
5722
5723
    /**
5724
     * Renders docBlock-parameters into human readable list
5725
     *
5726
     * @param array $parsed
5727
     * @return string List in HTML-format
5728
     */
5729
    public function convertDocBlockIntoList($parsed)
5730
    {
5731
        global $_lang;
5732
5733
        // Replace special placeholders & make URLs + Emails clickable
5734
        $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...
5735
        $regexUrl = "/((http|https|ftp|ftps)\:\/\/[^\/]+(\/[^\s]+[^,.?!:;\s])?)/";
5736
        $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';
5737
        $emailSubject = isset($parsed['name']) ? '?subject=' . $parsed['name'] : '';
5738
        $emailSubject .= isset($parsed['version']) ? ' v' . $parsed['version'] : '';
5739
        foreach ($parsed as $key => $val) {
5740
            if (is_array($val)) {
5741
                foreach ($val as $key2 => $val2) {
5742
                    $val2 = $this->parseText($val2, $ph);
5743 View Code Duplication
                    if (preg_match($regexUrl, $val2, $url)) {
5744
                        $val2 = preg_replace($regexUrl, "<a href=\"{$url[0]}\" target=\"_blank\">{$url[0]}</a> ", $val2);
5745
                    }
5746 View Code Duplication
                    if (preg_match($regexEmail, $val2, $url)) {
5747
                        $val2 = preg_replace($regexEmail, '<a href="mailto:\\1' . $emailSubject . '">\\1</a>', $val2);
5748
                    }
5749
                    $parsed[$key][$key2] = $val2;
5750
                }
5751
            } else {
5752
                $val = $this->parseText($val, $ph);
5753 View Code Duplication
                if (preg_match($regexUrl, $val, $url)) {
5754
                    $val = preg_replace($regexUrl, "<a href=\"{$url[0]}\" target=\"_blank\">{$url[0]}</a> ", $val);
5755
                }
5756 View Code Duplication
                if (preg_match($regexEmail, $val, $url)) {
5757
                    $val = preg_replace($regexEmail, '<a href="mailto:\\1' . $emailSubject . '">\\1</a>', $val);
5758
                }
5759
                $parsed[$key] = $val;
5760
            }
5761
        }
5762
5763
        $arrayParams = array(
5764
            'documentation' => $_lang['documentation'],
5765
            'reportissues' => $_lang['report_issues'],
5766
            'link' => $_lang['further_info'],
5767
            'author' => $_lang['author_infos']
5768
        );
5769
5770
        $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...
5771
        $list = isset($parsed['logo']) ? '<img src="' . $this->config['base_url'] . ltrim($parsed['logo'], "/") . '" style="float:right;max-width:100px;height:auto;" />' . $nl : '';
5772
        $list .= '<p>' . $nl;
5773
        $list .= isset($parsed['name']) ? '<strong>' . $parsed['name'] . '</strong><br/>' . $nl : '';
5774
        $list .= isset($parsed['description']) ? $parsed['description'] . $nl : '';
5775
        $list .= '</p><br/>' . $nl;
5776
        $list .= isset($parsed['version']) ? '<p><strong>' . $_lang['version'] . ':</strong> ' . $parsed['version'] . '</p>' . $nl : '';
5777
        $list .= isset($parsed['license']) ? '<p><strong>' . $_lang['license'] . ':</strong> ' . $parsed['license'] . '</p>' . $nl : '';
5778
        $list .= isset($parsed['lastupdate']) ? '<p><strong>' . $_lang['last_update'] . ':</strong> ' . $parsed['lastupdate'] . '</p>' . $nl : '';
5779
        $list .= '<br/>' . $nl;
5780
        $first = true;
5781
        foreach ($arrayParams as $param => $label) {
5782
            if (isset($parsed[$param])) {
5783
                if ($first) {
5784
                    $list .= '<p><strong>' . $_lang['references'] . '</strong></p>' . $nl;
5785
                    $list .= '<ul class="docBlockList">' . $nl;
5786
                    $first = false;
5787
                }
5788
                $list .= '    <li><strong>' . $label . '</strong>' . $nl;
5789
                $list .= '        <ul>' . $nl;
5790
                foreach ($parsed[$param] as $val) {
5791
                    $list .= '            <li>' . $val . '</li>' . $nl;
5792
                }
5793
                $list .= '        </ul></li>' . $nl;
5794
            }
5795
        }
5796
        $list .= !$first ? '</ul>' . $nl : '';
5797
5798
        return $list;
5799
    }
5800
5801
    /**
5802
     * @param string $string
5803
     * @return string
5804
     */
5805
    public function removeSanitizeSeed($string = '')
5806
    {
5807
        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...
5808
5809
        if (!$string || strpos($string, $sanitize_seed) === false) {
5810
            return $string;
5811
        }
5812
5813
        return str_replace($sanitize_seed, '', $string);
5814
    }
5815
5816
    /**
5817
     * @param string $content
5818
     * @return string
5819
     */
5820
    public function cleanUpMODXTags($content = '')
5821
    {
5822
        if ($this->minParserPasses < 1) {
5823
            return $content;
5824
        }
5825
5826
        $enable_filter = $this->config['enable_filter'];
5827
        $this->config['enable_filter'] = 1;
5828
        $_ = 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...
5829
        foreach ($_ as $brackets) {
5830
            list($left, $right) = explode(' ', $brackets);
5831
            if (strpos($content, $left) !== false) {
5832
                if ($left === '[*') {
5833
                    $content = $this->mergeDocumentContent($content);
5834
                } elseif ($left === '[(') {
5835
                    $content = $this->mergeSettingsContent($content);
5836
                } elseif ($left === '{{') {
5837
                    $content = $this->mergeChunkContent($content);
5838
                } elseif ($left === '[[') {
5839
                    $content = $this->evalSnippets($content);
5840
                }
5841
            }
5842
        }
5843
        foreach ($_ as $brackets) {
5844
            list($left, $right) = explode(' ', $brackets);
5845
            if (strpos($content, $left) !== false) {
5846
                $matches = $this->getTagsFromContent($content, $left, $right);
5847
                $content = str_replace($matches[0], '', $content);
5848
            }
5849
        }
5850
        $this->config['enable_filter'] = $enable_filter;
5851
        return $content;
5852
    }
5853
5854
    /**
5855
     * @param string $str
5856
     * @param string $allowable_tags
5857
     * @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...
5858
     */
5859
    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...
5860
    {
5861
        $str = strip_tags($str, $allowable_tags);
5862
        modx_sanitize_gpc($str);
5863
        return $str;
5864
    }
5865
5866
    /**
5867
     * @param string $name
5868
     * @param string $phpCode
5869
     */
5870
    public function addSnippet($name, $phpCode)
5871
    {
5872
        $this->snippetCache['#' . $name] = $phpCode;
5873
    }
5874
5875
    /**
5876
     * @param string $name
5877
     * @param string $text
5878
     */
5879
    public function addChunk($name, $text)
5880
    {
5881
        $this->chunkCache['#' . $name] = $text;
5882
    }
5883
5884
    /**
5885
     * @param string $phpcode
5886
     * @param string $evalmode
5887
     * @param string $safe_functions
5888
     * @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...
5889
     */
5890
    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...
5891
    {
5892
        if ($evalmode == '') {
5893
            $evalmode = $this->config['allow_eval'];
5894
        }
5895
        if ($safe_functions == '') {
5896
            $safe_functions = $this->config['safe_functions_at_eval'];
5897
        }
5898
5899
        modx_sanitize_gpc($phpcode);
5900
5901
        switch ($evalmode) {
5902
            case 'with_scan':
5903
                $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...
5904
                break;
5905
            case 'with_scan_at_post':
5906
                $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...
5907
                break;
5908
            case 'everytime_eval':
5909
                $isSafe = true;
5910
                break; // Should debug only
5911
            case 'dont_eval':
5912
            default:
5913
                return $phpcode;
5914
        }
5915
5916
        if (!$isSafe) {
5917
            $msg = $phpcode . "\n" . $this->currentSnippet . "\n" . print_r($_SERVER, true);
5918
            $title = sprintf('Unknown eval was executed (%s)', $this->htmlspecialchars(substr(trim($phpcode), 0, 50)));
5919
            $this->messageQuit($title, '', true, '', '', 'Parser', $msg);
5920
            return;
5921
        }
5922
5923
        ob_start();
5924
        $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...
5925
        $echo = ob_get_clean();
5926
5927
        if (is_array($return)) {
5928
            return 'array()';
5929
        }
5930
5931
        $output = $echo . $return;
5932
        modx_sanitize_gpc($output);
5933
        return $this->htmlspecialchars($output); // Maybe, all html tags are dangerous
5934
    }
5935
5936
    /**
5937
     * @param string $phpcode
5938
     * @param string $safe_functions
5939
     * @return bool
5940
     */
5941
    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...
5942
    { // 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...
5943
        if ($safe_functions == '') {
5944
            return false;
5945
        }
5946
5947
        $safe = explode(',', $safe_functions);
5948
5949
        $phpcode = rtrim($phpcode, ';') . ';';
5950
        $tokens = token_get_all('<?php ' . $phpcode);
5951
        foreach ($tokens as $i => $token) {
5952
            if (!is_array($token)) {
5953
                continue;
5954
            }
5955
            $tokens[$i]['token_name'] = token_name($token[0]);
5956
        }
5957
        foreach ($tokens as $token) {
5958
            if (!is_array($token)) {
5959
                continue;
5960
            }
5961
            switch ($token['token_name']) {
5962
                case 'T_STRING':
5963
                    if (!in_array($token[1], $safe)) {
5964
                        return false;
5965
                    }
5966
                    break;
5967
                case 'T_VARIABLE':
5968
                    if ($token[1] == '$GLOBALS') {
5969
                        return false;
5970
                    }
5971
                    break;
5972
                case 'T_EVAL':
5973
                    return false;
5974
            }
5975
        }
5976
        return true;
5977
    }
5978
5979
    /**
5980
     * @param string $str
5981
     * @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...
5982
     */
5983
    public function atBindFileContent($str = '')
5984
    {
5985
        $search_path = array('assets/tvs/', 'assets/chunks/', 'assets/templates/', $this->config['rb_base_url'] . 'files/', '');
5986
5987
        if (stripos($str, '@FILE') !== 0) {
5988
            return $str;
5989
        }
5990 View Code Duplication
        if (strpos($str, "\n") !== false) {
5991
            $str = substr($str, 0, strpos("\n", $str));
5992
        }
5993
5994
        if ($this->getExtFromFilename($str) === '.php') {
5995
            return 'Could not retrieve PHP file.';
5996
        }
5997
5998
        $str = substr($str, 6);
5999
        $str = trim($str);
6000
        if (strpos($str, '\\') !== false) {
6001
            $str = str_replace('\\', '/', $str);
6002
        }
6003
        $str = ltrim($str, '/');
6004
6005
        $errorMsg = sprintf("Could not retrieve string '%s'.", $str);
6006
6007
        foreach ($search_path as $path) {
6008
            $file_path = MODX_BASE_PATH . $path . $str;
6009
            if (strpos($file_path, MODX_MANAGER_PATH) === 0) {
6010
                return $errorMsg;
6011
            } elseif (is_file($file_path)) {
6012
                break;
6013
            } else {
6014
                $file_path = false;
6015
            }
6016
        }
6017
6018
        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...
6019
            return $errorMsg;
6020
        }
6021
6022
        $content = (string)file_get_contents($file_path);
6023
        if ($content === false) {
6024
            return $errorMsg;
6025
        }
6026
6027
        return $content;
6028
    }
6029
6030
    /**
6031
     * @param $str
6032
     * @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...
6033
     */
6034
    public function getExtFromFilename($str)
6035
    {
6036
        $str = strtolower(trim($str));
6037
        $pos = strrpos($str, '.');
6038
        if ($pos === false) {
6039
            return false;
6040
        } else {
6041
            return substr($str, $pos);
6042
        }
6043
    }
6044
    /***************************************************************************************/
6045
    /* End of API functions                                       */
6046
    /***************************************************************************************/
6047
6048
    /**
6049
     * PHP error handler set by http://www.php.net/manual/en/function.set-error-handler.php
6050
     *
6051
     * Checks the PHP error and calls messageQuit() unless:
6052
     *  - error_reporting() returns 0, or
6053
     *  - the PHP error level is 0, or
6054
     *  - the PHP error level is 8 (E_NOTICE) and stopOnNotice is false
6055
     *
6056
     * @param int $nr The PHP error level as per http://www.php.net/manual/en/errorfunc.constants.php
6057
     * @param string $text Error message
6058
     * @param string $file File where the error was detected
6059
     * @param string $line Line number within $file
6060
     * @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...
6061
     */
6062
    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...
6063
    {
6064
        if (error_reporting() == 0 || $nr == 0) {
6065
            return true;
6066
        }
6067
        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...
6068
            switch ($nr) {
6069
                case E_NOTICE:
6070
                    if ($this->error_reporting <= 2) {
6071
                        return true;
6072
                    }
6073
                    $isError = false;
6074
                    $msg = 'PHP Minor Problem (this message show logged in only)';
6075
                    break;
6076
                case E_STRICT:
6077 View Code Duplication
                case E_DEPRECATED:
6078
                    if ($this->error_reporting <= 1) {
6079
                        return true;
6080
                    }
6081
                    $isError = true;
6082
                    $msg = 'PHP Strict Standards Problem';
6083
                    break;
6084 View Code Duplication
                default:
6085
                    if ($this->error_reporting === 0) {
6086
                        return true;
6087
                    }
6088
                    $isError = true;
6089
                    $msg = 'PHP Parse Error';
6090
            }
6091
        }
6092
        if (is_readable($file)) {
6093
            $source = file($file);
6094
            $source = $this->htmlspecialchars($source[$line - 1]);
6095
        } else {
6096
            $source = "";
6097
        } //Error $nr in $file at $line: <div><code>$source</code></div>
6098
6099
        $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...
6100
    }
6101
6102
    /**
6103
     * @param string $msg
6104
     * @param string $query
6105
     * @param bool $is_error
6106
     * @param string $nr
6107
     * @param string $file
6108
     * @param string $source
6109
     * @param string $text
6110
     * @param string $line
6111
     * @param string $output
6112
     * @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...
6113
     */
6114
    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...
6115
    {
6116
        if (0 < $this->messageQuitCount) {
6117
            return;
6118
        }
6119
        $this->messageQuitCount++;
6120
6121
        if (!class_exists('makeTable')) {
6122
            include_once('extenders/maketable.class.php');
6123
        }
6124
        $MakeTable = new MakeTable();
6125
        $MakeTable->setTableClass('grid');
6126
        $MakeTable->setRowRegularClass('gridItem');
6127
        $MakeTable->setRowAlternateClass('gridAltItem');
6128
        $MakeTable->setColumnWidths(array('100px'));
6129
6130
        $table = array();
6131
6132
        $version = isset($GLOBALS['modx_version']) ? $GLOBALS['modx_version'] : '';
6133
        $release_date = isset($GLOBALS['release_date']) ? $GLOBALS['release_date'] : '';
6134
        $request_uri = "http://" . $_SERVER['HTTP_HOST'] . ($_SERVER["SERVER_PORT"] == 80 ? "" : (":" . $_SERVER["SERVER_PORT"])) . $_SERVER['REQUEST_URI'];
6135
        $request_uri = $this->htmlspecialchars($request_uri, ENT_QUOTES, $this->config['modx_charset']);
6136
        $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...
6137
        $referer = $this->htmlspecialchars($_SERVER['HTTP_REFERER'], ENT_QUOTES, $this->config['modx_charset']);
6138
        if ($is_error) {
6139
            $str = '<h2 style="color:red">&laquo; Evo Parse Error &raquo;</h2>';
6140
            if ($msg != 'PHP Parse Error') {
6141
                $str .= '<h3 style="color:red">' . $msg . '</h3>';
6142
            }
6143
        } else {
6144
            $str = '<h2 style="color:#003399">&laquo; Evo Debug/ stop message &raquo;</h2>';
6145
            $str .= '<h3 style="color:#003399">' . $msg . '</h3>';
6146
        }
6147
6148
        if (!empty($query)) {
6149
            $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>';
6150
        }
6151
6152
        $errortype = array(
6153
            E_ERROR => "ERROR",
6154
            E_WARNING => "WARNING",
6155
            E_PARSE => "PARSING ERROR",
6156
            E_NOTICE => "NOTICE",
6157
            E_CORE_ERROR => "CORE ERROR",
6158
            E_CORE_WARNING => "CORE WARNING",
6159
            E_COMPILE_ERROR => "COMPILE ERROR",
6160
            E_COMPILE_WARNING => "COMPILE WARNING",
6161
            E_USER_ERROR => "USER ERROR",
6162
            E_USER_WARNING => "USER WARNING",
6163
            E_USER_NOTICE => "USER NOTICE",
6164
            E_STRICT => "STRICT NOTICE",
6165
            E_RECOVERABLE_ERROR => "RECOVERABLE ERROR",
6166
            E_DEPRECATED => "DEPRECATED",
6167
            E_USER_DEPRECATED => "USER DEPRECATED"
6168
        );
6169
6170
        if (!empty($nr) || !empty($file)) {
6171
            if ($text != '') {
6172
                $str .= '<div style="font-weight:bold;border:1px solid #ccc;padding:8px;color:#333;background-color:#ffffcd;margin-bottom:15px;">Error : ' . $text . '</div>';
6173
            }
6174
            if ($output != '') {
6175
                $str .= '<div style="font-weight:bold;border:1px solid #ccc;padding:8px;color:#333;background-color:#ffffcd;margin-bottom:15px;">' . $output . '</div>';
6176
            }
6177
            if ($nr !== '') {
6178
                $table[] = array('ErrorType[num]', $errortype [$nr] . "[" . $nr . "]");
6179
            }
6180
            if ($file) {
6181
                $table[] = array('File', $file);
6182
            }
6183
            if ($line) {
6184
                $table[] = array('Line', $line);
6185
            }
6186
        }
6187
6188
        if ($source != '') {
6189
            $table[] = array("Source", $source);
6190
        }
6191
6192
        if (!empty($this->currentSnippet)) {
6193
            $table[] = array('Current Snippet', $this->currentSnippet);
6194
        }
6195
6196
        if (!empty($this->event->activePlugin)) {
6197
            $table[] = array('Current Plugin', $this->event->activePlugin . '(' . $this->event->name . ')');
6198
        }
6199
6200
        $str .= $MakeTable->create($table, array('Error information', ''));
6201
        $str .= "<br />";
6202
6203
        $table = array();
6204
        $table[] = array('REQUEST_URI', $request_uri);
6205
6206
        if ($this->manager->action) {
6207
            include_once(MODX_MANAGER_PATH . 'includes/actionlist.inc.php');
6208
            global $action_list;
6209
            $actionName = (isset($action_list[$this->manager->action])) ? " - {$action_list[$this->manager->action]}" : '';
6210
6211
            $table[] = array('Manager action', $this->manager->action . $actionName);
6212
        }
6213
6214
        if (preg_match('@^[0-9]+@', $this->documentIdentifier)) {
6215
            $resource = $this->getDocumentObject('id', $this->documentIdentifier);
6216
            $url = $this->makeUrl($this->documentIdentifier, '', '', 'full');
6217
            $table[] = array('Resource', '[' . $this->documentIdentifier . '] <a href="' . $url . '" target="_blank">' . $resource['pagetitle'] . '</a>');
6218
        }
6219
        $table[] = array('Referer', $referer);
6220
        $table[] = array('User Agent', $ua);
6221
        $table[] = array('IP', $_SERVER['REMOTE_ADDR']);
6222
        $table[] = array('Current time', date("Y-m-d H:i:s", $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time']));
6223
        $str .= $MakeTable->create($table, array('Basic info', ''));
6224
        $str .= "<br />";
6225
6226
        $table = array();
6227
        $table[] = array('MySQL', '[^qt^] ([^q^] Requests)');
6228
        $table[] = array('PHP', '[^p^]');
6229
        $table[] = array('Total', '[^t^]');
6230
        $table[] = array('Memory', '[^m^]');
6231
        $str .= $MakeTable->create($table, array('Benchmarks', ''));
6232
        $str .= "<br />";
6233
6234
        $totalTime = ($this->getMicroTime() - $this->tstart);
6235
6236
        $mem = memory_get_peak_usage(true);
6237
        $total_mem = $mem - $this->mstart;
6238
        $total_mem = ($total_mem / 1024 / 1024) . ' mb';
6239
6240
        $queryTime = $this->queryTime;
6241
        $phpTime = $totalTime - $queryTime;
6242
        $queries = isset($this->executedQueries) ? $this->executedQueries : 0;
6243
        $queryTime = sprintf("%2.4f s", $queryTime);
6244
        $totalTime = sprintf("%2.4f s", $totalTime);
6245
        $phpTime = sprintf("%2.4f s", $phpTime);
6246
6247
        $str = str_replace('[^q^]', $queries, $str);
6248
        $str = str_replace('[^qt^]', $queryTime, $str);
6249
        $str = str_replace('[^p^]', $phpTime, $str);
6250
        $str = str_replace('[^t^]', $totalTime, $str);
6251
        $str = str_replace('[^m^]', $total_mem, $str);
6252
6253
        if (isset($php_errormsg) && !empty($php_errormsg)) {
6254
            $str = "<b>{$php_errormsg}</b><br />\n{$str}";
6255
        }
6256
        $str .= $this->get_backtrace(debug_backtrace());
6257
        // Log error
6258
        if (!empty($this->currentSnippet)) {
6259
            $source = 'Snippet - ' . $this->currentSnippet;
6260
        } elseif (!empty($this->event->activePlugin)) {
6261
            $source = 'Plugin - ' . $this->event->activePlugin;
6262
        } elseif ($source !== '') {
6263
            $source = 'Parser - ' . $source;
6264
        } elseif ($query !== '') {
6265
            $source = 'SQL Query';
6266
        } else {
6267
            $source = 'Parser';
6268
        }
6269
        if ($msg) {
6270
            $source .= ' / ' . $msg;
6271
        }
6272
        if (isset($actionName) && !empty($actionName)) {
6273
            $source .= $actionName;
6274
        }
6275 View Code Duplication
        switch ($nr) {
6276
            case E_DEPRECATED:
6277
            case E_USER_DEPRECATED:
6278
            case E_STRICT:
6279
            case E_NOTICE:
6280
            case E_USER_NOTICE:
6281
                $error_level = 2;
6282
                break;
6283
            default:
6284
                $error_level = 3;
6285
        }
6286
        $this->logEvent(0, $error_level, $str, $source);
6287
6288
        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...
6289
            return true;
6290
        }
6291
        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...
6292
            return true;
6293
        }
6294
6295
        // Set 500 response header
6296
        if ($error_level !== 2) {
6297
            header('HTTP/1.1 500 Internal Server Error');
6298
        }
6299
6300
        // Display error
6301
        if (isset($_SESSION['mgrValidated'])) {
6302
            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>
6303
                 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
6304
                 <link rel="stylesheet" type="text/css" href="' . $this->config['site_manager_url'] . 'media/style/' . $this->config['manager_theme'] . '/style.css" />
6305
                 <style type="text/css">body { padding:10px; } td {font:inherit;}</style>
6306
                 </head><body>
6307
                 ' . $str . '</body></html>';
6308
        } else {
6309
            echo 'Error';
6310
        }
6311
        ob_end_flush();
6312
        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...
6313
    }
6314
6315
    /**
6316
     * @param $backtrace
6317
     * @return string
6318
     */
6319
    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...
6320
    {
6321
        if (!class_exists('makeTable')) {
6322
            include_once('extenders/maketable.class.php');
6323
        }
6324
        $MakeTable = new MakeTable();
6325
        $MakeTable->setTableClass('grid');
6326
        $MakeTable->setRowRegularClass('gridItem');
6327
        $MakeTable->setRowAlternateClass('gridAltItem');
6328
        $table = array();
6329
        $backtrace = array_reverse($backtrace);
6330
        foreach ($backtrace as $key => $val) {
6331
            $key++;
6332
            if (substr($val['function'], 0, 11) === 'messageQuit') {
6333
                break;
6334
            } elseif (substr($val['function'], 0, 8) === 'phpError') {
6335
                break;
6336
            }
6337
            $path = str_replace('\\', '/', $val['file']);
6338
            if (strpos($path, MODX_BASE_PATH) === 0) {
6339
                $path = substr($path, strlen(MODX_BASE_PATH));
6340
            }
6341
            switch ($val['type']) {
6342
                case '->':
6343
                case '::':
6344
                    $functionName = $val['function'] = $val['class'] . $val['type'] . $val['function'];
6345
                    break;
6346
                default:
6347
                    $functionName = $val['function'];
6348
            }
6349
            $tmp = 1;
6350
            $_ = (!empty($val['args'])) ? count($val['args']) : 0;
6351
            $args = array_pad(array(), $_, '$var');
6352
            $args = implode(", ", $args);
6353
            $modx = &$this;
6354
            $args = preg_replace_callback('/\$var/', function () use ($modx, &$tmp, $val) {
6355
                $arg = $val['args'][$tmp - 1];
6356
                switch (true) {
6357
                    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...
6358
                        $out = 'NULL';
6359
                        break;
6360
                    }
6361
                    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...
6362
                        $out = $arg;
6363
                        break;
6364
                    }
6365
                    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...
6366
                        $out = strlen($arg) > 20 ? 'string $var' . $tmp : ("'" . $this->htmlspecialchars(str_replace("'", "\\'", $arg)) . "'");
6367
                        break;
6368
                    }
6369
                    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...
6370
                        $out = $arg ? 'TRUE' : 'FALSE';
6371
                        break;
6372
                    }
6373
                    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...
6374
                        $out = 'array $var' . $tmp;
6375
                        break;
6376
                    }
6377
                    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...
6378
                        $out = get_class($arg) . ' $var' . $tmp;
6379
                        break;
6380
                    }
6381
                    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...
6382
                        $out = '$var' . $tmp;
6383
                    }
6384
                }
6385
                $tmp++;
6386
                return $out;
6387
            }, $args);
6388
            $line = array(
6389
                "<strong>" . $functionName . "</strong>(" . $args . ")",
6390
                $path . " on line " . $val['line']
6391
            );
6392
            $table[] = array(implode("<br />", $line));
6393
        }
6394
        return $MakeTable->create($table, array('Backtrace'));
6395
    }
6396
6397
    /**
6398
     * @return string
6399
     */
6400
    public function getRegisteredClientScripts()
6401
    {
6402
        return implode("\n", $this->jscripts);
6403
    }
6404
6405
    /**
6406
     * @return string
6407
     */
6408
    public function getRegisteredClientStartupScripts()
6409
    {
6410
        return implode("\n", $this->sjscripts);
6411
    }
6412
6413
    /**
6414
     * Format alias to be URL-safe. Strip invalid characters.
6415
     *
6416
     * @param string $alias Alias to be formatted
6417
     * @return string Safe alias
6418
     */
6419
    public function stripAlias($alias)
6420
    {
6421
        // let add-ons overwrite the default behavior
6422
        $results = $this->invokeEvent('OnStripAlias', array('alias' => $alias));
6423
        if (!empty($results)) {
6424
            // if multiple plugins are registered, only the last one is used
6425
            return end($results);
6426
        } else {
6427
            // default behavior: strip invalid characters and replace spaces with dashes.
6428
            $alias = strip_tags($alias); // strip HTML
6429
            $alias = preg_replace('/[^\.A-Za-z0-9 _-]/', '', $alias); // strip non-alphanumeric characters
6430
            $alias = preg_replace('/\s+/', '-', $alias); // convert white-space to dash
6431
            $alias = preg_replace('/-+/', '-', $alias);  // convert multiple dashes to one
6432
            $alias = trim($alias, '-'); // trim excess
6433
            return $alias;
6434
        }
6435
    }
6436
6437
    /**
6438
     * @param $size
6439
     * @return string
6440
     */
6441
    public function nicesize($size)
6442
    {
6443
        $sizes = array('Tb' => 1099511627776, 'Gb' => 1073741824, 'Mb' => 1048576, 'Kb' => 1024, 'b' => 1);
6444
        $precisions = count($sizes) - 1;
6445
        foreach ($sizes as $unit => $bytes) {
6446
            if ($size >= $bytes) {
6447
                return number_format($size / $bytes, $precisions) . ' ' . $unit;
6448
            }
6449
            $precisions--;
6450
        }
6451
        return '0 b';
6452
    }
6453
6454
    /**
6455
     * @param $parentid
6456
     * @param $alias
6457
     * @return bool
6458
     */
6459
    public function getHiddenIdFromAlias($parentid, $alias)
6460
    {
6461
        $table = $this->getFullTableName('site_content');
6462
        $query = $this->db->query("SELECT sc.id, children.id AS child_id, children.alias, COUNT(children2.id) AS children_count
6463
            FROM {$table} sc
6464
            JOIN {$table} children ON children.parent = sc.id
6465
            LEFT JOIN {$table} children2 ON children2.parent = children.id
6466
            WHERE sc.parent = {$parentid} AND sc.alias_visible = '0' GROUP BY children.id;");
6467
6468
        while ($child = $this->db->getRow($query)) {
6469
            if ($child['alias'] == $alias || $child['child_id'] == $alias) {
6470
                return $child['child_id'];
6471
            }
6472
6473
            if ($child['children_count'] > 0) {
6474
                $id = $this->getHiddenIdFromAlias($child['id'], $alias);
6475
                if ($id) {
6476
                    return $id;
6477
                }
6478
            }
6479
        }
6480
6481
        return false;
6482
    }
6483
6484
    /**
6485
     * @param $alias
6486
     * @return bool|int
6487
     */
6488
    public function getIdFromAlias($alias)
6489
    {
6490
        if (isset($this->documentListing[$alias])) {
6491
            return $this->documentListing[$alias];
6492
        }
6493
6494
        $tbl_site_content = $this->getFullTableName('site_content');
6495
        if ($this->config['use_alias_path'] == 1) {
6496
            if ($alias == '.') {
6497
                return 0;
6498
            }
6499
6500
            if (strpos($alias, '/') !== false) {
6501
                $_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...
6502
            } else {
6503
                $_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...
6504
            }
6505
            $id = 0;
6506
6507
            foreach ($_a as $alias) {
6508
                if ($id === false) {
6509
                    break;
6510
                }
6511
                $alias = $this->db->escape($alias);
6512
                $rs = $this->db->select('id', $tbl_site_content, "deleted=0 and parent='{$id}' and alias='{$alias}'");
6513
                if ($this->db->getRecordCount($rs) == 0) {
6514
                    $rs = $this->db->select('id', $tbl_site_content, "deleted=0 and parent='{$id}' and id='{$alias}'");
6515
                }
6516
                $next = $this->db->getValue($rs);
6517
                $id = !$next ? $this->getHiddenIdFromAlias($id, $alias) : $next;
6518
            }
6519
        } else {
6520
            $rs = $this->db->select('id', $tbl_site_content, "deleted=0 and alias='{$alias}'", 'parent, menuindex');
6521
            $id = $this->db->getValue($rs);
6522
            if (!$id) {
6523
                $id = false;
6524
            }
6525
        }
6526
        return $id;
6527
    }
6528
6529
    /**
6530
     * @param string $str
6531
     * @return bool|mixed|string
6532
     */
6533
    public function atBindInclude($str = '')
6534
    {
6535
        if (strpos($str, '@INCLUDE') !== 0) {
6536
            return $str;
6537
        }
6538 View Code Duplication
        if (strpos($str, "\n") !== false) {
6539
            $str = substr($str, 0, strpos("\n", $str));
6540
        }
6541
6542
        $str = substr($str, 9);
6543
        $str = trim($str);
6544
        $str = str_replace('\\', '/', $str);
6545
        $str = ltrim($str, '/');
6546
6547
        $tpl_dir = 'assets/templates/';
6548
6549
        if (strpos($str, MODX_MANAGER_PATH) === 0) {
6550
            return false;
6551
        } elseif (is_file(MODX_BASE_PATH . $str)) {
6552
            $file_path = MODX_BASE_PATH . $str;
6553
        } elseif (is_file(MODX_BASE_PATH . "{$tpl_dir}{$str}")) {
6554
            $file_path = MODX_BASE_PATH . $tpl_dir . $str;
6555
        } else {
6556
            return false;
6557
        }
6558
6559
        if (!$file_path || !is_file($file_path)) {
6560
            return false;
6561
        }
6562
6563
        ob_start();
6564
        $modx = &$this;
6565
        $result = include($file_path);
6566
        if ($result === 1) {
6567
            $result = '';
6568
        }
6569
        $content = ob_get_clean();
6570
        if (!$content && $result) {
6571
            $content = $result;
6572
        }
6573
        return $content;
6574
    }
6575
6576
    // php compat
6577
6578
    /**
6579
     * @param $str
6580
     * @param int $flags
6581
     * @param string $encode
6582
     * @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...
6583
     */
6584
    public function htmlspecialchars($str, $flags = ENT_COMPAT, $encode = '')
6585
    {
6586
        $this->loadExtension('PHPCOMPAT');
6587
        return $this->phpcompat->htmlspecialchars($str, $flags, $encode);
6588
    }
6589
6590
    /**
6591
     * @param $string
6592
     * @param bool $returnData
6593
     * @return bool|mixed
6594
     */
6595
    public function isJson($string, $returnData = false)
6596
    {
6597
        $data = json_decode($string, true);
6598
        return (json_last_error() == JSON_ERROR_NONE) ? ($returnData ? $data : true) : false;
6599
    }
6600
6601
    /**
6602
     * @param $key
6603
     * @return array
6604
     */
6605
    public function splitKeyAndFilter($key)
6606
    {
6607
        if ($this->config['enable_filter'] == 1 && strpos($key, ':') !== false && stripos($key, '@FILE') !== 0) {
6608
            list($key, $modifiers) = explode(':', $key, 2);
6609
        } else {
6610
            $modifiers = false;
6611
        }
6612
6613
        $key = trim($key);
6614
        if ($modifiers !== false) {
6615
            $modifiers = trim($modifiers);
6616
        }
6617
6618
        return array($key, $modifiers);
6619
    }
6620
6621
    /**
6622
     * @param string $value
6623
     * @param bool $modifiers
6624
     * @param string $key
6625
     * @return string
6626
     */
6627
    public function applyFilter($value = '', $modifiers = false, $key = '')
6628
    {
6629
        if ($modifiers === false || $modifiers == 'raw') {
6630
            return $value;
6631
        }
6632
        if ($modifiers !== false) {
6633
            $modifiers = trim($modifiers);
6634
        }
6635
6636
        $this->loadExtension('MODIFIERS');
6637
        return $this->filter->phxFilter($key, $value, $modifiers);
0 ignored issues
show
Bug introduced by
It seems like $modifiers defined by parameter $modifiers on line 6627 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...
6638
    }
6639
6640
    // End of class.
6641
6642
6643
    /**
6644
     * Get Clean Query String
6645
     *
6646
     * Fixes the issue where passing an array into the q get variable causes errors
6647
     *
6648
     */
6649
    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...
6650
    {
6651
        $q = MODX_CLI ? null : $_GET['q'];
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $q. Configured minimum length is 3.

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

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