Completed
Push — develop ( 5b92e2...a731d6 )
by Maxim
05:38
created

DocumentParser::__clone()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

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

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

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

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

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

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

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

namespace YourVendor;

class YourClass { }

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

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

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

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

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

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

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

Loading history...
169
    public $decoded_request_uri;
170
    /**
171
     * @var OldFunctions
172
     */
173
    public $old;
174
175
    /**
176
     * Hold the class instance.
177
     * @var DocumentParser
178
     */
179
    private static $instance = null;
180
181
    /**
182
     * Document constructor
183
     *
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
    function __call($method_name, $arguments)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

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

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

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

Loading history...
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
        $hash = $id;
770
        $tmp = null;
771
        $params = array();
772
        if(!empty($this->systemCacheKey)){
773
            $hash = $this->systemCacheKey;
774
        }else {
775
            if (!empty($_GET)) {
776
                // Sort GET parameters so that the order of parameters on the HTTP request don't affect the generated cache ID.
777
                $params = $_GET;
778
                ksort($params);
779
                $hash .= '_'.md5(http_build_query($params));
780
            }
781
        }
782
        $evtOut = $this->invokeEvent("OnMakePageCacheKey", array ("hash" => $hash, "id" => $id, 'params' => $params));
783
        if (is_array($evtOut) && count($evtOut) > 0){
784
            $tmp = array_pop($evtOut);
785
        }
786
        return empty($tmp) ? $hash : $tmp;
787
    }
788
789
    /**
790
     * @param $id
791
     * @param bool $loading
792
     * @return string
793
     */
794
    public function checkCache($id, $loading = false)
795
    {
796
        return $this->getDocumentObjectFromCache($id, $loading);
797
    }
798
799
    /**
800
     * Check the cache for a specific document/resource
801
     *
802
     * @param int $id
803
     * @param bool $loading
804
     * @return string
805
     */
806
    public function getDocumentObjectFromCache($id, $loading = false)
807
    {
808
        $key = ($this->config['cache_type'] == 2) ? $this->makePageCacheKey($id) : $id;
809
        if ($loading) {
810
            $this->cacheKey = $key;
811
        }
812
813
        $cache_path = $this->getHashFile($key);
814
815
        if (!is_file($cache_path)) {
816
            $this->documentGenerated = 1;
817
            return '';
818
        }
819
        $content = file_get_contents($cache_path, false);
820
        if (substr($content, 0, 5) === '<?php') {
821
            $content = substr($content, strpos($content, '?>') + 2);
822
        } // remove php header
823
        $a = explode('<!--__MODxCacheSpliter__-->', $content, 2);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $a. Configured minimum length is 3.

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

Loading history...
824
        if (count($a) == 1) {
825
            $result = $a[0];
826
        } // return only document content
827
        else {
828
            $docObj = unserialize($a[0]); // rebuild document object
829
            // check page security
830
            if ($docObj['privateweb'] && isset ($docObj['__MODxDocGroups__'])) {
831
                $pass = false;
832
                $usrGrps = $this->getUserDocGroups();
833
                $docGrps = explode(',', $docObj['__MODxDocGroups__']);
834
                // check is user has access to doc groups
835
                if (is_array($usrGrps)) {
836
                    foreach ($usrGrps as $k => $v) {
837
                        if (!in_array($v, $docGrps)) {
838
                            continue;
839
                        }
840
                        $pass = true;
841
                        break;
842
                    }
843
                }
844
                // diplay error pages if user has no access to cached doc
845
                if (!$pass) {
846
                    if ($this->config['unauthorized_page']) {
847
                        // check if file is not public
848
                        $rs = $this->db->select('count(id)', '[+prefix+]document_groups', "document='{$id}'", '', '1');
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rs. Configured minimum length is 3.

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

Loading history...
849
                        $total = $this->db->getValue($rs);
850
                    } else {
851
                        $total = 0;
852
                    }
853
854
                    if ($total > 0) {
855
                        $this->sendUnauthorizedPage();
856
                    } else {
857
                        $this->sendErrorPage();
858
                    }
859
860
                    exit; // stop here
0 ignored issues
show
Coding Style Compatibility introduced by
The method getDocumentObjectFromCache() contains an exit expression.

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

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

Loading history...
861
                }
862
            }
863
            // Grab the Scripts
864
            if (isset($docObj['__MODxSJScripts__'])) {
865
                $this->sjscripts = $docObj['__MODxSJScripts__'];
866
            }
867
            if (isset($docObj['__MODxJScripts__'])) {
868
                $this->jscripts = $docObj['__MODxJScripts__'];
869
            }
870
871
            // Remove intermediate variables
872
            unset($docObj['__MODxDocGroups__'], $docObj['__MODxSJScripts__'], $docObj['__MODxJScripts__']);
873
874
            $this->documentObject = $docObj;
875
876
            $result = $a[1]; // return document content
877
        }
878
879
        $this->documentGenerated = 0;
880
        // invoke OnLoadWebPageCache  event
881
        $this->documentContent = $result;
882
        $this->invokeEvent('OnLoadWebPageCache');
883
        return $result;
884
    }
885
886
    /**
887
     * Final processing and output of the document/resource.
888
     *
889
     * - runs uncached snippets
890
     * - add javascript to <head>
891
     * - removes unused placeholders
892
     * - converts URL tags [~...~] to URLs
893
     *
894
     * @param boolean $noEvent Default: false
895
     */
896
    public function outputContent($noEvent = false)
0 ignored issues
show
Coding Style introduced by
outputContent uses the super-global variable $_SERVER which is generally not recommended.

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

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

// Better
class Router
{
    private $host;

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

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

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

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
897
    {
898
        $this->documentOutput = $this->documentContent;
899
900
        if ($this->documentGenerated == 1 && $this->documentObject['cacheable'] == 1 && $this->documentObject['type'] == 'document' && $this->documentObject['published'] == 1) {
901
            if (!empty($this->sjscripts)) {
902
                $this->documentObject['__MODxSJScripts__'] = $this->sjscripts;
903
            }
904
            if (!empty($this->jscripts)) {
905
                $this->documentObject['__MODxJScripts__'] = $this->jscripts;
906
            }
907
        }
908
909
        // check for non-cached snippet output
910
        if (strpos($this->documentOutput, '[!') > -1) {
911
            $this->recentUpdate = $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'];
912
913
            $this->documentOutput = str_replace('[!', '[[', $this->documentOutput);
914
            $this->documentOutput = str_replace('!]', ']]', $this->documentOutput);
915
916
            // Parse document source
917
            $this->documentOutput = $this->parseDocumentSource($this->documentOutput);
918
        }
919
920
        // Moved from prepareResponse() by sirlancelot
921
        // Insert Startup jscripts & CSS scripts into template - template must have a <head> tag
922
        if ($js = $this->getRegisteredClientStartupScripts()) {
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $js. Configured minimum length is 3.

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

Loading history...
923
            // change to just before closing </head>
924
            // $this->documentContent = preg_replace("/(<head[^>]*>)/i", "\\1\n".$js, $this->documentContent);
0 ignored issues
show
Unused Code Comprehensibility introduced by
55% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
925
            $this->documentOutput = preg_replace("/(<\/head>)/i", $js . "\n\\1", $this->documentOutput);
926
        }
927
928
        // Insert jscripts & html block into template - template must have a </body> tag
929
        if ($js = $this->getRegisteredClientScripts()) {
930
            $this->documentOutput = preg_replace("/(<\/body>)/i", $js . "\n\\1", $this->documentOutput);
931
        }
932
        // End fix by sirlancelot
933
934
        $this->documentOutput = $this->cleanUpMODXTags($this->documentOutput);
935
936
        $this->documentOutput = $this->rewriteUrls($this->documentOutput);
937
938
        // send out content-type and content-disposition headers
939
        if (IN_PARSER_MODE == "true") {
940
            $type = !empty ($this->contentTypes[$this->documentIdentifier]) ? $this->contentTypes[$this->documentIdentifier] : "text/html";
941
            header('Content-Type: ' . $type . '; charset=' . $this->config['modx_charset']);
942
            //            if (($this->documentIdentifier == $this->config['error_page']) || $redirect_error)
0 ignored issues
show
Unused Code Comprehensibility introduced by
61% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
943
            //                header('HTTP/1.0 404 Not Found');
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
944
            if (!$this->checkPreview() && $this->documentObject['content_dispo'] == 1) {
945
                if ($this->documentObject['alias']) {
946
                    $name = $this->documentObject['alias'];
947
                } else {
948
                    // strip title of special characters
949
                    $name = $this->documentObject['pagetitle'];
950
                    $name = strip_tags($name);
951
                    $name = $this->cleanUpMODXTags($name);
952
                    $name = strtolower($name);
953
                    $name = preg_replace('/&.+?;/', '', $name); // kill entities
954
                    $name = preg_replace('/[^\.%a-z0-9 _-]/', '', $name);
955
                    $name = preg_replace('/\s+/', '-', $name);
956
                    $name = preg_replace('|-+|', '-', $name);
957
                    $name = trim($name, '-');
958
                }
959
                $header = 'Content-Disposition: attachment; filename=' . $name;
960
                header($header);
961
            }
962
        }
963
        $this->setConditional();
964
965
        $stats = $this->getTimerStats($this->tstart);
966
967
        $out =& $this->documentOutput;
968
        $out = str_replace("[^q^]", $stats['queries'], $out);
969
        $out = str_replace("[^qt^]", $stats['queryTime'], $out);
970
        $out = str_replace("[^p^]", $stats['phpTime'], $out);
971
        $out = str_replace("[^t^]", $stats['totalTime'], $out);
972
        $out = str_replace("[^s^]", $stats['source'], $out);
973
        $out = str_replace("[^m^]", $stats['phpMemory'], $out);
974
        //$this->documentOutput= $out;
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
975
976
        // invoke OnWebPagePrerender event
977
        if (!$noEvent) {
978
            $evtOut = $this->invokeEvent('OnWebPagePrerender', array('documentOutput' => $this->documentOutput));
979
            if (is_array($evtOut) && count($evtOut) > 0) {
980
                $this->documentOutput = $evtOut['0'];
981
            }
982
        }
983
984
        $this->documentOutput = $this->removeSanitizeSeed($this->documentOutput);
985
986
        if (strpos($this->documentOutput, '\{') !== false) {
987
            $this->documentOutput = $this->RecoveryEscapedTags($this->documentOutput);
988
        } elseif (strpos($this->documentOutput, '\[') !== false) {
989
            $this->documentOutput = $this->RecoveryEscapedTags($this->documentOutput);
990
        }
991
992
        echo $this->documentOutput;
993
994
        if ($this->dumpSQL) {
995
            echo $this->queryCode;
996
        }
997
        if ($this->dumpSnippets) {
998
            $sc = "";
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $sc. Configured minimum length is 3.

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

Loading history...
999
            $tt = 0;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $tt. Configured minimum length is 3.

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

Loading history...
1000
            foreach ($this->snippetsTime as $s => $v) {
1001
                $t = $v['time'];
1002
                $sname = $v['sname'];
1003
                $sc .= sprintf("%s. %s (%s)<br>", $s, $sname, sprintf("%2.2f ms", $t)); // currentSnippet
1004
                $tt += $t;
1005
            }
1006
            echo "<fieldset><legend><b>Snippets</b> (" . count($this->snippetsTime) . " / " . sprintf("%2.2f ms", $tt) . ")</legend>{$sc}</fieldset><br />";
1007
            echo $this->snippetsCode;
1008
        }
1009
        if ($this->dumpPlugins) {
1010
            $ps = "";
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ps. Configured minimum length is 3.

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

Loading history...
1011
            $tt = 0;
1012
            foreach ($this->pluginsTime as $s => $t) {
1013
                $ps .= "$s (" . sprintf("%2.2f ms", $t * 1000) . ")<br>";
1014
                $tt += $t;
1015
            }
1016
            echo "<fieldset><legend><b>Plugins</b> (" . count($this->pluginsTime) . " / " . sprintf("%2.2f ms", $tt * 1000) . ")</legend>{$ps}</fieldset><br />";
1017
            echo $this->pluginsCode;
1018
        }
1019
1020
        ob_end_flush();
1021
    }
1022
1023
    /**
1024
     * @param $contents
1025
     * @return mixed
1026
     */
1027
    public function RecoveryEscapedTags($contents)
0 ignored issues
show
Coding Style introduced by
This method is not in camel caps format.

This check looks for method names that are not written in camelCase.

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

Loading history...
1028
    {
1029
        list($sTags, $rTags) = $this->getTagsForEscape();
1030
        return str_replace($rTags, $sTags, $contents);
1031
    }
1032
1033
    /**
1034
     * @param string $tags
1035
     * @return array[]
1036
     */
1037
    public function getTagsForEscape($tags = '{{,}},[[,]],[!,!],[*,*],[(,)],[+,+],[~,~],[^,^]')
1038
    {
1039
        $srcTags = explode(',', $tags);
1040
        $repTags = array();
1041
        foreach ($srcTags as $tag) {
1042
            $repTags[] = '\\' . $tag[0] . '\\' . $tag[1];
1043
        }
1044
        return array($srcTags, $repTags);
1045
    }
1046
1047
    /**
1048
     * @param $tstart
1049
     * @return array
1050
     */
1051
    public function getTimerStats($tstart)
1052
    {
1053
        $stats = array();
1054
1055
        $stats['totalTime'] = ($this->getMicroTime() - $tstart);
1056
        $stats['queryTime'] = $this->queryTime;
1057
        $stats['phpTime'] = $stats['totalTime'] - $stats['queryTime'];
1058
1059
        $stats['queryTime'] = sprintf("%2.4f s", $stats['queryTime']);
1060
        $stats['totalTime'] = sprintf("%2.4f s", $stats['totalTime']);
1061
        $stats['phpTime'] = sprintf("%2.4f s", $stats['phpTime']);
1062
        $stats['source'] = $this->documentGenerated == 1 ? "database" : "cache";
1063
        $stats['queries'] = isset ($this->executedQueries) ? $this->executedQueries : 0;
1064
        $stats['phpMemory'] = (memory_get_peak_usage(true) / 1024 / 1024) . " mb";
1065
1066
        return $stats;
1067
    }
1068
1069
    public function setConditional()
0 ignored issues
show
Coding Style introduced by
setConditional uses the super-global variable $_POST which is generally not recommended.

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

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

// Better
class Router
{
    private $host;

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

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

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

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

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

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

// Better
class Router
{
    private $host;

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

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

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

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1070
    {
1071
        if (!empty($_POST) || (defined('MODX_API_MODE') && MODX_API_MODE) || $this->getLoginUserID('mgr') || !$this->useConditional || empty($this->recentUpdate)) {
1072
            return;
1073
        }
1074
        $last_modified = gmdate('D, d M Y H:i:s T', $this->recentUpdate);
1075
        $etag = md5($last_modified);
1076
        $HTTP_IF_MODIFIED_SINCE = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? $_SERVER['HTTP_IF_MODIFIED_SINCE'] : false;
1077
        $HTTP_IF_NONE_MATCH = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? $_SERVER['HTTP_IF_NONE_MATCH'] : false;
1078
        header('Pragma: no-cache');
1079
1080
        if ($HTTP_IF_MODIFIED_SINCE == $last_modified || strpos($HTTP_IF_NONE_MATCH, $etag) !== false) {
1081
            header('HTTP/1.1 304 Not Modified');
1082
            header('Content-Length: 0');
1083
            exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method setConditional() contains an exit expression.

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

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

Loading history...
1084
        } else {
1085
            header("Last-Modified: {$last_modified}");
1086
            header("ETag: '{$etag}'");
1087
        }
1088
    }
1089
1090
    /**
1091
     * Checks the publish state of page
1092
     */
1093
    public function updatePubStatus()
0 ignored issues
show
Coding Style introduced by
updatePubStatus uses the super-global variable $_SERVER which is generally not recommended.

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

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

// Better
class Router
{
    private $host;

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

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

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

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1094
    {
1095
        $cacheRefreshTime = 0;
1096
        $recent_update = 0;
1097
        @include(MODX_BASE_PATH . $this->getCacheFolder() . 'sitePublishing.idx.php');
1098
        $this->recentUpdate = $recent_update;
1099
1100
        $timeNow = $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'];
1101
        if ($timeNow < $cacheRefreshTime || $cacheRefreshTime == 0) {
1102
            return;
1103
        }
1104
1105
        // now, check for documents that need publishing
1106
        $field = array('published' => 1, 'publishedon' => $timeNow);
1107
        $where = "pub_date <= {$timeNow} AND pub_date!=0 AND published=0";
1108
        $result_pub = $this->db->select( 'id', '[+prefix+]site_content',  $where);
1109
        $this->db->update($field, '[+prefix+]site_content', $where);
1110 View Code Duplication
        if( $this->db->getRecordCount( $result_pub ) >= 1 ) { //Event Published doc
1111
            while ($row_unpub = $this->db->getRow($result_pub)) {
1112
                $this->invokeEvent("OnDocPublished", array (
1113
                    "docid" => $row_unpub['id']
1114
                ));
1115
            }
1116
        }
1117
1118
        // now, check for documents that need un-publishing
1119
        $field = array('published' => 0, 'publishedon' => 0);
1120
        $where = "unpub_date <= {$timeNow} AND unpub_date!=0 AND published=1";
1121
        $result_unpub = $this->db->select( 'id', '[+prefix+]site_content',  $where);
1122
        $this->db->update($field, '[+prefix+]site_content', $where);
1123 View Code Duplication
        if( $this->db->getRecordCount( $result_unpub ) >= 1 ) { //Event unPublished doc
1124
            while ($row_unpub = $this->db->getRow($result_unpub)) {
1125
                $this->invokeEvent("OnDocUnPublished", array (
1126
                    "docid" => $row_unpub['id']
1127
                ));
1128
            }
1129
        }
1130
1131
        $this->recentUpdate = $timeNow;
1132
1133
        // clear the cache
1134
        $this->clearCache('full');
1135
    }
1136
1137
    public function checkPublishStatus()
1138
    {
1139
        $this->updatePubStatus();
1140
    }
1141
1142
    /**
1143
     * Final jobs.
1144
     *
1145
     * - cache page
1146
     */
1147
    public function postProcess()
1148
    {
1149
        // if the current document was generated, cache it!
1150
        $cacheable = ($this->config['enable_cache'] && $this->documentObject['cacheable']) ? 1 : 0;
1151
        if ($cacheable && $this->documentGenerated && $this->documentObject['type'] == 'document' && $this->documentObject['published']) {
1152
            // invoke OnBeforeSaveWebPageCache event
1153
            $this->invokeEvent("OnBeforeSaveWebPageCache");
1154
1155
            if (!empty($this->cacheKey) && is_scalar($this->cacheKey)) {
1156
                // get and store document groups inside document object. Document groups will be used to check security on cache pages
1157
                $where = "document='{$this->documentIdentifier}'";
1158
                $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...
1159
                $docGroups = $this->db->getColumn('document_group', $rs);
1160
1161
                // Attach Document Groups and Scripts
1162
                if (is_array($docGroups)) {
1163
                    $this->documentObject['__MODxDocGroups__'] = implode(",", $docGroups);
1164
                }
1165
1166
                $docObjSerial = serialize($this->documentObject);
1167
                $cacheContent = $docObjSerial . "<!--__MODxCacheSpliter__-->" . $this->documentContent;
1168
                $page_cache_path = MODX_BASE_PATH . $this->getHashFile($this->cacheKey);
1169
                file_put_contents($page_cache_path, "<?php die('Unauthorized access.'); ?>$cacheContent");
1170
            }
1171
        }
1172
1173
        // Useful for example to external page counters/stats packages
1174
        $this->invokeEvent('OnWebPageComplete');
1175
1176
        // end post processing
1177
    }
1178
1179
    /**
1180
     * @param $content
1181
     * @param string $left
1182
     * @param string $right
1183
     * @return array
1184
     */
1185
    public function getTagsFromContent($content, $left = '[+', $right = '+]')
1186
    {
1187
        $_ = $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...
1188
        if (empty($_)) {
1189
            return array();
1190
        }
1191
        foreach ($_ as $v) {
1192
            $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...
1193
            $tags[1][] = $v;
1194
        }
1195
        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...
1196
    }
1197
1198
    /**
1199
     * @param $content
1200
     * @param string $left
1201
     * @param string $right
1202
     * @return array
1203
     */
1204
    public function _getTagsFromContent($content, $left = '[+', $right = '+]')
1205
    {
1206
        if (strpos($content, $left) === false) {
1207
            return array();
1208
        }
1209
        $spacer = md5('<<<EVO>>>');
1210
        if($left==='{{' && strpos($content,';}}')!==false)  $content = str_replace(';}}', sprintf(';}%s}',   $spacer),$content);
1211
        if($left==='{{' && strpos($content,'{{}}')!==false) $content = str_replace('{{}}',sprintf('{%$1s{}%$1s}',$spacer),$content);
1212
        if($left==='[[' && strpos($content,']]]]')!==false) $content = str_replace(']]]]',sprintf(']]%s]]',  $spacer),$content);
1213
        if($left==='[[' && strpos($content,']]]')!==false)  $content = str_replace(']]]', sprintf(']%s]]',   $spacer),$content);
1214
1215
        $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...
1216
        $pos[']]>'] = strpos($content, ']]>');
1217
1218
        if ($pos['<![CDATA['] !== false && $pos[']]>'] !== false) {
1219
            $content = substr($content, 0, $pos['<![CDATA[']) . substr($content, $pos[']]>'] + 3);
1220
        }
1221
1222
        $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...
1223
        $piece = array();
1224
        foreach ($lp as $lc => $lv) {
1225
            if ($lc !== 0) {
1226
                $piece[] = $left;
1227
            }
1228
            if (strpos($lv, $right) === false) {
1229
                $piece[] = $lv;
1230
            } else {
1231
                $rp = explode($right, $lv);
1232
                foreach ($rp as $rc => $rv) {
1233
                    if ($rc !== 0) {
1234
                        $piece[] = $right;
1235
                    }
1236
                    $piece[] = $rv;
1237
                }
1238
            }
1239
        }
1240
        $lc = 0;
1241
        $rc = 0;
1242
        $fetch = '';
1243
        $tags = array();
1244
        foreach ($piece as $v) {
1245
            if ($v === $left) {
1246
                if (0 < $lc) {
1247
                    $fetch .= $left;
1248
                }
1249
                $lc++;
1250
            } elseif ($v === $right) {
1251
                if ($lc === 0) {
1252
                    continue;
1253
                }
1254
                $rc++;
1255
                if ($lc === $rc) {
1256
                    // #1200 Enable modifiers in Wayfinder - add nested placeholders to $tags like for $fetch = "phx:input=`[+wf.linktext+]`:test"
1257
                    if (strpos($fetch, $left) !== false) {
1258
                        $nested = $this->_getTagsFromContent($fetch, $left, $right);
1259
                        foreach ($nested as $tag) {
1260
                            if (!in_array($tag, $tags)) {
1261
                                $tags[] = $tag;
1262
                            }
1263
                        }
1264
                    }
1265
1266
                    if (!in_array($fetch, $tags)) {  // Avoid double Matches
1267
                        $tags[] = $fetch; // Fetch
1268
                    };
1269
                    $fetch = ''; // and reset
1270
                    $lc = 0;
1271
                    $rc = 0;
1272
                } else {
1273
                    $fetch .= $right;
1274
                }
1275
            } else {
1276
                if (0 < $lc) {
1277
                    $fetch .= $v;
1278
                } else {
1279
                    continue;
1280
                }
1281
            }
1282
        }
1283
        foreach($tags as $i=>$tag) {
1284
            if(strpos($tag,$spacer)!==false) $tags[$i] = str_replace($spacer, '', $tag);
1285
        }
1286
        return $tags;
1287
    }
1288
1289
    /**
1290
     * Merge content fields and TVs
1291
     *
1292
     * @param $content
1293
     * @param bool $ph
1294
     * @return string
1295
     * @internal param string $template
1296
     */
1297
    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...
1298
    {
1299 View Code Duplication
        if ($this->config['enable_at_syntax']) {
1300
            if (stripos($content, '<@LITERAL>') !== false) {
1301
                $content = $this->escapeLiteralTagsContent($content);
1302
            }
1303
        }
1304
        if (strpos($content, '[*') === false) {
1305
            return $content;
1306
        }
1307
        if (!isset($this->documentIdentifier)) {
1308
            return $content;
1309
        }
1310
        if (!isset($this->documentObject) || empty($this->documentObject)) {
1311
            return $content;
1312
        }
1313
1314
        if (!$ph) {
1315
            $ph = $this->documentObject;
1316
        }
1317
1318
        $matches = $this->getTagsFromContent($content, '[*', '*]');
1319
        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...
1320
            return $content;
1321
        }
1322
1323
        foreach ($matches[1] as $i => $key) {
1324
            if(strpos($key,'[+')!==false) continue; // Allow chunk {{chunk?&param=`xxx`}} with [*tv_name_[+param+]*] as content
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
1325
            if (substr($key, 0, 1) == '#') {
1326
                $key = substr($key, 1);
1327
            } // remove # for QuickEdit format
1328
1329
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1330
            if (strpos($key, '@') !== false) {
1331
                list($key, $context) = explode('@', $key, 2);
1332
            } else {
1333
                $context = false;
1334
            }
1335
1336
            // 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...
1337
            if ($context) {
1338
                $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...
1339
            } else {
1340
                $value = isset($ph[$key]) ? $ph[$key] : '';
1341
            }
1342
1343
            if (is_array($value)) {
1344
                include_once(MODX_MANAGER_PATH . 'includes/tmplvars.format.inc.php');
1345
                include_once(MODX_MANAGER_PATH . 'includes/tmplvars.commands.inc.php');
1346
                $value = getTVDisplayFormat($value[0], $value[1], $value[2], $value[3], $value[4]);
1347
            }
1348
1349
            $s = &$matches[0][$i];
1350
            if ($modifiers !== false) {
1351
                $value = $this->applyFilter($value, $modifiers, $key);
1352
            }
1353
1354 View Code Duplication
            if (strpos($content, $s) !== false) {
1355
                $content = str_replace($s, $value, $content);
1356
            } elseif($this->debug) {
1357
                $this->addLog('mergeDocumentContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1358
            }
1359
        }
1360
1361
        return $content;
1362
    }
1363
1364
    /**
1365
     * @param $key
1366
     * @param bool|int $parent
1367
     * @return bool|mixed|string
1368
     */
1369
    public function _contextValue($key, $parent = false)
1370
    {
1371
        if (preg_match('/@\d+\/u/', $key)) {
1372
            $key = str_replace(array('@', '/u'), array('@u(', ')'), $key);
1373
        }
1374
        list($key, $str) = explode('@', $key, 2);
1375
1376
        if (strpos($str, '(')) {
1377
            list($context, $option) = explode('(', $str, 2);
1378
        } else {
1379
            list($context, $option) = array($str, false);
1380
        }
1381
1382
        if ($option) {
1383
            $option = trim($option, ')(\'"`');
1384
        }
1385
1386
        switch (strtolower($context)) {
1387
            case 'site_start':
1388
                $docid = $this->config['site_start'];
1389
                break;
1390
            case 'parent':
1391
            case 'p':
1392
                $docid = $parent;
1393
                if ($docid == 0) {
1394
                    $docid = $this->config['site_start'];
1395
                }
1396
                break;
1397
            case 'ultimateparent':
1398
            case 'uparent':
1399
            case 'up':
1400
            case 'u':
1401 View Code Duplication
                if (strpos($str, '(') !== false) {
1402
                    $top = substr($str, strpos($str, '('));
1403
                    $top = trim($top, '()"\'');
1404
                } else {
1405
                    $top = 0;
1406
                }
1407
                $docid = $this->getUltimateParentId($this->documentIdentifier, $top);
1408
                break;
1409
            case 'alias':
1410
                $str = substr($str, strpos($str, '('));
1411
                $str = trim($str, '()"\'');
1412
                $docid = $this->getIdFromAlias($str);
1413
                break;
1414 View Code Duplication
            case 'prev':
1415
                if (!$option) {
1416
                    $option = 'menuindex,ASC';
1417
                } elseif (strpos($option, ',') === false) {
1418
                    $option .= ',ASC';
1419
                }
1420
                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...
1421
                $children = $this->getActiveChildren($parent, $by, $dir);
1422
                $find = false;
1423
                $prev = false;
1424
                foreach ($children as $row) {
1425
                    if ($row['id'] == $this->documentIdentifier) {
1426
                        $find = true;
1427
                        break;
1428
                    }
1429
                    $prev = $row;
1430
                }
1431
                if ($find) {
1432
                    if (isset($prev[$key])) {
1433
                        return $prev[$key];
1434
                    } else {
1435
                        $docid = $prev['id'];
1436
                    }
1437
                } else {
1438
                    $docid = '';
1439
                }
1440
                break;
1441 View Code Duplication
            case 'next':
1442
                if (!$option) {
1443
                    $option = 'menuindex,ASC';
1444
                } elseif (strpos($option, ',') === false) {
1445
                    $option .= ',ASC';
1446
                }
1447
                list($by, $dir) = explode(',', $option, 2);
1448
                $children = $this->getActiveChildren($parent, $by, $dir);
1449
                $find = false;
1450
                $next = false;
1451
                foreach ($children as $row) {
1452
                    if ($find) {
1453
                        $next = $row;
1454
                        break;
1455
                    }
1456
                    if ($row['id'] == $this->documentIdentifier) {
1457
                        $find = true;
1458
                    }
1459
                }
1460
                if ($find) {
1461
                    if (isset($next[$key])) {
1462
                        return $next[$key];
1463
                    } else {
1464
                        $docid = $next['id'];
1465
                    }
1466
                } else {
1467
                    $docid = '';
1468
                }
1469
                break;
1470
            default:
1471
                $docid = $str;
1472
        }
1473
        if (preg_match('@^[1-9][0-9]*$@', $docid)) {
1474
            $value = $this->getField($key, $docid);
1475
        } else {
1476
            $value = '';
1477
        }
1478
        return $value;
1479
    }
1480
1481
    /**
1482
     * Merge system settings
1483
     *
1484
     * @param $content
1485
     * @param bool|array $ph
1486
     * @return string
1487
     * @internal param string $template
1488
     */
1489
    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...
1490
    {
1491 View Code Duplication
        if ($this->config['enable_at_syntax']) {
1492
            if (stripos($content, '<@LITERAL>') !== false) {
1493
                $content = $this->escapeLiteralTagsContent($content);
1494
            }
1495
        }
1496
        if (strpos($content, '[(') === false) {
1497
            return $content;
1498
        }
1499
1500
        if (empty($ph)) {
1501
            $ph = $this->config;
1502
        }
1503
1504
        $matches = $this->getTagsFromContent($content, '[(', ')]');
1505
        if (empty($matches)) {
1506
            return $content;
1507
        }
1508
1509
        foreach ($matches[1] as $i => $key) {
1510
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1511
1512
            if (isset($ph[$key])) {
1513
                $value = $ph[$key];
1514
            } else {
1515
                continue;
1516
            }
1517
1518
            if ($modifiers !== false) {
1519
                $value = $this->applyFilter($value, $modifiers, $key);
1520
            }
1521
            $s = &$matches[0][$i];
1522 View Code Duplication
            if (strpos($content, $s) !== false) {
1523
                $content = str_replace($s, $value, $content);
1524
            } elseif($this->debug) {
1525
                $this->addLog('mergeSettingsContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1526
            }
1527
        }
1528
        return $content;
1529
    }
1530
1531
    /**
1532
     * Merge chunks
1533
     *
1534
     * @param string $content
1535
     * @param bool|array $ph
1536
     * @return string
1537
     */
1538
    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...
1539
    {
1540
        if ($this->config['enable_at_syntax']) {
1541
            if (strpos($content, '{{ ') !== false) {
1542
                $content = str_replace(array('{{ ', ' }}'), array('\{\{ ', ' \}\}'), $content);
1543
            }
1544
            if (stripos($content, '<@LITERAL>') !== false) {
1545
                $content = $this->escapeLiteralTagsContent($content);
1546
            }
1547
        }
1548
        if (strpos($content, '{{') === false) {
1549
            return $content;
1550
        }
1551
1552
        if (empty($ph)) {
1553
            $ph = $this->chunkCache;
1554
        }
1555
1556
        $matches = $this->getTagsFromContent($content, '{{', '}}');
1557
        if (empty($matches)) {
1558
            return $content;
1559
        }
1560
1561
        foreach ($matches[1] as $i => $key) {
1562
            $snip_call = $this->_split_snip_call($key);
1563
            $key = $snip_call['name'];
1564
            $params = $this->getParamsFromString($snip_call['params']);
1565
1566
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1567
1568
            if (!isset($ph[$key])) {
1569
                $ph[$key] = $this->getChunk($key);
1570
            }
1571
            $value = $ph[$key];
1572
1573
            if (is_null($value)) {
1574
                continue;
1575
            }
1576
1577
            $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 1564 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...
1578
            $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 1564 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...
1579
            if ($this->config['enable_at_syntax']) {
1580
                $value = $this->mergeConditionalTagsContent($value);
1581
            }
1582
            $value = $this->mergeDocumentContent($value);
1583
            $value = $this->mergeSettingsContent($value);
1584
            $value = $this->mergeChunkContent($value);
1585
1586
            if ($modifiers !== false) {
1587
                $value = $this->applyFilter($value, $modifiers, $key);
1588
            }
1589
1590
            $s = &$matches[0][$i];
1591 View Code Duplication
            if (strpos($content, $s) !== false) {
1592
                $content = str_replace($s, $value, $content);
1593
            } elseif($this->debug) {
1594
                $this->addLog('mergeChunkContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1595
            }
1596
        }
1597
        return $content;
1598
    }
1599
1600
    /**
1601
     * Merge placeholder values
1602
     *
1603
     * @param string $content
1604
     * @param bool|array $ph
1605
     * @return string
1606
     */
1607
    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...
1608
    {
1609
1610 View Code Duplication
        if ($this->config['enable_at_syntax']) {
1611
            if (stripos($content, '<@LITERAL>') !== false) {
1612
                $content = $this->escapeLiteralTagsContent($content);
1613
            }
1614
        }
1615
        if (strpos($content, '[+') === false) {
1616
            return $content;
1617
        }
1618
1619
        if (empty($ph)) {
1620
            $ph = $this->placeholders;
1621
        }
1622
1623
        if ($this->config['enable_at_syntax']) {
1624
            $content = $this->mergeConditionalTagsContent($content);
1625
        }
1626
1627
        $content = $this->mergeDocumentContent($content);
1628
        $content = $this->mergeSettingsContent($content);
1629
        $matches = $this->getTagsFromContent($content, '[+', '+]');
1630
        if (empty($matches)) {
1631
            return $content;
1632
        }
1633
        foreach ($matches[1] as $i => $key) {
1634
1635
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1636
1637
            if (isset($ph[$key])) {
1638
                $value = $ph[$key];
1639
            } elseif ($key === 'phx') {
1640
                $value = '';
1641
            } else {
1642
                continue;
1643
            }
1644
1645
            if ($modifiers !== false) {
1646
                $modifiers = $this->mergePlaceholderContent($modifiers);
1647
                $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...
1648
            }
1649
            $s = &$matches[0][$i];
1650 View Code Duplication
            if (strpos($content, $s) !== false) {
1651
                $content = str_replace($s, $value, $content);
1652
            } elseif($this->debug) {
1653
                $this->addLog('mergePlaceholderContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1654
            }
1655
        }
1656
        return $content;
1657
    }
1658
1659
    /**
1660
     * @param $content
1661
     * @param string $iftag
1662
     * @param string $elseiftag
1663
     * @param string $elsetag
1664
     * @param string $endiftag
1665
     * @return mixed|string
1666
     */
1667
    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...
1668
    {
1669
        if (strpos($content, '@IF') !== false) {
1670
            $content = $this->_prepareCTag($content, $iftag, $elseiftag, $elsetag, $endiftag);
1671
        }
1672
1673
        if (strpos($content, $iftag) === false) {
1674
            return $content;
1675
        }
1676
1677
        $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...
1678
        $content = str_replace(array('<?php', '<?=', '<?', '?>'), array("{$sp}b", "{$sp}p", "{$sp}s", "{$sp}e"), $content);
1679
1680
        $pieces = explode('<@IF:', $content);
1681 View Code Duplication
        foreach ($pieces as $i => $split) {
1682
            if ($i === 0) {
1683
                $content = $split;
1684
                continue;
1685
            }
1686
            list($cmd, $text) = explode('>', $split, 2);
1687
            $cmd = str_replace("'", "\'", $cmd);
1688
            $content .= "<?php if(\$this->_parseCTagCMD('" . $cmd . "')): ?>";
1689
            $content .= $text;
1690
        }
1691
        $pieces = explode('<@ELSEIF:', $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 elseif(\$this->_parseCTagCMD('" . $cmd . "')): ?>";
1700
            $content .= $text;
1701
        }
1702
1703
        $content = str_replace(array('<@ELSE>', '<@ENDIF>'), array('<?php else:?>', '<?php endif;?>'), $content);
1704
        ob_start();
1705
        $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...
1706
        $content = ob_get_clean();
1707
        $content = str_replace(array("{$sp}b", "{$sp}p", "{$sp}s", "{$sp}e"), array('<?php', '<?=', '<?', '?>'), $content);
1708
1709
        return $content;
1710
    }
1711
1712
    /**
1713
     * @param $content
1714
     * @param string $iftag
1715
     * @param string $elseiftag
1716
     * @param string $elsetag
1717
     * @param string $endiftag
1718
     * @return mixed
1719
     */
1720
    private function _prepareCTag($content, $iftag = '<@IF:', $elseiftag = '<@ELSEIF:', $elsetag = '<@ELSE>', $endiftag = '<@ENDIF>')
1721
    {
1722
        if (strpos($content, '<!--@IF ') !== false) {
1723
            $content = str_replace('<!--@IF ', $iftag, $content);
1724
        } // for jp
1725
        if (strpos($content, '<!--@IF:') !== false) {
1726
            $content = str_replace('<!--@IF:', $iftag, $content);
1727
        }
1728
        if (strpos($content, $iftag) === false) {
1729
            return $content;
1730
        }
1731
        if (strpos($content, '<!--@ELSEIF:') !== false) {
1732
            $content = str_replace('<!--@ELSEIF:', $elseiftag, $content);
1733
        } // for jp
1734
        if (strpos($content, '<!--@ELSE-->') !== false) {
1735
            $content = str_replace('<!--@ELSE-->', $elsetag, $content);
1736
        }  // for jp
1737
        if (strpos($content, '<!--@ENDIF-->') !== false) {
1738
            $content = str_replace('<!--@ENDIF-->', $endiftag, $content);
1739
        }    // for jp
1740
        if (strpos($content, '<@ENDIF-->') !== false) {
1741
            $content = str_replace('<@ENDIF-->', $endiftag, $content);
1742
        }
1743
        $tags = array($iftag, $elseiftag, $elsetag, $endiftag);
1744
        $content = str_ireplace($tags, $tags, $content); // Change to capital letters
1745
        return $content;
1746
    }
1747
1748
    /**
1749
     * @param $cmd
1750
     * @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...
1751
     */
1752
    private function _parseCTagCMD($cmd)
1753
    {
1754
        $cmd = trim($cmd);
1755
        $reverse = substr($cmd, 0, 1) === '!' ? true : false;
1756
        if ($reverse) {
1757
            $cmd = ltrim($cmd, '!');
1758
        }
1759
        if (strpos($cmd, '[!') !== false) {
1760
            $cmd = str_replace(array('[!', '!]'), array('[[', ']]'), $cmd);
1761
        }
1762
        $safe = 0;
1763
        while ($safe < 20) {
1764
            $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...
1765
            if (strpos($cmd, '[*') !== false) {
1766
                $cmd = $this->mergeDocumentContent($cmd);
1767
            }
1768
            if (strpos($cmd, '[(') !== false) {
1769
                $cmd = $this->mergeSettingsContent($cmd);
1770
            }
1771
            if (strpos($cmd, '{{') !== false) {
1772
                $cmd = $this->mergeChunkContent($cmd);
1773
            }
1774
            if (strpos($cmd, '[[') !== false) {
1775
                $cmd = $this->evalSnippets($cmd);
1776
            }
1777
            if (strpos($cmd, '[+') !== false && strpos($cmd, '[[') === false) {
1778
                $cmd = $this->mergePlaceholderContent($cmd);
1779
            }
1780
            if ($bt === md5($cmd)) {
1781
                break;
1782
            }
1783
            $safe++;
1784
        }
1785
        $cmd = ltrim($cmd);
1786
        $cmd = rtrim($cmd, '-');
1787
        $cmd = str_ireplace(array(' and ', ' or '), array('&&', '||'), $cmd);
1788
1789
        if (!preg_match('@^[0-9]*$@', $cmd) && preg_match('@^[0-9<= \-\+\*/\(\)%!&|]*$@', $cmd)) {
1790
            $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...
1791
        } else {
1792
            $_ = 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...
1793
            foreach ($_ as $left) {
1794
                if (strpos($cmd, $left) !== false) {
1795
                    $cmd = 0;
1796
                    break;
1797
                }
1798
            }
1799
        }
1800
        $cmd = trim($cmd);
1801
        if (!preg_match('@^[0-9]+$@', $cmd)) {
1802
            $cmd = empty($cmd) ? 0 : 1;
1803
        } elseif ($cmd <= 0) {
1804
            $cmd = 0;
1805
        }
1806
1807
        if ($reverse) {
1808
            $cmd = !$cmd;
1809
        }
1810
1811
        return $cmd;
1812
    }
1813
1814
    /**
1815
     * Remove Comment-Tags from output like <!--@- Comment -@-->
1816
     * @param $content
1817
     * @param string $left
1818
     * @param string $right
1819
     * @return mixed
1820
     */
1821
    function ignoreCommentedTagsContent($content, $left = '<!--@-', $right = '-@-->')
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1822
    {
1823
        if (strpos($content, $left) === false) {
1824
            return $content;
1825
        }
1826
1827
        $matches = $this->getTagsFromContent($content, $left, $right);
1828
        if (!empty($matches)) {
1829
            foreach ($matches[0] as $i => $v) {
1830
                $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...
1831
            }
1832
            $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...
1833
            if (strpos($content, $left) !== false) {
1834
                $content = str_replace($matches[0], '', $content);
1835
            }
1836
        }
1837
        return $content;
1838
    }
1839
1840
    /**
1841
     * @param $content
1842
     * @param string $left
1843
     * @param string $right
1844
     * @return mixed
1845
     */
1846
    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...
1847
    {
1848
        if (stripos($content, $left) === false) {
1849
            return $content;
1850
        }
1851
1852
        $matches = $this->getTagsFromContent($content, $left, $right);
1853
        if (empty($matches)) {
1854
            return $content;
1855
        }
1856
1857
        list($sTags, $rTags) = $this->getTagsForEscape();
1858
        foreach ($matches[1] as $i => $v) {
1859
            $v = str_ireplace($sTags, $rTags, $v);
1860
            $s = &$matches[0][$i];
1861 View Code Duplication
            if (strpos($content, $s) !== false) {
1862
                $content = str_replace($s, $v, $content);
1863
            } elseif($this->debug) {
1864
                $this->addLog('ignoreCommentedTagsContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1865
            }
1866
        }
1867
        return $content;
1868
    }
1869
1870
    /**
1871
     * Detect PHP error according to MODX error level
1872
     *
1873
     * @param integer $error PHP error level
1874
     * @return boolean Error detected
1875
     */
1876
1877
    public function detectError($error)
1878
    {
1879
        $detected = false;
1880
        if ($this->config['error_reporting'] == 99 && $error) {
1881
            $detected = true;
1882
        } elseif ($this->config['error_reporting'] == 2 && ($error & ~E_NOTICE)) {
1883
            $detected = true;
1884
        } elseif ($this->config['error_reporting'] == 1 && ($error & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT)) {
1885
            $detected = true;
1886
        }
1887
        return $detected;
1888
    }
1889
1890
    /**
1891
     * Run a plugin
1892
     *
1893
     * @param string $pluginCode Code to run
1894
     * @param array $params
1895
     */
1896
    public function evalPlugin($pluginCode, $params)
1897
    {
1898
        $modx = &$this;
1899
        $modx->event->params = &$params; // store params inside event object
1900
        if (is_array($params)) {
1901
            extract($params, EXTR_SKIP);
1902
        }
1903
        /* 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...
1904
        // 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.
1905
        // Related to https://github.com/modxcms/evolution/issues/1130
1906
        $lock_file_path = MODX_BASE_PATH . 'assets/cache/lock_' . str_replace(' ','-',strtolower($this->event->activePlugin)) . '.pageCache.php';
1907
        if($this->isBackend()) {
1908
            if(is_file($lock_file_path)) {
1909
                $msg = sprintf("Plugin parse error, Temporarily disabled '%s'.", $this->event->activePlugin);
1910
                $this->logEvent(0, 3, $msg, $msg);
1911
                return;
1912
            }
1913
            elseif(stripos($this->event->activePlugin,'ElementsInTree')===false) touch($lock_file_path);
1914
        }*/
1915
        ob_start();
1916
        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...
1917
        $msg = ob_get_contents();
1918
        ob_end_clean();
1919
        // When reached here, no fatal error occured so the lock should be removed.
1920
        /*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...
1921
1922 View Code Duplication
        if ((0 < $this->config['error_reporting']) && $msg && isset($php_errormsg)) {
1923
            $error_info = error_get_last();
1924
            if ($this->detectError($error_info['type'])) {
1925
                $msg = ($msg === false) ? 'ob_get_contents() error' : $msg;
1926
                $this->messageQuit('PHP Parse Error', '', true, $error_info['type'], $error_info['file'], 'Plugin', $error_info['message'], $error_info['line'], $msg);
1927
                if ($this->isBackend()) {
1928
                    $this->event->alert('An error occurred while loading. Please see the event log for more information.<p>' . $msg . '</p>');
1929
                }
1930
            }
1931
        } else {
1932
            echo $msg;
1933
        }
1934
        unset($modx->event->params);
1935
    }
1936
1937
    /**
1938
     * Run a snippet
1939
     *
1940
     * @param $phpcode
1941
     * @param array $params
1942
     * @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...
1943
     * @internal param string $snippet Code to run
1944
     */
1945
    public function evalSnippet($phpcode, $params)
1946
    {
1947
        $modx = &$this;
1948
        /*
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...
1949
        if(isset($params) && is_array($params)) {
1950
            foreach($params as $k=>$v) {
1951
                $v = strtolower($v);
1952
                if($v==='false')    $params[$k] = false;
1953
                elseif($v==='true') $params[$k] = true;
1954
            }
1955
        }*/
1956
        $modx->event->params = &$params; // store params inside event object
1957
        if (is_array($params)) {
1958
            extract($params, EXTR_SKIP);
1959
        }
1960
        ob_start();
1961
        if (strpos($phpcode, ';') !== false) {
1962
            $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...
1963
        } else {
1964
            $return = call_user_func_array($phpcode, array($params));
1965
        }
1966
        $echo = ob_get_contents();
1967
        ob_end_clean();
1968 View Code Duplication
        if ((0 < $this->config['error_reporting']) && isset($php_errormsg)) {
1969
            $error_info = error_get_last();
1970
            if ($this->detectError($error_info['type'])) {
1971
                $echo = ($echo === false) ? 'ob_get_contents() error' : $echo;
1972
                $this->messageQuit('PHP Parse Error', '', true, $error_info['type'], $error_info['file'], 'Snippet', $error_info['message'], $error_info['line'], $echo);
1973
                if ($this->isBackend()) {
1974
                    $this->event->alert('An error occurred while loading. Please see the event log for more information<p>' . $echo . $return . '</p>');
1975
                }
1976
            }
1977
        }
1978
        unset($modx->event->params);
1979
        if (is_array($return) || is_object($return)) {
1980
            return $return;
1981
        } else {
1982
            return $echo . $return;
1983
        }
1984
    }
1985
1986
    /**
1987
     * Run snippets as per the tags in $documentSource and replace the tags with the returned values.
1988
     *
1989
     * @param $content
1990
     * @return string
1991
     * @internal param string $documentSource
1992
     */
1993
    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...
1994
    {
1995
        if (strpos($content, '[[') === false) {
1996
            return $content;
1997
        }
1998
1999
        $matches = $this->getTagsFromContent($content, '[[', ']]');
2000
2001
        if (empty($matches)) {
2002
            return $content;
2003
        }
2004
2005
        $this->snipLapCount++;
2006
        if ($this->dumpSnippets) {
2007
            $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);
2008
        }
2009
2010
        foreach ($matches[1] as $i => $call) {
2011
            $s = &$matches[0][$i];
2012
            if (substr($call, 0, 2) === '$_') {
2013
                if (strpos($content, '_PHX_INTERNAL_') === false) {
2014
                    $value = $this->_getSGVar($call);
2015
                } else {
2016
                    $value = $s;
2017
                }
2018 View Code Duplication
                if (strpos($content, $s) !== false) {
2019
                    $content = str_replace($s, $value, $content);
2020
                } elseif($this->debug) {
2021
                    $this->addLog('evalSnippetsSGVar parse error', $_SERVER['REQUEST_URI'] . $s, 2);
2022
                }
2023
                continue;
2024
            }
2025
            $value = $this->_get_snip_result($call);
2026
            if (is_null($value)) {
2027
                continue;
2028
            }
2029
2030 View Code Duplication
            if (strpos($content, $s) !== false) {
2031
                $content = str_replace($s, $value, $content);
2032
            } elseif($this->debug) {
2033
                $this->addLog('evalSnippets parse error', $_SERVER['REQUEST_URI'] . $s, 2);
2034
            }
2035
        }
2036
2037
        if ($this->dumpSnippets) {
2038
            $this->snippetsCode .= '</fieldset><br />';
2039
        }
2040
2041
        return $content;
2042
    }
2043
2044
    /**
2045
     * @param $value
2046
     * @return mixed|string
2047
     */
2048
    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...
2049
    { // Get super globals
2050
        $key = $value;
2051
        $_ = $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...
2052
        $this->config['enable_filter'] = 1;
2053
        list($key, $modifiers) = $this->splitKeyAndFilter($key);
2054
        $this->config['enable_filter'] = $_;
2055
        $key = str_replace(array('(', ')'), array("['", "']"), $key);
2056
        $key = rtrim($key, ';');
2057
        if (strpos($key, '$_SESSION') !== false) {
2058
            $_ = $_SESSION;
2059
            $key = str_replace('$_SESSION', '$_', $key);
2060
            if (isset($_['mgrFormValues'])) {
2061
                unset($_['mgrFormValues']);
2062
            }
2063
            if (isset($_['token'])) {
2064
                unset($_['token']);
2065
            }
2066
        }
2067
        if (strpos($key, '[') !== false) {
2068
            $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...
2069
        } 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...
2070
            $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...
2071
        } else {
2072
            $value = '';
2073
        }
2074
        if ($modifiers !== false) {
2075
            $value = $this->applyFilter($value, $modifiers, $key);
2076
        }
2077
        return $value;
2078
    }
2079
2080
    /**
2081
     * @param $piece
2082
     * @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...
2083
     */
2084
    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...
2085
    {
2086
        if (ltrim($piece) !== $piece) {
2087
            return '';
2088
        }
2089
2090
        $eventtime = $this->dumpSnippets ? $this->getMicroTime() : 0;
2091
2092
        $snip_call = $this->_split_snip_call($piece);
2093
        $key = $snip_call['name'];
2094
2095
        list($key, $modifiers) = $this->splitKeyAndFilter($key);
2096
        $snip_call['name'] = $key;
2097
        $snippetObject = $this->_getSnippetObject($key);
2098
        if (is_null($snippetObject['content'])) {
2099
            return null;
2100
        }
2101
2102
        $this->currentSnippet = $snippetObject['name'];
2103
2104
        // current params
2105
        $params = $this->getParamsFromString($snip_call['params']);
2106
2107
        if (!isset($snippetObject['properties'])) {
2108
            $snippetObject['properties'] = '';
2109
        }
2110
        $default_params = $this->parseProperties($snippetObject['properties'], $this->currentSnippet, 'snippet');
2111
        $params = array_merge($default_params, $params);
2112
2113
        $value = $this->evalSnippet($snippetObject['content'], $params);
2114
        $this->currentSnippet = '';
2115
        if ($modifiers !== false) {
2116
            $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...
2117
        }
2118
2119
        if ($this->dumpSnippets) {
2120
            $eventtime = $this->getMicroTime() - $eventtime;
2121
            $eventtime = sprintf('%2.2f ms', $eventtime * 1000);
2122
            $code = str_replace("\t", '  ', $this->htmlspecialchars($value));
2123
            $piece = str_replace("\t", '  ', $this->htmlspecialchars($piece));
2124
            $print_r_params = str_replace("\t", '  ', $this->htmlspecialchars('$modx->event->params = ' . print_r($params, true)));
2125
            $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);
2126
            $this->snippetsTime[] = array('sname' => $key, 'time' => $eventtime);
2127
        }
2128
        return $value;
2129
    }
2130
2131
    /**
2132
     * @param string $string
2133
     * @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...
2134
     */
2135
    public function getParamsFromString($string = '')
2136
    {
2137
        if (empty($string)) {
2138
            return array();
2139
        }
2140
2141
        if (strpos($string, '&_PHX_INTERNAL_') !== false) {
2142
            $string = str_replace(array('&_PHX_INTERNAL_091_&', '&_PHX_INTERNAL_093_&'), array('[', ']'), $string);
2143
        }
2144
2145
        $_ = $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...
2146
        $this->documentOutput = $string;
2147
        $this->invokeEvent('OnBeforeParseParams');
2148
        $string = $this->documentOutput;
2149
        $this->documentOutput = $_;
2150
2151
        $_tmp = $string;
2152
        $_tmp = ltrim($_tmp, '?&');
2153
        $temp_params = array();
2154
        $key = '';
2155
        $value = null;
2156
        while ($_tmp !== '') {
2157
            $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...
2158
            $char = substr($_tmp, 0, 1);
2159
            $_tmp = substr($_tmp, 1);
2160
2161
            if ($char === '=') {
2162
                $_tmp = trim($_tmp);
2163
                $delim = substr($_tmp, 0, 1);
2164
                if (in_array($delim, array('"', "'", '`'))) {
2165
                    $null = null;
2166
                    //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...
2167
                    list($null, $value, $_tmp) = explode($delim, $_tmp, 3);
2168
                    unset($null);
2169
2170
                    if (substr(trim($_tmp), 0, 2) === '//') {
2171
                        $_tmp = strstr(trim($_tmp), "\n");
2172
                    }
2173
                    $i = 0;
2174
                    while ($delim === '`' && substr(trim($_tmp), 0, 1) !== '&' && 1 < substr_count($_tmp, '`')) {
2175
                        list($inner, $outer, $_tmp) = explode('`', $_tmp, 3);
2176
                        $value .= "`{$inner}`{$outer}";
2177
                        $i++;
2178
                        if (100 < $i) {
2179
                            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...
2180
                        }
2181
                    }
2182
                    if ($i && $delim === '`') {
2183
                        $value = rtrim($value, '`');
2184
                    }
2185
                } elseif (strpos($_tmp, '&') !== false) {
2186
                    list($value, $_tmp) = explode('&', $_tmp, 2);
2187
                    $value = trim($value);
2188
                } else {
2189
                    $value = $_tmp;
2190
                    $_tmp = '';
2191
                }
2192
            } elseif ($char === '&') {
2193
                if (trim($key) !== '') {
2194
                    $value = '1';
2195
                } else {
2196
                    continue;
2197
                }
2198
            } elseif ($_tmp === '') {
2199
                $key .= $char;
2200
                $value = '1';
2201
            } elseif ($key !== '' || trim($char) !== '') {
2202
                $key .= $char;
2203
            }
2204
2205
            if (isset($value) && !is_null($value)) {
2206
                if (strpos($key, 'amp;') !== false) {
2207
                    $key = str_replace('amp;', '', $key);
2208
                }
2209
                $key = trim($key);
2210 View Code Duplication
                if (strpos($value, '[!') !== false) {
2211
                    $value = str_replace(array('[!', '!]'), array('[[', ']]'), $value);
2212
                }
2213
                $value = $this->mergeDocumentContent($value);
2214
                $value = $this->mergeSettingsContent($value);
2215
                $value = $this->mergeChunkContent($value);
2216
                $value = $this->evalSnippets($value);
2217
                if (substr($value, 0, 6) !== '@CODE:') {
2218
                    $value = $this->mergePlaceholderContent($value);
2219
                }
2220
2221
                $temp_params[][$key] = $value;
2222
2223
                $key = '';
2224
                $value = null;
2225
2226
                $_tmp = ltrim($_tmp, " ,\t");
2227
                if (substr($_tmp, 0, 2) === '//') {
2228
                    $_tmp = strstr($_tmp, "\n");
2229
                }
2230
            }
2231
2232
            if ($_tmp === $bt) {
2233
                $key = trim($key);
2234
                if ($key !== '') {
2235
                    $temp_params[][$key] = '';
2236
                }
2237
                break;
2238
            }
2239
        }
2240
2241
        foreach ($temp_params as $p) {
2242
            $k = key($p);
2243
            if (substr($k, -2) === '[]') {
2244
                $k = substr($k, 0, -2);
2245
                $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...
2246
            } elseif (strpos($k, '[') !== false && substr($k, -1) === ']') {
2247
                list($k, $subk) = explode('[', $k, 2);
2248
                $subk = substr($subk, 0, -1);
2249
                $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...
2250
            } else {
2251
                $params[$k] = current($p);
2252
            }
2253
        }
2254
        return $params;
2255
    }
2256
2257
    /**
2258
     * @param $str
2259
     * @return bool|int
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use integer|false.

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

Loading history...
2260
     */
2261
    public function _getSplitPosition($str)
2262
    {
2263
        $closeOpt = false;
2264
        $maybePos = false;
2265
        $inFilter = false;
2266
        $total = strlen($str);
2267
        for ($i = 0; $i < $total; $i++) {
2268
            $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...
2269
            $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...
2270
            if (!$inFilter) {
2271
                if ($c === ':') {
2272
                    $inFilter = true;
2273
                } elseif ($c === '?') {
2274
                    $pos = $i;
2275
                } elseif ($c === ' ') {
2276
                    $maybePos = $i;
2277
                } 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...
2278
                    $pos = $maybePos;
2279
                } elseif ($c === "\n") {
2280
                    $pos = $i;
2281
                } else {
2282
                    $pos = false;
2283
                }
2284
            } else {
2285
                if ($cc == $closeOpt) {
2286
                    $closeOpt = false;
2287
                } elseif ($c == $closeOpt) {
2288
                    $closeOpt = false;
2289
                } 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...
2290
                    continue;
2291
                } elseif ($cc === "('") {
2292
                    $closeOpt = "')";
2293
                } elseif ($cc === '("') {
2294
                    $closeOpt = '")';
2295
                } elseif ($cc === '(`') {
2296
                    $closeOpt = '`)';
2297
                } elseif ($c === '(') {
2298
                    $closeOpt = ')';
2299
                } elseif ($c === '?') {
2300
                    $pos = $i;
2301
                } elseif ($c === ' ' && strpos($str, '?') === false) {
2302
                    $pos = $i;
2303
                } else {
2304
                    $pos = false;
2305
                }
2306
            }
2307
            if ($pos) {
0 ignored issues
show
Bug introduced by
The variable $pos does not seem to be defined for all execution paths leading up to this point.

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

Let’s take a look at an example:

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

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

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

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

Available Fixes

  1. Check for existence of the variable explicitly:

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

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

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug Best Practice introduced by
The expression $pos of type integer|false is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

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

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

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

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
2308
                break;
2309
            }
2310
        }
2311
        return $pos;
2312
    }
2313
2314
    /**
2315
     * @param $call
2316
     * @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...
2317
     */
2318
    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...
2319
    {
2320
        $spacer = md5('dummy');
2321 View Code Duplication
        if (strpos($call, ']]>') !== false) {
2322
            $call = str_replace(']]>', "]{$spacer}]>", $call);
2323
        }
2324
2325
        $splitPosition = $this->_getSplitPosition($call);
2326
2327
        if ($splitPosition !== false) {
2328
            $name = substr($call, 0, $splitPosition);
2329
            $params = substr($call, $splitPosition + 1);
2330
        } else {
2331
            $name = $call;
2332
            $params = '';
2333
        }
2334
2335
        $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...
2336 View Code Duplication
        if (strpos($params, $spacer) !== false) {
2337
            $params = str_replace("]{$spacer}]>", ']]>', $params);
2338
        }
2339
        $snip['params'] = ltrim($params, "?& \t\n");
2340
2341
        return $snip;
2342
    }
2343
2344
    /**
2345
     * @param $snip_name
2346
     * @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...
2347
     */
2348
    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...
2349
    {
2350
        if (isset($this->snippetCache[$snip_name])) {
2351
            $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...
2352
            $snippetObject['content'] = $this->snippetCache[$snip_name];
2353
            if (isset($this->snippetCache["{$snip_name}Props"])) {
2354
                if (!isset($this->snippetCache["{$snip_name}Props"])) {
2355
                    $this->snippetCache["{$snip_name}Props"] = '';
2356
                }
2357
                $snippetObject['properties'] = $this->snippetCache["{$snip_name}Props"];
2358
            }
2359
        } elseif (substr($snip_name, 0, 1) === '@' && isset($this->pluginEvent[trim($snip_name, '@')])) {
2360
            $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...
2361
            $snippetObject['content'] = sprintf('$rs=$this->invokeEvent("%s",$params);echo trim(implode("",$rs));', trim($snip_name, '@'));
2362
            $snippetObject['properties'] = '';
2363
        } else {
2364
            $where = sprintf("name='%s' AND disabled=0", $this->db->escape($snip_name));
2365
            $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...
2366
            $count = $this->db->getRecordCount($rs);
2367
            if (1 < $count) {
2368
                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...
2369
            }
2370
            if ($count) {
2371
                $row = $this->db->getRow($rs);
2372
                $snip_content = $row['snippet'];
2373
                $snip_prop = $row['properties'];
2374
            } else {
2375
                $snip_content = null;
2376
                $snip_prop = '';
2377
            }
2378
            $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...
2379
            $snippetObject['content'] = $snip_content;
2380
            $snippetObject['properties'] = $snip_prop;
2381
            $this->snippetCache[$snip_name] = $snip_content;
2382
            $this->snippetCache["{$snip_name}Props"] = $snip_prop;
2383
        }
2384
        return $snippetObject;
2385
    }
2386
2387
    /**
2388
     * @param $text
2389
     * @return mixed
2390
     */
2391
    public function toAlias($text)
2392
    {
2393
        $suff = $this->config['friendly_url_suffix'];
2394
        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);
2395
    }
2396
2397
    /**
2398
     * makeFriendlyURL
2399
     *
2400
     * @desc Create an URL.
2401
     *
2402
     * @param $pre {string} - Friendly URL Prefix. @required
2403
     * @param $suff {string} - Friendly URL Suffix. @required
2404
     * @param $alias {string} - Full document path. @required
2405
     * @param int $isfolder {0; 1}
2406
     * - Is it a folder? Default: 0.
2407
     * @param int $id {integer}
2408
     * - Document id. Default: 0.
2409
     * @return mixed|string {string} - Result URL.
2410
     * - Result URL.
2411
     */
2412
    public function makeFriendlyURL($pre, $suff, $alias, $isfolder = 0, $id = 0)
2413
    {
2414
        if ($id == $this->config['site_start'] && $this->config['seostrict'] === '1') {
2415
            $url = $this->config['base_url'];
2416
        } else {
2417
            $Alias = explode('/', $alias);
2418
            $alias = array_pop($Alias);
2419
            $dir = implode('/', $Alias);
2420
            unset($Alias);
2421
2422
            if ($this->config['make_folders'] === '1' && $isfolder == 1) {
2423
                $suff = '/';
2424
            }
2425
2426
            $url = ($dir != '' ? $dir . '/' : '') . $pre . $alias . $suff;
2427
        }
2428
2429
        $evtOut = $this->invokeEvent('OnMakeDocUrl', array(
2430
            'id' => $id,
2431
            'url' => $url
2432
        ));
2433
2434
        if (is_array($evtOut) && count($evtOut) > 0) {
2435
            $url = array_pop($evtOut);
2436
        }
2437
2438
        return $url;
2439
    }
2440
2441
    /**
2442
     * Convert URL tags [~...~] to URLs
2443
     *
2444
     * @param string $documentSource
2445
     * @return string
2446
     */
2447
    public function rewriteUrls($documentSource)
2448
    {
2449
        // rewrite the urls
2450
        if ($this->config['friendly_urls'] == 1) {
2451
            $aliases = array();
2452
            if (is_array($this->documentListing)) {
2453
                foreach ($this->documentListing as $path => $docid) { // This is big Loop on large site!
2454
                    $aliases[$docid] = $path;
2455
                    $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...
2456
                }
2457
            }
2458
2459
            if ($this->config['aliaslistingfolder'] == 1) {
2460
                preg_match_all('!\[\~([0-9]+)\~\]!ise', $documentSource, $match);
2461
                $ids = implode(',', array_unique($match['1']));
2462
                if ($ids) {
2463
                    $res = $this->db->select("id,alias,isfolder,parent,alias_visible", $this->getFullTableName('site_content'), "id IN (" . $ids . ") AND isfolder = '0'");
2464
                    while ($row = $this->db->getRow($res)) {
2465
                        if ($this->config['use_alias_path'] == '1' && $row['parent'] != 0) {
2466
                            $parent = $row['parent'];
2467
                            $path = $aliases[$parent];
2468
2469
                            while (isset($this->aliasListing[$parent]) && $this->aliasListing[$parent]['alias_visible'] == 0) {
2470
                                $path = $this->aliasListing[$parent]['path'];
2471
                                $parent = $this->aliasListing[$parent]['parent'];
2472
                            }
2473
2474
                            $aliases[$row['id']] = $path . '/' . $row['alias'];
2475
                        } else {
2476
                            $aliases[$row['id']] = $row['alias'];
2477
                        }
2478
                        $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...
2479
                    }
2480
                }
2481
            }
2482
            $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...
2483
            $isfriendly = ($this->config['friendly_alias_urls'] == 1 ? 1 : 0);
2484
            $pref = $this->config['friendly_url_prefix'];
2485
            $suff = $this->config['friendly_url_suffix'];
2486
            $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...
2487
                global $modx;
2488
                $thealias = $aliases[$m[1]];
2489
                $thefolder = $isfolder[$m[1]];
2490
                if ($isfriendly && isset($thealias)) {
2491
                    //found friendly url
2492
                    $out = ($modx->config['seostrict'] == '1' ? $modx->toAlias($modx->makeFriendlyURL($pref, $suff, $thealias, $thefolder, $m[1])) : $modx->makeFriendlyURL($pref, $suff, $thealias, $thefolder, $m[1]));
2493
                } else {
2494
                    //not found friendly url
2495
                    $out = $modx->makeFriendlyURL($pref, $suff, $m[1]);
2496
                }
2497
                return $out;
2498
            }, $documentSource);
2499
2500
        } else {
2501
            $in = '!\[\~([0-9]+)\~\]!is';
2502
            $out = "index.php?id=" . '\1';
2503
            $documentSource = preg_replace($in, $out, $documentSource);
2504
        }
2505
2506
        return $documentSource;
2507
    }
2508
2509
    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...
2510
    {
2511
        $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...
2512
        // FIX URLs
2513
        if (empty($this->documentIdentifier) || $this->config['seostrict'] == '0' || $this->config['friendly_urls'] == '0') {
2514
            return;
2515
        }
2516
        if ($this->config['site_status'] == 0) {
2517
            return;
2518
        }
2519
2520
        $scheme = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https' : 'http';
2521
        $len_base_url = strlen($this->config['base_url']);
2522
2523
        $url_path = $q;//LANG
2524
2525 View Code Duplication
        if (substr($url_path, 0, $len_base_url) === $this->config['base_url']) {
2526
            $url_path = substr($url_path, $len_base_url);
2527
        }
2528
2529
        $strictURL = $this->toAlias($this->makeUrl($this->documentIdentifier));
2530
2531 View Code Duplication
        if (substr($strictURL, 0, $len_base_url) === $this->config['base_url']) {
2532
            $strictURL = substr($strictURL, $len_base_url);
2533
        }
2534
        $http_host = $_SERVER['HTTP_HOST'];
2535
        $requestedURL = "{$scheme}://{$http_host}" . '/' . $q; //LANG
2536
2537
        $site_url = $this->config['site_url'];
2538
        $url_query_string = explode('?', $_SERVER['REQUEST_URI']);
2539
        // Strip conflicting id/q from query string
2540
        $qstring = !empty($url_query_string[1]) ? preg_replace("#(^|&)(q|id)=[^&]+#", '', $url_query_string[1]) : '';
2541
2542
        if ($this->documentIdentifier == $this->config['site_start']) {
2543
            if ($requestedURL != $this->config['site_url']) {
2544
                // Force redirect of site start
2545
                // $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...
2546
                if ($qstring) {
2547
                    $url = "{$site_url}?{$qstring}";
2548
                } else {
2549
                    $url = $site_url;
2550
                }
2551
                if ($this->config['base_url'] != $_SERVER['REQUEST_URI']) {
2552
                    if (empty($_POST)) {
2553
                        if (($this->config['base_url'] . '?' . $qstring) != $_SERVER['REQUEST_URI']) {
2554
                            $this->sendRedirect($url, 0, 'REDIRECT_HEADER', 'HTTP/1.0 301 Moved Permanently');
2555
                            exit(0);
0 ignored issues
show
Coding Style Compatibility introduced by
The method sendStrictURI() contains an exit expression.

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

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

Loading history...
2556
                        }
2557
                    }
2558
                }
2559
            }
2560
        } elseif ($url_path != $strictURL && $this->documentIdentifier != $this->config['error_page']) {
2561
            // Force page redirect
2562
            //$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...
2563
            if (!empty($qstring)) {
2564
                $url = "{$site_url}{$strictURL}?{$qstring}";
2565
            } else {
2566
                $url = "{$site_url}{$strictURL}";
2567
            }
2568
            $this->sendRedirect($url, 0, 'REDIRECT_HEADER', 'HTTP/1.0 301 Moved Permanently');
2569
            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...
2570
        }
2571
        return;
2572
    }
2573
2574
    /**
2575
     * Get all db fields and TVs for a document/resource
2576
     *
2577
     * @param string $method
2578
     * @param mixed $identifier
2579
     * @param bool $isPrepareResponse
2580
     * @return array
2581
     */
2582
    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...
2583
    {
2584
2585
        $cacheKey = md5(print_r(func_get_args(), true));
2586
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
2587
            return $this->tmpCache[__FUNCTION__][$cacheKey];
2588
        }
2589
2590
        $tblsc = $this->getFullTableName("site_content");
2591
        $tbldg = $this->getFullTableName("document_groups");
2592
2593
        // allow alias to be full path
2594
        if ($method == 'alias') {
2595
            $identifier = $this->cleanDocumentIdentifier($identifier);
2596
            $method = $this->documentMethod;
2597
        }
2598
        if ($method == 'alias' && $this->config['use_alias_path'] && array_key_exists($identifier, $this->documentListing)) {
2599
            $method = 'id';
2600
            $identifier = $this->documentListing[$identifier];
2601
        }
2602
2603
        $out = $this->invokeEvent('OnBeforeLoadDocumentObject', compact('method', 'identifier'));
2604
        if (is_array($out) && is_array($out[0])) {
2605
            $documentObject = $out[0];
2606
        } else {
2607
            // get document groups for current user
2608
            if ($docgrp = $this->getUserDocGroups()) {
2609
                $docgrp = implode(",", $docgrp);
2610
            }
2611
            // get document
2612
            $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
2613
            $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...
2614
                LEFT JOIN {$tbldg} dg ON dg.document = sc.id", "sc.{$method} = '{$identifier}' AND ({$access})", "", 1);
2615
            if ($this->db->getRecordCount($rs) < 1) {
2616
                $seclimit = 0;
2617
                if ($this->config['unauthorized_page']) {
2618
                    // method may still be alias, while identifier is not full path alias, e.g. id not found above
2619
                    if ($method === 'alias') {
2620
                        $secrs = $this->db->select('count(dg.id)', "{$tbldg} as dg, {$tblsc} as sc", "dg.document = sc.id AND sc.alias = '{$identifier}'", '', 1);
2621
                    } else {
2622
                        $secrs = $this->db->select('count(id)', $tbldg, "document = '{$identifier}'", '', 1);
2623
                    }
2624
                    // check if file is not public
2625
                    $seclimit = $this->db->getValue($secrs);
2626
                }
2627
                if ($seclimit > 0) {
2628
                    // match found but not publicly accessible, send the visitor to the unauthorized_page
2629
                    $this->sendUnauthorizedPage();
2630
                    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...
2631
                } else {
2632
                    $this->sendErrorPage();
2633
                    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...
2634
                }
2635
            }
2636
            # 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...
2637
            $documentObject = $this->db->getRow($rs);
2638
2639
            if ($isPrepareResponse === 'prepareResponse') {
2640
                $this->documentObject = &$documentObject;
2641
            }
2642
            $out = $this->invokeEvent('OnLoadDocumentObject', compact('method', 'identifier', 'documentObject'));
2643
            if (is_array($out) && is_array($out[0])) {
2644
                $documentObject = $out[0];
2645
            }
2646
            if ($documentObject['template']) {
2647
                // load TVs and merge with document - Orig by Apodigm - Docvars
2648
                $rs = $this->db->select("tv.*, IF(tvc.value!='',tvc.value,tv.default_text) as value", $this->getFullTableName("site_tmplvars") . " tv
2649
                INNER JOIN " . $this->getFullTableName("site_tmplvar_templates") . " tvtpl ON tvtpl.tmplvarid = tv.id
2650
                LEFT JOIN " . $this->getFullTableName("site_tmplvar_contentvalues") . " tvc ON tvc.tmplvarid=tv.id AND tvc.contentid = '{$documentObject['id']}'", "tvtpl.templateid = '{$documentObject['template']}'");
2651
                $tmplvars = array();
2652
                while ($row = $this->db->getRow($rs)) {
2653
                    $tmplvars[$row['name']] = array(
2654
                        $row['name'],
2655
                        $row['value'],
2656
                        $row['display'],
2657
                        $row['display_params'],
2658
                        $row['type']
2659
                    );
2660
                }
2661
                $documentObject = array_merge($documentObject, $tmplvars);
2662
            }
2663
            $out = $this->invokeEvent('OnAfterLoadDocumentObject', compact('method', 'identifier', 'documentObject'));
2664
            if (is_array($out) && array_key_exists(0, $out) !== false && is_array($out[0])) {
2665
                $documentObject = $out[0];
2666
            }
2667
        }
2668
2669
        $this->tmpCache[__FUNCTION__][$cacheKey] = $documentObject;
2670
2671
        return $documentObject;
2672
    }
2673
2674
    /**
2675
     * Parse a source string.
2676
     *
2677
     * Handles most MODX tags. Exceptions include:
2678
     *   - uncached snippet tags [!...!]
2679
     *   - URL tags [~...~]
2680
     *
2681
     * @param string $source
2682
     * @return string
2683
     */
2684
    public function parseDocumentSource($source)
2685
    {
2686
        // set the number of times we are to parse the document source
2687
        $this->minParserPasses = empty ($this->minParserPasses) ? 2 : $this->minParserPasses;
2688
        $this->maxParserPasses = empty ($this->maxParserPasses) ? 10 : $this->maxParserPasses;
2689
        $passes = $this->minParserPasses;
2690
        for ($i = 0; $i < $passes; $i++) {
2691
            // get source length if this is the final pass
2692
            if ($i == ($passes - 1)) {
2693
                $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...
2694
            }
2695
            if ($this->dumpSnippets == 1) {
2696
                $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>";
2697
            }
2698
2699
            // invoke OnParseDocument event
2700
            $this->documentOutput = $source; // store source code so plugins can
2701
            $this->invokeEvent("OnParseDocument"); // work on it via $modx->documentOutput
2702
            $source = $this->documentOutput;
2703
2704
            if ($this->config['enable_at_syntax']) {
2705
                $source = $this->ignoreCommentedTagsContent($source);
2706
                $source = $this->mergeConditionalTagsContent($source);
2707
            }
2708
2709
            $source = $this->mergeSettingsContent($source);
2710
            $source = $this->mergeDocumentContent($source);
2711
            $source = $this->mergeChunkContent($source);
2712
            $source = $this->evalSnippets($source);
2713
            $source = $this->mergePlaceholderContent($source);
2714
2715
            if ($this->dumpSnippets == 1) {
2716
                $this->snippetsCode .= "</fieldset><br />";
2717
            }
2718
            if ($i == ($passes - 1) && $i < ($this->maxParserPasses - 1)) {
2719
                // check if source content was changed
2720
                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...
2721
                    $passes++;
2722
                } // if content change then increase passes because
2723
            } // we have not yet reached maxParserPasses
2724
        }
2725
        return $source;
2726
    }
2727
2728
    /**
2729
     * Starts the parsing operations.
2730
     *
2731
     * - connects to the db
2732
     * - gets the settings (including system_settings)
2733
     * - gets the document/resource identifier as in the query string
2734
     * - finally calls prepareResponse()
2735
     */
2736
    public function executeParser()
2737
    {
2738
        if(MODX_CLI) {
2739
            throw new RuntimeException('Call DocumentParser::executeParser on CLI mode');
2740
        }
2741
2742
        //error_reporting(0);
2743
        set_error_handler(array(
2744
            & $this,
2745
            "phpError"
2746
        ), E_ALL);
2747
        $this->db->connect();
2748
2749
        // get the settings
2750
        if (empty ($this->config)) {
2751
            $this->getSettings();
2752
        }
2753
2754
        $this->_IIS_furl_fix(); // IIS friendly url fix
2755
2756
        // check site settings
2757
        if ($this->checkSiteStatus()) {
2758
            // make sure the cache doesn't need updating
2759
            $this->updatePubStatus();
2760
2761
            // find out which document we need to display
2762
            $this->documentMethod = filter_input(INPUT_GET, 'q') ? 'alias' : 'id';
2763
            $this->documentIdentifier = $this->getDocumentIdentifier($this->documentMethod);
2764
        } else {
2765
            header('HTTP/1.0 503 Service Unavailable');
2766
            $this->systemCacheKey = 'unavailable';
2767
            if (!$this->config['site_unavailable_page']) {
2768
                // display offline message
2769
                $this->documentContent = $this->config['site_unavailable_message'];
2770
                $this->outputContent();
2771
                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...
2772
            } else {
2773
                // setup offline page document settings
2774
                $this->documentMethod = 'id';
2775
                $this->documentIdentifier = $this->config['site_unavailable_page'];
2776
            }
2777
        }
2778
2779
        if ($this->documentMethod == "alias") {
2780
            $this->documentIdentifier = $this->cleanDocumentIdentifier($this->documentIdentifier);
2781
2782
            // Check use_alias_path and check if $this->virtualDir is set to anything, then parse the path
2783
            if ($this->config['use_alias_path'] == 1) {
2784
                $alias = (strlen($this->virtualDir) > 0 ? $this->virtualDir . '/' : '') . $this->documentIdentifier;
2785
                if (isset($this->documentListing[$alias])) {
2786
                    $this->documentIdentifier = $this->documentListing[$alias];
2787
                } else {
2788
                    //@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...
2789
                    if ($this->config['aliaslistingfolder'] == 1) {
2790
                        $tbl_site_content = $this->getFullTableName('site_content');
2791
2792
                        $parentId = $this->getIdFromAlias($this->virtualDir);
2793
                        $parentId = ($parentId > 0) ? $parentId : '0';
2794
2795
                        $docAlias = $this->db->escape($this->documentIdentifier);
2796
2797
                        $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...
2798
                        if ($this->db->getRecordCount($rs) == 0) {
2799
                            $this->sendErrorPage();
2800
                        }
2801
                        $docId = $this->db->getValue($rs);
2802
2803
                        if (!$docId) {
2804
                            $alias = $this->q;
2805
                            if (!empty($this->config['friendly_url_suffix'])) {
2806
                                $pos = strrpos($alias, $this->config['friendly_url_suffix']);
2807
2808
                                if ($pos !== false) {
2809
                                    $alias = substr($alias, 0, $pos);
2810
                                }
2811
                            }
2812
                            $docId = $this->getIdFromAlias($alias);
2813
                        }
2814
2815
                        if ($docId > 0) {
2816
                            $this->documentIdentifier = $docId;
2817
                        } else {
2818
                            /*
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...
2819
                            $rs  = $this->db->select('id', $tbl_site_content, "deleted=0 and alias='{$docAlias}'");
2820
                            if($this->db->getRecordCount($rs)==0)
2821
                            {
2822
                                $rs  = $this->db->select('id', $tbl_site_content, "deleted=0 and id='{$docAlias}'");
2823
                            }
2824
                            $docId = $this->db->getValue($rs);
2825
2826
                            if ($docId > 0)
2827
                            {
2828
                                $this->documentIdentifier = $docId;
2829
2830
                            }else{
2831
                            */
2832
                            $this->sendErrorPage();
2833
                            //}
2834
                        }
2835
                    } else {
2836
                        $this->sendErrorPage();
2837
                    }
2838
                }
2839
            } else {
2840
                if (isset($this->documentListing[$this->documentIdentifier])) {
2841
                    $this->documentIdentifier = $this->documentListing[$this->documentIdentifier];
2842
                } else {
2843
                    $docAlias = $this->db->escape($this->documentIdentifier);
2844
                    $rs = $this->db->select('id', $this->getFullTableName('site_content'), "deleted=0 and alias='{$docAlias}'");
2845
                    $this->documentIdentifier = (int)$this->db->getValue($rs);
2846
                }
2847
            }
2848
            $this->documentMethod = 'id';
2849
        }
2850
2851
        //$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...
2852
        // invoke OnWebPageInit event
2853
        $this->invokeEvent("OnWebPageInit");
2854
        // invoke OnLogPageView event
2855
        if ($this->config['track_visitors'] == 1) {
2856
            $this->invokeEvent("OnLogPageHit");
2857
        }
2858
        if ($this->config['seostrict'] === '1') {
2859
            $this->sendStrictURI();
2860
        }
2861
        $this->prepareResponse();
2862
    }
2863
2864
    /**
2865
     * @param $path
2866
     * @param null $suffix
2867
     * @return mixed
2868
     */
2869
    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...
2870
    {
2871
        $exp = explode('/', $path);
2872
        return str_replace($suffix, '', end($exp));
2873
    }
2874
2875
    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...
2876
    {
2877
        if ($this->config['friendly_urls'] != 1) {
2878
            return;
2879
        }
2880
2881
        if (strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') === false) {
2882
            return;
2883
        }
2884
2885
        $url = $_SERVER['QUERY_STRING'];
2886
        $err = substr($url, 0, 3);
2887
        if ($err !== '404' && $err !== '405') {
2888
            return;
2889
        }
2890
2891
        $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...
2892
        unset ($_GET[$k[0]]);
2893
        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...
2894
        $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...
2895
        $_SERVER['QUERY_STRING'] = $qp['query'];
2896
        if (!empty ($qp['query'])) {
2897
            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...
2898
            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...
2899
                $_REQUEST[$n] = $_GET[$n] = $v;
2900
            }
2901
        }
2902
        $_SERVER['PHP_SELF'] = $this->config['base_url'] . $qp['path'];
2903
        $this->q = $qp['path'];
2904
        return $qp['path'];
2905
    }
2906
2907
    /**
2908
     * The next step called at the end of executeParser()
2909
     *
2910
     * - checks cache
2911
     * - checks if document/resource is deleted/unpublished
2912
     * - checks if resource is a weblink and redirects if so
2913
     * - gets template and parses it
2914
     * - ensures that postProcess is called when PHP is finished
2915
     */
2916
    public function prepareResponse()
2917
    {
2918
        // we now know the method and identifier, let's check the cache
2919
2920
        if ($this->config['enable_cache'] == 2 && $this->isLoggedIn()) {
2921
            $this->config['enable_cache'] = 0;
2922
        }
2923
2924
        if ($this->config['enable_cache']) {
2925
            $this->documentContent = $this->getDocumentObjectFromCache($this->documentIdentifier, true);
2926
        } else {
2927
            $this->documentContent = '';
2928
        }
2929
2930
        if ($this->documentContent == '') {
2931
            // get document object from DB
2932
            $this->documentObject = $this->getDocumentObject($this->documentMethod, $this->documentIdentifier, 'prepareResponse');
2933
2934
            // write the documentName to the object
2935
            $this->documentName = &$this->documentObject['pagetitle'];
2936
2937
            // check if we should not hit this document
2938
            if ($this->documentObject['donthit'] == 1) {
2939
                $this->config['track_visitors'] = 0;
2940
            }
2941
2942
            if ($this->documentObject['deleted'] == 1) {
2943
                $this->sendErrorPage();
2944
            } // validation routines
2945
            elseif ($this->documentObject['published'] == 0) {
2946
                $this->_sendErrorForUnpubPage();
2947
            } elseif ($this->documentObject['type'] == 'reference') {
2948
                $this->_sendRedirectForRefPage($this->documentObject['content']);
2949
            }
2950
2951
            // get the template and start parsing!
2952
            if (!$this->documentObject['template']) {
2953
                $templateCode = '[*content*]';
2954
            } // use blank template
2955
            else {
2956
                $templateCode = $this->_getTemplateCodeFromDB($this->documentObject['template']);
2957
            }
2958
2959
            if (substr($templateCode, 0, 8) === '@INCLUDE') {
2960
                $templateCode = $this->atBindInclude($templateCode);
2961
            }
2962
2963
2964
            $this->documentContent = &$templateCode;
2965
2966
            // invoke OnLoadWebDocument event
2967
            $this->invokeEvent('OnLoadWebDocument');
2968
2969
            // Parse document source
2970
            $this->documentContent = $this->parseDocumentSource($templateCode);
2971
2972
            $this->documentGenerated = 1;
2973
        } else {
2974
            $this->documentGenerated = 0;
2975
        }
2976
2977
        if ($this->config['error_page'] == $this->documentIdentifier && $this->config['error_page'] != $this->config['site_start']) {
2978
            header('HTTP/1.0 404 Not Found');
2979
        }
2980
2981
        register_shutdown_function(array(
2982
            &$this,
2983
            'postProcess'
2984
        )); // tell PHP to call postProcess when it shuts down
2985
        $this->outputContent();
2986
        //$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...
2987
    }
2988
2989
    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...
2990
    {
2991
        // Can't view unpublished pages !$this->checkPreview()
2992
        if (!$this->hasPermission('view_unpublished')) {
2993
            $this->sendErrorPage();
2994
        } else {
2995
            // Inculde the necessary files to check document permissions
2996
            include_once(MODX_MANAGER_PATH . 'processors/user_documents_permissions.class.php');
2997
            $udperms = new udperms();
2998
            $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...
2999
            $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...
3000
            $udperms->role = $_SESSION['mgrRole'];
3001
            // Doesn't have access to this document
3002
            if (!$udperms->checkPermissions()) {
3003
                $this->sendErrorPage();
3004
            }
3005
        }
3006
    }
3007
3008
    /**
3009
     * @param $url
3010
     */
3011
    public function _sendRedirectForRefPage($url)
3012
    {
3013
        // check whether it's a reference
3014
        if (preg_match('@^[1-9][0-9]*$@', $url)) {
3015
            $url = $this->makeUrl($url); // if it's a bare document id
3016
        } elseif (strpos($url, '[~') !== false) {
3017
            $url = $this->rewriteUrls($url); // if it's an internal docid tag, process it
3018
        }
3019
        $this->sendRedirect($url, 0, '', 'HTTP/1.0 301 Moved Permanently');
3020
        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...
3021
    }
3022
3023
    /**
3024
     * @param $templateID
3025
     * @return mixed
3026
     */
3027
    public function _getTemplateCodeFromDB($templateID)
3028
    {
3029
        $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...
3030
        if ($this->db->getRecordCount($rs) == 1) {
3031
            return $this->db->getValue($rs);
3032
        } else {
3033
            $this->messageQuit('Incorrect number of templates returned from database');
3034
        }
3035
    }
3036
3037
    /**
3038
     * Returns an array of all parent record IDs for the id passed.
3039
     *
3040
     * @param int $id Docid to get parents for.
3041
     * @param int $height The maximum number of levels to go up, default 10.
3042
     * @return array
3043
     */
3044
    public function getParentIds($id, $height = 10)
3045
    {
3046
        $parents = array();
3047
        while ($id && $height--) {
3048
            $thisid = $id;
3049
            if ($this->config['aliaslistingfolder'] == 1) {
3050
                $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");
3051
                if (!$id || $id == '0') {
3052
                    break;
3053
                }
3054
            } else {
3055
                $id = $this->aliasListing[$id]['parent'];
3056
                if (!$id) {
3057
                    break;
3058
                }
3059
            }
3060
            $parents[$thisid] = $id;
3061
        }
3062
        return $parents;
3063
    }
3064
3065
    /**
3066
     * @param $id
3067
     * @param int $top
3068
     * @return mixed
3069
     */
3070
    public function getUltimateParentId($id, $top = 0)
3071
    {
3072
        $i = 0;
3073
        while ($id && $i < 20) {
3074
            if ($top == $this->aliasListing[$id]['parent']) {
3075
                break;
3076
            }
3077
            $id = $this->aliasListing[$id]['parent'];
3078
            $i++;
3079
        }
3080
        return $id;
3081
    }
3082
3083
    /**
3084
     * Returns an array of child IDs belonging to the specified parent.
3085
     *
3086
     * @param int $id The parent resource/document to start from
3087
     * @param int $depth How many levels deep to search for children, default: 10
3088
     * @param array $children Optional array of docids to merge with the result.
3089
     * @return array Contains the document Listing (tree) like the sitemap
3090
     */
3091
    public function getChildIds($id, $depth = 10, $children = array())
3092
    {
3093
3094
        $cacheKey = md5(print_r(func_get_args(), true));
3095
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3096
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3097
        }
3098
3099
        if ($this->config['aliaslistingfolder'] == 1) {
3100
3101
            $res = $this->db->select("id,alias,isfolder,parent", $this->getFullTableName('site_content'), "parent IN (" . $id . ") AND deleted = '0'");
3102
            $idx = array();
3103
            while ($row = $this->db->getRow($res)) {
3104
                $pAlias = '';
3105
                if (isset($this->aliasListing[$row['parent']])) {
3106
                    $pAlias .= !empty($this->aliasListing[$row['parent']]['path']) ? $this->aliasListing[$row['parent']]['path'] . '/' : '';
3107
                    $pAlias .= !empty($this->aliasListing[$row['parent']]['alias']) ? $this->aliasListing[$row['parent']]['alias'] . '/' : '';
3108
                };
3109
                $children[$pAlias . $row['alias']] = $row['id'];
3110
                if ($row['isfolder'] == 1) {
3111
                    $idx[] = $row['id'];
3112
                }
3113
            }
3114
            $depth--;
3115
            $idx = implode(',', $idx);
3116
            if (!empty($idx)) {
3117
                if ($depth) {
3118
                    $children = $this->getChildIds($idx, $depth, $children);
3119
                }
3120
            }
3121
            $this->tmpCache[__FUNCTION__][$cacheKey] = $children;
3122
            return $children;
3123
3124
        } else {
3125
3126
            // Initialise a static array to index parents->children
3127
            static $documentMap_cache = array();
3128
            if (!count($documentMap_cache)) {
3129
                foreach ($this->documentMap as $document) {
3130
                    foreach ($document as $p => $c) {
3131
                        $documentMap_cache[$p][] = $c;
3132
                    }
3133
                }
3134
            }
3135
3136
            // Get all the children for this parent node
3137
            if (isset($documentMap_cache[$id])) {
3138
                $depth--;
3139
3140
                foreach ($documentMap_cache[$id] as $childId) {
3141
                    $pkey = (strlen($this->aliasListing[$childId]['path']) ? "{$this->aliasListing[$childId]['path']}/" : '') . $this->aliasListing[$childId]['alias'];
3142
                    if (!strlen($pkey)) {
3143
                        $pkey = "{$childId}";
3144
                    }
3145
                    $children[$pkey] = $childId;
3146
3147
                    if ($depth && isset($documentMap_cache[$childId])) {
3148
                        $children += $this->getChildIds($childId, $depth);
3149
                    }
3150
                }
3151
            }
3152
            $this->tmpCache[__FUNCTION__][$cacheKey] = $children;
3153
            return $children;
3154
3155
        }
3156
    }
3157
3158
    /**
3159
     * Displays a javascript alert message in the web browser and quit
3160
     *
3161
     * @param string $msg Message to show
3162
     * @param string $url URL to redirect to
3163
     */
3164
    public function webAlertAndQuit($msg, $url = "")
3165
    {
3166
        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...
3167
        if (substr(strtolower($url), 0, 11) == "javascript:") {
3168
            $fnc = substr($url, 11);
3169
        } elseif ($url) {
3170
            $fnc = "window.location.href='" . addslashes($url) . "';";
3171
        } else {
3172
            $fnc = "history.back(-1);";
3173
        }
3174
        echo "<html><head>
3175
            <title>MODX :: Alert</title>
3176
            <meta http-equiv=\"Content-Type\" content=\"text/html; charset={$modx_manager_charset};\">
3177
            <script>
3178
                function __alertQuit() {
3179
                    alert('" . addslashes($msg) . "');
3180
                    {$fnc}
3181
                }
3182
                window.setTimeout('__alertQuit();',100);
3183
            </script>
3184
            </head><body>
3185
            <p>{$msg}</p>
3186
            </body></html>";
3187
        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...
3188
    }
3189
3190
    /**
3191
     * Returns 1 if user has the currect permission
3192
     *
3193
     * @param string $pm Permission name
3194
     * @return int Why not bool?
3195
     */
3196
    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...
3197
    {
3198
        $state = 0;
3199
        $pms = $_SESSION['mgrPermissions'];
3200
        if ($pms) {
3201
            $state = ((bool)$pms[$pm] === true);
3202
        }
3203
        return (int)$state;
3204
    }
3205
3206
    /**
3207
     * Returns true if element is locked
3208
     *
3209
     * @param int $type Types: 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3210
     * @param int $id Element- / Resource-id
3211
     * @param bool $includeThisUser true = Return also info about actual user
3212
     * @return array lock-details or null
3213
     */
3214
    public function elementIsLocked($type, $id, $includeThisUser = false)
3215
    {
3216
        $id = (int)$id;
3217
        $type = (int)$type;
3218
        if (!$type || !$id) {
3219
            return null;
3220
        }
3221
3222
        // Build lockedElements-Cache at first call
3223
        $this->buildLockedElementsCache();
3224
3225
        if (!$includeThisUser && $this->lockedElements[$type][$id]['sid'] == $this->sid) {
3226
            return null;
3227
        }
3228
3229
        if (isset($this->lockedElements[$type][$id])) {
3230
            return $this->lockedElements[$type][$id];
3231
        } else {
3232
            return null;
3233
        }
3234
    }
3235
3236
    /**
3237
     * Returns Locked Elements as Array
3238
     *
3239
     * @param int $type Types: 0=all, 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3240
     * @param bool $minimumDetails true =
3241
     * @return array|mixed|null
3242
     */
3243
    public function getLockedElements($type = 0, $minimumDetails = false)
3244
    {
3245
        $this->buildLockedElementsCache();
3246
3247
        if (!$minimumDetails) {
3248
            $lockedElements = $this->lockedElements;
3249
        } else {
3250
            // Minimum details for HTML / Ajax-requests
3251
            $lockedElements = array();
3252
            foreach ($this->lockedElements as $elType => $elements) {
3253
                foreach ($elements as $elId => $el) {
3254
                    $lockedElements[$elType][$elId] = array(
3255
                        'username' => $el['username'],
3256
                        'lasthit_df' => $el['lasthit_df'],
3257
                        'state' => $this->determineLockState($el['internalKey'])
3258
                    );
3259
                }
3260
            }
3261
        }
3262
3263
        if ($type == 0) {
3264
            return $lockedElements;
3265
        }
3266
3267
        $type = (int)$type;
3268
        if (isset($lockedElements[$type])) {
3269
            return $lockedElements[$type];
3270
        } else {
3271
            return array();
3272
        }
3273
    }
3274
3275
    /**
3276
     * Builds the Locked Elements Cache once
3277
     */
3278
    public function buildLockedElementsCache()
3279
    {
3280
        if (is_null($this->lockedElements)) {
3281
            $this->lockedElements = array();
3282
            $this->cleanupExpiredLocks();
3283
3284
            $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...
3285
                LEFT JOIN {$this->getFullTableName('manager_users')} mu on ul.internalKey = mu.id");
3286
            while ($row = $this->db->getRow($rs)) {
3287
                $this->lockedElements[$row['elementType']][$row['elementId']] = array(
3288
                    'sid' => $row['sid'],
3289
                    'internalKey' => $row['internalKey'],
3290
                    'username' => $row['username'],
3291
                    'elementType' => $row['elementType'],
3292
                    'elementId' => $row['elementId'],
3293
                    'lasthit' => $row['lasthit'],
3294
                    'lasthit_df' => $this->toDateFormat($row['lasthit']),
3295
                    'state' => $this->determineLockState($row['sid'])
3296
                );
3297
            }
3298
        }
3299
    }
3300
3301
    /**
3302
     * Cleans up the active user locks table
3303
     */
3304
    public function cleanupExpiredLocks()
3305
    {
3306
        // Clean-up active_user_sessions first
3307
        $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
3308
        $validSessionTimeLimit = $this->time - $timeout;
3309
        $this->db->delete($this->getFullTableName('active_user_sessions'), "lasthit < {$validSessionTimeLimit}");
3310
3311
        // Clean-up active_user_locks
3312
        $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...
3313
        $count = $this->db->getRecordCount($rs);
3314
        if ($count) {
3315
            $rs = $this->db->makeArray($rs);
3316
            $userSids = array();
3317
            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...
3318
                $userSids[] = $row['sid'];
3319
            }
3320
            $userSids = "'" . implode("','", $userSids) . "'";
3321
            $this->db->delete($this->getFullTableName('active_user_locks'), "sid NOT IN({$userSids})");
3322
        } else {
3323
            $this->db->delete($this->getFullTableName('active_user_locks'));
3324
        }
3325
3326
    }
3327
3328
    /**
3329
     * Cleans up the active users table
3330
     */
3331
    public function cleanupMultipleActiveUsers()
3332
    {
3333
        $timeout = 20 * 60; // Delete multiple user-sessions after 20min
3334
        $validSessionTimeLimit = $this->time - $timeout;
3335
3336
        $activeUserSids = array();
3337
        $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...
3338
        $count = $this->db->getRecordCount($rs);
3339
        if ($count) {
3340
            $rs = $this->db->makeArray($rs);
3341
            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...
3342
                $activeUserSids[] = $row['sid'];
3343
            }
3344
        }
3345
3346
        $rs = $this->db->select("sid,internalKey,lasthit", "{$this->getFullTableName('active_users')}", "", "lasthit DESC");
3347
        if ($this->db->getRecordCount($rs)) {
3348
            $rs = $this->db->makeArray($rs);
3349
            $internalKeyCount = array();
3350
            $deleteSids = '';
3351
            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...
3352
                if (!isset($internalKeyCount[$row['internalKey']])) {
3353
                    $internalKeyCount[$row['internalKey']] = 0;
3354
                }
3355
                $internalKeyCount[$row['internalKey']]++;
3356
3357
                if ($internalKeyCount[$row['internalKey']] > 1 && !in_array($row['sid'], $activeUserSids) && $row['lasthit'] < $validSessionTimeLimit) {
3358
                    $deleteSids .= $deleteSids == '' ? '' : ' OR ';
3359
                    $deleteSids .= "sid='{$row['sid']}'";
3360
                };
3361
3362
            }
3363
            if ($deleteSids) {
3364
                $this->db->delete($this->getFullTableName('active_users'), $deleteSids);
3365
            }
3366
        }
3367
3368
    }
3369
3370
    /**
3371
     * Determines state of a locked element acc. to user-permissions
3372
     *
3373
     * @param $sid
3374
     * @return int $state States: 0=No display, 1=viewing this element, 2=locked, 3=show unlock-button
3375
     * @internal param int $internalKey : ID of User who locked actual element
3376
     */
3377
    public function determineLockState($sid)
3378
    {
3379
        $state = 0;
3380
        if ($this->hasPermission('display_locks')) {
3381
            if ($sid == $this->sid) {
3382
                $state = 1;
3383
            } else {
3384
                if ($this->hasPermission('remove_locks')) {
3385
                    $state = 3;
3386
                } else {
3387
                    $state = 2;
3388
                }
3389
            }
3390
        }
3391
        return $state;
3392
    }
3393
3394
    /**
3395
     * Locks an element
3396
     *
3397
     * @param int $type Types: 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3398
     * @param int $id Element- / Resource-id
3399
     * @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...
3400
     */
3401
    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...
3402
    {
3403
        $userId = $this->isBackend() && $_SESSION['mgrInternalKey'] ? $_SESSION['mgrInternalKey'] : 0;
3404
        $type = (int)$type;
3405
        $id = (int)$id;
3406
        if (!$type || !$id || !$userId) {
3407
            return false;
3408
        }
3409
3410
        $sql = sprintf('REPLACE INTO %s (internalKey, elementType, elementId, lasthit, sid)
3411
                VALUES (%d, %d, %d, %d, \'%s\')', $this->getFullTableName('active_user_locks'), $userId, $type, $id, $this->time, $this->sid);
3412
        $this->db->query($sql);
3413
    }
3414
3415
    /**
3416
     * Unlocks an element
3417
     *
3418
     * @param int $type Types: 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3419
     * @param int $id Element- / Resource-id
3420
     * @param bool $includeAllUsers true = Deletes not only own user-locks
3421
     * @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...
3422
     */
3423
    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...
3424
    {
3425
        $userId = $this->isBackend() && $_SESSION['mgrInternalKey'] ? $_SESSION['mgrInternalKey'] : 0;
3426
        $type = (int)$type;
3427
        $id = (int)$id;
3428
        if (!$type || !$id) {
3429
            return false;
3430
        }
3431
3432
        if (!$includeAllUsers) {
3433
            $sql = sprintf('DELETE FROM %s WHERE internalKey = %d AND elementType = %d AND elementId = %d;', $this->getFullTableName('active_user_locks'), $userId, $type, $id);
3434
        } else {
3435
            $sql = sprintf('DELETE FROM %s WHERE elementType = %d AND elementId = %d;', $this->getFullTableName('active_user_locks'), $type, $id);
3436
        }
3437
        $this->db->query($sql);
3438
    }
3439
3440
    /**
3441
     * Updates table "active_user_sessions" with userid, lasthit, IP
3442
     */
3443
    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...
3444
    {
3445
        if (!$this->sid) {
3446
            return;
3447
        }
3448
3449
        // web users are stored with negative keys
3450
        $userId = $this->getLoginUserType() == 'manager' ? $this->getLoginUserID() : -$this->getLoginUserID();
3451
3452
        // Get user IP
3453 View Code Duplication
        if ($cip = getenv("HTTP_CLIENT_IP")) {
3454
            $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...
3455
        } elseif ($cip = getenv("HTTP_X_FORWARDED_FOR")) {
3456
            $ip = $cip;
3457
        } elseif ($cip = getenv("REMOTE_ADDR")) {
3458
            $ip = $cip;
3459
        } else {
3460
            $ip = "UNKNOWN";
3461
        }
3462
        $_SESSION['ip'] = $ip;
3463
3464
        $sql = sprintf('REPLACE INTO %s (internalKey, lasthit, ip, sid)
3465
            VALUES (%d, %d, \'%s\', \'%s\')', $this->getFullTableName('active_user_sessions'), $userId, $this->time, $ip, $this->sid);
3466
        $this->db->query($sql);
3467
    }
3468
3469
    /**
3470
     * Add an a alert message to the system event log
3471
     *
3472
     * @param int $evtid Event ID
3473
     * @param int $type Types: 1 = information, 2 = warning, 3 = error
3474
     * @param string $msg Message to be logged
3475
     * @param string $source source of the event (module, snippet name, etc.)
3476
     *                       Default: Parser
3477
     */
3478
    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...
3479
    {
3480
        $msg = $this->db->escape($msg);
3481
        if (strpos($GLOBALS['database_connection_charset'], 'utf8') === 0 && extension_loaded('mbstring')) {
3482
            $esc_source = mb_substr($source, 0, 50, "UTF-8");
3483
        } else {
3484
            $esc_source = substr($source, 0, 50);
3485
        }
3486
        $esc_source = $this->db->escape($esc_source);
3487
3488
        $LoginUserID = $this->getLoginUserID();
3489
        if ($LoginUserID == '') {
3490
            $LoginUserID = 0;
3491
        }
3492
3493
        $usertype = $this->isFrontend() ? 1 : 0;
3494
        $evtid = (int)$evtid;
3495
        $type = (int)$type;
3496
3497
        // Types: 1 = information, 2 = warning, 3 = error
3498
        if ($type < 1) {
3499
            $type = 1;
3500
        } elseif ($type > 3) {
3501
            $type = 3;
3502
        }
3503
3504
        $this->db->insert(array(
3505
            'eventid' => $evtid,
3506
            'type' => $type,
3507
            'createdon' => $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'],
3508
            'source' => $esc_source,
3509
            'description' => $msg,
3510
            'user' => $LoginUserID,
3511
            'usertype' => $usertype
3512
        ), $this->getFullTableName("event_log"));
3513
3514
        if (isset($this->config['send_errormail']) && $this->config['send_errormail'] !== '0') {
3515
            if ($this->config['send_errormail'] <= $type) {
3516
                $this->sendmail(array(
3517
                    'subject' => 'MODX System Error on ' . $this->config['site_name'],
3518
                    'body' => 'Source: ' . $source . ' - The details of the error could be seen in the MODX system events log.',
3519
                    'type' => 'text'
3520
                ));
3521
            }
3522
        }
3523
    }
3524
3525
    /**
3526
     * @param array $params
3527
     * @param string $msg
3528
     * @param array $files
3529
     * @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...
3530
     */
3531
    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...
3532
    {
3533
        if (isset($params) && is_string($params)) {
3534
            if (strpos($params, '=') === false) {
3535
                if (strpos($params, '@') !== false) {
3536
                    $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...
3537
                } else {
3538
                    $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...
3539
                }
3540
            } else {
3541
                $params_array = explode(',', $params);
3542
                foreach ($params_array as $k => $v) {
3543
                    $k = trim($k);
3544
                    $v = trim($v);
3545
                    $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...
3546
                }
3547
            }
3548
        } else {
3549
            $p = $params;
3550
            unset($params);
3551
        }
3552
        if (isset($p['sendto'])) {
3553
            $p['to'] = $p['sendto'];
3554
        }
3555
3556
        if (isset($p['to']) && preg_match('@^[0-9]+$@', $p['to'])) {
3557
            $userinfo = $this->getUserInfo($p['to']);
3558
            $p['to'] = $userinfo['email'];
3559
        }
3560
        if (isset($p['from']) && preg_match('@^[0-9]+$@', $p['from'])) {
3561
            $userinfo = $this->getUserInfo($p['from']);
3562
            $p['from'] = $userinfo['email'];
3563
            $p['fromname'] = $userinfo['username'];
3564
        }
3565
        if ($msg === '' && !isset($p['body'])) {
3566
            $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...
3567
        } elseif (is_string($msg) && 0 < strlen($msg)) {
3568
            $p['body'] = $msg;
3569
        }
3570
3571
        $this->loadExtension('MODxMailer');
3572
        $sendto = (!isset($p['to'])) ? $this->config['emailsender'] : $p['to'];
3573
        $sendto = explode(',', $sendto);
3574
        foreach ($sendto as $address) {
3575
            list($name, $address) = $this->mail->address_split($address);
3576
            $this->mail->AddAddress($address, $name);
3577
        }
3578 View Code Duplication
        if (isset($p['cc'])) {
3579
            $p['cc'] = explode(',', $p['cc']);
3580
            foreach ($p['cc'] as $address) {
3581
                list($name, $address) = $this->mail->address_split($address);
3582
                $this->mail->AddCC($address, $name);
3583
            }
3584
        }
3585 View Code Duplication
        if (isset($p['bcc'])) {
3586
            $p['bcc'] = explode(',', $p['bcc']);
3587
            foreach ($p['bcc'] as $address) {
3588
                list($name, $address) = $this->mail->address_split($address);
3589
                $this->mail->AddBCC($address, $name);
3590
            }
3591
        }
3592
        if (isset($p['from']) && strpos($p['from'], '<') !== false && substr($p['from'], -1) === '>') {
3593
            list($p['fromname'], $p['from']) = $this->mail->address_split($p['from']);
3594
        }
3595
        $this->mail->From = (!isset($p['from'])) ? $this->config['emailsender'] : $p['from'];
3596
        $this->mail->FromName = (!isset($p['fromname'])) ? $this->config['site_name'] : $p['fromname'];
3597
        $this->mail->Subject = (!isset($p['subject'])) ? $this->config['emailsubject'] : $p['subject'];
3598
        $this->mail->Body = $p['body'];
3599
        if (isset($p['type']) && $p['type'] == 'text') {
3600
            $this->mail->IsHTML(false);
3601
        }
3602
        if (!is_array($files)) {
3603
            $files = array();
3604
        }
3605
        foreach ($files as $f) {
3606
            if (file_exists(MODX_BASE_PATH . $f) && is_file(MODX_BASE_PATH . $f) && is_readable(MODX_BASE_PATH . $f)) {
3607
                $this->mail->AddAttachment(MODX_BASE_PATH . $f);
3608
            }
3609
        }
3610
        $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...
3611
        return $rs;
3612
    }
3613
3614
    /**
3615
     * @param string $target
3616
     * @param int $limit
3617
     * @param int $trim
3618
     */
3619
    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...
3620
    {
3621
        if ($limit < $trim) {
3622
            $trim = $limit;
3623
        }
3624
3625
        $table_name = $this->getFullTableName($target);
3626
        $count = $this->db->getValue($this->db->select('COUNT(id)', $table_name));
3627
        $over = $count - $limit;
3628
        if (0 < $over) {
3629
            $trim = ($over + $trim);
3630
            $this->db->delete($table_name, '', '', $trim);
3631
        }
3632
        $this->db->optimize($table_name);
3633
    }
3634
3635
    /**
3636
     * Returns true if we are currently in the manager backend
3637
     *
3638
     * @return boolean
3639
     */
3640
    public function isBackend()
3641
    {
3642
        return (defined('IN_MANAGER_MODE') && IN_MANAGER_MODE === true);
3643
    }
3644
3645
    /**
3646
     * Returns true if we are currently in the frontend
3647
     *
3648
     * @return boolean
3649
     */
3650
    public function isFrontend()
3651
    {
3652
        return ! $this->isBackend();
3653
    }
3654
3655
    /**
3656
     * Gets all child documents of the specified document, including those which are unpublished or deleted.
3657
     *
3658
     * @param int $id The Document identifier to start with
3659
     * @param string $sort Sort field
3660
     *                     Default: menuindex
3661
     * @param string $dir Sort direction, ASC and DESC is possible
3662
     *                    Default: ASC
3663
     * @param string $fields Default: id, pagetitle, description, parent, alias, menutitle
3664
     * @return array
3665
     */
3666 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...
3667
    {
3668
3669
        $cacheKey = md5(print_r(func_get_args(), true));
3670
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3671
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3672
        }
3673
3674
        $tblsc = $this->getFullTableName("site_content");
3675
        $tbldg = $this->getFullTableName("document_groups");
3676
        // modify field names to use sc. table reference
3677
        $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3678
        $sort = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
3679
        // get document groups for current user
3680
        if ($docgrp = $this->getUserDocGroups()) {
3681
            $docgrp = implode(",", $docgrp);
3682
        }
3683
        // build query
3684
        $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
3685
        $result = $this->db->select("DISTINCT {$fields}", "{$tblsc} sc
3686
                LEFT JOIN {$tbldg} dg on dg.document = sc.id", "sc.parent = '{$id}' AND ({$access}) GROUP BY sc.id", "{$sort} {$dir}");
3687
        $resourceArray = $this->db->makeArray($result);
3688
        $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
3689
        return $resourceArray;
3690
    }
3691
3692
    /**
3693
     * Gets all active child documents of the specified document, i.e. those which published and not deleted.
3694
     *
3695
     * @param int $id The Document identifier to start with
3696
     * @param string $sort Sort field
3697
     *                     Default: menuindex
3698
     * @param string $dir Sort direction, ASC and DESC is possible
3699
     *                    Default: ASC
3700
     * @param string $fields Default: id, pagetitle, description, parent, alias, menutitle
3701
     * @return array
3702
     */
3703 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...
3704
    {
3705
        $cacheKey = md5(print_r(func_get_args(), true));
3706
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3707
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3708
        }
3709
3710
        $tblsc = $this->getFullTableName("site_content");
3711
        $tbldg = $this->getFullTableName("document_groups");
3712
3713
        // modify field names to use sc. table reference
3714
        $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3715
        $sort = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
3716
        // get document groups for current user
3717
        if ($docgrp = $this->getUserDocGroups()) {
3718
            $docgrp = implode(",", $docgrp);
3719
        }
3720
        // build query
3721
        $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
3722
        $result = $this->db->select("DISTINCT {$fields}", "{$tblsc} sc
3723
                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}");
3724
        $resourceArray = $this->db->makeArray($result);
3725
3726
        $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
3727
3728
        return $resourceArray;
3729
    }
3730
3731
    /**
3732
     * getDocumentChildren
3733
     * @version 1.1.1 (2014-02-19)
3734
     *
3735
     * @desc Returns the children of the selected document/folder as an associative array.
3736
     *
3737
     * @param $parentid {integer} - The parent document identifier. Default: 0 (site root).
3738
     * @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.
3739
     * @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.
3740
     * @param $fields {comma separated string; '*'} - Comma separated list of document fields to get. Default: '*' (all fields).
3741
     * @param $where {string} - Where condition in SQL style. Should include a leading 'AND '. Default: ''.
3742
     * @param $sort {comma separated string} - Should be a comma-separated list of field names on which to sort. Default: 'menuindex'.
3743
     * @param $dir {'ASC'; 'DESC'} - Sort direction, ASC and DESC is possible. Default: 'ASC'.
3744
     * @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).
3745
     *
3746
     * @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...
3747
     */
3748
    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...
3749
    {
3750
3751
        $cacheKey = md5(print_r(func_get_args(), true));
3752
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3753
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3754
        }
3755
3756
        $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...
3757
        $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...
3758
3759
        if ($where != '') {
3760
            $where = 'AND ' . $where;
3761
        }
3762
3763
        // modify field names to use sc. table reference
3764
        $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3765
        $sort = ($sort == '') ? '' : 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
3766
3767
        // get document groups for current user
3768
        if ($docgrp = $this->getUserDocGroups()) {
3769
            $docgrp = implode(',', $docgrp);
3770
        }
3771
3772
        // build query
3773
        $access = ($this->isFrontend() ? 'sc.privateweb=0' : '1="' . $_SESSION['mgrRole'] . '" OR sc.privatemgr=0') . (!$docgrp ? '' : ' OR dg.document_group IN (' . $docgrp . ')');
3774
3775
        $tblsc = $this->getFullTableName('site_content');
3776
        $tbldg = $this->getFullTableName('document_groups');
3777
3778
        $result = $this->db->select("DISTINCT {$fields}", "{$tblsc} sc
3779
                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);
3780
3781
        $resourceArray = $this->db->makeArray($result);
3782
3783
        $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
3784
3785
        return $resourceArray;
3786
    }
3787
3788
    /**
3789
     * getDocuments
3790
     * @version 1.1.1 (2013-02-19)
3791
     *
3792
     * @desc Returns required documents (their fields).
3793
     *
3794
     * @param $ids {array; comma separated string} - Documents Ids to get. @required
3795
     * @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.
3796
     * @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.
3797
     * @param $fields {comma separated string; '*'} - Documents fields to get. Default: '*'.
3798
     * @param $where {string} - SQL WHERE clause. Default: ''.
3799
     * @param $sort {comma separated string} - A comma-separated list of field names to sort by. Default: 'menuindex'.
3800
     * @param $dir {'ASC'; 'DESC'} - Sorting direction. Default: 'ASC'.
3801
     * @param $limit {string} - SQL LIMIT (without 'LIMIT '). An empty string means no limit. Default: ''.
3802
     *
3803
     * @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...
3804
     */
3805
    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...
3806
    {
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
3938
        $cacheKey = md5(print_r(func_get_args(), true));
3939
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3940
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3941
        }
3942
3943
        if ($pageid == 0) {
3944
            return false;
3945
        } else {
3946
            $tblsc = $this->getFullTableName("site_content");
3947
            $tbldg = $this->getFullTableName("document_groups");
3948
            $activeSql = $active == 1 ? "AND sc.published=1 AND sc.deleted=0" : "";
3949
            // modify field names to use sc. table reference
3950
            $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3951
            // get document groups for current user
3952
            if ($docgrp = $this->getUserDocGroups()) {
3953
                $docgrp = implode(",", $docgrp);
3954
            }
3955
            $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
3956
            $result = $this->db->select($fields, "{$tblsc} sc LEFT JOIN {$tbldg} dg on dg.document = sc.id", "(sc.id='{$pageid}' {$activeSql}) AND ({$access})", "", 1);
3957
            $pageInfo = $this->db->getRow($result);
3958
3959
            $this->tmpCache[__FUNCTION__][$cacheKey] = $pageInfo;
3960
3961
            return $pageInfo;
3962
        }
3963
    }
3964
3965
    /**
3966
     * Returns the parent document/resource of the given docid
3967
     *
3968
     * @param int $pid The parent docid. If -1, then fetch the current document/resource's parent
3969
     *                 Default: -1
3970
     * @param int $active Should we fetch only published and undeleted documents/resources?
3971
     *                     1 = yes, 0 = no
3972
     *                     Default: 1
3973
     * @param string $fields List of fields
3974
     *                       Default: id, pagetitle, description, alias
3975
     * @return boolean|array
3976
     */
3977
    public function getParent($pid = -1, $active = 1, $fields = 'id, pagetitle, description, alias, parent')
3978
    {
3979
        if ($pid == -1) {
3980
            $pid = $this->documentObject['parent'];
3981
            return ($pid == 0) ? false : $this->getPageInfo($pid, $active, $fields);
3982
        } else if ($pid == 0) {
3983
            return false;
3984
        } else {
3985
            // first get the child document
3986
            $child = $this->getPageInfo($pid, $active, "parent");
3987
            // now return the child's parent
3988
            $pid = ($child['parent']) ? $child['parent'] : 0;
3989
            return ($pid == 0) ? false : $this->getPageInfo($pid, $active, $fields);
3990
        }
3991
    }
3992
3993
    /**
3994
     * Returns the id of the current snippet.
3995
     *
3996
     * @return int
3997
     */
3998
    public function getSnippetId()
3999
    {
4000
        if ($this->currentSnippet) {
4001
            $tbl = $this->getFullTableName("site_snippets");
4002
            $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...
4003
            if ($snippetId = $this->db->getValue($rs)) {
4004
                return $snippetId;
4005
            }
4006
        }
4007
        return 0;
4008
    }
4009
4010
    /**
4011
     * Returns the name of the current snippet.
4012
     *
4013
     * @return string
4014
     */
4015
    public function getSnippetName()
4016
    {
4017
        return $this->currentSnippet;
4018
    }
4019
4020
    /**
4021
     * Clear the cache of MODX.
4022
     *
4023
     * @param string $type
4024
     * @param bool $report
4025
     * @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...
4026
     */
4027
    public function clearCache($type = '', $report = false)
4028
    {
4029
        $cache_dir = MODX_BASE_PATH . $this->getCacheFolder();
4030
        if (is_array($type)) {
4031
            foreach ($type as $_) {
4032
                $this->clearCache($_, $report);
4033
            }
4034
        } elseif ($type == 'full') {
4035
            include_once(MODX_MANAGER_PATH . 'processors/cache_sync.class.processor.php');
4036
            $sync = new synccache();
4037
            $sync->setCachepath($cache_dir);
4038
            $sync->setReport($report);
4039
            $sync->emptyCache();
4040
        } elseif (preg_match('@^[1-9][0-9]*$@', $type)) {
4041
            $key = ($this->config['cache_type'] == 2) ? $this->makePageCacheKey($type) : $type;
4042
            $file_name = "docid_" . $key . "_*.pageCache.php";
4043
            $cache_path = $cache_dir . $file_name;
4044
            $files = glob($cache_path);
4045
            $files[] = $cache_dir . "docid_" . $key . ".pageCache.php";
4046
            foreach ($files as $file) {
4047
                if (!is_file($file)) {
4048
                    continue;
4049
                }
4050
                unlink($file);
4051
            }
4052
        } else {
4053
            $files = glob($cache_dir . '*');
4054
            foreach ($files as $file) {
4055
                $name = basename($file);
4056
                if (strpos($name, '.pageCache.php') === false) {
4057
                    continue;
4058
                }
4059
                if (!is_file($file)) {
4060
                    continue;
4061
                }
4062
                unlink($file);
4063
            }
4064
        }
4065
    }
4066
4067
    /**
4068
     * makeUrl
4069
     *
4070
     * @desc Create an URL for the given document identifier. The url prefix and postfix are used, when “friendly_url” is active.
4071
     *
4072
     * @param $id {integer} - The document identifier. @required
4073
     * @param string $alias {string}
4074
     * - The alias name for the document. Default: ''.
4075
     * @param string $args {string}
4076
     * - The paramaters to add to the URL. Default: ''.
4077
     * @param string $scheme {string}
4078
     * - With full as valus, the site url configuration is used. Default: ''.
4079
     * @return mixed|string {string} - Result URL.
4080
     * - Result URL.
4081
     */
4082
    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...
4083
    {
4084
        $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...
4085
        $virtualDir = isset($this->config['virtual_dir']) ? $this->config['virtual_dir'] : '';
4086
        $f_url_prefix = $this->config['friendly_url_prefix'];
4087
        $f_url_suffix = $this->config['friendly_url_suffix'];
4088
4089
        if (!is_numeric($id)) {
4090
            $this->messageQuit("`{$id}` is not numeric and may not be passed to makeUrl()");
4091
        }
4092
4093
        if ($args !== '') {
4094
            // add ? or & to $args if missing
4095
            $args = ltrim($args, '?&');
4096
            $_ = 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...
4097
4098
            if ($_ === false && $this->config['friendly_urls'] == 1) {
4099
                $args = "?{$args}";
4100
            } else {
4101
                $args = "&{$args}";
4102
            }
4103
        }
4104
4105
        if ($id != $this->config['site_start']) {
4106
            if ($this->config['friendly_urls'] == 1 && $alias == '') {
4107
                $alias = $id;
4108
                $alPath = '';
4109
4110
                if ($this->config['friendly_alias_urls'] == 1) {
4111
4112
                    if ($this->config['aliaslistingfolder'] == 1) {
4113
                        $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...
4114
                    } else {
4115
                        $al = $this->aliasListing[$id];
4116
                    }
4117
4118
                    if ($al['isfolder'] === 1 && $this->config['make_folders'] === '1') {
4119
                        $f_url_suffix = '/';
4120
                    }
4121
4122
                    $alPath = !empty ($al['path']) ? $al['path'] . '/' : '';
4123
4124
                    if ($al && $al['alias']) {
4125
                        $alias = $al['alias'];
4126
                    }
4127
4128
                }
4129
4130
                $alias = $alPath . $f_url_prefix . $alias . $f_url_suffix;
4131
                $url = "{$alias}{$args}";
4132
            } else {
4133
                $url = "index.php?id={$id}{$args}";
4134
            }
4135
        } else {
4136
            $url = $args;
4137
        }
4138
4139
        $host = $this->config['base_url'];
4140
4141
        // check if scheme argument has been set
4142
        if ($scheme != '') {
4143
            // for backward compatibility - check if the desired scheme is different than the current scheme
4144
            if (is_numeric($scheme) && $scheme != $_SERVER['HTTPS']) {
4145
                $scheme = ($_SERVER['HTTPS'] ? 'http' : 'https');
4146
            }
4147
4148
            //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...
4149
            $host = $scheme == 'full' ? $this->config['site_url'] : $scheme . '://' . $_SERVER['HTTP_HOST'] . $host;
4150
        }
4151
4152
        //fix strictUrl by Bumkaka
4153
        if ($this->config['seostrict'] == '1') {
4154
            $url = $this->toAlias($url);
4155
        }
4156
4157
        if ($this->config['xhtml_urls']) {
4158
            $url = preg_replace("/&(?!amp;)/", "&amp;", $host . $virtualDir . $url);
4159
        } else {
4160
            $url = $host . $virtualDir . $url;
4161
        }
4162
4163
        $evtOut = $this->invokeEvent('OnMakeDocUrl', array(
4164
            'id' => $id,
4165
            'url' => $url
4166
        ));
4167
4168
        if (is_array($evtOut) && count($evtOut) > 0) {
4169
            $url = array_pop($evtOut);
4170
        }
4171
4172
        return $url;
4173
    }
4174
4175
    /**
4176
     * @param $id
4177
     * @return mixed
4178
     */
4179
    public function getAliasListing($id)
4180
    {
4181
        if (isset($this->aliasListing[$id])) {
4182
            $out = $this->aliasListing[$id];
4183
        } else {
4184
            $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...
4185
            if ($this->db->getRecordCount($q) == '1') {
4186
                $q = $this->db->getRow($q);
4187
                $this->aliasListing[$id] = array(
4188
                    'id' => (int)$q['id'],
4189
                    'alias' => $q['alias'] == '' ? $q['id'] : $q['alias'],
4190
                    'parent' => (int)$q['parent'],
4191
                    'isfolder' => (int)$q['isfolder'],
4192
                );
4193
                if ($this->aliasListing[$id]['parent'] > 0) {
4194
                    //fix alias_path_usage
4195
                    if ($this->config['use_alias_path'] == '1') {
4196
                        //&& $tmp['path'] != '' - fix error slash with epty path
4197
                        $tmp = $this->getAliasListing($this->aliasListing[$id]['parent']);
4198
                        $this->aliasListing[$id]['path'] = $tmp['path'] . ($tmp['alias_visible'] ? (($tmp['parent'] > 0 && $tmp['path'] != '') ? '/' : '') . $tmp['alias'] : '');
4199
                    } else {
4200
                        $this->aliasListing[$id]['path'] = '';
4201
                    }
4202
                }
4203
4204
                $out = $this->aliasListing[$id];
4205
            }
4206
        }
4207
        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...
4208
    }
4209
4210
    /**
4211
     * Returns an entry from the config
4212
     *
4213
     * Note: most code accesses the config array directly and we will continue to support this.
4214
     *
4215
     * @param string $name
4216
     * @return bool|string
4217
     */
4218
    public function getConfig($name = '')
4219
    {
4220
        if (!empty ($this->config[$name])) {
4221
            return $this->config[$name];
4222
        } else {
4223
            return false;
4224
        }
4225
    }
4226
4227
    /**
4228
     * Returns the MODX version information as version, branch, release date and full application name.
4229
     *
4230
     * @param null $data
4231
     * @return array
4232
     */
4233
4234
    public function getVersionData($data = null)
4235
    {
4236
        $out = array();
4237
        if (empty($this->version) || !is_array($this->version)) {
4238
            //include for compatibility modx version < 1.0.10
4239
            include MODX_MANAGER_PATH . "includes/version.inc.php";
4240
            $this->version = array();
4241
            $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...
4242
            $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...
4243
            $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...
4244
            $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...
4245
            $this->version['new_version'] = isset($this->config['newversiontext']) ? $this->config['newversiontext'] : '';
4246
        }
4247
        return (!is_null($data) && is_array($this->version) && isset($this->version[$data])) ? $this->version[$data] : $this->version;
4248
    }
4249
4250
    /**
4251
     * Executes a snippet.
4252
     *
4253
     * @param string $snippetName
4254
     * @param array $params Default: Empty array
4255
     * @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...
4256
     */
4257
    public function runSnippet($snippetName, $params = array())
4258
    {
4259
        if (isset ($this->snippetCache[$snippetName])) {
4260
            $snippet = $this->snippetCache[$snippetName];
4261
            $properties = !empty($this->snippetCache[$snippetName . "Props"]) ? $this->snippetCache[$snippetName . "Props"] : '';
4262
        } else { // not in cache so let's check the db
4263
            $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;";
4264
            $result = $this->db->query($sql);
4265
            if ($this->db->getRecordCount($result) == 1) {
4266
                $row = $this->db->getRow($result);
4267
                $snippet = $this->snippetCache[$snippetName] = $row['snippet'];
4268
                $mergedProperties = array_merge($this->parseProperties($row['properties']), $this->parseProperties($row['sharedproperties']));
4269
                $properties = $this->snippetCache[$snippetName . "Props"] = json_encode($mergedProperties);
4270
            } else {
4271
                $snippet = $this->snippetCache[$snippetName] = "return false;";
4272
                $properties = $this->snippetCache[$snippetName . "Props"] = '';
4273
            }
4274
        }
4275
        // load default params/properties
4276
        $parameters = $this->parseProperties($properties, $snippetName, 'snippet');
4277
        $parameters = array_merge($parameters, $params);
4278
4279
        // run snippet
4280
        return $this->evalSnippet($snippet, $parameters);
4281
    }
4282
4283
    /**
4284
     * Returns the chunk content for the given chunk name
4285
     *
4286
     * @param string $chunkName
4287
     * @return boolean|string
4288
     */
4289
    public function getChunk($chunkName)
4290
    {
4291
        $out = null;
4292
        if (empty($chunkName)) {
4293
            return $out;
4294
        }
4295
        if (isset ($this->chunkCache[$chunkName])) {
4296
            $out = $this->chunkCache[$chunkName];
4297
        } else if (stripos($chunkName, '@FILE') === 0) {
4298
            $out = $this->chunkCache[$chunkName] = $this->atBindFileContent($chunkName);
4299
        } else {
4300
            $where = sprintf("`name`='%s' AND disabled=0", $this->db->escape($chunkName));
4301
            $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...
4302
            if ($this->db->getRecordCount($rs) == 1) {
4303
                $row = $this->db->getRow($rs);
4304
                $out = $this->chunkCache[$chunkName] = $row['snippet'];
4305
            } else {
4306
                $out = $this->chunkCache[$chunkName] = null;
4307
            }
4308
        }
4309
        return $out;
4310
    }
4311
4312
    /**
4313
     * parseText
4314
     * @version 1.0 (2013-10-17)
4315
     *
4316
     * @desc Replaces placeholders in text with required values.
4317
     *
4318
     * @param string $tpl
4319
     * @param array $ph
4320
     * @param string $left
4321
     * @param string $right
4322
     * @param bool $execModifier
4323
     * @return string {string} - Parsed text.
4324
     * - Parsed text.
4325
     * @internal param $chunk {string} - String to parse. - String to parse. @required
4326
     * @internal param $chunkArr {array} - Array of values. Key — placeholder name, value — value. - Array of values. Key — placeholder name, value — value. @required
4327
     * @internal param $prefix {string} - Placeholders prefix. Default: '[+'. - Placeholders prefix. Default: '[+'.
4328
     * @internal param $suffix {string} - Placeholders suffix. Default: '+]'. - Placeholders suffix. Default: '+]'.
4329
     *
4330
     */
4331
    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...
4332
    {
4333
        if (empty($ph) || empty($tpl)) {
4334
            return $tpl;
4335
        }
4336
4337 View Code Duplication
        if ($this->config['enable_at_syntax']) {
4338
            if (stripos($tpl, '<@LITERAL>') !== false) {
4339
                $tpl = $this->escapeLiteralTagsContent($tpl);
4340
            }
4341
        }
4342
4343
        $matches = $this->getTagsFromContent($tpl, $left, $right);
4344
        if (empty($matches)) {
4345
            return $tpl;
4346
        }
4347
4348
        foreach ($matches[1] as $i => $key) {
4349
4350
            if (strpos($key, ':') !== false && $execModifier) {
4351
                list($key, $modifiers) = $this->splitKeyAndFilter($key);
4352
            } else {
4353
                $modifiers = false;
4354
            }
4355
4356
            //          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...
4357
            if (!array_key_exists($key, $ph)) {
4358
                continue;
4359
            } //NULL values must be saved in placeholders, if we got them from database string
4360
4361
            $value = $ph[$key];
4362
4363
            $s = &$matches[0][$i];
4364
            if ($modifiers !== false) {
4365
                if (strpos($modifiers, $left) !== false) {
4366
                    $modifiers = $this->parseText($modifiers, $ph, $left, $right);
4367
                }
4368
                $value = $this->applyFilter($value, $modifiers, $key);
4369
            }
4370 View Code Duplication
            if (strpos($tpl, $s) !== false) {
4371
                $tpl = str_replace($s, $value, $tpl);
4372
            } elseif($this->debug) {
4373
                $this->addLog('parseText parse error', $_SERVER['REQUEST_URI'] . $s, 2);
4374
            }
4375
        }
4376
4377
        return $tpl;
4378
    }
4379
4380
    /**
4381
     * parseChunk
4382
     * @version 1.1 (2013-10-17)
4383
     *
4384
     * @desc Replaces placeholders in a chunk with required values.
4385
     *
4386
     * @param $chunkName {string} - Name of chunk to parse. @required
4387
     * @param $chunkArr {array} - Array of values. Key — placeholder name, value — value. @required
4388
     * @param string $prefix {string}
4389
     * - Placeholders prefix. Default: '{'.
4390
     * @param string $suffix {string}
4391
     * - Placeholders suffix. Default: '}'.
4392
     * @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...
4393
     * - Parsed chunk or false if $chunkArr is not array.
4394
     */
4395
    public function parseChunk($chunkName, $chunkArr, $prefix = '{', $suffix = '}')
4396
    {
4397
        //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...
4398
        if (!is_array($chunkArr)) {
4399
            return false;
4400
        }
4401
4402
        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...
4403
    }
4404
4405
    /**
4406
     * getTpl
4407
     * get template for snippets
4408
     * @param $tpl {string}
4409
     * @return bool|string {string}
4410
     */
4411
    public function getTpl($tpl)
4412
    {
4413
        $template = $tpl;
4414
        if (preg_match("/^@([^:\s]+)[:\s]+(.+)$/s", trim($tpl), $match)) {
4415
            $command = strtoupper($match[1]);
4416
            $template = $match[2];
4417
        }
4418
        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...
4419
            case 'CODE':
4420
                break;
4421
            case 'FILE':
4422
                $template = file_get_contents(MODX_BASE_PATH . $template);
4423
                break;
4424
            case 'CHUNK':
4425
                $template = $this->getChunk($template);
4426
                break;
4427
            case 'DOCUMENT':
4428
                $doc = $this->getDocument($template, 'content', 'all');
4429
                $template = $doc['content'];
4430
                break;
4431
            case 'SELECT':
4432
                $this->db->getValue($this->db->query("SELECT {$template}"));
4433
                break;
4434
            default:
4435
                if (!($template = $this->getChunk($tpl))) {
4436
                    $template = $tpl;
4437
                }
4438
        }
4439
        return $template;
4440
    }
4441
4442
    /**
4443
     * Returns the timestamp in the date format defined in $this->config['datetime_format']
4444
     *
4445
     * @param int $timestamp Default: 0
4446
     * @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.
4447
     * @return string
4448
     */
4449
    public function toDateFormat($timestamp = 0, $mode = '')
4450
    {
4451
        $timestamp = trim($timestamp);
4452
        if ($mode !== 'formatOnly' && empty($timestamp)) {
4453
            return '-';
4454
        }
4455
        $timestamp = (int)$timestamp;
4456
4457
        switch ($this->config['datetime_format']) {
4458
            case 'YYYY/mm/dd':
4459
                $dateFormat = '%Y/%m/%d';
4460
                break;
4461
            case 'dd-mm-YYYY':
4462
                $dateFormat = '%d-%m-%Y';
4463
                break;
4464
            case 'mm/dd/YYYY':
4465
                $dateFormat = '%m/%d/%Y';
4466
                break;
4467
            /*
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...
4468
            case 'dd-mmm-YYYY':
4469
                $dateFormat = '%e-%b-%Y';
4470
                break;
4471
            */
4472
        }
4473
4474
        if (empty($mode)) {
4475
            $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...
4476
        } elseif ($mode == 'dateOnly') {
4477
            $strTime = strftime($dateFormat, $timestamp);
4478
        } elseif ($mode == 'formatOnly') {
4479
            $strTime = $dateFormat;
4480
        }
4481
        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...
4482
    }
4483
4484
    /**
4485
     * Make a timestamp from a string corresponding to the format in $this->config['datetime_format']
4486
     *
4487
     * @param string $str
4488
     * @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...
4489
     */
4490
    public function toTimeStamp($str)
4491
    {
4492
        $str = trim($str);
4493
        if (empty($str)) {
4494
            return '';
4495
        }
4496
4497
        switch ($this->config['datetime_format']) {
4498 View Code Duplication
            case 'YYYY/mm/dd':
4499
                if (!preg_match('/^[0-9]{4}\/[0-9]{2}\/[0-9]{2}[0-9 :]*$/', $str)) {
4500
                    return '';
4501
                }
4502
                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...
4503
                break;
4504 View Code Duplication
            case 'dd-mm-YYYY':
4505
                if (!preg_match('/^[0-9]{2}-[0-9]{2}-[0-9]{4}[0-9 :]*$/', $str)) {
4506
                    return '';
4507
                }
4508
                list ($d, $m, $Y, $H, $M, $S) = sscanf($str, '%2d-%2d-%4d %2d:%2d:%2d');
4509
                break;
4510 View Code Duplication
            case 'mm/dd/YYYY':
4511
                if (!preg_match('/^[0-9]{2}\/[0-9]{2}\/[0-9]{4}[0-9 :]*$/', $str)) {
4512
                    return '';
4513
                }
4514
                list ($m, $d, $Y, $H, $M, $S) = sscanf($str, '%2d/%2d/%4d %2d:%2d:%2d');
4515
                break;
4516
            /*
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...
4517
            case 'dd-mmm-YYYY':
4518
                if (!preg_match('/^[0-9]{2}-[0-9a-z]+-[0-9]{4}[0-9 :]*$/i', $str)) {return '';}
4519
                list ($m, $d, $Y, $H, $M, $S) = sscanf($str, '%2d-%3s-%4d %2d:%2d:%2d');
4520
                break;
4521
            */
4522
        }
4523
        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...
4524
            $H = 0;
4525
            $M = 0;
4526
            $S = 0;
4527
        }
4528
        $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...
4529
        $timeStamp = (int)$timeStamp;
4530
        return $timeStamp;
4531
    }
4532
4533
    /**
4534
     * Get the TVs of a document's children. Returns an array where each element represents one child doc.
4535
     *
4536
     * Ignores deleted children. Gets all children - there is no where clause available.
4537
     *
4538
     * @param int $parentid The parent docid
4539
     *                 Default: 0 (site root)
4540
     * @param array $tvidnames . Which TVs to fetch - Can relate to the TV ids in the db (array elements should be numeric only)
4541
     *                                               or the TV names (array elements should be names only)
4542
     *                      Default: Empty array
4543
     * @param int $published Whether published or unpublished documents are in the result
4544
     *                      Default: 1
4545
     * @param string $docsort How to sort the result array (field)
4546
     *                      Default: menuindex
4547
     * @param ASC|string $docsortdir How to sort the result array (direction)
4548
     *                      Default: ASC
4549
     * @param string $tvfields Fields to fetch from site_tmplvars, default '*'
4550
     *                      Default: *
4551
     * @param string $tvsort How to sort each element of the result array i.e. how to sort the TVs (field)
4552
     *                      Default: rank
4553
     * @param string $tvsortdir How to sort each element of the result array i.e. how to sort the TVs (direction)
4554
     *                      Default: ASC
4555
     * @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...
4556
     */
4557
    public function getDocumentChildrenTVars($parentid = 0, $tvidnames = array(), $published = 1, $docsort = "menuindex", $docsortdir = "ASC", $tvfields = "*", $tvsort = "rank", $tvsortdir = "ASC")
4558
    {
4559
        $docs = $this->getDocumentChildren($parentid, $published, 0, '*', '', $docsort, $docsortdir);
4560
        if (!$docs) {
4561
            return false;
4562
        } else {
4563
            $result = array();
4564
            // get user defined template variables
4565
            if ($tvfields) {
4566
                $_ = 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...
4567
                foreach ($_ as $i => $v) {
4568
                    if ($v === 'value') {
4569
                        unset($_[$i]);
4570
                    } else {
4571
                        $_[$i] = 'tv.' . $v;
4572
                    }
4573
                }
4574
                $fields = implode(',', $_);
4575
            } else {
4576
                $fields = "tv.*";
4577
            }
4578
4579
            if ($tvsort != '') {
4580
                $tvsort = 'tv.' . implode(',tv.', array_filter(array_map('trim', explode(',', $tvsort))));
4581
            }
4582 View Code Duplication
            if ($tvidnames == "*") {
4583
                $query = "tv.id<>0";
4584
            } else {
4585
                $query = (is_numeric($tvidnames[0]) ? "tv.id" : "tv.name") . " IN ('" . implode("','", $tvidnames) . "')";
4586
            }
4587
4588
            $this->getUserDocGroups();
4589
4590
            foreach ($docs as $doc) {
4591
4592
                $docid = $doc['id'];
4593
4594
                $rs = $this->db->select("{$fields}, IF(tvc.value!='',tvc.value,tv.default_text) as value ", "[+prefix+]site_tmplvars tv
4595
                        INNER JOIN [+prefix+]site_tmplvar_templates tvtpl ON tvtpl.tmplvarid = tv.id
4596
                        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}" : ""));
4597
                $tvs = $this->db->makeArray($rs);
4598
4599
                // get default/built-in template variables
4600
                ksort($doc);
4601
                foreach ($doc as $key => $value) {
4602
                    if ($tvidnames == '*' || in_array($key, $tvidnames)) {
4603
                        $tvs[] = array('name' => $key, 'value' => $value);
4604
                    }
4605
                }
4606
                if (is_array($tvs) && count($tvs)) {
4607
                    $result[] = $tvs;
4608
                }
4609
            }
4610
            return $result;
4611
        }
4612
    }
4613
4614
    /**
4615
     * getDocumentChildrenTVarOutput
4616
     * @version 1.1 (2014-02-19)
4617
     *
4618
     * @desc Returns an array where each element represents one child doc and contains the result from getTemplateVarOutput().
4619
     *
4620
     * @param int $parentid {integer}
4621
     * - Id of parent document. Default: 0 (site root).
4622
     * @param array $tvidnames {array; '*'}
4623
     * - Which TVs to fetch. In the form expected by getTemplateVarOutput(). Default: array().
4624
     * @param int $published {0; 1; 'all'}
4625
     * - 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.
4626
     * @param string $sortBy {string}
4627
     * - How to sort the result array (field). Default: 'menuindex'.
4628
     * @param string $sortDir {'ASC'; 'DESC'}
4629
     * - How to sort the result array (direction). Default: 'ASC'.
4630
     * @param string $where {string}
4631
     * - SQL WHERE condition (use only document fields, not TV). Default: ''.
4632
     * @param string $resultKey {string; false}
4633
     * - Field, which values are keys into result array. Use the “false”, that result array keys just will be numbered. Default: 'id'.
4634
     * @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...
4635
     * - Result array, or false.
4636
     */
4637
    public function getDocumentChildrenTVarOutput($parentid = 0, $tvidnames = array(), $published = 1, $sortBy = 'menuindex', $sortDir = 'ASC', $where = '', $resultKey = 'id')
4638
    {
4639
        $docs = $this->getDocumentChildren($parentid, $published, 0, 'id', $where, $sortBy, $sortDir);
4640
4641
        if (!$docs) {
4642
            return false;
4643
        } else {
4644
            $result = array();
4645
4646
            $unsetResultKey = false;
4647
4648
            if ($resultKey !== false) {
4649
                if (is_array($tvidnames)) {
4650
                    if (count($tvidnames) != 0 && !in_array($resultKey, $tvidnames)) {
4651
                        $tvidnames[] = $resultKey;
4652
                        $unsetResultKey = true;
4653
                    }
4654
                } else if ($tvidnames != '*' && $tvidnames != $resultKey) {
4655
                    $tvidnames = array($tvidnames, $resultKey);
4656
                    $unsetResultKey = true;
4657
                }
4658
            }
4659
4660
            for ($i = 0; $i < count($docs); $i++) {
4661
                $tvs = $this->getTemplateVarOutput($tvidnames, $docs[$i]['id'], $published);
4662
4663
                if ($tvs) {
4664
                    if ($resultKey !== false && array_key_exists($resultKey, $tvs)) {
4665
                        $result[$tvs[$resultKey]] = $tvs;
4666
4667
                        if ($unsetResultKey) {
4668
                            unset($result[$tvs[$resultKey]][$resultKey]);
4669
                        }
4670
                    } else {
4671
                        $result[] = $tvs;
4672
                    }
4673
                }
4674
            }
4675
4676
            return $result;
4677
        }
4678
    }
4679
4680
    /**
4681
     * Modified by Raymond for TV - Orig Modified by Apodigm - DocVars
4682
     * Returns a single site_content field or TV record from the db.
4683
     *
4684
     * If a site content field the result is an associative array of 'name' and 'value'.
4685
     *
4686
     * If a TV the result is an array representing a db row including the fields specified in $fields.
4687
     *
4688
     * @param string $idname Can be a TV id or name
4689
     * @param string $fields Fields to fetch from site_tmplvars. Default: *
4690
     * @param string|type $docid Docid. Defaults to empty string which indicates the current document.
4691
     * @param int $published Whether published or unpublished documents are in the result
4692
     *                        Default: 1
4693
     * @return bool
4694
     */
4695 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...
4696
    {
4697
        if ($idname == "") {
4698
            return false;
4699
        } else {
4700
            $result = $this->getTemplateVars(array($idname), $fields, $docid, $published, "", ""); //remove sorting for speed
4701
            return ($result != false) ? $result[0] : false;
4702
        }
4703
    }
4704
4705
    /**
4706
     * getTemplateVars
4707
     * @version 1.0.1 (2014-02-19)
4708
     *
4709
     * @desc Returns an array of site_content field fields and/or TV records from the db.
4710
     * Elements representing a site content field consist of an associative array of 'name' and 'value'.
4711
     * Elements representing a TV consist of an array representing a db row including the fields specified in $fields.
4712
     *
4713
     * @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
4714
     * @param $fields {comma separated string; '*'} - Fields names in the TV table of MODx database. Default: '*'
4715
     * @param $docid {integer; ''} - Id of a document to get. Default: an empty string which indicates the current document.
4716
     * @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.
4717
     * @param $sort {comma separated string} - Fields of the TV table to sort by. Default: 'rank'.
4718
     * @param $dir {'ASC'; 'DESC'} - How to sort the result array (direction). Default: 'ASC'.
4719
     *
4720
     * @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...
4721
     */
4722
    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...
4723
    {
4724
        $cacheKey = md5(print_r(func_get_args(), true));
4725
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
4726
            return $this->tmpCache[__FUNCTION__][$cacheKey];
4727
        }
4728
4729
        if (($idnames != '*' && !is_array($idnames)) || empty($idnames) ) {
4730
            return false;
4731
        } else {
4732
4733
            // get document record
4734
            if ($docid == '') {
4735
                $docid = $this->documentIdentifier;
4736
                $docRow = $this->documentObject;
4737
            } else {
4738
                $docRow = $this->getDocument($docid, '*', $published);
4739
4740
                if (!$docRow) {
4741
                    $this->tmpCache[__FUNCTION__][$cacheKey] = false;
4742
                    return false;
4743
                }
4744
            }
4745
4746
            // get user defined template variables
4747
            $fields = ($fields == '') ? 'tv.*' : 'tv.' . implode(',tv.', array_filter(array_map('trim', explode(',', $fields))));
4748
            $sort = ($sort == '') ? '' : 'tv.' . implode(',tv.', array_filter(array_map('trim', explode(',', $sort))));
4749
4750 View Code Duplication
            if ($idnames == '*') {
4751
                $query = 'tv.id<>0';
4752
            } else {
4753
                $query = (is_numeric($idnames[0]) ? 'tv.id' : 'tv.name') . " IN ('" . implode("','", $idnames) . "')";
4754
            }
4755
4756
            $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...
4757
                    INNER JOIN " . $this->getFullTableName('site_tmplvar_templates') . " tvtpl ON tvtpl.tmplvarid = tv.id
4758
                    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}" : ""));
4759
4760
            $result = $this->db->makeArray($rs);
4761
4762
            // get default/built-in template variables
4763
            if(is_array($docRow)){
4764
                ksort($docRow);
4765
4766
                foreach ($docRow as $key => $value) {
4767
                    if ($idnames == '*' || in_array($key, $idnames)) {
4768
                        array_push($result, array(
4769
                            'name' => $key,
4770
                            'value' => $value
4771
                        ));
4772
                    }
4773
                }
4774
            }
4775
4776
            $this->tmpCache[__FUNCTION__][$cacheKey] = $result;
4777
4778
            return $result;
4779
        }
4780
    }
4781
4782
    /**
4783
     * getTemplateVarOutput
4784
     * @version 1.0.1 (2014-02-19)
4785
     *
4786
     * @desc Returns an associative array containing TV rendered output values.
4787
     *
4788
     * @param array $idnames {array; '*'}
4789
     * - 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
4790
     * @param string $docid {integer; ''}
4791
     * - Id of a document to get. Default: an empty string which indicates the current document.
4792
     * @param int $published {0; 1; 'all'}
4793
     * - 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.
4794
     * @param string $sep {string}
4795
     * - Separator that is used while concatenating in getTVDisplayFormat(). Default: ''.
4796
     * @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...
4797
     * - Result array, or false.
4798
     */
4799
    public function getTemplateVarOutput($idnames = array(), $docid = '', $published = 1, $sep = '')
4800
    {
4801
        if (is_array($idnames) && empty($idnames) ) {
4802
            return false;
4803
        } else {
4804
            $output = array();
4805
            $vars = ($idnames == '*' || is_array($idnames)) ? $idnames : array($idnames);
4806
4807
            $docid = (int)$docid > 0 ? (int)$docid : $this->documentIdentifier;
4808
            // remove sort for speed
4809
            $result = $this->getTemplateVars($vars, '*', $docid, $published, '', '');
4810
4811
            if ($result == false) {
4812
                return false;
4813
            } else {
4814
                $baspath = MODX_MANAGER_PATH . 'includes';
4815
                include_once $baspath . '/tmplvars.format.inc.php';
4816
                include_once $baspath . '/tmplvars.commands.inc.php';
4817
4818
                for ($i = 0; $i < count($result); $i++) {
4819
                    $row = $result[$i];
4820
4821
                    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...
4822
                        $output[$row['name']] = $row['value'];
4823
                    } else {
4824
                        $output[$row['name']] = getTVDisplayFormat($row['name'], $row['value'], $row['display'], $row['display_params'], $row['type'], $docid, $sep);
4825
                    }
4826
                }
4827
4828
                return $output;
4829
            }
4830
        }
4831
    }
4832
4833
    /**
4834
     * Returns the full table name based on db settings
4835
     *
4836
     * @param string $tbl Table name
4837
     * @return string Table name with prefix
4838
     */
4839
    public function getFullTableName($tbl)
4840
    {
4841
        return $this->db->config['dbase'] . ".`" . $this->db->config['table_prefix'] . $tbl . "`";
4842
    }
4843
4844
    /**
4845
     * Returns the placeholder value
4846
     *
4847
     * @param string $name Placeholder name
4848
     * @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...
4849
     */
4850
    public function getPlaceholder($name)
4851
    {
4852
        return isset($this->placeholders[$name]) ? $this->placeholders[$name] : null;
4853
    }
4854
4855
    /**
4856
     * Sets a value for a placeholder
4857
     *
4858
     * @param string $name The name of the placeholder
4859
     * @param string $value The value of the placeholder
4860
     */
4861
    public function setPlaceholder($name, $value)
4862
    {
4863
        $this->placeholders[$name] = $value;
4864
    }
4865
4866
    /**
4867
     * Set placeholders en masse via an array or object.
4868
     *
4869
     * @param object|array $subject
4870
     * @param string $prefix
4871
     */
4872
    public function toPlaceholders($subject, $prefix = '')
4873
    {
4874
        if (is_object($subject)) {
4875
            $subject = get_object_vars($subject);
4876
        }
4877
        if (is_array($subject)) {
4878
            foreach ($subject as $key => $value) {
4879
                $this->toPlaceholder($key, $value, $prefix);
4880
            }
4881
        }
4882
    }
4883
4884
    /**
4885
     * For use by toPlaceholders(); For setting an array or object element as placeholder.
4886
     *
4887
     * @param string $key
4888
     * @param object|array $value
4889
     * @param string $prefix
4890
     */
4891
    public function toPlaceholder($key, $value, $prefix = '')
4892
    {
4893
        if (is_array($value) || is_object($value)) {
4894
            $this->toPlaceholders($value, "{$prefix}{$key}.");
4895
        } else {
4896
            $this->setPlaceholder("{$prefix}{$key}", $value);
4897
        }
4898
    }
4899
4900
    /**
4901
     * Returns the manager relative URL/path with respect to the site root.
4902
     *
4903
     * @global string $base_url
4904
     * @return string The complete URL to the manager folder
4905
     */
4906
    public function getManagerPath()
4907
    {
4908
        return MODX_MANAGER_URL;
4909
    }
4910
4911
    /**
4912
     * Returns the cache relative URL/path with respect to the site root.
4913
     *
4914
     * @global string $base_url
4915
     * @return string The complete URL to the cache folder
4916
     */
4917
    public function getCachePath()
4918
    {
4919
        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...
4920
        $pth = $base_url . $this->getCacheFolder();
4921
        return $pth;
4922
    }
4923
4924
    /**
4925
     * Sends a message to a user's message box.
4926
     *
4927
     * @param string $type Type of the message
4928
     * @param string $to The recipient of the message
4929
     * @param string $from The sender of the message
4930
     * @param string $subject The subject of the message
4931
     * @param string $msg The message body
4932
     * @param int $private Whether it is a private message, or not
4933
     *                     Default : 0
4934
     */
4935
    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...
4936
    {
4937
        $private = ($private) ? 1 : 0;
4938 View Code Duplication
        if (!is_numeric($to)) {
4939
            // Query for the To ID
4940
            $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...
4941
            $to = $this->db->getValue($rs);
4942
        }
4943 View Code Duplication
        if (!is_numeric($from)) {
4944
            // Query for the From ID
4945
            $rs = $this->db->select('id', $this->getFullTableName("manager_users"), "username='{$from}'");
4946
            $from = $this->db->getValue($rs);
4947
        }
4948
        // insert a new message into user_messages
4949
        $this->db->insert(array(
4950
            'type' => $type,
4951
            'subject' => $subject,
4952
            'message' => $msg,
4953
            'sender' => $from,
4954
            'recipient' => $to,
4955
            'private' => $private,
4956
            'postdate' => $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'],
4957
            'messageread' => 0,
4958
        ), $this->getFullTableName('user_messages'));
4959
    }
4960
4961
    /**
4962
     * Returns current user id.
4963
     *
4964
     * @param string $context . Default is an empty string which indicates the method should automatically pick 'web (frontend) or 'mgr' (backend)
4965
     * @return string
4966
     */
4967 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...
4968
    {
4969
        $out = false;
4970
4971
        if (!empty($context)) {
4972
            if (is_scalar($context) && isset($_SESSION[$context . 'Validated'])) {
4973
                $out = $_SESSION[$context . 'InternalKey'];
4974
            }
4975
        } else {
4976
            switch (true) {
4977
                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...
4978
                    $out = $_SESSION['webInternalKey'];
4979
                    break;
4980
                }
4981
                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...
4982
                    $out = $_SESSION['mgrInternalKey'];
4983
                    break;
4984
                }
4985
            }
4986
        }
4987
        return $out;
4988
    }
4989
4990
    /**
4991
     * Returns current user name
4992
     *
4993
     * @param string $context . Default is an empty string which indicates the method should automatically pick 'web (frontend) or 'mgr' (backend)
4994
     * @return string
4995
     */
4996 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...
4997
    {
4998
        $out = false;
4999
5000
        if (!empty($context)) {
5001
            if (is_scalar($context) && isset($_SESSION[$context . 'Validated'])) {
5002
                $out = $_SESSION[$context . 'Shortname'];
5003
            }
5004
        } else {
5005
            switch (true) {
5006
                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...
5007
                    $out = $_SESSION['webShortname'];
5008
                    break;
5009
                }
5010
                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...
5011
                    $out = $_SESSION['mgrShortname'];
5012
                    break;
5013
                }
5014
            }
5015
        }
5016
        return $out;
5017
    }
5018
5019
    /**
5020
     * Returns current login user type - web or manager
5021
     *
5022
     * @return string
5023
     */
5024
    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...
5025
    {
5026
        if ($this->isFrontend() && isset ($_SESSION['webValidated'])) {
5027
            return 'web';
5028
        } elseif ($this->isBackend() && isset ($_SESSION['mgrValidated'])) {
5029
            return 'manager';
5030
        } else {
5031
            return '';
5032
        }
5033
    }
5034
5035
    /**
5036
     * Returns a user info record for the given manager user
5037
     *
5038
     * @param int $uid
5039
     * @return boolean|string
5040
     */
5041
    public function getUserInfo($uid)
5042
    {
5043
        if (isset($this->tmpCache[__FUNCTION__][$uid])) {
5044
            return $this->tmpCache[__FUNCTION__][$uid];
5045
        }
5046
5047
        $from = '[+prefix+]manager_users mu INNER JOIN [+prefix+]user_attributes mua ON mua.internalkey=mu.id';
5048
        $where = sprintf("mu.id='%s'", $this->db->escape($uid));
5049
        $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...
5050
5051
        if (!$this->db->getRecordCount($rs)) {
5052
            return $this->tmpCache[__FUNCTION__][$uid] = false;
5053
        }
5054
5055
        $row = $this->db->getRow($rs);
5056 View Code Duplication
        if (!isset($row['usertype']) || !$row['usertype']) {
5057
            $row['usertype'] = 'manager';
5058
        }
5059
5060
        $this->tmpCache[__FUNCTION__][$uid] = $row;
5061
5062
        return $row;
5063
    }
5064
5065
    /**
5066
     * Returns a record for the web user
5067
     *
5068
     * @param int $uid
5069
     * @return boolean|string
5070
     */
5071
    public function getWebUserInfo($uid)
5072
    {
5073
        $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...
5074
                INNER JOIN " . $this->getFullTableName("web_user_attributes") . " wua ON wua.internalkey=wu.id", "wu.id='{$uid}'");
5075
        if ($row = $this->db->getRow($rs)) {
5076 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...
5077
                $row["usertype"] = "web";
5078
            }
5079
            return $row;
5080
        }
5081
    }
5082
5083
    /**
5084
     * Returns an array of document groups that current user is assigned to.
5085
     * This function will first return the web user doc groups when running from
5086
     * frontend otherwise it will return manager user's docgroup.
5087
     *
5088
     * @param boolean $resolveIds Set to true to return the document group names
5089
     *                            Default: false
5090
     * @return string|array
5091
     */
5092
    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...
5093
    {
5094
        if ($this->isFrontend() && isset($_SESSION['webDocgroups']) && isset($_SESSION['webValidated'])) {
5095
            $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...
5096
            $dgn = isset($_SESSION['webDocgrpNames']) ? $_SESSION['webDocgrpNames'] : false;
5097
        } else if ($this->isBackend() && isset($_SESSION['mgrDocgroups']) && isset($_SESSION['mgrValidated'])) {
5098
            $dg = $_SESSION['mgrDocgroups'];
5099
            $dgn = isset($_SESSION['mgrDocgrpNames']) ? $_SESSION['mgrDocgrpNames'] : false;
5100
        } else {
5101
            $dg = '';
5102
        }
5103
        if (!$resolveIds) {
5104
            return $dg;
5105
        } else if (is_array($dgn)) {
5106
            return $dgn;
0 ignored issues
show
Bug introduced by
The variable $dgn does not seem to be defined for all execution paths leading up to this point.

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

Let’s take a look at an example:

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

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

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

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

Available Fixes

  1. Check for existence of the variable explicitly:

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

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

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
5107
        } else if (is_array($dg)) {
5108
            // resolve ids to names
5109
            $dgn = array();
5110
            $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...
5111
            while ($row = $this->db->getRow($ds)) {
5112
                $dgn[] = $row['name'];
5113
            }
5114
            // cache docgroup names to session
5115
            if ($this->isFrontend()) {
5116
                $_SESSION['webDocgrpNames'] = $dgn;
5117
            } else {
5118
                $_SESSION['mgrDocgrpNames'] = $dgn;
5119
            }
5120
            return $dgn;
5121
        }
5122
    }
5123
5124
    /**
5125
     * Change current web user's password
5126
     *
5127
     * @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...
5128
     * @param string $oldPwd
5129
     * @param string $newPwd
5130
     * @return string|boolean Returns true if successful, oterhwise return error
5131
     *                        message
5132
     */
5133
    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...
5134
    {
5135
        $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...
5136
        if ($_SESSION["webValidated"] == 1) {
5137
            $tbl = $this->getFullTableName("web_users");
5138
            $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...
5139
            if ($row = $this->db->getRow($ds)) {
5140
                if ($row["password"] == md5($oldPwd)) {
5141
                    if (strlen($newPwd) < 6) {
5142
                        return "Password is too short!";
5143
                    } elseif ($newPwd == "") {
5144
                        return "You didn't specify a password for this user!";
5145
                    } else {
5146
                        $this->db->update(array(
5147
                            'password' => $this->db->escape($newPwd),
5148
                        ), $tbl, "id='" . $this->getLoginUserID() . "'");
5149
                        // invoke OnWebChangePassword event
5150
                        $this->invokeEvent("OnWebChangePassword", array(
5151
                            "userid" => $row["id"],
5152
                            "username" => $row["username"],
5153
                            "userpassword" => $newPwd
5154
                        ));
5155
                        return true;
5156
                    }
5157
                } else {
5158
                    return "Incorrect password.";
5159
                }
5160
            }
5161
        }
5162
        return $rt;
5163
    }
5164
5165
    /**
5166
     * Returns true if the current web user is a member the specified groups
5167
     *
5168
     * @param array $groupNames
5169
     * @return boolean
5170
     */
5171
    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...
5172
    {
5173
        if (!is_array($groupNames)) {
5174
            return false;
5175
        }
5176
        // check cache
5177
        $grpNames = isset ($_SESSION['webUserGroupNames']) ? $_SESSION['webUserGroupNames'] : false;
5178
        if (!is_array($grpNames)) {
5179
            $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...
5180
                    INNER JOIN " . $this->getFullTableName("web_groups") . " wg ON wg.webgroup=wgn.id AND wg.webuser='" . $this->getLoginUserID() . "'");
5181
            $grpNames = $this->db->getColumn("name", $rs);
5182
            // save to cache
5183
            $_SESSION['webUserGroupNames'] = $grpNames;
5184
        }
5185
        foreach ($groupNames as $k => $v) {
5186
            if (in_array(trim($v), $grpNames)) {
5187
                return true;
5188
            }
5189
        }
5190
        return false;
5191
    }
5192
5193
    /**
5194
     * Registers Client-side CSS scripts - these scripts are loaded at inside
5195
     * the <head> tag
5196
     *
5197
     * @param string $src
5198
     * @param string $media Default: Empty string
5199
     * @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...
5200
     */
5201
    public function regClientCSS($src, $media = '')
5202
    {
5203
        if (empty($src) || isset ($this->loadedjscripts[$src])) {
5204
            return '';
5205
        }
5206
        $nextpos = max(array_merge(array(0), array_keys($this->sjscripts))) + 1;
5207
        $this->loadedjscripts[$src]['startup'] = true;
5208
        $this->loadedjscripts[$src]['version'] = '0';
5209
        $this->loadedjscripts[$src]['pos'] = $nextpos;
5210
        if (strpos(strtolower($src), "<style") !== false || strpos(strtolower($src), "<link") !== false) {
5211
            $this->sjscripts[$nextpos] = $src;
5212
        } else {
5213
            $this->sjscripts[$nextpos] = "\t" . '<link rel="stylesheet" type="text/css" href="' . $src . '" ' . ($media ? 'media="' . $media . '" ' : '') . '/>';
5214
        }
5215
    }
5216
5217
    /**
5218
     * Registers Startup Client-side JavaScript - these scripts are loaded at inside the <head> tag
5219
     *
5220
     * @param string $src
5221
     * @param array $options Default: 'name'=>'', 'version'=>'0', 'plaintext'=>false
5222
     */
5223
    public function regClientStartupScript($src, $options = array('name' => '', 'version' => '0', 'plaintext' => false))
5224
    {
5225
        $this->regClientScript($src, $options, true);
5226
    }
5227
5228
    /**
5229
     * Registers Client-side JavaScript these scripts are loaded at the end of the page unless $startup is true
5230
     *
5231
     * @param string $src
5232
     * @param array $options Default: 'name'=>'', 'version'=>'0', 'plaintext'=>false
5233
     * @param boolean $startup Default: false
5234
     * @return string
5235
     */
5236
    public function regClientScript($src, $options = array('name' => '', 'version' => '0', 'plaintext' => false), $startup = false)
5237
    {
5238
        if (empty($src)) {
5239
            return '';
5240
        } // nothing to register
5241
        if (!is_array($options)) {
5242
            if (is_bool($options))  // backward compatibility with old plaintext parameter
5243
            {
5244
                $options = array('plaintext' => $options);
5245
            } elseif (is_string($options)) // Also allow script name as 2nd param
5246
            {
5247
                $options = array('name' => $options);
5248
            } else {
5249
                $options = array();
5250
            }
5251
        }
5252
        $name = isset($options['name']) ? strtolower($options['name']) : '';
5253
        $version = isset($options['version']) ? $options['version'] : '0';
5254
        $plaintext = isset($options['plaintext']) ? $options['plaintext'] : false;
5255
        $key = !empty($name) ? $name : $src;
5256
        unset($overwritepos); // probably unnecessary--just making sure
5257
5258
        $useThisVer = true;
5259
        if (isset($this->loadedjscripts[$key])) { // a matching script was found
5260
            // if existing script is a startup script, make sure the candidate is also a startup script
5261
            if ($this->loadedjscripts[$key]['startup']) {
5262
                $startup = true;
5263
            }
5264
5265
            if (empty($name)) {
5266
                $useThisVer = false; // if the match was based on identical source code, no need to replace the old one
5267
            } else {
5268
                $useThisVer = version_compare($this->loadedjscripts[$key]['version'], $version, '<');
5269
            }
5270
5271
            if ($useThisVer) {
5272
                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...
5273
                    // remove old script from the bottom of the page (new one will be at the top)
5274
                    unset($this->jscripts[$this->loadedjscripts[$key]['pos']]);
5275
                } else {
5276
                    // overwrite the old script (the position may be important for dependent scripts)
5277
                    $overwritepos = $this->loadedjscripts[$key]['pos'];
5278
                }
5279
            } else { // Use the original version
5280
                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...
5281
                    // need to move the exisiting script to the head
5282
                    $version = $this->loadedjscripts[$key][$version];
5283
                    $src = $this->jscripts[$this->loadedjscripts[$key]['pos']];
5284
                    unset($this->jscripts[$this->loadedjscripts[$key]['pos']]);
5285
                } else {
5286
                    return ''; // the script is already in the right place
5287
                }
5288
            }
5289
        }
5290
5291
        if ($useThisVer && $plaintext != true && (strpos(strtolower($src), "<script") === false)) {
5292
            $src = "\t" . '<script type="text/javascript" src="' . $src . '"></script>';
5293
        }
5294
        if ($startup) {
5295
            $pos = isset($overwritepos) ? $overwritepos : max(array_merge(array(0), array_keys($this->sjscripts))) + 1;
5296
            $this->sjscripts[$pos] = $src;
5297
        } else {
5298
            $pos = isset($overwritepos) ? $overwritepos : max(array_merge(array(0), array_keys($this->jscripts))) + 1;
5299
            $this->jscripts[$pos] = $src;
5300
        }
5301
        $this->loadedjscripts[$key]['version'] = $version;
5302
        $this->loadedjscripts[$key]['startup'] = $startup;
5303
        $this->loadedjscripts[$key]['pos'] = $pos;
5304
        return '';
5305
    }
5306
5307
    /**
5308
     * Returns all registered JavaScripts
5309
     *
5310
     * @return string
5311
     */
5312
    public function regClientStartupHTMLBlock($html)
5313
    {
5314
        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...
5315
    }
5316
5317
    /**
5318
     * Returns all registered startup scripts
5319
     *
5320
     * @return string
5321
     */
5322
    public function regClientHTMLBlock($html)
5323
    {
5324
        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...
5325
    }
5326
5327
    /**
5328
     * Remove unwanted html tags and snippet, settings and tags
5329
     *
5330
     * @param string $html
5331
     * @param string $allowed Default: Empty string
5332
     * @return string
5333
     */
5334
    public function stripTags($html, $allowed = "")
5335
    {
5336
        $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...
5337
        $t = preg_replace('~\[\*(.*?)\*\]~', "", $t); //tv
5338
        $t = preg_replace('~\[\[(.*?)\]\]~', "", $t); //snippet
5339
        $t = preg_replace('~\[\!(.*?)\!\]~', "", $t); //snippet
5340
        $t = preg_replace('~\[\((.*?)\)\]~', "", $t); //settings
5341
        $t = preg_replace('~\[\+(.*?)\+\]~', "", $t); //placeholders
5342
        $t = preg_replace('~{{(.*?)}}~', "", $t); //chunks
5343
        return $t;
5344
    }
5345
5346
    /**
5347
     * Add an event listener to a plugin - only for use within the current execution cycle
5348
     *
5349
     * @param string $evtName
5350
     * @param string $pluginName
5351
     * @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...
5352
     */
5353
    public function addEventListener($evtName, $pluginName)
5354
    {
5355
        if (!$evtName || !$pluginName) {
5356
            return false;
5357
        }
5358
        if (!array_key_exists($evtName, $this->pluginEvent)) {
5359
            $this->pluginEvent[$evtName] = array();
5360
        }
5361
        return array_push($this->pluginEvent[$evtName], $pluginName); // return array count
5362
    }
5363
5364
    /**
5365
     * Remove event listener - only for use within the current execution cycle
5366
     *
5367
     * @param string $evtName
5368
     * @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...
5369
     */
5370
    public function removeEventListener($evtName)
5371
    {
5372
        if (!$evtName) {
5373
            return false;
5374
        }
5375
        unset ($this->pluginEvent[$evtName]);
5376
    }
5377
5378
    /**
5379
     * Remove all event listeners - only for use within the current execution cycle
5380
     */
5381
    public function removeAllEventListener()
5382
    {
5383
        unset ($this->pluginEvent);
5384
        $this->pluginEvent = array();
5385
    }
5386
5387
    /**
5388
     * Invoke an event.
5389
     *
5390
     * @param string $evtName
5391
     * @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.
5392
     * @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...
5393
     */
5394
    public function invokeEvent($evtName, $extParams = array())
5395
    {
5396
        if (!$evtName) {
5397
            return false;
5398
        }
5399
        if (!isset ($this->pluginEvent[$evtName])) {
5400
            return false;
5401
        }
5402
5403
        $results = null;
5404
        foreach ($this->pluginEvent[$evtName] as $pluginName) { // start for loop
5405
            if ($this->dumpPlugins) {
5406
                $eventtime = $this->getMicroTime();
5407
            }
5408
            // reset event object
5409
            $e = &$this->event;
5410
            $e->_resetEventObject();
5411
            $e->name = $evtName;
5412
            $e->activePlugin = $pluginName;
5413
5414
            // get plugin code
5415
            $_ = $this->getPluginCode($pluginName);
5416
            $pluginCode = $_['code'];
5417
            $pluginProperties = $_['props'];
5418
5419
            // load default params/properties
5420
            $parameter = $this->parseProperties($pluginProperties);
5421
            if (!is_array($parameter)) {
5422
                $parameter = array();
5423
            }
5424
            if (!empty($extParams)) {
5425
                $parameter = array_merge($parameter, $extParams);
5426
            }
5427
5428
            // eval plugin
5429
            $this->evalPlugin($pluginCode, $parameter);
5430
5431
            if (class_exists('PHxParser')) {
5432
                $this->config['enable_filter'] = 0;
5433
            }
5434
5435
            if ($this->dumpPlugins) {
5436
                $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...
5437
                $this->pluginsCode .= sprintf('<fieldset><legend><b>%s / %s</b> (%2.2f ms)</legend>', $evtName, $pluginName, $eventtime * 1000);
5438
                foreach ($parameter as $k => $v) {
5439
                    $this->pluginsCode .= "{$k} => " . print_r($v, true) . '<br>';
5440
                }
5441
                $this->pluginsCode .= '</fieldset><br />';
5442
                $this->pluginsTime["{$evtName} / {$pluginName}"] += $eventtime;
5443
            }
5444
            if ($e->_output != '') {
5445
                $results[] = $e->_output;
5446
            }
5447
            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...
5448
                break;
5449
            }
5450
        }
5451
5452
        $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...
5453
        return $results;
5454
    }
5455
5456
    /**
5457
     * Returns plugin-code and properties
5458
     *
5459
     * @param string $pluginName
5460
     * @return array Associative array consisting of 'code' and 'props'
5461
     */
5462
    public function getPluginCode($pluginName)
5463
    {
5464
        $plugin = array();
5465
        if (isset ($this->pluginCache[$pluginName])) {
5466
            $pluginCode = $this->pluginCache[$pluginName];
5467
            $pluginProperties = isset($this->pluginCache[$pluginName . "Props"]) ? $this->pluginCache[$pluginName . "Props"] : '';
5468
        } else {
5469
            $pluginName = $this->db->escape($pluginName);
5470
            $result = $this->db->select('name, plugincode, properties', $this->getFullTableName("site_plugins"), "name='{$pluginName}' AND disabled=0");
5471
            if ($row = $this->db->getRow($result)) {
5472
                $pluginCode = $this->pluginCache[$row['name']] = $row['plugincode'];
5473
                $pluginProperties = $this->pluginCache[$row['name'] . "Props"] = $row['properties'];
5474
            } else {
5475
                $pluginCode = $this->pluginCache[$pluginName] = "return false;";
5476
                $pluginProperties = '';
5477
            }
5478
        }
5479
        $plugin['code'] = $pluginCode;
5480
        $plugin['props'] = $pluginProperties;
5481
5482
        return $plugin;
5483
    }
5484
5485
    /**
5486
     * Parses a resource property string and returns the result as an array
5487
     *
5488
     * @param string $propertyString
5489
     * @param string|null $elementName
5490
     * @param string|null $elementType
5491
     * @return array Associative array in the form property name => property value
5492
     */
5493
    public function parseProperties($propertyString, $elementName = null, $elementType = null)
5494
    {
5495
        $propertyString = trim($propertyString);
5496
        $propertyString = str_replace('{}', '', $propertyString);
5497
        $propertyString = str_replace('} {', ',', $propertyString);
5498
        if (empty($propertyString)) {
5499
            return array();
5500
        }
5501
        if ($propertyString == '{}') {
5502
            return array();
5503
        }
5504
5505
        $jsonFormat = $this->isJson($propertyString, true);
5506
        $property = array();
5507
        // old format
5508
        if ($jsonFormat === false) {
5509
            $props = explode('&', $propertyString);
5510
            foreach ($props as $prop) {
5511
5512
                if (empty($prop)) {
5513
                    continue;
5514
                } elseif (strpos($prop, '=') === false) {
5515
                    $property[trim($prop)] = '';
5516
                    continue;
5517
                }
5518
5519
                $_ = explode('=', $prop, 2);
5520
                $key = trim($_[0]);
5521
                $p = explode(';', trim($_[1]));
5522
                switch ($p[1]) {
5523
                    case 'list':
5524
                    case 'list-multi':
5525
                    case 'checkbox':
5526
                    case 'radio':
5527
                        $value = !isset($p[3]) ? '' : $p[3];
5528
                        break;
5529
                    default:
5530
                        $value = !isset($p[2]) ? '' : $p[2];
5531
                }
5532
                if (!empty($key)) {
5533
                    $property[$key] = $value;
5534
                }
5535
            }
5536
            // new json-format
5537
        } else if (!empty($jsonFormat)) {
5538
            foreach ($jsonFormat as $key => $row) {
5539
                if (!empty($key)) {
5540
                    if (is_array($row)) {
5541
                        if (isset($row[0]['value'])) {
5542
                            $value = $row[0]['value'];
5543
                        }
5544
                    } else {
5545
                        $value = $row;
5546
                    }
5547
                    if (isset($value) && $value !== '') {
5548
                        $property[$key] = $value;
5549
                    }
5550
                }
5551
            }
5552
        }
5553
        if (!empty($elementName) && !empty($elementType)) {
5554
            $out = $this->invokeEvent('OnParseProperties', array(
5555
                'element' => $elementName,
5556
                'type' => $elementType,
5557
                'args' => $property
5558
            ));
5559
            if (is_array($out)) {
5560
                $out = array_pop($out);
5561
            }
5562
            if (is_array($out)) {
5563
                $property = $out;
5564
            }
5565
        }
5566
        return $property;
5567
    }
5568
5569
    /**
5570
     * Parses docBlock from a file and returns the result as an array
5571
     *
5572
     * @param string $element_dir
5573
     * @param string $filename
5574
     * @param boolean $escapeValues
5575
     * @return array Associative array in the form property name => property value
5576
     */
5577
    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...
5578
    {
5579
        $params = array();
5580
        $fullpath = $element_dir . '/' . $filename;
5581
        if (is_readable($fullpath)) {
5582
            $tpl = @fopen($fullpath, "r");
5583
            if ($tpl) {
5584
                $params['filename'] = $filename;
5585
                $docblock_start_found = false;
5586
                $name_found = false;
5587
                $description_found = false;
5588
                $docblock_end_found = false;
5589
                $arrayParams = array('author', 'documentation', 'reportissues', 'link');
5590
5591
                while (!feof($tpl)) {
5592
                    $line = fgets($tpl);
5593
                    $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...
5594
                    $docblock_start_found = $r['docblock_start_found'];
5595
                    $name_found = $r['name_found'];
5596
                    $description_found = $r['description_found'];
5597
                    $docblock_end_found = $r['docblock_end_found'];
5598
                    $param = $r['param'];
5599
                    $val = $r['val'];
5600
                    if (!$docblock_end_found) {
5601
                        break;
5602
                    }
5603
                    if (!$docblock_start_found || !$name_found || !$description_found || empty($param)) {
5604
                        continue;
5605
                    }
5606 View Code Duplication
                    if (!empty($param)) {
5607
                        if (in_array($param, $arrayParams)) {
5608
                            if (!isset($params[$param])) {
5609
                                $params[$param] = array();
5610
                            }
5611
                            $params[$param][] = $escapeValues ? $this->db->escape($val) : $val;
5612
                        } else {
5613
                            $params[$param] = $escapeValues ? $this->db->escape($val) : $val;
5614
                        }
5615
                    }
5616
                }
5617
                @fclose($tpl);
5618
            }
5619
        }
5620
        return $params;
5621
    }
5622
5623
    /**
5624
     * Parses docBlock from string and returns the result as an array
5625
     *
5626
     * @param string $string
5627
     * @param boolean $escapeValues
5628
     * @return array Associative array in the form property name => property value
5629
     */
5630
    public function parseDocBlockFromString($string, $escapeValues = false)
5631
    {
5632
        $params = array();
5633
        if (!empty($string)) {
5634
            $string = str_replace('\r\n', '\n', $string);
5635
            $exp = explode('\n', $string);
5636
            $docblock_start_found = false;
5637
            $name_found = false;
5638
            $description_found = false;
5639
            $docblock_end_found = false;
5640
            $arrayParams = array('author', 'documentation', 'reportissues', 'link');
5641
5642
            foreach ($exp as $line) {
5643
                $r = $this->parseDocBlockLine($line, $docblock_start_found, $name_found, $description_found, $docblock_end_found);
5644
                $docblock_start_found = $r['docblock_start_found'];
5645
                $name_found = $r['name_found'];
5646
                $description_found = $r['description_found'];
5647
                $docblock_end_found = $r['docblock_end_found'];
5648
                $param = $r['param'];
5649
                $val = $r['val'];
5650
                if (!$docblock_start_found) {
5651
                    continue;
5652
                }
5653
                if ($docblock_end_found) {
5654
                    break;
5655
                }
5656 View Code Duplication
                if (!empty($param)) {
5657
                    if (in_array($param, $arrayParams)) {
5658
                        if (!isset($params[$param])) {
5659
                            $params[$param] = array();
5660
                        }
5661
                        $params[$param][] = $escapeValues ? $this->db->escape($val) : $val;
5662
                    } else {
5663
                        $params[$param] = $escapeValues ? $this->db->escape($val) : $val;
5664
                    }
5665
                }
5666
            }
5667
        }
5668
        return $params;
5669
    }
5670
5671
    /**
5672
     * Parses docBlock of a component´s source-code and returns the result as an array
5673
     * (modified parseDocBlock() from modules/stores/setup.info.php by Bumkaka & Dmi3yy)
5674
     *
5675
     * @param string $line
5676
     * @param boolean $docblock_start_found
5677
     * @param boolean $name_found
5678
     * @param boolean $description_found
5679
     * @param boolean $docblock_end_found
5680
     * @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...
5681
     */
5682
    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...
5683
    {
5684
        $param = '';
5685
        $val = '';
5686
        $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...
5687
        if (!$docblock_start_found) {
5688
            // find docblock start
5689
            if (strpos($line, '/**') !== false) {
5690
                $docblock_start_found = true;
5691
            }
5692
        } elseif (!$name_found) {
5693
            // find name
5694
            if (preg_match("/^\s+\*\s+(.+)/", $line, $ma)) {
5695
                $param = 'name';
5696
                $val = trim($ma[1]);
5697
                $name_found = !empty($val);
5698
            }
5699
        } elseif (!$description_found) {
5700
            // find description
5701
            if (preg_match("/^\s+\*\s+(.+)/", $line, $ma)) {
5702
                $param = 'description';
5703
                $val = trim($ma[1]);
5704
                $description_found = !empty($val);
5705
            }
5706
        } else {
5707
            if (preg_match("/^\s+\*\s+\@([^\s]+)\s+(.+)/", $line, $ma)) {
5708
                $param = trim($ma[1]);
5709
                $val = trim($ma[2]);
5710
                if (!empty($param) && !empty($val)) {
5711
                    if ($param == 'internal') {
5712
                        $ma = null;
5713
                        if (preg_match("/\@([^\s]+)\s+(.+)/", $val, $ma)) {
5714
                            $param = trim($ma[1]);
5715
                            $val = trim($ma[2]);
5716
                        }
5717
                    }
5718
                }
5719
            } elseif (preg_match("/^\s*\*\/\s*$/", $line)) {
5720
                $docblock_end_found = true;
5721
            }
5722
        }
5723
        return array(
5724
            'docblock_start_found' => $docblock_start_found,
5725
            'name_found' => $name_found,
5726
            'description_found' => $description_found,
5727
            'docblock_end_found' => $docblock_end_found,
5728
            'param' => $param,
5729
            'val' => $val
5730
        );
5731
    }
5732
5733
    /**
5734
     * Renders docBlock-parameters into human readable list
5735
     *
5736
     * @param array $parsed
5737
     * @return string List in HTML-format
5738
     */
5739
    public function convertDocBlockIntoList($parsed)
5740
    {
5741
        global $_lang;
5742
5743
        // Replace special placeholders & make URLs + Emails clickable
5744
        $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...
5745
        $regexUrl = "/((http|https|ftp|ftps)\:\/\/[^\/]+(\/[^\s]+[^,.?!:;\s])?)/";
5746
        $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';
5747
        $emailSubject = isset($parsed['name']) ? '?subject=' . $parsed['name'] : '';
5748
        $emailSubject .= isset($parsed['version']) ? ' v' . $parsed['version'] : '';
5749
        foreach ($parsed as $key => $val) {
5750
            if (is_array($val)) {
5751
                foreach ($val as $key2 => $val2) {
5752
                    $val2 = $this->parseText($val2, $ph);
5753 View Code Duplication
                    if (preg_match($regexUrl, $val2, $url)) {
5754
                        $val2 = preg_replace($regexUrl, "<a href=\"{$url[0]}\" target=\"_blank\">{$url[0]}</a> ", $val2);
5755
                    }
5756 View Code Duplication
                    if (preg_match($regexEmail, $val2, $url)) {
5757
                        $val2 = preg_replace($regexEmail, '<a href="mailto:\\1' . $emailSubject . '">\\1</a>', $val2);
5758
                    }
5759
                    $parsed[$key][$key2] = $val2;
5760
                }
5761
            } else {
5762
                $val = $this->parseText($val, $ph);
5763 View Code Duplication
                if (preg_match($regexUrl, $val, $url)) {
5764
                    $val = preg_replace($regexUrl, "<a href=\"{$url[0]}\" target=\"_blank\">{$url[0]}</a> ", $val);
5765
                }
5766 View Code Duplication
                if (preg_match($regexEmail, $val, $url)) {
5767
                    $val = preg_replace($regexEmail, '<a href="mailto:\\1' . $emailSubject . '">\\1</a>', $val);
5768
                }
5769
                $parsed[$key] = $val;
5770
            }
5771
        }
5772
5773
        $arrayParams = array(
5774
            'documentation' => $_lang['documentation'],
5775
            'reportissues' => $_lang['report_issues'],
5776
            'link' => $_lang['further_info'],
5777
            'author' => $_lang['author_infos']
5778
        );
5779
5780
        $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...
5781
        $list = isset($parsed['logo']) ? '<img src="' . $this->config['base_url'] . ltrim($parsed['logo'], "/") . '" style="float:right;max-width:100px;height:auto;" />' . $nl : '';
5782
        $list .= '<p>' . $nl;
5783
        $list .= isset($parsed['name']) ? '<strong>' . $parsed['name'] . '</strong><br/>' . $nl : '';
5784
        $list .= isset($parsed['description']) ? $parsed['description'] . $nl : '';
5785
        $list .= '</p><br/>' . $nl;
5786
        $list .= isset($parsed['version']) ? '<p><strong>' . $_lang['version'] . ':</strong> ' . $parsed['version'] . '</p>' . $nl : '';
5787
        $list .= isset($parsed['license']) ? '<p><strong>' . $_lang['license'] . ':</strong> ' . $parsed['license'] . '</p>' . $nl : '';
5788
        $list .= isset($parsed['lastupdate']) ? '<p><strong>' . $_lang['last_update'] . ':</strong> ' . $parsed['lastupdate'] . '</p>' . $nl : '';
5789
        $list .= '<br/>' . $nl;
5790
        $first = true;
5791
        foreach ($arrayParams as $param => $label) {
5792
            if (isset($parsed[$param])) {
5793
                if ($first) {
5794
                    $list .= '<p><strong>' . $_lang['references'] . '</strong></p>' . $nl;
5795
                    $list .= '<ul class="docBlockList">' . $nl;
5796
                    $first = false;
5797
                }
5798
                $list .= '    <li><strong>' . $label . '</strong>' . $nl;
5799
                $list .= '        <ul>' . $nl;
5800
                foreach ($parsed[$param] as $val) {
5801
                    $list .= '            <li>' . $val . '</li>' . $nl;
5802
                }
5803
                $list .= '        </ul></li>' . $nl;
5804
            }
5805
        }
5806
        $list .= !$first ? '</ul>' . $nl : '';
5807
5808
        return $list;
5809
    }
5810
5811
    /**
5812
     * @param string $string
5813
     * @return string
5814
     */
5815
    public function removeSanitizeSeed($string = '')
5816
    {
5817
        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...
5818
5819
        if (!$string || strpos($string, $sanitize_seed) === false) {
5820
            return $string;
5821
        }
5822
5823
        return str_replace($sanitize_seed, '', $string);
5824
    }
5825
5826
    /**
5827
     * @param string $content
5828
     * @return string
5829
     */
5830
    public function cleanUpMODXTags($content = '')
5831
    {
5832
        if ($this->minParserPasses < 1) {
5833
            return $content;
5834
        }
5835
5836
        $enable_filter = $this->config['enable_filter'];
5837
        $this->config['enable_filter'] = 1;
5838
        $_ = 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...
5839
        foreach ($_ as $brackets) {
5840
            list($left, $right) = explode(' ', $brackets);
5841
            if (strpos($content, $left) !== false) {
5842
                if ($left === '[*') {
5843
                    $content = $this->mergeDocumentContent($content);
5844
                } elseif ($left === '[(') {
5845
                    $content = $this->mergeSettingsContent($content);
5846
                } elseif ($left === '{{') {
5847
                    $content = $this->mergeChunkContent($content);
5848
                } elseif ($left === '[[') {
5849
                    $content = $this->evalSnippets($content);
5850
                }
5851
            }
5852
        }
5853
        foreach ($_ as $brackets) {
5854
            list($left, $right) = explode(' ', $brackets);
5855
            if (strpos($content, $left) !== false) {
5856
                $matches = $this->getTagsFromContent($content, $left, $right);
5857
                $content = str_replace($matches[0], '', $content);
5858
            }
5859
        }
5860
        $this->config['enable_filter'] = $enable_filter;
5861
        return $content;
5862
    }
5863
5864
    /**
5865
     * @param string $str
5866
     * @param string $allowable_tags
5867
     * @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...
5868
     */
5869
    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...
5870
    {
5871
        $str = strip_tags($str, $allowable_tags);
5872
        modx_sanitize_gpc($str);
5873
        return $str;
5874
    }
5875
5876
    /**
5877
     * @param string $name
5878
     * @param string $phpCode
5879
     */
5880
    public function addSnippet($name, $phpCode)
5881
    {
5882
        $this->snippetCache['#' . $name] = $phpCode;
5883
    }
5884
5885
    /**
5886
     * @param string $name
5887
     * @param string $text
5888
     */
5889
    public function addChunk($name, $text)
5890
    {
5891
        $this->chunkCache['#' . $name] = $text;
5892
    }
5893
5894
    /**
5895
     * @param string $phpcode
5896
     * @param string $evalmode
5897
     * @param string $safe_functions
5898
     * @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...
5899
     */
5900
    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...
5901
    {
5902
        if ($evalmode == '') {
5903
            $evalmode = $this->config['allow_eval'];
5904
        }
5905
        if ($safe_functions == '') {
5906
            $safe_functions = $this->config['safe_functions_at_eval'];
5907
        }
5908
5909
        modx_sanitize_gpc($phpcode);
5910
5911
        switch ($evalmode) {
5912
            case 'with_scan'         :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
5913
                $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...
5914
                break;
5915
            case 'with_scan_at_post' :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
5916
                $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...
5917
                break;
5918
            case 'everytime_eval'    :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
5919
                $isSafe = true;
5920
                break; // Should debug only
5921
            case 'dont_eval'         :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
5922
            default                  :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a DEFAULT statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in the default statement.

switch ($expr) {
    default : //wrong
        doSomething();
        break;
}

switch ($expr) {
    default: //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
5923
                return $phpcode;
5924
        }
5925
5926
        if (!$isSafe) {
5927
            $msg = $phpcode . "\n" . $this->currentSnippet . "\n" . print_r($_SERVER, true);
5928
            $title = sprintf('Unknown eval was executed (%s)', $this->htmlspecialchars(substr(trim($phpcode), 0, 50)));
5929
            $this->messageQuit($title, '', true, '', '', 'Parser', $msg);
5930
            return;
5931
        }
5932
5933
        ob_start();
5934
        $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...
5935
        $echo = ob_get_clean();
5936
5937
        if (is_array($return)) {
5938
            return 'array()';
5939
        }
5940
5941
        $output = $echo . $return;
5942
        modx_sanitize_gpc($output);
5943
        return $this->htmlspecialchars($output); // Maybe, all html tags are dangerous
5944
    }
5945
5946
    /**
5947
     * @param string $phpcode
5948
     * @param string $safe_functions
5949
     * @return bool
5950
     */
5951
    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...
5952
    { // 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...
5953
        if ($safe_functions == '') {
5954
            return false;
5955
        }
5956
5957
        $safe = explode(',', $safe_functions);
5958
5959
        $phpcode = rtrim($phpcode, ';') . ';';
5960
        $tokens = token_get_all('<?php ' . $phpcode);
5961
        foreach ($tokens as $i => $token) {
5962
            if (!is_array($token)) {
5963
                continue;
5964
            }
5965
            $tokens[$i]['token_name'] = token_name($token[0]);
5966
        }
5967
        foreach ($tokens as $token) {
5968
            if (!is_array($token)) {
5969
                continue;
5970
            }
5971
            switch ($token['token_name']) {
5972
                case 'T_STRING':
5973
                    if (!in_array($token[1], $safe)) {
5974
                        return false;
5975
                    }
5976
                    break;
5977
                case 'T_VARIABLE':
5978
                    if ($token[1] == '$GLOBALS') {
5979
                        return false;
5980
                    }
5981
                    break;
5982
                case 'T_EVAL':
5983
                    return false;
5984
            }
5985
        }
5986
        return true;
5987
    }
5988
5989
    /**
5990
     * @param string $str
5991
     * @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...
5992
     */
5993
    public function atBindFileContent($str = '')
5994
    {
5995
5996
        $search_path = array('assets/tvs/', 'assets/chunks/', 'assets/templates/', $this->config['rb_base_url'] . 'files/', '');
5997
5998
        if (stripos($str, '@FILE') !== 0) {
5999
            return $str;
6000
        }
6001 View Code Duplication
        if (strpos($str, "\n") !== false) {
6002
            $str = substr($str, 0, strpos("\n", $str));
6003
        }
6004
6005
        if ($this->getExtFromFilename($str) === '.php') {
6006
            return 'Could not retrieve PHP file.';
6007
        }
6008
6009
        $str = substr($str, 6);
6010
        $str = trim($str);
6011
        if (strpos($str, '\\') !== false) {
6012
            $str = str_replace('\\', '/', $str);
6013
        }
6014
        $str = ltrim($str, '/');
6015
6016
        $errorMsg = sprintf("Could not retrieve string '%s'.", $str);
6017
6018
        foreach ($search_path as $path) {
6019
            $file_path = MODX_BASE_PATH . $path . $str;
6020
            if (strpos($file_path, MODX_MANAGER_PATH) === 0) {
6021
                return $errorMsg;
6022
            } elseif (is_file($file_path)) {
6023
                break;
6024
            } else {
6025
                $file_path = false;
6026
            }
6027
        }
6028
6029
        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...
6030
            return $errorMsg;
6031
        }
6032
6033
        $content = (string)file_get_contents($file_path);
6034
        if ($content === false) {
6035
            return $errorMsg;
6036
        }
6037
6038
        return $content;
6039
    }
6040
6041
    /**
6042
     * @param $str
6043
     * @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...
6044
     */
6045
    public function getExtFromFilename($str)
6046
    {
6047
        $str = strtolower(trim($str));
6048
        $pos = strrpos($str, '.');
6049
        if ($pos === false) {
6050
            return false;
6051
        } else {
6052
            return substr($str, $pos);
6053
        }
6054
    }
6055
    /***************************************************************************************/
6056
    /* End of API functions                                       */
6057
    /***************************************************************************************/
6058
6059
    /**
6060
     * PHP error handler set by http://www.php.net/manual/en/function.set-error-handler.php
6061
     *
6062
     * Checks the PHP error and calls messageQuit() unless:
6063
     *  - error_reporting() returns 0, or
6064
     *  - the PHP error level is 0, or
6065
     *  - the PHP error level is 8 (E_NOTICE) and stopOnNotice is false
6066
     *
6067
     * @param int $nr The PHP error level as per http://www.php.net/manual/en/errorfunc.constants.php
6068
     * @param string $text Error message
6069
     * @param string $file File where the error was detected
6070
     * @param string $line Line number within $file
6071
     * @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...
6072
     */
6073
    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...
6074
    {
6075
        if (error_reporting() == 0 || $nr == 0) {
6076
            return true;
6077
        }
6078
        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...
6079
            switch ($nr) {
6080
                case E_NOTICE:
6081
                    if ($this->error_reporting <= 2) {
6082
                        return true;
6083
                    }
6084
                    $isError = false;
6085
                    $msg = 'PHP Minor Problem (this message show logged in only)';
6086
                    break;
6087
                case E_STRICT:
6088 View Code Duplication
                case E_DEPRECATED:
6089
                    if ($this->error_reporting <= 1) {
6090
                        return true;
6091
                    }
6092
                    $isError = true;
6093
                    $msg = 'PHP Strict Standards Problem';
6094
                    break;
6095 View Code Duplication
                default:
6096
                    if ($this->error_reporting === 0) {
6097
                        return true;
6098
                    }
6099
                    $isError = true;
6100
                    $msg = 'PHP Parse Error';
6101
            }
6102
        }
6103
        if (is_readable($file)) {
6104
            $source = file($file);
6105
            $source = $this->htmlspecialchars($source[$line - 1]);
6106
        } else {
6107
            $source = "";
6108
        } //Error $nr in $file at $line: <div><code>$source</code></div>
6109
6110
        $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...
6111
    }
6112
6113
    /**
6114
     * @param string $msg
6115
     * @param string $query
6116
     * @param bool $is_error
6117
     * @param string $nr
6118
     * @param string $file
6119
     * @param string $source
6120
     * @param string $text
6121
     * @param string $line
6122
     * @param string $output
6123
     * @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...
6124
     */
6125
    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...
6126
    {
6127
6128
        if (0 < $this->messageQuitCount) {
6129
            return;
6130
        }
6131
        $this->messageQuitCount++;
6132
6133
        if (!class_exists('makeTable')) {
6134
            include_once('extenders/maketable.class.php');
6135
        }
6136
        $MakeTable = new MakeTable();
6137
        $MakeTable->setTableClass('grid');
6138
        $MakeTable->setRowRegularClass('gridItem');
6139
        $MakeTable->setRowAlternateClass('gridAltItem');
6140
        $MakeTable->setColumnWidths(array('100px'));
6141
6142
        $table = array();
6143
6144
        $version = isset ($GLOBALS['modx_version']) ? $GLOBALS['modx_version'] : '';
6145
        $release_date = isset ($GLOBALS['release_date']) ? $GLOBALS['release_date'] : '';
6146
        $request_uri = "http://" . $_SERVER['HTTP_HOST'] . ($_SERVER["SERVER_PORT"] == 80 ? "" : (":" . $_SERVER["SERVER_PORT"])) . $_SERVER['REQUEST_URI'];
6147
        $request_uri = $this->htmlspecialchars($request_uri, ENT_QUOTES, $this->config['modx_charset']);
6148
        $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...
6149
        $referer = $this->htmlspecialchars($_SERVER['HTTP_REFERER'], ENT_QUOTES, $this->config['modx_charset']);
6150
        if ($is_error) {
6151
            $str = '<h2 style="color:red">&laquo; Evo Parse Error &raquo;</h2>';
6152
            if ($msg != 'PHP Parse Error') {
6153
                $str .= '<h3 style="color:red">' . $msg . '</h3>';
6154
            }
6155
        } else {
6156
            $str = '<h2 style="color:#003399">&laquo; Evo Debug/ stop message &raquo;</h2>';
6157
            $str .= '<h3 style="color:#003399">' . $msg . '</h3>';
6158
        }
6159
6160
        if (!empty ($query)) {
6161
            $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>';
6162
        }
6163
6164
        $errortype = array(
6165
            E_ERROR => "ERROR",
6166
            E_WARNING => "WARNING",
6167
            E_PARSE => "PARSING ERROR",
6168
            E_NOTICE => "NOTICE",
6169
            E_CORE_ERROR => "CORE ERROR",
6170
            E_CORE_WARNING => "CORE WARNING",
6171
            E_COMPILE_ERROR => "COMPILE ERROR",
6172
            E_COMPILE_WARNING => "COMPILE WARNING",
6173
            E_USER_ERROR => "USER ERROR",
6174
            E_USER_WARNING => "USER WARNING",
6175
            E_USER_NOTICE => "USER NOTICE",
6176
            E_STRICT => "STRICT NOTICE",
6177
            E_RECOVERABLE_ERROR => "RECOVERABLE ERROR",
6178
            E_DEPRECATED => "DEPRECATED",
6179
            E_USER_DEPRECATED => "USER DEPRECATED"
6180
        );
6181
6182
        if (!empty($nr) || !empty($file)) {
6183
            if ($text != '') {
6184
                $str .= '<div style="font-weight:bold;border:1px solid #ccc;padding:8px;color:#333;background-color:#ffffcd;margin-bottom:15px;">Error : ' . $text . '</div>';
6185
            }
6186
            if ($output != '') {
6187
                $str .= '<div style="font-weight:bold;border:1px solid #ccc;padding:8px;color:#333;background-color:#ffffcd;margin-bottom:15px;">' . $output . '</div>';
6188
            }
6189
            if ($nr !== '') {
6190
                $table[] = array('ErrorType[num]', $errortype [$nr] . "[" . $nr . "]");
6191
            }
6192
            if ($file) {
6193
                $table[] = array('File', $file);
6194
            }
6195
            if ($line) {
6196
                $table[] = array('Line', $line);
6197
            }
6198
6199
        }
6200
6201
        if ($source != '') {
6202
            $table[] = array("Source", $source);
6203
        }
6204
6205
        if (!empty($this->currentSnippet)) {
6206
            $table[] = array('Current Snippet', $this->currentSnippet);
6207
        }
6208
6209
        if (!empty($this->event->activePlugin)) {
6210
            $table[] = array('Current Plugin', $this->event->activePlugin . '(' . $this->event->name . ')');
6211
        }
6212
6213
        $str .= $MakeTable->create($table, array('Error information', ''));
6214
        $str .= "<br />";
6215
6216
        $table = array();
6217
        $table[] = array('REQUEST_URI', $request_uri);
6218
6219
        if ($this->manager->action) {
6220
            include_once(MODX_MANAGER_PATH . 'includes/actionlist.inc.php');
6221
            global $action_list;
6222
            $actionName = (isset($action_list[$this->manager->action])) ? " - {$action_list[$this->manager->action]}" : '';
6223
6224
            $table[] = array('Manager action', $this->manager->action . $actionName);
6225
        }
6226
6227
        if (preg_match('@^[0-9]+@', $this->documentIdentifier)) {
6228
            $resource = $this->getDocumentObject('id', $this->documentIdentifier);
6229
            $url = $this->makeUrl($this->documentIdentifier, '', '', 'full');
6230
            $table[] = array('Resource', '[' . $this->documentIdentifier . '] <a href="' . $url . '" target="_blank">' . $resource['pagetitle'] . '</a>');
6231
        }
6232
        $table[] = array('Referer', $referer);
6233
        $table[] = array('User Agent', $ua);
6234
        $table[] = array('IP', $_SERVER['REMOTE_ADDR']);
6235
        $table[] = array('Current time', date("Y-m-d H:i:s", $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time']));
6236
        $str .= $MakeTable->create($table, array('Basic info', ''));
6237
        $str .= "<br />";
6238
6239
        $table = array();
6240
        $table[] = array('MySQL', '[^qt^] ([^q^] Requests)');
6241
        $table[] = array('PHP', '[^p^]');
6242
        $table[] = array('Total', '[^t^]');
6243
        $table[] = array('Memory', '[^m^]');
6244
        $str .= $MakeTable->create($table, array('Benchmarks', ''));
6245
        $str .= "<br />";
6246
6247
        $totalTime = ($this->getMicroTime() - $this->tstart);
6248
6249
        $mem = memory_get_peak_usage(true);
6250
        $total_mem = $mem - $this->mstart;
6251
        $total_mem = ($total_mem / 1024 / 1024) . ' mb';
6252
6253
        $queryTime = $this->queryTime;
6254
        $phpTime = $totalTime - $queryTime;
6255
        $queries = isset ($this->executedQueries) ? $this->executedQueries : 0;
6256
        $queryTime = sprintf("%2.4f s", $queryTime);
6257
        $totalTime = sprintf("%2.4f s", $totalTime);
6258
        $phpTime = sprintf("%2.4f s", $phpTime);
6259
6260
        $str = str_replace('[^q^]', $queries, $str);
6261
        $str = str_replace('[^qt^]', $queryTime, $str);
6262
        $str = str_replace('[^p^]', $phpTime, $str);
6263
        $str = str_replace('[^t^]', $totalTime, $str);
6264
        $str = str_replace('[^m^]', $total_mem, $str);
6265
6266
        if (isset($php_errormsg) && !empty($php_errormsg)) {
6267
            $str = "<b>{$php_errormsg}</b><br />\n{$str}";
6268
        }
6269
        $str .= $this->get_backtrace(debug_backtrace());
6270
        // Log error
6271
        if (!empty($this->currentSnippet)) {
6272
            $source = 'Snippet - ' . $this->currentSnippet;
6273
        } elseif (!empty($this->event->activePlugin)) {
6274
            $source = 'Plugin - ' . $this->event->activePlugin;
6275
        } elseif ($source !== '') {
6276
            $source = 'Parser - ' . $source;
6277
        } elseif ($query !== '') {
6278
            $source = 'SQL Query';
6279
        } else {
6280
            $source = 'Parser';
6281
        }
6282
        if ($msg) {
6283
            $source .= ' / ' . $msg;
6284
        }
6285
        if (isset($actionName) && !empty($actionName)) {
6286
            $source .= $actionName;
6287
        }
6288 View Code Duplication
        switch ($nr) {
6289
            case E_DEPRECATED :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
6290
            case E_USER_DEPRECATED :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
6291
            case E_STRICT :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
6292
            case E_NOTICE :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
6293
            case E_USER_NOTICE :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
6294
                $error_level = 2;
6295
                break;
6296
            default:
6297
                $error_level = 3;
6298
        }
6299
        $this->logEvent(0, $error_level, $str, $source);
6300
6301
        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...
6302
            return true;
6303
        }
6304
        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...
6305
            return true;
6306
        }
6307
6308
        // Set 500 response header
6309
        if ($error_level !== 2) {
6310
            header('HTTP/1.1 500 Internal Server Error');
6311
        }
6312
6313
        // Display error
6314
        if (isset($_SESSION['mgrValidated'])) {
6315
            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>
6316
                 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
6317
                 <link rel="stylesheet" type="text/css" href="' . $this->config['site_manager_url'] . 'media/style/' . $this->config['manager_theme'] . '/style.css" />
6318
                 <style type="text/css">body { padding:10px; } td {font:inherit;}</style>
6319
                 </head><body>
6320
                 ' . $str . '</body></html>';
6321
6322
        } else {
6323
            echo 'Error';
6324
        }
6325
        ob_end_flush();
6326
        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...
6327
    }
6328
6329
    /**
6330
     * @param $backtrace
6331
     * @return string
6332
     */
6333
    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...
6334
    {
6335
        if (!class_exists('makeTable')) {
6336
            include_once('extenders/maketable.class.php');
6337
        }
6338
        $MakeTable = new MakeTable();
6339
        $MakeTable->setTableClass('grid');
6340
        $MakeTable->setRowRegularClass('gridItem');
6341
        $MakeTable->setRowAlternateClass('gridAltItem');
6342
        $table = array();
6343
        $backtrace = array_reverse($backtrace);
6344
        foreach ($backtrace as $key => $val) {
6345
            $key++;
6346
            if (substr($val['function'], 0, 11) === 'messageQuit') {
6347
                break;
6348
            } elseif (substr($val['function'], 0, 8) === 'phpError') {
6349
                break;
6350
            }
6351
            $path = str_replace('\\', '/', $val['file']);
6352
            if (strpos($path, MODX_BASE_PATH) === 0) {
6353
                $path = substr($path, strlen(MODX_BASE_PATH));
6354
            }
6355
            switch ($val['type']) {
6356
                case '->':
6357
                case '::':
6358
                    $functionName = $val['function'] = $val['class'] . $val['type'] . $val['function'];
6359
                    break;
6360
                default:
6361
                    $functionName = $val['function'];
6362
            }
6363
            $tmp = 1;
6364
            $_ = (!empty($val['args'])) ? count($val['args']) : 0;
6365
            $args = array_pad(array(), $_, '$var');
6366
            $args = implode(", ", $args);
6367
            $modx = &$this;
6368
            $args = preg_replace_callback('/\$var/', function () use ($modx, &$tmp, $val) {
6369
                $arg = $val['args'][$tmp - 1];
6370
                switch (true) {
6371
                    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...
6372
                        $out = 'NULL';
6373
                        break;
6374
                    }
6375
                    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...
6376
                        $out = $arg;
6377
                        break;
6378
                    }
6379
                    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...
6380
                        $out = strlen($arg) > 20 ? 'string $var' . $tmp : ("'" . $this->htmlspecialchars(str_replace("'", "\\'", $arg)) . "'");
6381
                        break;
6382
                    }
6383
                    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...
6384
                        $out = $arg ? 'TRUE' : 'FALSE';
6385
                        break;
6386
                    }
6387
                    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...
6388
                        $out = 'array $var' . $tmp;
6389
                        break;
6390
                    }
6391
                    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...
6392
                        $out = get_class($arg) . ' $var' . $tmp;
6393
                        break;
6394
                    }
6395
                    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...
6396
                        $out = '$var' . $tmp;
6397
                    }
6398
                }
6399
                $tmp++;
6400
                return $out;
6401
            }, $args);
6402
            $line = array(
6403
                "<strong>" . $functionName . "</strong>(" . $args . ")",
6404
                $path . " on line " . $val['line']
6405
            );
6406
            $table[] = array(implode("<br />", $line));
6407
        }
6408
        return $MakeTable->create($table, array('Backtrace'));
6409
    }
6410
6411
    /**
6412
     * @return string
6413
     */
6414
    public function getRegisteredClientScripts()
6415
    {
6416
        return implode("\n", $this->jscripts);
6417
    }
6418
6419
    /**
6420
     * @return string
6421
     */
6422
    public function getRegisteredClientStartupScripts()
6423
    {
6424
        return implode("\n", $this->sjscripts);
6425
    }
6426
6427
    /**
6428
     * Format alias to be URL-safe. Strip invalid characters.
6429
     *
6430
     * @param string $alias Alias to be formatted
6431
     * @return string Safe alias
6432
     */
6433
    public function stripAlias($alias)
6434
    {
6435
        // let add-ons overwrite the default behavior
6436
        $results = $this->invokeEvent('OnStripAlias', array('alias' => $alias));
6437
        if (!empty($results)) {
6438
            // if multiple plugins are registered, only the last one is used
6439
            return end($results);
6440
        } else {
6441
            // default behavior: strip invalid characters and replace spaces with dashes.
6442
            $alias = strip_tags($alias); // strip HTML
6443
            $alias = preg_replace('/[^\.A-Za-z0-9 _-]/', '', $alias); // strip non-alphanumeric characters
6444
            $alias = preg_replace('/\s+/', '-', $alias); // convert white-space to dash
6445
            $alias = preg_replace('/-+/', '-', $alias);  // convert multiple dashes to one
6446
            $alias = trim($alias, '-'); // trim excess
6447
            return $alias;
6448
        }
6449
    }
6450
6451
    /**
6452
     * @param $size
6453
     * @return string
6454
     */
6455
    public function nicesize($size)
6456
    {
6457
        $sizes = array('Tb' => 1099511627776, 'Gb' => 1073741824, 'Mb' => 1048576, 'Kb' => 1024, 'b' => 1);
6458
        $precisions = count($sizes) - 1;
6459
        foreach ($sizes as $unit => $bytes) {
6460
            if ($size >= $bytes) {
6461
                return number_format($size / $bytes, $precisions) . ' ' . $unit;
6462
            }
6463
            $precisions--;
6464
        }
6465
        return '0 b';
6466
    }
6467
6468
    /**
6469
     * @param $parentid
6470
     * @param $alias
6471
     * @return bool
6472
     */
6473
    public function getHiddenIdFromAlias($parentid, $alias)
6474
    {
6475
        $table = $this->getFullTableName('site_content');
6476
        $query = $this->db->query("SELECT sc.id, children.id AS child_id, children.alias, COUNT(children2.id) AS children_count
6477
            FROM {$table} sc
6478
            JOIN {$table} children ON children.parent = sc.id
6479
            LEFT JOIN {$table} children2 ON children2.parent = children.id
6480
            WHERE sc.parent = {$parentid} AND sc.alias_visible = '0' GROUP BY children.id;");
6481
6482
        while ($child = $this->db->getRow($query)) {
6483
            if ($child['alias'] == $alias || $child['child_id'] == $alias) {
6484
                return $child['child_id'];
6485
            }
6486
6487
            if ($child['children_count'] > 0) {
6488
                $id = $this->getHiddenIdFromAlias($child['id'], $alias);
6489
                if ($id) {
6490
                    return $id;
6491
                }
6492
            }
6493
        }
6494
6495
        return false;
6496
    }
6497
6498
    /**
6499
     * @param $alias
6500
     * @return bool|int
6501
     */
6502
    public function getIdFromAlias($alias)
6503
    {
6504
        if (isset($this->documentListing[$alias])) {
6505
            return $this->documentListing[$alias];
6506
        }
6507
6508
        $tbl_site_content = $this->getFullTableName('site_content');
6509
        if ($this->config['use_alias_path'] == 1) {
6510
            if ($alias == '.') {
6511
                return 0;
6512
            }
6513
6514
            if (strpos($alias, '/') !== false) {
6515
                $_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...
6516
            } else {
6517
                $_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...
6518
            }
6519
            $id = 0;
6520
6521
            foreach ($_a as $alias) {
6522
                if ($id === false) {
6523
                    break;
6524
                }
6525
                $alias = $this->db->escape($alias);
6526
                $rs = $this->db->select('id', $tbl_site_content, "deleted=0 and parent='{$id}' and alias='{$alias}'");
6527
                if ($this->db->getRecordCount($rs) == 0) {
6528
                    $rs = $this->db->select('id', $tbl_site_content, "deleted=0 and parent='{$id}' and id='{$alias}'");
6529
                }
6530
                $next = $this->db->getValue($rs);
6531
                $id = !$next ? $this->getHiddenIdFromAlias($id, $alias) : $next;
6532
            }
6533
        } else {
6534
            $rs = $this->db->select('id', $tbl_site_content, "deleted=0 and alias='{$alias}'", 'parent, menuindex');
6535
            $id = $this->db->getValue($rs);
6536
            if (!$id) {
6537
                $id = false;
6538
            }
6539
        }
6540
        return $id;
6541
    }
6542
6543
    /**
6544
     * @param string $str
6545
     * @return bool|mixed|string
6546
     */
6547
    public function atBindInclude($str = '')
6548
    {
6549
        if (strpos($str, '@INCLUDE') !== 0) {
6550
            return $str;
6551
        }
6552 View Code Duplication
        if (strpos($str, "\n") !== false) {
6553
            $str = substr($str, 0, strpos("\n", $str));
6554
        }
6555
6556
        $str = substr($str, 9);
6557
        $str = trim($str);
6558
        $str = str_replace('\\', '/', $str);
6559
        $str = ltrim($str, '/');
6560
6561
        $tpl_dir = 'assets/templates/';
6562
6563
        if (strpos($str, MODX_MANAGER_PATH) === 0) {
6564
            return false;
6565
        } elseif (is_file(MODX_BASE_PATH . $str)) {
6566
            $file_path = MODX_BASE_PATH . $str;
6567
        } elseif (is_file(MODX_BASE_PATH . "{$tpl_dir}{$str}")) {
6568
            $file_path = MODX_BASE_PATH . $tpl_dir . $str;
6569
        } else {
6570
            return false;
6571
        }
6572
6573
        if (!$file_path || !is_file($file_path)) {
6574
            return false;
6575
        }
6576
6577
        ob_start();
6578
        $modx = &$this;
6579
        $result = include($file_path);
6580
        if ($result === 1) {
6581
            $result = '';
6582
        }
6583
        $content = ob_get_clean();
6584
        if (!$content && $result) {
6585
            $content = $result;
6586
        }
6587
        return $content;
6588
    }
6589
6590
    // php compat
6591
6592
    /**
6593
     * @param $str
6594
     * @param int $flags
6595
     * @param string $encode
6596
     * @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...
6597
     */
6598
    public function htmlspecialchars($str, $flags = ENT_COMPAT, $encode = '')
6599
    {
6600
        $this->loadExtension('PHPCOMPAT');
6601
        return $this->phpcompat->htmlspecialchars($str, $flags, $encode);
6602
    }
6603
6604
    /**
6605
     * @param $string
6606
     * @param bool $returnData
6607
     * @return bool|mixed
6608
     */
6609
    public function isJson($string, $returnData = false)
6610
    {
6611
        $data = json_decode($string, true);
6612
        return (json_last_error() == JSON_ERROR_NONE) ? ($returnData ? $data : true) : false;
6613
    }
6614
6615
    /**
6616
     * @param $key
6617
     * @return array
6618
     */
6619
    public function splitKeyAndFilter($key)
6620
    {
6621
        if ($this->config['enable_filter'] == 1 && strpos($key, ':') !== false && stripos($key, '@FILE') !== 0) {
6622
            list($key, $modifiers) = explode(':', $key, 2);
6623
        } else {
6624
            $modifiers = false;
6625
        }
6626
6627
        $key = trim($key);
6628
        if ($modifiers !== false) {
6629
            $modifiers = trim($modifiers);
6630
        }
6631
6632
        return array($key, $modifiers);
6633
    }
6634
6635
    /**
6636
     * @param string $value
6637
     * @param bool $modifiers
6638
     * @param string $key
6639
     * @return string
6640
     */
6641
    public function applyFilter($value = '', $modifiers = false, $key = '')
6642
    {
6643
        if ($modifiers === false || $modifiers == 'raw') {
6644
            return $value;
6645
        }
6646
        if ($modifiers !== false) {
6647
            $modifiers = trim($modifiers);
6648
        }
6649
6650
        $this->loadExtension('MODIFIERS');
6651
        return $this->filter->phxFilter($key, $value, $modifiers);
0 ignored issues
show
Bug introduced by
It seems like $modifiers defined by parameter $modifiers on line 6641 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...
6652
    }
6653
6654
    // End of class.
6655
6656
6657
    /**
6658
     * Get Clean Query String
6659
     *
6660
     * Fixes the issue where passing an array into the q get variable causes errors
6661
     *
6662
     */
6663
    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...
6664
    {
6665
        $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...
6666
6667
        //Return null if the query doesn't exist
6668
        if (empty($q)) {
6669
            return null;
6670
        }
6671
6672
        //If we have a string, return it
6673
        if (is_string($q)) {
6674
            return $q;
6675
        }
6676
6677
        //If we have an array, return the first element
6678
        if (is_array($q)) {
6679
            return $q[0];
6680
        }
6681
    }
6682
6683
    /**
6684
     * @param string $title
6685
     * @param string $msg
6686
     * @param int $type
6687
     */
6688
    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...
6689
    {
6690
        if ($title === '') {
6691
            $title = 'no title';
6692
        }
6693
        if (is_array($msg)) {
6694
            $msg = '<pre>' . print_r($msg, true) . '</pre>';
6695
        } elseif ($msg === '') {
6696
            $msg = $_SERVER['REQUEST_URI'];
6697
        }
6698
        $this->logEvent(0, $type, $msg, $title);
6699
    }
6700
6701
}
6702
6703
/**
6704
 * System Event Class
6705
 */
6706
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...
6707
{
6708
    public $name = '';
6709
    public $_propagate = true;
6710
    public $_output = '';
6711
    public $activated = false;
6712
    public $activePlugin = '';
6713
    public $params = array();
6714
6715
    /**
6716
     * @param string $name Name of the event
6717
     */
6718
    public function __construct($name = "")
6719
    {
6720
        $this->_resetEventObject();
6721
        $this->name = $name;
6722
    }
6723
6724
    /**
6725
     * Display a message to the user
6726
     *
6727
     * @global array $SystemAlertMsgQueque
6728
     * @param string $msg The message
6729
     */
6730
    public function alert($msg)
6731
    {
6732
        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...
6733
        if ($msg == "") {
6734
            return;
6735
        }
6736
        if (is_array($SystemAlertMsgQueque)) {
6737
            $title = '';
6738
            if ($this->name && $this->activePlugin) {
6739
                $title = "<div><b>" . $this->activePlugin . "</b> - <span style='color:maroon;'>" . $this->name . "</span></div>";
6740
            }
6741
            $SystemAlertMsgQueque[] = "$title<div style='margin-left:10px;margin-top:3px;'>$msg</div>";
6742
        }
6743
    }
6744
6745
    /**
6746
     * Output
6747
     *
6748
     * @param string $msg
6749
     */
6750
    public function output($msg)
6751
    {
6752
        $this->_output .= $msg;
6753
    }
6754
6755
    /**
6756
     * Stop event propogation
6757
     */
6758
    public function stopPropagation()
6759
    {
6760
        $this->_propagate = false;
6761
    }
6762
6763
    public function _resetEventObject()
6764
    {
6765
        unset ($this->returnedValues);
6766
        $this->name = "";
6767
        $this->_output = "";
6768
        $this->_propagate = true;
6769
        $this->activated = false;
6770
    }
6771
}