Completed
Pull Request — develop (#530)
by
unknown
05:43
created

DocumentParser::jsonResponse()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 4
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
    public $ajaxMode = false;
171
172
    /**
173
     * Document constructor
174
     *
175
     * @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...
176
     */
177
    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...
178
    {
179
        if ($this->isLoggedIn()) {
180
            ini_set('display_errors', 1);
181
        }
182
        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...
183
        if (substr(PHP_OS, 0, 3) === 'WIN' && $database_server === 'localhost') {
184
            $database_server = '127.0.0.1';
185
        }
186
        $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...
187
        $this->dbConfig = &$this->db->config; // alias for backward compatibility
188
        // events
189
        $this->event = new SystemEvent();
190
        $this->Event = &$this->event; //alias for backward compatibility
191
        // set track_errors ini variable
192
        @ ini_set("track_errors", "1"); // enable error tracking in $php_errormsg
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
193
        $this->time = $_SERVER['REQUEST_TIME']; // for having global timestamp
194
195
        $this->q = self::_getCleanQueryString();
196
    }
197
198
    /**
199
     * @param $method_name
200
     * @param $arguments
201
     * @return mixed
202
     */
203
    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...
204
    {
205
        include_once(MODX_MANAGER_PATH . 'includes/extenders/deprecated.functions.inc.php');
206
        if (method_exists($this->old, $method_name)) {
0 ignored issues
show
Bug introduced by
The property old does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
207
            $error_type = 1;
208
        } else {
209
            $error_type = 3;
210
        }
211
212
        if (!isset($this->config['error_reporting']) || 1 < $this->config['error_reporting']) {
213
            if ($error_type == 1) {
214
                $title = 'Call deprecated method';
215
                $msg = $this->htmlspecialchars("\$modx->{$method_name}() is deprecated function");
216
            } else {
217
                $title = 'Call undefined method';
218
                $msg = $this->htmlspecialchars("\$modx->{$method_name}() is undefined function");
219
            }
220
            $info = debug_backtrace();
221
            $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...
222
            if (!empty($this->currentSnippet)) {
223
                $m[] = 'Snippet - ' . $this->currentSnippet;
224
            } elseif (!empty($this->event->activePlugin)) {
225
                $m[] = 'Plugin - ' . $this->event->activePlugin;
226
            }
227
            $m[] = $this->decoded_request_uri;
228
            $m[] = str_replace('\\', '/', $info[0]['file']) . '(line:' . $info[0]['line'] . ')';
229
            $msg = implode('<br />', $m);
230
            $this->logEvent(0, $error_type, $msg, $title);
231
        }
232
        if (method_exists($this->old, $method_name)) {
233
            return call_user_func_array(array($this->old, $method_name), $arguments);
234
        }
235
    }
236
237
    /**
238
     * @param string $connector
239
     * @return bool
240
     */
241
    public function checkSQLconnect($connector = 'db')
242
    {
243
        $flag = false;
244
        if (is_scalar($connector) && !empty($connector) && isset($this->{$connector}) && $this->{$connector} instanceof DBAPI) {
245
            $flag = (bool)$this->{$connector}->conn;
246
        }
247
        return $flag;
248
    }
249
250
    /**
251
     * Loads an extension from the extenders folder.
252
     * You can load any extension creating a boot file:
253
     * MODX_MANAGER_PATH."includes/extenders/ex_{$extname}.inc.php"
254
     * $extname - extension name in lowercase
255
     *
256
     * @param $extname
257
     * @param bool $reload
258
     * @return bool
259
     */
260
    public function loadExtension($extname, $reload = true)
261
    {
262
        $out = false;
263
        $flag = ($reload || !in_array($extname, $this->extensions));
264
        if ($this->checkSQLconnect('db') && $flag) {
265
            $evtOut = $this->invokeEvent('OnBeforeLoadExtension', array('name' => $extname, 'reload' => $reload));
266
            if (is_array($evtOut) && count($evtOut) > 0) {
267
                $out = array_pop($evtOut);
268
            }
269
        }
270
        if (!$out && $flag) {
271
            $extname = trim(str_replace(array('..', '/', '\\'), '', strtolower($extname)));
272
            $filename = MODX_MANAGER_PATH . "includes/extenders/ex_{$extname}.inc.php";
273
            $out = is_file($filename) ? include $filename : false;
274
        }
275
        if ($out && !in_array($extname, $this->extensions)) {
276
            $this->extensions[] = $extname;
277
        }
278
        return $out;
279
    }
280
281
    /**
282
     * Returns the current micro time
283
     *
284
     * @return float
285
     */
286
    public function getMicroTime()
287
    {
288
        list ($usec, $sec) = explode(' ', microtime());
289
        return ((float)$usec + (float)$sec);
290
    }
291
292
    /**
293
     * Redirect
294
     *
295
     * @param string $url
296
     * @param int $count_attempts
297
     * @param string $type $type
298
     * @param string $responseCode
299
     * @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...
300
     * @global string $base_url
301
     * @global string $site_url
302
     */
303
    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...
304
    {
305
        $header = '';
306
        if (empty ($url)) {
307
            return false;
308
        }
309
        if ($count_attempts == 1) {
310
            // append the redirect count string to the url
311
            $currentNumberOfRedirects = isset ($_REQUEST['err']) ? $_REQUEST['err'] : 0;
312
            if ($currentNumberOfRedirects > 3) {
313
                $this->messageQuit('Redirection attempt failed - please ensure the document you\'re trying to redirect to exists. <p>Redirection URL: <i>' . $url . '</i></p>');
314
            } else {
315
                $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...
316
                if (strpos($url, "?") > 0) {
317
                    $url .= "&err=$currentNumberOfRedirects";
318
                } else {
319
                    $url .= "?err=$currentNumberOfRedirects";
320
                }
321
            }
322
        }
323
        if ($type == 'REDIRECT_REFRESH') {
324
            $header = 'Refresh: 0;URL=' . $url;
325
        } elseif ($type == 'REDIRECT_META') {
326
            $header = '<META HTTP-EQUIV="Refresh" CONTENT="0; URL=' . $url . '" />';
327
            echo $header;
328
            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...
329
        } elseif ($type == 'REDIRECT_HEADER' || empty ($type)) {
330
            // check if url has /$base_url
331
            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...
332
            if (substr($url, 0, strlen($base_url)) == $base_url) {
333
                // append $site_url to make it work with Location:
334
                $url = $site_url . substr($url, strlen($base_url));
335
            }
336
            if (strpos($url, "\n") === false) {
337
                $header = 'Location: ' . $url;
338
            } else {
339
                $this->messageQuit('No newline allowed in redirect url.');
340
            }
341
        }
342
        if ($responseCode && (strpos($responseCode, '30') !== false)) {
343
            header($responseCode);
344
        }
345
346
        if(!empty($header)) {
347
            header($header);
348
        }
349
350
        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...
351
    }
352
353
    /**
354
     * Forward to another page
355
     *
356
     * @param int $id
357
     * @param string $responseCode
358
     */
359
    public function sendForward($id, $responseCode = '')
360
    {
361
        if ($this->forwards > 0) {
362
            $this->forwards = $this->forwards - 1;
363
            $this->documentIdentifier = $id;
364
            $this->documentMethod = 'id';
365
            if ($responseCode) {
366
                header($responseCode);
367
            }
368
            $this->prepareResponse();
369
            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...
370
        } else {
371
            $this->messageQuit("Internal Server Error id={$id}");
372
            header('HTTP/1.0 500 Internal Server Error');
373
            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...
374
        }
375
    }
376
377
    /**
378
     * Redirect to the error page, by calling sendForward(). This is called for example when the page was not found.
379
     * @param bool $noEvent
380
     */
381
    public function sendErrorPage($noEvent = false)
382
    {
383
        $this->systemCacheKey = 'notfound';
384
        if (!$noEvent) {
385
            // invoke OnPageNotFound event
386
            $this->invokeEvent('OnPageNotFound');
387
        }
388
        $url = $this->config['error_page'] ? $this->config['error_page'] : $this->config['site_start'];
389
        $this->sendForward($url, 'HTTP/1.0 404 Not Found');
390
        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...
391
    }
392
393
    /**
394
     * @param bool $noEvent
395
     */
396
    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...
397
    {
398
        // invoke OnPageUnauthorized event
399
        $_REQUEST['refurl'] = $this->documentIdentifier;
400
        $this->systemCacheKey = 'unauth';
401
        if (!$noEvent) {
402
            $this->invokeEvent('OnPageUnauthorized');
403
        }
404
        if ($this->config['unauthorized_page']) {
405
            $unauthorizedPage = $this->config['unauthorized_page'];
406
        } elseif ($this->config['error_page']) {
407
            $unauthorizedPage = $this->config['error_page'];
408
        } else {
409
            $unauthorizedPage = $this->config['site_start'];
410
        }
411
        $this->sendForward($unauthorizedPage, 'HTTP/1.1 401 Unauthorized');
412
        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...
413
    }
414
415
    /**
416
     * Get MODX settings including, but not limited to, the system_settings table
417
     */
418
    public function getSettings()
419
    {
420
        if (!isset($this->config['site_name'])) {
421
            $this->recoverySiteCache();
422
        }
423
424
        // setup default site id - new installation should generate a unique id for the site.
425
        if (!isset($this->config['site_id'])) {
426
            $this->config['site_id'] = "MzGeQ2faT4Dw06+U49x3";
427
        }
428
429
        // store base_url and base_path inside config array
430
        $this->config['base_url'] = MODX_BASE_URL;
431
        $this->config['base_path'] = MODX_BASE_PATH;
432
        $this->config['site_url'] = MODX_SITE_URL;
433
        $this->config['valid_hostnames'] = MODX_SITE_HOSTNAMES;
434
        $this->config['site_manager_url'] = MODX_MANAGER_URL;
435
        $this->config['site_manager_path'] = MODX_MANAGER_PATH;
436
        $this->error_reporting = $this->config['error_reporting'];
437
        $this->config['filemanager_path'] = str_replace('[(base_path)]', MODX_BASE_PATH, $this->config['filemanager_path']);
438
        $this->config['rb_base_dir'] = str_replace('[(base_path)]', MODX_BASE_PATH, $this->config['rb_base_dir']);
439
440
        if (!isset($this->config['enable_at_syntax'])) {
441
            $this->config['enable_at_syntax'] = 1;
442
        } // @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...
443
444
        // now merge user settings into evo-configuration
445
        $this->getUserSettings();
446
    }
447
448
    private function recoverySiteCache()
449
    {
450
        $site_cache_dir = MODX_BASE_PATH . $this->getCacheFolder();
451
        $site_cache_path = $site_cache_dir . 'siteCache.idx.php';
452
453
        if (is_file($site_cache_path)) {
454
            include($site_cache_path);
455
        }
456
        if (isset($this->config['site_name'])) {
457
            return;
458
        }
459
460
        include_once(MODX_MANAGER_PATH . 'processors/cache_sync.class.processor.php');
461
        $cache = new synccache();
462
        $cache->setCachepath($site_cache_dir);
463
        $cache->setReport(false);
464
        $cache->buildCache($this);
465
466
        clearstatcache();
467
        if (is_file($site_cache_path)) {
468
            include($site_cache_path);
469
        }
470
        if (isset($this->config['site_name'])) {
471
            return;
472
        }
473
474
        $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...
475
        while ($row = $this->db->getRow($rs)) {
476
            $this->config[$row['setting_name']] = $row['setting_value'];
477
        }
478
479
        if (!$this->config['enable_filter']) {
480
            return;
481
        }
482
483
        $where = "plugincode LIKE '%phx.parser.class.inc.php%OnParseDocument();%' AND disabled != 1";
484
        $rs = $this->db->select('id', '[+prefix+]site_plugins', $where);
485
        if ($this->db->getRecordCount($rs)) {
486
            $this->config['enable_filter'] = '0';
487
        }
488
    }
489
490
    /**
491
     * Get user settings and merge into MODX configuration
492
     */
493
    public function getUserSettings()
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
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...
494
    {
495
        $tbl_web_user_settings = $this->getFullTableName('web_user_settings');
496
        $tbl_user_settings = $this->getFullTableName('user_settings');
497
498
        // load user setting if user is logged in
499
        $usrSettings = array();
500
        if ($id = $this->getLoginUserID()) {
501
            $usrType = $this->getLoginUserType();
502
            if (isset ($usrType) && $usrType == 'manager') {
503
                $usrType = 'mgr';
504
            }
505
506
            if ($usrType == 'mgr' && $this->isBackend()) {
507
                // invoke the OnBeforeManagerPageInit event, only if in backend
508
                $this->invokeEvent("OnBeforeManagerPageInit");
509
            }
510
511
            if (isset ($_SESSION[$usrType . 'UsrConfigSet'])) {
512
                $usrSettings = &$_SESSION[$usrType . 'UsrConfigSet'];
513
            } else {
514
                if ($usrType == 'web') {
515
                    $from = $tbl_web_user_settings;
516
                    $where = "webuser='{$id}'";
517
                } else {
518
                    $from = $tbl_user_settings;
519
                    $where = "user='{$id}'";
520
                }
521
522
                $which_browser_default = $this->configGlobal['which_browser'] ? $this->configGlobal['which_browser'] : $this->config['which_browser'];
523
524
                $result = $this->db->select('setting_name, setting_value', $from, $where);
525
                while ($row = $this->db->getRow($result)) {
526 View Code Duplication
                    if ($row['setting_name'] == 'which_browser' && $row['setting_value'] == 'default') {
527
                        $row['setting_value'] = $which_browser_default;
528
                    }
529
                    $usrSettings[$row['setting_name']] = $row['setting_value'];
530
                }
531
                if (isset ($usrType)) {
532
                    $_SESSION[$usrType . 'UsrConfigSet'] = $usrSettings;
533
                } // store user settings in session
534
            }
535
        }
536
        if ($this->isFrontend() && $mgrid = $this->getLoginUserID('mgr')) {
537
            $musrSettings = array();
538
            if (isset ($_SESSION['mgrUsrConfigSet'])) {
539
                $musrSettings = &$_SESSION['mgrUsrConfigSet'];
540
            } else {
541
                if ($result = $this->db->select('setting_name, setting_value', $tbl_user_settings, "user='{$mgrid}'")) {
542
                    while ($row = $this->db->getRow($result)) {
543
                        $musrSettings[$row['setting_name']] = $row['setting_value'];
544
                    }
545
                    $_SESSION['mgrUsrConfigSet'] = $musrSettings; // store user settings in session
546
                }
547
            }
548
            if (!empty ($musrSettings)) {
549
                $usrSettings = array_merge($musrSettings, $usrSettings);
550
            }
551
        }
552
        // save global values before overwriting/merging array
553
        foreach ($usrSettings as $param => $value) {
554
            if (isset($this->config[$param])) {
555
                $this->configGlobal[$param] = $this->config[$param];
556
            }
557
        }
558
559
        $this->config = array_merge($this->config, $usrSettings);
560
        $this->config['filemanager_path'] = str_replace('[(base_path)]', MODX_BASE_PATH, $this->config['filemanager_path']);
561
        $this->config['rb_base_dir'] = str_replace('[(base_path)]', MODX_BASE_PATH, $this->config['rb_base_dir']);
562
563
        return $usrSettings;
564
    }
565
566
    /**
567
     * Returns the document identifier of the current request
568
     *
569
     * @param string $method id and alias are allowed
570
     * @return int
571
     */
572
    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...
573
    {
574
        // function to test the query and find the retrieval method
575
        if ($method === 'alias') {
576
            return $this->db->escape($_REQUEST['q']);
577
        }
578
579
        $id_ = filter_input(INPUT_GET, 'id');
580
        if ($id_) {
581
            if (preg_match('@^[1-9][0-9]*$@', $id_)) {
582
                return $id_;
583
            } else {
584
                $this->sendErrorPage();
585
            }
586
        } elseif (strpos($_SERVER['REQUEST_URI'], 'index.php/') !== false) {
587
            $this->sendErrorPage();
588
        } else {
589
            return $this->config['site_start'];
590
        }
591
    }
592
593
    /**
594
     * Check for manager or webuser login session since v1.2
595
     *
596
     * @param string $context
597
     * @return bool
598
     */
599
    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...
600
    {
601
        if (substr($context, 0, 1) == 'm') {
602
            $_ = '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...
603
        } else {
604
            $_ = 'webValidated';
605
        }
606
607
        if (isset($_SESSION[$_]) && !empty($_SESSION[$_])) {
0 ignored issues
show
Coding Style introduced by
The if-else statement can be simplified to return isset($_SESSION[$... !empty($_SESSION[$_]);.
Loading history...
608
            return true;
609
        } else {
610
            return false;
611
        }
612
    }
613
614
    /**
615
     * Check for manager login session
616
     *
617
     * @return boolean
618
     */
619
    public function checkSession()
620
    {
621
        return $this->isLoggedin();
622
    }
623
624
    /**
625
     * Checks, if a the result is a preview
626
     *
627
     * @return boolean
628
     */
629
    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...
630
    {
631
        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...
632
            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...
633
                return true;
634
            } else {
635
                return false;
636
            }
637
        } else {
638
            return false;
639
        }
640
    }
641
642
    /**
643
     * check if site is offline
644
     *
645
     * @return boolean
646
     */
647
    public function checkSiteStatus()
648
    {
649
        if ($this->config['site_status']) {
650
            return true;
651
        }  // site online
652
        elseif ($this->isLoggedin()) {
653
            return true;
654
        }  // site offline but launched via the manager
655
        else {
656
            return false;
657
        } // site is offline
658
    }
659
660
    /**
661
     * Create a 'clean' document identifier with path information, friendly URL suffix and prefix.
662
     *
663
     * @param string $qOrig
664
     * @return string
665
     */
666
    public function cleanDocumentIdentifier($qOrig)
667
    {
668
        if (!$qOrig) {
669
            $qOrig = $this->config['site_start'];
670
        }
671
        $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...
672
673
        $pre = $this->config['friendly_url_prefix'];
674
        $suf = $this->config['friendly_url_suffix'];
675
        $pre = preg_quote($pre, '/');
676
        $suf = preg_quote($suf, '/');
677 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...
678
            $q = $_[1];
679
        }
680 View Code Duplication
        if ($suf && preg_match('@(.*)' . $suf . '$@', $q, $_)) {
681
            $q = $_[1];
682
        }
683
684
        /* First remove any / before or after */
685
        $q = trim($q, '/');
686
687
        /* Save path if any */
688
        /* FS#476 and FS#308: only return virtualDir if friendly paths are enabled */
689
        if ($this->config['use_alias_path'] == 1) {
690
            $_ = strrpos($q, '/');
691
            $this->virtualDir = $_ !== false ? substr($q, 0, $_) : '';
692
            if ($_ !== false) {
693
                $q = preg_replace('@.*/@', '', $q);
694
            }
695
        } else {
696
            $this->virtualDir = '';
697
        }
698
699
        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 */
700
            /* FS#476 and FS#308: check that id is valid in terms of virtualDir structure */
701
            if ($this->config['use_alias_path'] == 1) {
702
                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))))) {
703
                    $this->documentMethod = 'id';
704
                    return $q;
705
                } else { /* not a valid id in terms of virtualDir, treat as alias */
706
                    $this->documentMethod = 'alias';
707
                    return $q;
708
                }
709
            } else {
710
                $this->documentMethod = 'id';
711
                return $q;
712
            }
713
        } else { /* we didn't get an ID back, so instead we assume it's an alias */
714
            if ($this->config['friendly_alias_urls'] != 1) {
715
                $q = $qOrig;
716
            }
717
            $this->documentMethod = 'alias';
718
            return $q;
719
        }
720
    }
721
722
    /**
723
     * @return string
724
     */
725
    public function getCacheFolder()
726
    {
727
        return "assets/cache/";
728
    }
729
730
    /**
731
     * @param $key
732
     * @return string
733
     */
734
    public function getHashFile($key)
735
    {
736
        return $this->getCacheFolder() . "docid_" . $key . ".pageCache.php";
737
    }
738
739
    /**
740
     * @param $id
741
     * @return array|mixed|null|string
742
     */
743
    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...
744
        $hash = $id;
745
        $tmp = null;
746
        $params = array();
747
        if(!empty($this->systemCacheKey)){
748
            $hash = $this->systemCacheKey;
749
        }else {
750
            if (!empty($_GET)) {
751
                // Sort GET parameters so that the order of parameters on the HTTP request don't affect the generated cache ID.
752
                $params = $_GET;
753
                ksort($params);
754
                $hash .= '_'.md5(http_build_query($params));
755
            }
756
        }
757
        $evtOut = $this->invokeEvent("OnMakePageCacheKey", array ("hash" => $hash, "id" => $id, 'params' => $params));
758
        if (is_array($evtOut) && count($evtOut) > 0){
759
            $tmp = array_pop($evtOut);
760
        }
761
        return empty($tmp) ? $hash : $tmp;
762
    }
763
764
    /**
765
     * @param $id
766
     * @param bool $loading
767
     * @return string
768
     */
769
    public function checkCache($id, $loading = false)
770
    {
771
        return $this->getDocumentObjectFromCache($id, $loading);
772
    }
773
774
    /**
775
     * Check the cache for a specific document/resource
776
     *
777
     * @param int $id
778
     * @param bool $loading
779
     * @return string
780
     */
781
    public function getDocumentObjectFromCache($id, $loading = false)
782
    {
783
        $key = ($this->config['cache_type'] == 2) ? $this->makePageCacheKey($id) : $id;
784
        if ($loading) {
785
            $this->cacheKey = $key;
786
        }
787
788
        $cache_path = $this->getHashFile($key);
789
790
        if (!is_file($cache_path)) {
791
            $this->documentGenerated = 1;
792
            return '';
793
        }
794
        $content = file_get_contents($cache_path, false);
795
        if (substr($content, 0, 5) === '<?php') {
796
            $content = substr($content, strpos($content, '?>') + 2);
797
        } // remove php header
798
        $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...
799
        if (count($a) == 1) {
800
            $result = $a[0];
801
        } // return only document content
802
        else {
803
            $docObj = unserialize($a[0]); // rebuild document object
804
            // check page security
805
            if ($docObj['privateweb'] && isset ($docObj['__MODxDocGroups__'])) {
806
                $pass = false;
807
                $usrGrps = $this->getUserDocGroups();
808
                $docGrps = explode(',', $docObj['__MODxDocGroups__']);
809
                // check is user has access to doc groups
810
                if (is_array($usrGrps)) {
811
                    foreach ($usrGrps as $k => $v) {
812
                        if (!in_array($v, $docGrps)) {
813
                            continue;
814
                        }
815
                        $pass = true;
816
                        break;
817
                    }
818
                }
819
                // diplay error pages if user has no access to cached doc
820
                if (!$pass) {
821
                    if ($this->config['unauthorized_page']) {
822
                        // check if file is not public
823
                        $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...
824
                        $total = $this->db->getValue($rs);
825
                    } else {
826
                        $total = 0;
827
                    }
828
829
                    if ($total > 0) {
830
                        $this->sendUnauthorizedPage();
831
                    } else {
832
                        $this->sendErrorPage();
833
                    }
834
835
                    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...
836
                }
837
            }
838
            // Grab the Scripts
839
            if (isset($docObj['__MODxSJScripts__'])) {
840
                $this->sjscripts = $docObj['__MODxSJScripts__'];
841
            }
842
            if (isset($docObj['__MODxJScripts__'])) {
843
                $this->jscripts = $docObj['__MODxJScripts__'];
844
            }
845
846
            // Remove intermediate variables
847
            unset($docObj['__MODxDocGroups__'], $docObj['__MODxSJScripts__'], $docObj['__MODxJScripts__']);
848
849
            $this->documentObject = $docObj;
850
851
            $result = $a[1]; // return document content
852
        }
853
854
        $this->documentGenerated = 0;
855
        // invoke OnLoadWebPageCache  event
856
        $this->documentContent = $result;
857
        $this->invokeEvent('OnLoadWebPageCache');
858
        return $result;
859
    }
860
861
    /**
862
     * Final processing and output of the document/resource.
863
     *
864
     * - runs uncached snippets
865
     * - add javascript to <head>
866
     * - removes unused placeholders
867
     * - converts URL tags [~...~] to URLs
868
     *
869
     * @param boolean $noEvent Default: false
870
     */
871
    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...
872
    {
873
        $this->documentOutput = $this->documentContent;
874
875
        if ($this->documentGenerated == 1 && $this->documentObject['cacheable'] == 1 && $this->documentObject['type'] == 'document' && $this->documentObject['published'] == 1) {
876
            if (!empty($this->sjscripts)) {
877
                $this->documentObject['__MODxSJScripts__'] = $this->sjscripts;
878
            }
879
            if (!empty($this->jscripts)) {
880
                $this->documentObject['__MODxJScripts__'] = $this->jscripts;
881
            }
882
        }
883
884
        // check for non-cached snippet output
885
        if (strpos($this->documentOutput, '[!') > -1) {
886
            $this->recentUpdate = $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'];
887
888
            $this->documentOutput = str_replace('[!', '[[', $this->documentOutput);
889
            $this->documentOutput = str_replace('!]', ']]', $this->documentOutput);
890
891
            // Parse document source
892
            $this->documentOutput = $this->parseDocumentSource($this->documentOutput);
893
        }
894
895
        // Moved from prepareResponse() by sirlancelot
896
        // Insert Startup jscripts & CSS scripts into template - template must have a <head> tag
897
        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...
898
            // change to just before closing </head>
899
            // $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...
900
            $this->documentOutput = preg_replace("/(<\/head>)/i", $js . "\n\\1", $this->documentOutput);
901
        }
902
903
        // Insert jscripts & html block into template - template must have a </body> tag
904
        if ($js = $this->getRegisteredClientScripts()) {
905
            $this->documentOutput = preg_replace("/(<\/body>)/i", $js . "\n\\1", $this->documentOutput);
906
        }
907
        // End fix by sirlancelot
908
909
        $this->documentOutput = $this->cleanUpMODXTags($this->documentOutput);
910
911
        $this->documentOutput = $this->rewriteUrls($this->documentOutput);
912
913
        // send out content-type and content-disposition headers
914
        if (IN_PARSER_MODE == "true") {
915
            $type = !empty ($this->contentTypes[$this->documentIdentifier]) ? $this->contentTypes[$this->documentIdentifier] : "text/html";
916
            header('Content-Type: ' . $type . '; charset=' . $this->config['modx_charset']);
917
            //            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...
918
            //                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...
919
            if (!$this->checkPreview() && $this->documentObject['content_dispo'] == 1) {
920
                if ($this->documentObject['alias']) {
921
                    $name = $this->documentObject['alias'];
922
                } else {
923
                    // strip title of special characters
924
                    $name = $this->documentObject['pagetitle'];
925
                    $name = strip_tags($name);
926
                    $name = $this->cleanUpMODXTags($name);
927
                    $name = strtolower($name);
928
                    $name = preg_replace('/&.+?;/', '', $name); // kill entities
929
                    $name = preg_replace('/[^\.%a-z0-9 _-]/', '', $name);
930
                    $name = preg_replace('/\s+/', '-', $name);
931
                    $name = preg_replace('|-+|', '-', $name);
932
                    $name = trim($name, '-');
933
                }
934
                $header = 'Content-Disposition: attachment; filename=' . $name;
935
                header($header);
936
            }
937
        }
938
        $this->setConditional();
939
940
        $stats = $this->getTimerStats($this->tstart);
941
942
        $out =& $this->documentOutput;
943
        $out = str_replace("[^q^]", $stats['queries'], $out);
944
        $out = str_replace("[^qt^]", $stats['queryTime'], $out);
945
        $out = str_replace("[^p^]", $stats['phpTime'], $out);
946
        $out = str_replace("[^t^]", $stats['totalTime'], $out);
947
        $out = str_replace("[^s^]", $stats['source'], $out);
948
        $out = str_replace("[^m^]", $stats['phpMemory'], $out);
949
        //$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...
950
951
        // invoke OnWebPagePrerender event
952
        if (!$noEvent) {
953
            $evtOut = $this->invokeEvent('OnWebPagePrerender', array('documentOutput' => $this->documentOutput));
954
            if (is_array($evtOut) && count($evtOut) > 0) {
955
                $this->documentOutput = $evtOut['0'];
956
            }
957
        }
958
959
        $this->documentOutput = $this->removeSanitizeSeed($this->documentOutput);
960
961
        if (strpos($this->documentOutput, '\{') !== false) {
962
            $this->documentOutput = $this->RecoveryEscapedTags($this->documentOutput);
963
        } elseif (strpos($this->documentOutput, '\[') !== false) {
964
            $this->documentOutput = $this->RecoveryEscapedTags($this->documentOutput);
965
        }
966
967
        echo $this->documentOutput;
968
969
        if ($this->dumpSQL) {
970
            echo $this->queryCode;
971
        }
972
        if ($this->dumpSnippets) {
973
            $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...
974
            $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...
975
            foreach ($this->snippetsTime as $s => $v) {
976
                $t = $v['time'];
977
                $sname = $v['sname'];
978
                $sc .= sprintf("%s. %s (%s)<br>", $s, $sname, sprintf("%2.2f ms", $t)); // currentSnippet
979
                $tt += $t;
980
            }
981
            echo "<fieldset><legend><b>Snippets</b> (" . count($this->snippetsTime) . " / " . sprintf("%2.2f ms", $tt) . ")</legend>{$sc}</fieldset><br />";
982
            echo $this->snippetsCode;
983
        }
984
        if ($this->dumpPlugins) {
985
            $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...
986
            $tt = 0;
987
            foreach ($this->pluginsTime as $s => $t) {
988
                $ps .= "$s (" . sprintf("%2.2f ms", $t * 1000) . ")<br>";
989
                $tt += $t;
990
            }
991
            echo "<fieldset><legend><b>Plugins</b> (" . count($this->pluginsTime) . " / " . sprintf("%2.2f ms", $tt * 1000) . ")</legend>{$ps}</fieldset><br />";
992
            echo $this->pluginsCode;
993
        }
994
995
        ob_end_flush();
996
    }
997
998
    /**
999
     * @param $contents
1000
     * @return mixed
1001
     */
1002
    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...
1003
    {
1004
        list($sTags, $rTags) = $this->getTagsForEscape();
1005
        return str_replace($rTags, $sTags, $contents);
1006
    }
1007
1008
    /**
1009
     * @param string $tags
1010
     * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use 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...
1011
     */
1012
    public function getTagsForEscape($tags = '{{,}},[[,]],[!,!],[*,*],[(,)],[+,+],[~,~],[^,^]')
1013
    {
1014
        $srcTags = explode(',', $tags);
1015
        $repTags = array();
1016
        foreach ($srcTags as $tag) {
1017
            $repTags[] = '\\' . $tag[0] . '\\' . $tag[1];
1018
        }
1019
        return array($srcTags, $repTags);
1020
    }
1021
1022
    /**
1023
     * @param $tstart
1024
     * @return array
1025
     */
1026
    public function getTimerStats($tstart)
1027
    {
1028
        $stats = array();
1029
1030
        $stats['totalTime'] = ($this->getMicroTime() - $tstart);
1031
        $stats['queryTime'] = $this->queryTime;
1032
        $stats['phpTime'] = $stats['totalTime'] - $stats['queryTime'];
1033
1034
        $stats['queryTime'] = sprintf("%2.4f s", $stats['queryTime']);
1035
        $stats['totalTime'] = sprintf("%2.4f s", $stats['totalTime']);
1036
        $stats['phpTime'] = sprintf("%2.4f s", $stats['phpTime']);
1037
        $stats['source'] = $this->documentGenerated == 1 ? "database" : "cache";
1038
        $stats['queries'] = isset ($this->executedQueries) ? $this->executedQueries : 0;
1039
        $stats['phpMemory'] = (memory_get_peak_usage(true) / 1024 / 1024) . " mb";
1040
1041
        return $stats;
1042
    }
1043
1044
    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...
1045
    {
1046
        if (!empty($_POST) || (defined('MODX_API_MODE') && MODX_API_MODE) || $this->getLoginUserID('mgr') || !$this->useConditional || empty($this->recentUpdate)) {
1047
            return;
1048
        }
1049
        $last_modified = gmdate('D, d M Y H:i:s T', $this->recentUpdate);
1050
        $etag = md5($last_modified);
1051
        $HTTP_IF_MODIFIED_SINCE = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? $_SERVER['HTTP_IF_MODIFIED_SINCE'] : false;
1052
        $HTTP_IF_NONE_MATCH = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? $_SERVER['HTTP_IF_NONE_MATCH'] : false;
1053
        header('Pragma: no-cache');
1054
1055
        if ($HTTP_IF_MODIFIED_SINCE == $last_modified || strpos($HTTP_IF_NONE_MATCH, $etag) !== false) {
1056
            header('HTTP/1.1 304 Not Modified');
1057
            header('Content-Length: 0');
1058
            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...
1059
        } else {
1060
            header("Last-Modified: {$last_modified}");
1061
            header("ETag: '{$etag}'");
1062
        }
1063
    }
1064
1065
    /**
1066
     * Checks the publish state of page
1067
     */
1068
    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...
1069
    {
1070
        $cacheRefreshTime = 0;
1071
        $recent_update = 0;
1072
        @include(MODX_BASE_PATH . $this->getCacheFolder() . 'sitePublishing.idx.php');
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1073
        $this->recentUpdate = $recent_update;
1074
1075
        $timeNow = $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'];
1076
        if ($timeNow < $cacheRefreshTime || $cacheRefreshTime == 0) {
1077
            return;
1078
        }
1079
1080
        // now, check for documents that need publishing
1081
        $field = array('published' => 1, 'publishedon' => $timeNow);
1082
        $where = "pub_date <= {$timeNow} AND pub_date!=0 AND published=0";
1083
        $this->db->update($field, '[+prefix+]site_content', $where);
1084
1085
        // now, check for documents that need un-publishing
1086
        $field = array('published' => 0, 'publishedon' => 0);
1087
        $where = "unpub_date <= {$timeNow} AND unpub_date!=0 AND published=1";
1088
        $this->db->update($field, '[+prefix+]site_content', $where);
1089
1090
        $this->recentUpdate = $timeNow;
1091
1092
        // clear the cache
1093
        $this->clearCache('full');
1094
    }
1095
1096
    public function checkPublishStatus()
1097
    {
1098
        $this->updatePubStatus();
1099
    }
1100
1101
    /**
1102
     * Final jobs.
1103
     *
1104
     * - cache page
1105
     */
1106
    public function postProcess()
1107
    {
1108
        // if the current document was generated, cache it!
1109
        $cacheable = ($this->config['enable_cache'] && $this->documentObject['cacheable']) ? 1 : 0;
1110
        if ($cacheable && $this->documentGenerated && $this->documentObject['type'] == 'document' && $this->documentObject['published']) {
1111
            // invoke OnBeforeSaveWebPageCache event
1112
            $this->invokeEvent("OnBeforeSaveWebPageCache");
1113
1114
            if (!empty($this->cacheKey) && is_scalar($this->cacheKey)) {
1115
                // get and store document groups inside document object. Document groups will be used to check security on cache pages
1116
                $where = "document='{$this->documentIdentifier}'";
1117
                $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...
1118
                $docGroups = $this->db->getColumn('document_group', $rs);
1119
1120
                // Attach Document Groups and Scripts
1121
                if (is_array($docGroups)) {
1122
                    $this->documentObject['__MODxDocGroups__'] = implode(",", $docGroups);
1123
                }
1124
1125
                $docObjSerial = serialize($this->documentObject);
1126
                $cacheContent = $docObjSerial . "<!--__MODxCacheSpliter__-->" . $this->documentContent;
1127
                $page_cache_path = MODX_BASE_PATH . $this->getHashFile($this->cacheKey);
1128
                file_put_contents($page_cache_path, "<?php die('Unauthorized access.'); ?>$cacheContent");
1129
            }
1130
        }
1131
1132
        // Useful for example to external page counters/stats packages
1133
        $this->invokeEvent('OnWebPageComplete');
1134
1135
        // end post processing
1136
    }
1137
1138
    /**
1139
     * @param $content
1140
     * @param string $left
1141
     * @param string $right
1142
     * @return array
1143
     */
1144
    public function getTagsFromContent($content, $left = '[+', $right = '+]')
1145
    {
1146
        $_ = $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...
1147
        if (empty($_)) {
1148
            return array();
1149
        }
1150
        foreach ($_ as $v) {
1151
            $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...
1152
            $tags[1][] = $v;
1153
        }
1154
        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...
1155
    }
1156
1157
    /**
1158
     * @param $content
1159
     * @param string $left
1160
     * @param string $right
1161
     * @return array
1162
     */
1163
    public function _getTagsFromContent($content, $left = '[+', $right = '+]')
1164
    {
1165
        if (strpos($content, $left) === false) {
1166
            return array();
1167
        }
1168
        $spacer = md5('<<<EVO>>>');
1169
        if($left==='{{' && strpos($content,';}}')!==false)  $content = str_replace(';}}', sprintf(';}%s}',   $spacer),$content);
1170
        if($left==='{{' && strpos($content,'{{}}')!==false) $content = str_replace('{{}}',sprintf('{%$1s{}%$1s}',$spacer),$content);
1171
        if($left==='[[' && strpos($content,']]]]')!==false) $content = str_replace(']]]]',sprintf(']]%s]]',  $spacer),$content);
1172
        if($left==='[[' && strpos($content,']]]')!==false)  $content = str_replace(']]]', sprintf(']%s]]',   $spacer),$content);
1173
1174
        $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...
1175
        $pos[']]>'] = strpos($content, ']]>');
1176
1177
        if ($pos['<![CDATA['] !== false && $pos[']]>'] !== false) {
1178
            $content = substr($content, 0, $pos['<![CDATA[']) . substr($content, $pos[']]>'] + 3);
1179
        }
1180
1181
        $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...
1182
        $piece = array();
1183
        foreach ($lp as $lc => $lv) {
1184
            if ($lc !== 0) {
1185
                $piece[] = $left;
1186
            }
1187
            if (strpos($lv, $right) === false) {
1188
                $piece[] = $lv;
1189
            } else {
1190
                $rp = explode($right, $lv);
1191
                foreach ($rp as $rc => $rv) {
1192
                    if ($rc !== 0) {
1193
                        $piece[] = $right;
1194
                    }
1195
                    $piece[] = $rv;
1196
                }
1197
            }
1198
        }
1199
        $lc = 0;
1200
        $rc = 0;
1201
        $fetch = '';
1202
        $tags = array();
1203
        foreach ($piece as $v) {
1204
            if ($v === $left) {
1205
                if (0 < $lc) {
1206
                    $fetch .= $left;
1207
                }
1208
                $lc++;
1209
            } elseif ($v === $right) {
1210
                if ($lc === 0) {
1211
                    continue;
1212
                }
1213
                $rc++;
1214
                if ($lc === $rc) {
1215
                    // #1200 Enable modifiers in Wayfinder - add nested placeholders to $tags like for $fetch = "phx:input=`[+wf.linktext+]`:test"
1216
                    if (strpos($fetch, $left) !== false) {
1217
                        $nested = $this->_getTagsFromContent($fetch, $left, $right);
1218
                        foreach ($nested as $tag) {
1219
                            if (!in_array($tag, $tags)) {
1220
                                $tags[] = $tag;
1221
                            }
1222
                        }
1223
                    }
1224
1225
                    if (!in_array($fetch, $tags)) {  // Avoid double Matches
1226
                        $tags[] = $fetch; // Fetch
1227
                    };
1228
                    $fetch = ''; // and reset
1229
                    $lc = 0;
1230
                    $rc = 0;
1231
                } else {
1232
                    $fetch .= $right;
1233
                }
1234
            } else {
1235
                if (0 < $lc) {
1236
                    $fetch .= $v;
1237
                } else {
1238
                    continue;
1239
                }
1240
            }
1241
        }
1242
        foreach($tags as $i=>$tag) {
1243
            if(strpos($tag,$spacer)!==false) $tags[$i] = str_replace($spacer, '', $tag);
1244
        }
1245
        return $tags;
1246
    }
1247
1248
    /**
1249
     * Merge content fields and TVs
1250
     *
1251
     * @param $content
1252
     * @param bool $ph
1253
     * @return string
1254
     * @internal param string $template
1255
     */
1256
    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...
1257
    {
1258 View Code Duplication
        if ($this->config['enable_at_syntax']) {
1259
            if (stripos($content, '<@LITERAL>') !== false) {
1260
                $content = $this->escapeLiteralTagsContent($content);
1261
            }
1262
        }
1263
        if (strpos($content, '[*') === false) {
1264
            return $content;
1265
        }
1266
        if (!isset($this->documentIdentifier)) {
1267
            return $content;
1268
        }
1269
        if (!isset($this->documentObject) || empty($this->documentObject)) {
1270
            return $content;
1271
        }
1272
1273
        if (!$ph) {
1274
            $ph = $this->documentObject;
1275
        }
1276
1277
        $matches = $this->getTagsFromContent($content, '[*', '*]');
1278
        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...
1279
            return $content;
1280
        }
1281
1282
        foreach ($matches[1] as $i => $key) {
1283
            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...
1284
            if (substr($key, 0, 1) == '#') {
1285
                $key = substr($key, 1);
1286
            } // remove # for QuickEdit format
1287
1288
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1289
            if (strpos($key, '@') !== false) {
1290
                list($key, $context) = explode('@', $key, 2);
1291
            } else {
1292
                $context = false;
1293
            }
1294
1295
            // 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...
1296
            if ($context) {
1297
                $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.

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...
1298
            } else {
1299
                $value = isset($ph[$key]) ? $ph[$key] : '';
1300
            }
1301
1302
            if (is_array($value)) {
1303
                include_once(MODX_MANAGER_PATH . 'includes/tmplvars.format.inc.php');
1304
                include_once(MODX_MANAGER_PATH . 'includes/tmplvars.commands.inc.php');
1305
                $value = getTVDisplayFormat($value[0], $value[1], $value[2], $value[3], $value[4]);
1306
            }
1307
1308
            $s = &$matches[0][$i];
1309
            if ($modifiers !== false) {
1310
                $value = $this->applyFilter($value, $modifiers, $key);
1311
            }
1312
1313 View Code Duplication
            if (strpos($content, $s) !== false) {
1314
                $content = str_replace($s, $value, $content);
1315
            } elseif($this->debug) {
1316
                $this->addLog('mergeDocumentContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1317
            }
1318
        }
1319
1320
        return $content;
1321
    }
1322
1323
    /**
1324
     * @param $key
1325
     * @param bool $parent
1326
     * @return bool|mixed|string
1327
     */
1328
    public function _contextValue($key, $parent = false)
1329
    {
1330
        if (preg_match('/@\d+\/u/', $key)) {
1331
            $key = str_replace(array('@', '/u'), array('@u(', ')'), $key);
1332
        }
1333
        list($key, $str) = explode('@', $key, 2);
1334
1335
        if (strpos($str, '(')) {
1336
            list($context, $option) = explode('(', $str, 2);
1337
        } else {
1338
            list($context, $option) = array($str, false);
1339
        }
1340
1341
        if ($option) {
1342
            $option = trim($option, ')(\'"`');
1343
        }
1344
1345
        switch (strtolower($context)) {
1346
            case 'site_start':
1347
                $docid = $this->config['site_start'];
1348
                break;
1349
            case 'parent':
1350
            case 'p':
1351
                $docid = $parent;
1352
                if ($docid == 0) {
1353
                    $docid = $this->config['site_start'];
1354
                }
1355
                break;
1356
            case 'ultimateparent':
1357
            case 'uparent':
1358
            case 'up':
1359
            case 'u':
1360 View Code Duplication
                if (strpos($str, '(') !== false) {
1361
                    $top = substr($str, strpos($str, '('));
1362
                    $top = trim($top, '()"\'');
1363
                } else {
1364
                    $top = 0;
1365
                }
1366
                $docid = $this->getUltimateParentId($this->documentIdentifier, $top);
1367
                break;
1368
            case 'alias':
1369
                $str = substr($str, strpos($str, '('));
1370
                $str = trim($str, '()"\'');
1371
                $docid = $this->getIdFromAlias($str);
1372
                break;
1373 View Code Duplication
            case 'prev':
1374
                if (!$option) {
1375
                    $option = 'menuindex,ASC';
1376
                } elseif (strpos($option, ',') === false) {
1377
                    $option .= ',ASC';
1378
                }
1379
                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...
1380
                $children = $this->getActiveChildren($parent, $by, $dir);
1381
                $find = false;
1382
                $prev = false;
1383
                foreach ($children as $row) {
1384
                    if ($row['id'] == $this->documentIdentifier) {
1385
                        $find = true;
1386
                        break;
1387
                    }
1388
                    $prev = $row;
1389
                }
1390
                if ($find) {
1391
                    if (isset($prev[$key])) {
1392
                        return $prev[$key];
1393
                    } else {
1394
                        $docid = $prev['id'];
1395
                    }
1396
                } else {
1397
                    $docid = '';
1398
                }
1399
                break;
1400 View Code Duplication
            case 'next':
1401
                if (!$option) {
1402
                    $option = 'menuindex,ASC';
1403
                } elseif (strpos($option, ',') === false) {
1404
                    $option .= ',ASC';
1405
                }
1406
                list($by, $dir) = explode(',', $option, 2);
1407
                $children = $this->getActiveChildren($parent, $by, $dir);
1408
                $find = false;
1409
                $next = false;
1410
                foreach ($children as $row) {
1411
                    if ($find) {
1412
                        $next = $row;
1413
                        break;
1414
                    }
1415
                    if ($row['id'] == $this->documentIdentifier) {
1416
                        $find = true;
1417
                    }
1418
                }
1419
                if ($find) {
1420
                    if (isset($next[$key])) {
1421
                        return $next[$key];
1422
                    } else {
1423
                        $docid = $next['id'];
1424
                    }
1425
                } else {
1426
                    $docid = '';
1427
                }
1428
                break;
1429
            default:
1430
                $docid = $str;
1431
        }
1432
        if (preg_match('@^[1-9][0-9]*$@', $docid)) {
1433
            $value = $this->getField($key, $docid);
1434
        } else {
1435
            $value = '';
1436
        }
1437
        return $value;
1438
    }
1439
1440
    /**
1441
     * Merge system settings
1442
     *
1443
     * @param $content
1444
     * @param bool $ph
1445
     * @return string
1446
     * @internal param string $template
1447
     */
1448
    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...
1449
    {
1450 View Code Duplication
        if ($this->config['enable_at_syntax']) {
1451
            if (stripos($content, '<@LITERAL>') !== false) {
1452
                $content = $this->escapeLiteralTagsContent($content);
1453
            }
1454
        }
1455
        if (strpos($content, '[(') === false) {
1456
            return $content;
1457
        }
1458
1459
        if (!$ph) {
1460
            $ph = $this->config;
1461
        }
1462
1463
        $matches = $this->getTagsFromContent($content, '[(', ')]');
1464
        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...
1465
            return $content;
1466
        }
1467
1468
        foreach ($matches[1] as $i => $key) {
1469
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1470
1471
            if (isset($ph[$key])) {
1472
                $value = $ph[$key];
1473
            } else {
1474
                continue;
1475
            }
1476
1477
            if ($modifiers !== false) {
1478
                $value = $this->applyFilter($value, $modifiers, $key);
1479
            }
1480
            $s = &$matches[0][$i];
1481 View Code Duplication
            if (strpos($content, $s) !== false) {
1482
                $content = str_replace($s, $value, $content);
1483
            } elseif($this->debug) {
1484
                $this->addLog('mergeSettingsContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1485
            }
1486
        }
1487
        return $content;
1488
    }
1489
1490
    /**
1491
     * Merge chunks
1492
     *
1493
     * @param string $content
1494
     * @param bool $ph
1495
     * @return string
1496
     */
1497
    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...
1498
    {
1499
        if ($this->config['enable_at_syntax']) {
1500
            if (strpos($content, '{{ ') !== false) {
1501
                $content = str_replace(array('{{ ', ' }}'), array('\{\{ ', ' \}\}'), $content);
1502
            }
1503
            if (stripos($content, '<@LITERAL>') !== false) {
1504
                $content = $this->escapeLiteralTagsContent($content);
1505
            }
1506
        }
1507
        if (strpos($content, '{{') === false) {
1508
            return $content;
1509
        }
1510
1511
        if (!$ph) {
1512
            $ph = $this->chunkCache;
1513
        }
1514
1515
        $matches = $this->getTagsFromContent($content, '{{', '}}');
1516
        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...
1517
            return $content;
1518
        }
1519
1520
        foreach ($matches[1] as $i => $key) {
1521
            $snip_call = $this->_split_snip_call($key);
1522
            $key = $snip_call['name'];
1523
            $params = $this->getParamsFromString($snip_call['params']);
1524
1525
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1526
1527
            if (!isset($ph[$key])) {
1528
                $ph[$key] = $this->getChunk($key);
1529
            }
1530
            $value = $ph[$key];
1531
1532
            if (is_null($value)) {
1533
                continue;
1534
            }
1535
1536
            $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 1523 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...
1537
            $value = $this->mergePlaceholderContent($value, $params);  // parse page global placeholers
0 ignored issues
show
Documentation introduced by
$params is of type array|null, 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...
1538
            if ($this->config['enable_at_syntax']) {
1539
                $value = $this->mergeConditionalTagsContent($value);
1540
            }
1541
            $value = $this->mergeDocumentContent($value);
1542
            $value = $this->mergeSettingsContent($value);
1543
            $value = $this->mergeChunkContent($value);
1544
1545
            if ($modifiers !== false) {
1546
                $value = $this->applyFilter($value, $modifiers, $key);
1547
            }
1548
1549
            $s = &$matches[0][$i];
1550 View Code Duplication
            if (strpos($content, $s) !== false) {
1551
                $content = str_replace($s, $value, $content);
1552
            } elseif($this->debug) {
1553
                $this->addLog('mergeChunkContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1554
            }
1555
        }
1556
        return $content;
1557
    }
1558
1559
    /**
1560
     * Merge placeholder values
1561
     *
1562
     * @param string $content
1563
     * @param bool $ph
1564
     * @return string
1565
     */
1566
    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...
1567
    {
1568
1569 View Code Duplication
        if ($this->config['enable_at_syntax']) {
1570
            if (stripos($content, '<@LITERAL>') !== false) {
1571
                $content = $this->escapeLiteralTagsContent($content);
1572
            }
1573
        }
1574
        if (strpos($content, '[+') === false) {
1575
            return $content;
1576
        }
1577
1578
        if (!$ph) {
1579
            $ph = $this->placeholders;
1580
        }
1581
1582
        if ($this->config['enable_at_syntax']) {
1583
            $content = $this->mergeConditionalTagsContent($content);
1584
        }
1585
1586
        $content = $this->mergeDocumentContent($content);
1587
        $content = $this->mergeSettingsContent($content);
1588
        $matches = $this->getTagsFromContent($content, '[+', '+]');
1589
        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...
1590
            return $content;
1591
        }
1592
        foreach ($matches[1] as $i => $key) {
1593
1594
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1595
1596
            if (isset($ph[$key])) {
1597
                $value = $ph[$key];
1598
            } elseif ($key === 'phx') {
1599
                $value = '';
1600
            } else {
1601
                continue;
1602
            }
1603
1604
            if ($modifiers !== false) {
1605
                $modifiers = $this->mergePlaceholderContent($modifiers);
1606
                $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...
1607
            }
1608
            $s = &$matches[0][$i];
1609 View Code Duplication
            if (strpos($content, $s) !== false) {
1610
                $content = str_replace($s, $value, $content);
1611
            } elseif($this->debug) {
1612
                $this->addLog('mergePlaceholderContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1613
            }
1614
        }
1615
        return $content;
1616
    }
1617
1618
    /**
1619
     * @param $content
1620
     * @param string $iftag
1621
     * @param string $elseiftag
1622
     * @param string $elsetag
1623
     * @param string $endiftag
1624
     * @return mixed|string
1625
     */
1626
    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...
1627
    {
1628
        if (strpos($content, '@IF') !== false) {
1629
            $content = $this->_prepareCTag($content, $iftag, $elseiftag, $elsetag, $endiftag);
1630
        }
1631
1632
        if (strpos($content, $iftag) === false) {
1633
            return $content;
1634
        }
1635
1636
        $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...
1637
        $content = str_replace(array('<?php', '<?=', '<?', '?>'), array("{$sp}b", "{$sp}p", "{$sp}s", "{$sp}e"), $content);
1638
1639
        $pieces = explode('<@IF:', $content);
1640 View Code Duplication
        foreach ($pieces as $i => $split) {
1641
            if ($i === 0) {
1642
                $content = $split;
1643
                continue;
1644
            }
1645
            list($cmd, $text) = explode('>', $split, 2);
1646
            $cmd = str_replace("'", "\'", $cmd);
1647
            $content .= "<?php if(\$this->_parseCTagCMD('" . $cmd . "')): ?>";
1648
            $content .= $text;
1649
        }
1650
        $pieces = explode('<@ELSEIF:', $content);
1651 View Code Duplication
        foreach ($pieces as $i => $split) {
1652
            if ($i === 0) {
1653
                $content = $split;
1654
                continue;
1655
            }
1656
            list($cmd, $text) = explode('>', $split, 2);
1657
            $cmd = str_replace("'", "\'", $cmd);
1658
            $content .= "<?php elseif(\$this->_parseCTagCMD('" . $cmd . "')): ?>";
1659
            $content .= $text;
1660
        }
1661
1662
        $content = str_replace(array('<@ELSE>', '<@ENDIF>'), array('<?php else:?>', '<?php endif;?>'), $content);
1663
        ob_start();
1664
        $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...
1665
        $content = ob_get_clean();
1666
        $content = str_replace(array("{$sp}b", "{$sp}p", "{$sp}s", "{$sp}e"), array('<?php', '<?=', '<?', '?>'), $content);
1667
1668
        return $content;
1669
    }
1670
1671
    /**
1672
     * @param $content
1673
     * @param string $iftag
1674
     * @param string $elseiftag
1675
     * @param string $elsetag
1676
     * @param string $endiftag
1677
     * @return mixed
1678
     */
1679
    private function _prepareCTag($content, $iftag = '<@IF:', $elseiftag = '<@ELSEIF:', $elsetag = '<@ELSE>', $endiftag = '<@ENDIF>')
1680
    {
1681
        if (strpos($content, '<!--@IF ') !== false) {
1682
            $content = str_replace('<!--@IF ', $iftag, $content);
1683
        } // for jp
1684
        if (strpos($content, '<!--@IF:') !== false) {
1685
            $content = str_replace('<!--@IF:', $iftag, $content);
1686
        }
1687
        if (strpos($content, $iftag) === false) {
1688
            return $content;
1689
        }
1690
        if (strpos($content, '<!--@ELSEIF:') !== false) {
1691
            $content = str_replace('<!--@ELSEIF:', $elseiftag, $content);
1692
        } // for jp
1693
        if (strpos($content, '<!--@ELSE-->') !== false) {
1694
            $content = str_replace('<!--@ELSE-->', $elsetag, $content);
1695
        }  // for jp
1696
        if (strpos($content, '<!--@ENDIF-->') !== false) {
1697
            $content = str_replace('<!--@ENDIF-->', $endiftag, $content);
1698
        }    // for jp
1699
        if (strpos($content, '<@ENDIF-->') !== false) {
1700
            $content = str_replace('<@ENDIF-->', $endiftag, $content);
1701
        }
1702
        $tags = array($iftag, $elseiftag, $elsetag, $endiftag);
1703
        $content = str_ireplace($tags, $tags, $content); // Change to capital letters
1704
        return $content;
1705
    }
1706
1707
    /**
1708
     * @param $cmd
1709
     * @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...
1710
     */
1711
    private function _parseCTagCMD($cmd)
1712
    {
1713
        $cmd = trim($cmd);
1714
        $reverse = substr($cmd, 0, 1) === '!' ? true : false;
1715
        if ($reverse) {
1716
            $cmd = ltrim($cmd, '!');
1717
        }
1718
        if (strpos($cmd, '[!') !== false) {
1719
            $cmd = str_replace(array('[!', '!]'), array('[[', ']]'), $cmd);
1720
        }
1721
        $safe = 0;
1722
        while ($safe < 20) {
1723
            $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...
1724
            if (strpos($cmd, '[*') !== false) {
1725
                $cmd = $this->mergeDocumentContent($cmd);
1726
            }
1727
            if (strpos($cmd, '[(') !== false) {
1728
                $cmd = $this->mergeSettingsContent($cmd);
1729
            }
1730
            if (strpos($cmd, '{{') !== false) {
1731
                $cmd = $this->mergeChunkContent($cmd);
1732
            }
1733
            if (strpos($cmd, '[[') !== false) {
1734
                $cmd = $this->evalSnippets($cmd);
1735
            }
1736
            if (strpos($cmd, '[+') !== false && strpos($cmd, '[[') === false) {
1737
                $cmd = $this->mergePlaceholderContent($cmd);
1738
            }
1739
            if ($bt === md5($cmd)) {
1740
                break;
1741
            }
1742
            $safe++;
1743
        }
1744
        $cmd = ltrim($cmd);
1745
        $cmd = rtrim($cmd, '-');
1746
        $cmd = str_ireplace(array(' and ', ' or '), array('&&', '||'), $cmd);
1747
1748
        if (!preg_match('@^[0-9]*$@', $cmd) && preg_match('@^[0-9<= \-\+\*/\(\)%!&|]*$@', $cmd)) {
1749
            $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...
1750
        } else {
1751
            $_ = 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...
1752
            foreach ($_ as $left) {
1753
                if (strpos($cmd, $left) !== false) {
1754
                    $cmd = 0;
1755
                    break;
1756
                }
1757
            }
1758
        }
1759
        $cmd = trim($cmd);
1760
        if (!preg_match('@^[0-9]+$@', $cmd)) {
1761
            $cmd = empty($cmd) ? 0 : 1;
1762
        } elseif ($cmd <= 0) {
1763
            $cmd = 0;
1764
        }
1765
1766
        if ($reverse) {
1767
            $cmd = !$cmd;
1768
        }
1769
1770
        return $cmd;
1771
    }
1772
1773
    /**
1774
     * Remove Comment-Tags from output like <!--@- Comment -@-->
1775
     * @param $content
1776
     * @param string $left
1777
     * @param string $right
1778
     * @return mixed
1779
     */
1780
    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...
1781
    {
1782
        if (strpos($content, $left) === false) {
1783
            return $content;
1784
        }
1785
1786
        $matches = $this->getTagsFromContent($content, $left, $right);
1787
        if (!empty($matches)) {
1788
            foreach ($matches[0] as $i => $v) {
1789
                $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...
1790
            }
1791
            $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...
1792
            if (strpos($content, $left) !== false) {
1793
                $content = str_replace($matches[0], '', $content);
1794
            }
1795
        }
1796
        return $content;
1797
    }
1798
1799
    /**
1800
     * @param $content
1801
     * @param string $left
1802
     * @param string $right
1803
     * @return mixed
1804
     */
1805
    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...
1806
    {
1807
        if (stripos($content, $left) === false) {
1808
            return $content;
1809
        }
1810
1811
        $matches = $this->getTagsFromContent($content, $left, $right);
1812
        if (empty($matches)) {
1813
            return $content;
1814
        }
1815
1816
        list($sTags, $rTags) = $this->getTagsForEscape();
1817
        foreach ($matches[1] as $i => $v) {
1818
            $v = str_ireplace($sTags, $rTags, $v);
1819
            $s = &$matches[0][$i];
1820 View Code Duplication
            if (strpos($content, $s) !== false) {
1821
                $content = str_replace($s, $v, $content);
1822
            } elseif($this->debug) {
1823
                $this->addLog('ignoreCommentedTagsContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1824
            }
1825
        }
1826
        return $content;
1827
    }
1828
1829
    /**
1830
     * Detect PHP error according to MODX error level
1831
     *
1832
     * @param integer $error PHP error level
1833
     * @return boolean Error detected
1834
     */
1835
1836
    public function detectError($error)
1837
    {
1838
        $detected = false;
1839
        if ($this->config['error_reporting'] == 99 && $error) {
1840
            $detected = true;
1841
        } elseif ($this->config['error_reporting'] == 2 && ($error & ~E_NOTICE)) {
1842
            $detected = true;
1843
        } elseif ($this->config['error_reporting'] == 1 && ($error & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT)) {
1844
            $detected = true;
1845
        }
1846
        return $detected;
1847
    }
1848
1849
    /**
1850
     * Run a plugin
1851
     *
1852
     * @param string $pluginCode Code to run
1853
     * @param array $params
1854
     */
1855
    public function evalPlugin($pluginCode, $params)
1856
    {
1857
        $modx = &$this;
1858
        $modx->event->params = &$params; // store params inside event object
1859
        if (is_array($params)) {
1860
            extract($params, EXTR_SKIP);
1861
        }
1862
        /* 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...
1863
        // 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.
1864
        // Related to https://github.com/modxcms/evolution/issues/1130
1865
        $lock_file_path = MODX_BASE_PATH . 'assets/cache/lock_' . str_replace(' ','-',strtolower($this->event->activePlugin)) . '.pageCache.php';
1866
        if($this->isBackend()) {
1867
            if(is_file($lock_file_path)) {
1868
                $msg = sprintf("Plugin parse error, Temporarily disabled '%s'.", $this->event->activePlugin);
1869
                $this->logEvent(0, 3, $msg, $msg);
1870
                return;
1871
            }
1872
            elseif(stripos($this->event->activePlugin,'ElementsInTree')===false) touch($lock_file_path);
1873
        }*/
1874
        ob_start();
1875
        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...
1876
        $msg = ob_get_contents();
1877
        ob_end_clean();
1878
        // When reached here, no fatal error occured so the lock should be removed.
1879
        /*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...
1880
1881 View Code Duplication
        if ((0 < $this->config['error_reporting']) && $msg && isset($php_errormsg)) {
1882
            $error_info = error_get_last();
1883
            if ($this->detectError($error_info['type'])) {
1884
                $msg = ($msg === false) ? 'ob_get_contents() error' : $msg;
1885
                $this->messageQuit('PHP Parse Error', '', true, $error_info['type'], $error_info['file'], 'Plugin', $error_info['message'], $error_info['line'], $msg);
1886
                if ($this->isBackend()) {
1887
                    $this->event->alert('An error occurred while loading. Please see the event log for more information.<p>' . $msg . '</p>');
1888
                }
1889
            }
1890
        } else {
1891
            echo $msg;
1892
        }
1893
        unset($modx->event->params);
1894
    }
1895
1896
    /**
1897
     * Run a snippet
1898
     *
1899
     * @param $phpcode
1900
     * @param array $params
1901
     * @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...
1902
     * @internal param string $snippet Code to run
1903
     */
1904
    public function evalSnippet($phpcode, $params)
1905
    {
1906
        $modx = &$this;
1907
        /*
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...
1908
        if(isset($params) && is_array($params)) {
1909
            foreach($params as $k=>$v) {
1910
                $v = strtolower($v);
1911
                if($v==='false')    $params[$k] = false;
1912
                elseif($v==='true') $params[$k] = true;
1913
            }
1914
        }*/
1915
        $modx->event->params = &$params; // store params inside event object
1916
        if (is_array($params)) {
1917
            extract($params, EXTR_SKIP);
1918
        }
1919
        ob_start();
1920
        if (strpos($phpcode, ';') !== false) {
1921
            $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...
1922
        } else {
1923
            $return = call_user_func_array($phpcode, array($params));
1924
        }
1925
        $echo = ob_get_contents();
1926
        ob_end_clean();
1927 View Code Duplication
        if ((0 < $this->config['error_reporting']) && isset($php_errormsg)) {
1928
            $error_info = error_get_last();
1929
            if ($this->detectError($error_info['type'])) {
1930
                $echo = ($echo === false) ? 'ob_get_contents() error' : $echo;
1931
                $this->messageQuit('PHP Parse Error', '', true, $error_info['type'], $error_info['file'], 'Snippet', $error_info['message'], $error_info['line'], $echo);
1932
                if ($this->isBackend()) {
1933
                    $this->event->alert('An error occurred while loading. Please see the event log for more information<p>' . $echo . $return . '</p>');
1934
                }
1935
            }
1936
        }
1937
        unset($modx->event->params);
1938
        if (is_array($return) || is_object($return)) {
1939
            return $return;
1940
        } else {
1941
            return $echo . $return;
1942
        }
1943
    }
1944
1945
    /**
1946
     * Run snippets as per the tags in $documentSource and replace the tags with the returned values.
1947
     *
1948
     * @param $content
1949
     * @return string
1950
     * @internal param string $documentSource
1951
     */
1952
    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...
1953
    {
1954
        if (strpos($content, '[[') === false) {
1955
            return $content;
1956
        }
1957
1958
        $matches = $this->getTagsFromContent($content, '[[', ']]');
1959
1960
        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...
1961
            return $content;
1962
        }
1963
1964
        $this->snipLapCount++;
1965
        if ($this->dumpSnippets) {
1966
            $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);
1967
        }
1968
1969
        foreach ($matches[1] as $i => $call) {
1970
            $s = &$matches[0][$i];
1971
            if (substr($call, 0, 2) === '$_') {
1972
                if (strpos($content, '_PHX_INTERNAL_') === false) {
1973
                    $value = $this->_getSGVar($call);
1974
                } else {
1975
                    $value = $s;
1976
                }
1977 View Code Duplication
                if (strpos($content, $s) !== false) {
1978
                    $content = str_replace($s, $value, $content);
1979
                } elseif($this->debug) {
1980
                    $this->addLog('evalSnippetsSGVar parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1981
                }
1982
                continue;
1983
            }
1984
            $value = $this->_get_snip_result($call);
1985
            if (is_null($value)) {
1986
                continue;
1987
            }
1988
1989 View Code Duplication
            if (strpos($content, $s) !== false) {
1990
                $content = str_replace($s, $value, $content);
1991
            } elseif($this->debug) {
1992
                $this->addLog('evalSnippets parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1993
            }
1994
        }
1995
1996
        if ($this->dumpSnippets) {
1997
            $this->snippetsCode .= '</fieldset><br />';
1998
        }
1999
2000
        return $content;
2001
    }
2002
2003
    /**
2004
     * @param $value
2005
     * @return mixed|string
2006
     */
2007
    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...
2008
    { // Get super globals
2009
        $key = $value;
2010
        $_ = $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...
2011
        $this->config['enable_filter'] = 1;
2012
        list($key, $modifiers) = $this->splitKeyAndFilter($key);
2013
        $this->config['enable_filter'] = $_;
2014
        $key = str_replace(array('(', ')'), array("['", "']"), $key);
2015
        $key = rtrim($key, ';');
2016
        if (strpos($key, '$_SESSION') !== false) {
2017
            $_ = $_SESSION;
2018
            $key = str_replace('$_SESSION', '$_', $key);
2019
            if (isset($_['mgrFormValues'])) {
2020
                unset($_['mgrFormValues']);
2021
            }
2022
            if (isset($_['token'])) {
2023
                unset($_['token']);
2024
            }
2025
        }
2026
        if (strpos($key, '[') !== false) {
2027
            $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...
2028
        } 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...
2029
            $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...
2030
        } else {
2031
            $value = '';
2032
        }
2033
        if ($modifiers !== false) {
2034
            $value = $this->applyFilter($value, $modifiers, $key);
2035
        }
2036
        return $value;
2037
    }
2038
2039
    /**
2040
     * @param $piece
2041
     * @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...
2042
     */
2043
    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...
2044
    {
2045
        if (ltrim($piece) !== $piece) {
2046
            return '';
2047
        }
2048
2049
        if ($this->dumpSnippets) {
2050
            $eventtime = $this->getMicroTime();
2051
        }
2052
        $snip_call = $this->_split_snip_call($piece);
2053
        $key = $snip_call['name'];
2054
2055
        list($key, $modifiers) = $this->splitKeyAndFilter($key);
2056
        $snip_call['name'] = $key;
2057
        $snippetObject = $this->_getSnippetObject($key);
2058
        if (is_null($snippetObject['content'])) {
2059
            return null;
2060
        }
2061
2062
        $this->currentSnippet = $snippetObject['name'];
2063
2064
        // current params
2065
        $params = $this->getParamsFromString($snip_call['params']);
2066
2067
        if (!isset($snippetObject['properties'])) {
2068
            $snippetObject['properties'] = '';
2069
        }
2070
        $default_params = $this->parseProperties($snippetObject['properties'], $this->currentSnippet, 'snippet');
2071
        $params = array_merge($default_params, $params);
2072
2073
        $value = $this->evalSnippet($snippetObject['content'], $params);
2074
        $this->currentSnippet = '';
2075
        if ($modifiers !== false) {
2076
            $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...
2077
        }
2078
2079
        if ($this->dumpSnippets) {
2080
            $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...
2081
            $eventtime = sprintf('%2.2f ms', $eventtime * 1000);
2082
            $code = str_replace("\t", '  ', $this->htmlspecialchars($value));
2083
            $piece = str_replace("\t", '  ', $this->htmlspecialchars($piece));
2084
            $print_r_params = str_replace("\t", '  ', $this->htmlspecialchars('$modx->event->params = ' . print_r($params, true)));
2085
            $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);
2086
            $this->snippetsTime[] = array('sname' => $key, 'time' => $eventtime);
2087
        }
2088
        return $value;
2089
    }
2090
2091
    /**
2092
     * @param string $string
2093
     * @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...
2094
     */
2095
    public function getParamsFromString($string = '')
2096
    {
2097
        if (empty($string)) {
2098
            return array();
2099
        }
2100
2101
        if (strpos($string, '&_PHX_INTERNAL_') !== false) {
2102
            $string = str_replace(array('&_PHX_INTERNAL_091_&', '&_PHX_INTERNAL_093_&'), array('[', ']'), $string);
2103
        }
2104
2105
        $_ = $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...
2106
        $this->documentOutput = $string;
2107
        $this->invokeEvent('OnBeforeParseParams');
2108
        $string = $this->documentOutput;
2109
        $this->documentOutput = $_;
2110
2111
        $_tmp = $string;
2112
        $_tmp = ltrim($_tmp, '?&');
2113
        $temp_params = array();
2114
        $key = '';
2115
        $value = null;
2116
        while ($_tmp !== '') {
2117
            $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...
2118
            $char = substr($_tmp, 0, 1);
2119
            $_tmp = substr($_tmp, 1);
2120
2121
            if ($char === '=') {
2122
                $_tmp = trim($_tmp);
2123
                $delim = substr($_tmp, 0, 1);
2124
                if (in_array($delim, array('"', "'", '`'))) {
2125
                    $null = null;
2126
                    //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...
2127
                    list($null, $value, $_tmp) = explode($delim, $_tmp, 3);
2128
                    unset($null);
2129
2130
                    if (substr(trim($_tmp), 0, 2) === '//') {
2131
                        $_tmp = strstr(trim($_tmp), "\n");
2132
                    }
2133
                    $i = 0;
2134
                    while ($delim === '`' && substr(trim($_tmp), 0, 1) !== '&' && 1 < substr_count($_tmp, '`')) {
2135
                        list($inner, $outer, $_tmp) = explode('`', $_tmp, 3);
2136
                        $value .= "`{$inner}`{$outer}";
2137
                        $i++;
2138
                        if (100 < $i) {
2139
                            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...
2140
                        }
2141
                    }
2142
                    if ($i && $delim === '`') {
2143
                        $value = rtrim($value, '`');
2144
                    }
2145
                } elseif (strpos($_tmp, '&') !== false) {
2146
                    list($value, $_tmp) = explode('&', $_tmp, 2);
2147
                    $value = trim($value);
2148
                } else {
2149
                    $value = $_tmp;
2150
                    $_tmp = '';
2151
                }
2152
            } elseif ($char === '&') {
2153
                if (trim($key) !== '') {
2154
                    $value = '1';
2155
                } else {
2156
                    continue;
2157
                }
2158
            } elseif ($_tmp === '') {
2159
                $key .= $char;
2160
                $value = '1';
2161
            } elseif ($key !== '' || trim($char) !== '') {
2162
                $key .= $char;
2163
            }
2164
2165
            if (isset($value) && !is_null($value)) {
2166
                if (strpos($key, 'amp;') !== false) {
2167
                    $key = str_replace('amp;', '', $key);
2168
                }
2169
                $key = trim($key);
2170 View Code Duplication
                if (strpos($value, '[!') !== false) {
2171
                    $value = str_replace(array('[!', '!]'), array('[[', ']]'), $value);
2172
                }
2173
                $value = $this->mergeDocumentContent($value);
2174
                $value = $this->mergeSettingsContent($value);
2175
                $value = $this->mergeChunkContent($value);
2176
                $value = $this->evalSnippets($value);
2177
                if (substr($value, 0, 6) !== '@CODE:') {
2178
                    $value = $this->mergePlaceholderContent($value);
2179
                }
2180
2181
                $temp_params[][$key] = $value;
2182
2183
                $key = '';
2184
                $value = null;
2185
2186
                $_tmp = ltrim($_tmp, " ,\t");
2187
                if (substr($_tmp, 0, 2) === '//') {
2188
                    $_tmp = strstr($_tmp, "\n");
2189
                }
2190
            }
2191
2192
            if ($_tmp === $bt) {
2193
                $key = trim($key);
2194
                if ($key !== '') {
2195
                    $temp_params[][$key] = '';
2196
                }
2197
                break;
2198
            }
2199
        }
2200
2201
        foreach ($temp_params as $p) {
2202
            $k = key($p);
2203
            if (substr($k, -2) === '[]') {
2204
                $k = substr($k, 0, -2);
2205
                $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...
2206
            } elseif (strpos($k, '[') !== false && substr($k, -1) === ']') {
2207
                list($k, $subk) = explode('[', $k, 2);
2208
                $subk = substr($subk, 0, -1);
2209
                $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...
2210
            } else {
2211
                $params[$k] = current($p);
2212
            }
2213
        }
2214
        return $params;
2215
    }
2216
2217
    /**
2218
     * @param $str
2219
     * @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...
2220
     */
2221
    public function _getSplitPosition($str)
2222
    {
2223
        $closeOpt = false;
2224
        $maybePos = false;
2225
        $inFilter = false;
2226
        $total = strlen($str);
2227
        for ($i = 0; $i < $total; $i++) {
2228
            $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...
2229
            $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...
2230
            if (!$inFilter) {
2231
                if ($c === ':') {
2232
                    $inFilter = true;
2233
                } elseif ($c === '?') {
2234
                    $pos = $i;
2235
                } elseif ($c === ' ') {
2236
                    $maybePos = $i;
2237
                } 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...
2238
                    $pos = $maybePos;
2239
                } elseif ($c === "\n") {
2240
                    $pos = $i;
2241
                } else {
2242
                    $pos = false;
2243
                }
2244
            } else {
2245
                if ($cc == $closeOpt) {
2246
                    $closeOpt = false;
2247
                } elseif ($c == $closeOpt) {
2248
                    $closeOpt = false;
2249
                } 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...
2250
                    continue;
2251
                } elseif ($cc === "('") {
2252
                    $closeOpt = "')";
2253
                } elseif ($cc === '("') {
2254
                    $closeOpt = '")';
2255
                } elseif ($cc === '(`') {
2256
                    $closeOpt = '`)';
2257
                } elseif ($c === '(') {
2258
                    $closeOpt = ')';
2259
                } elseif ($c === '?') {
2260
                    $pos = $i;
2261
                } elseif ($c === ' ' && strpos($str, '?') === false) {
2262
                    $pos = $i;
2263
                } else {
2264
                    $pos = false;
2265
                }
2266
            }
2267
            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...
2268
                break;
2269
            }
2270
        }
2271
        return $pos;
2272
    }
2273
2274
    /**
2275
     * @param $call
2276
     * @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...
2277
     */
2278
    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...
2279
    {
2280
        $spacer = md5('dummy');
2281 View Code Duplication
        if (strpos($call, ']]>') !== false) {
2282
            $call = str_replace(']]>', "]{$spacer}]>", $call);
2283
        }
2284
2285
        $splitPosition = $this->_getSplitPosition($call);
2286
2287
        if ($splitPosition !== false) {
2288
            $name = substr($call, 0, $splitPosition);
2289
            $params = substr($call, $splitPosition + 1);
2290
        } else {
2291
            $name = $call;
2292
            $params = '';
2293
        }
2294
2295
        $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...
2296 View Code Duplication
        if (strpos($params, $spacer) !== false) {
2297
            $params = str_replace("]{$spacer}]>", ']]>', $params);
2298
        }
2299
        $snip['params'] = ltrim($params, "?& \t\n");
2300
2301
        return $snip;
2302
    }
2303
2304
    /**
2305
     * @param $snip_name
2306
     * @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...
2307
     */
2308
    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...
2309
    {
2310
        if (isset($this->snippetCache[$snip_name])) {
2311
            $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...
2312
            $snippetObject['content'] = $this->snippetCache[$snip_name];
2313
            if (isset($this->snippetCache["{$snip_name}Props"])) {
2314
                if (!isset($this->snippetCache["{$snip_name}Props"])) {
2315
                    $this->snippetCache["{$snip_name}Props"] = '';
2316
                }
2317
                $snippetObject['properties'] = $this->snippetCache["{$snip_name}Props"];
2318
            }
2319
        } elseif (substr($snip_name, 0, 1) === '@' && isset($this->pluginEvent[trim($snip_name, '@')])) {
2320
            $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...
2321
            $snippetObject['content'] = sprintf('$rs=$this->invokeEvent("%s",$params);echo trim(join("",$rs));', trim($snip_name, '@'));
2322
            $snippetObject['properties'] = '';
2323
        } else {
2324
            $where = sprintf("name='%s' AND disabled=0", $this->db->escape($snip_name));
2325
            $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...
2326
            $count = $this->db->getRecordCount($rs);
2327
            if (1 < $count) {
2328
                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...
2329
            }
2330
            if ($count) {
2331
                $row = $this->db->getRow($rs);
2332
                $snip_content = $row['snippet'];
2333
                $snip_prop = $row['properties'];
2334
            } else {
2335
                $snip_content = null;
2336
                $snip_prop = '';
2337
            }
2338
            $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...
2339
            $snippetObject['content'] = $snip_content;
2340
            $snippetObject['properties'] = $snip_prop;
2341
            $this->snippetCache[$snip_name] = $snip_content;
2342
            $this->snippetCache["{$snip_name}Props"] = $snip_prop;
2343
        }
2344
        return $snippetObject;
2345
    }
2346
2347
    /**
2348
     * @param $text
2349
     * @return mixed
2350
     */
2351
    public function toAlias($text)
2352
    {
2353
        $suff = $this->config['friendly_url_suffix'];
2354
        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);
2355
    }
2356
2357
    /**
2358
     * makeFriendlyURL
2359
     *
2360
     * @desc Create an URL.
2361
     *
2362
     * @param $pre {string} - Friendly URL Prefix. @required
2363
     * @param $suff {string} - Friendly URL Suffix. @required
2364
     * @param $alias {string} - Full document path. @required
2365
     * @param int $isfolder {0; 1}
2366
     * - Is it a folder? Default: 0.
2367
     * @param int $id {integer}
2368
     * - Document id. Default: 0.
2369
     * @return mixed|string {string} - Result URL.
2370
     * - Result URL.
2371
     */
2372
    public function makeFriendlyURL($pre, $suff, $alias, $isfolder = 0, $id = 0)
2373
    {
2374
        if ($id == $this->config['site_start'] && $this->config['seostrict'] === '1') {
2375
            $url = $this->config['base_url'];
2376
        } else {
2377
            $Alias = explode('/', $alias);
2378
            $alias = array_pop($Alias);
2379
            $dir = implode('/', $Alias);
2380
            unset($Alias);
2381
2382
            if ($this->config['make_folders'] === '1' && $isfolder == 1) {
2383
                $suff = '/';
2384
            }
2385
2386
            $url = ($dir != '' ? $dir . '/' : '') . $pre . $alias . $suff;
2387
        }
2388
2389
        $evtOut = $this->invokeEvent('OnMakeDocUrl', array(
2390
            'id' => $id,
2391
            'url' => $url
2392
        ));
2393
2394
        if (is_array($evtOut) && count($evtOut) > 0) {
2395
            $url = array_pop($evtOut);
2396
        }
2397
2398
        return $url;
2399
    }
2400
2401
    /**
2402
     * Convert URL tags [~...~] to URLs
2403
     *
2404
     * @param string $documentSource
2405
     * @return string
2406
     */
2407
    public function rewriteUrls($documentSource)
2408
    {
2409
        // rewrite the urls
2410
        if ($this->config['friendly_urls'] == 1) {
2411
            $aliases = array();
2412
            if (is_array($this->documentListing)) {
2413
                foreach ($this->documentListing as $path => $docid) { // This is big Loop on large site!
2414
                    $aliases[$docid] = $path;
2415
                    $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...
2416
                }
2417
            }
2418
2419
            if ($this->config['aliaslistingfolder'] == 1) {
2420
                preg_match_all('!\[\~([0-9]+)\~\]!ise', $documentSource, $match);
2421
                $ids = implode(',', array_unique($match['1']));
2422
                if ($ids) {
2423
                    $res = $this->db->select("id,alias,isfolder,parent,alias_visible", $this->getFullTableName('site_content'), "id IN (" . $ids . ") AND isfolder = '0'");
2424
                    while ($row = $this->db->getRow($res)) {
2425
                        if ($this->config['use_alias_path'] == '1' && $row['parent'] != 0) {
2426
                            $parent = $row['parent'];
2427
                            $path = $aliases[$parent];
2428
2429
                            while (isset($this->aliasListing[$parent]) && $this->aliasListing[$parent]['alias_visible'] == 0) {
2430
                                $path = $this->aliasListing[$parent]['path'];
2431
                                $parent = $this->aliasListing[$parent]['parent'];
2432
                            }
2433
2434
                            $aliases[$row['id']] = $path . '/' . $row['alias'];
2435
                        } else {
2436
                            $aliases[$row['id']] = $row['alias'];
2437
                        }
2438
                        $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...
2439
                    }
2440
                }
2441
            }
2442
            $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...
2443
            $isfriendly = ($this->config['friendly_alias_urls'] == 1 ? 1 : 0);
2444
            $pref = $this->config['friendly_url_prefix'];
2445
            $suff = $this->config['friendly_url_suffix'];
2446
            $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...
2447
                global $modx;
1 ignored issue
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...
2448
                $thealias = $aliases[$m[1]];
2449
                $thefolder = $isfolder[$m[1]];
2450
                if ($isfriendly && isset($thealias)) {
2451
                    //found friendly url
2452
                    $out = ($modx->config['seostrict'] == '1' ? $modx->toAlias($modx->makeFriendlyURL($pref, $suff, $thealias, $thefolder, $m[1])) : $modx->makeFriendlyURL($pref, $suff, $thealias, $thefolder, $m[1]));
2453
                } else {
2454
                    //not found friendly url
2455
                    $out = $modx->makeFriendlyURL($pref, $suff, $m[1]);
2456
                }
2457
                return $out;
2458
            }, $documentSource);
2459
2460
        } else {
2461
            $in = '!\[\~([0-9]+)\~\]!is';
2462
            $out = "index.php?id=" . '\1';
2463
            $documentSource = preg_replace($in, $out, $documentSource);
2464
        }
2465
2466
        return $documentSource;
2467
    }
2468
2469
    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...
2470
    {
2471
        $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...
2472
        // FIX URLs
2473
        if (empty($this->documentIdentifier) || $this->config['seostrict'] == '0' || $this->config['friendly_urls'] == '0') {
2474
            return;
2475
        }
2476
        if ($this->config['site_status'] == 0) {
2477
            return;
2478
        }
2479
2480
        $scheme = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https' : 'http';
2481
        $len_base_url = strlen($this->config['base_url']);
2482
2483
        $url_path = $q;//LANG
2484
2485 View Code Duplication
        if (substr($url_path, 0, $len_base_url) === $this->config['base_url']) {
2486
            $url_path = substr($url_path, $len_base_url);
2487
        }
2488
2489
        $strictURL = $this->toAlias($this->makeUrl($this->documentIdentifier));
2490
2491 View Code Duplication
        if (substr($strictURL, 0, $len_base_url) === $this->config['base_url']) {
2492
            $strictURL = substr($strictURL, $len_base_url);
2493
        }
2494
        $http_host = $_SERVER['HTTP_HOST'];
2495
        $requestedURL = "{$scheme}://{$http_host}" . '/' . $q; //LANG
2496
2497
        $site_url = $this->config['site_url'];
2498
2499
        if ($this->documentIdentifier == $this->config['site_start']) {
2500
            if ($requestedURL != $this->config['site_url']) {
2501
                // Force redirect of site start
2502
                // $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...
2503
                $qstring = isset($url_query_string) ? preg_replace("#(^|&)(q|id)=[^&]+#", '', $url_query_string) : ''; // Strip conflicting id/q from query string
0 ignored issues
show
Bug introduced by
The variable $url_query_string seems to never exist, and therefore isset should always return false. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
2504
                if ($qstring) {
2505
                    $url = "{$site_url}?{$qstring}";
2506
                } else {
2507
                    $url = $site_url;
2508
                }
2509
                if ($this->config['base_url'] != $_SERVER['REQUEST_URI']) {
2510
                    if (empty($_POST)) {
2511
                        if (($this->config['base_url'] . '?' . $qstring) != $_SERVER['REQUEST_URI']) {
2512
                            $this->sendRedirect($url, 0, 'REDIRECT_HEADER', 'HTTP/1.0 301 Moved Permanently');
2513
                            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...
2514
                        }
2515
                    }
2516
                }
2517
            }
2518
        } elseif ($url_path != $strictURL && $this->documentIdentifier != $this->config['error_page']) {
2519
            // Force page redirect
2520
            //$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...
2521
2522
            if (!empty($url_query_string)) {
2523
                $qstring = preg_replace("#(^|&)(q|id)=[^&]+#", '', $url_query_string);
2524
            }  // Strip conflicting id/q from query string
2525
            if (!empty($qstring)) {
2526
                $url = "{$site_url}{$strictURL}?{$qstring}";
2527
            } else {
2528
                $url = "{$site_url}{$strictURL}";
2529
            }
2530
            $this->sendRedirect($url, 0, 'REDIRECT_HEADER', 'HTTP/1.0 301 Moved Permanently');
2531
            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...
2532
        }
2533
        return;
2534
    }
2535
2536
    /**
2537
     * Get all db fields and TVs for a document/resource
2538
     *
2539
     * @param string $method
2540
     * @param mixed $identifier
2541
     * @param bool $isPrepareResponse
2542
     * @return array
2543
     */
2544
    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...
2545
    {
2546
2547
        $cacheKey = md5(print_r(func_get_args(), true));
2548
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
2549
            return $this->tmpCache[__FUNCTION__][$cacheKey];
2550
        }
2551
2552
        $tblsc = $this->getFullTableName("site_content");
2553
        $tbldg = $this->getFullTableName("document_groups");
2554
2555
        // allow alias to be full path
2556
        if ($method == 'alias') {
2557
            $identifier = $this->cleanDocumentIdentifier($identifier);
2558
            $method = $this->documentMethod;
2559
        }
2560
        if ($method == 'alias' && $this->config['use_alias_path'] && array_key_exists($identifier, $this->documentListing)) {
2561
            $method = 'id';
2562
            $identifier = $this->documentListing[$identifier];
2563
        }
2564
2565
        $out = $this->invokeEvent('OnBeforeLoadDocumentObject', compact('method', 'identifier'));
2566
        if (is_array($out) && is_array($out[0])) {
2567
            $documentObject = $out[0];
2568
        } else {
2569
            // get document groups for current user
2570
            if ($docgrp = $this->getUserDocGroups()) {
2571
                $docgrp = implode(",", $docgrp);
2572
            }
2573
            // get document
2574
            $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
2575
            $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...
2576
                LEFT JOIN {$tbldg} dg ON dg.document = sc.id", "sc.{$method} = '{$identifier}' AND ({$access})", "", 1);
2577
            if ($this->db->getRecordCount($rs) < 1) {
2578
                $seclimit = 0;
2579
                if ($this->config['unauthorized_page']) {
2580
                    // method may still be alias, while identifier is not full path alias, e.g. id not found above
2581
                    if ($method === 'alias') {
2582
                        $secrs = $this->db->select('count(dg.id)', "{$tbldg} as dg, {$tblsc} as sc", "dg.document = sc.id AND sc.alias = '{$identifier}'", '', 1);
2583
                    } else {
2584
                        $secrs = $this->db->select('count(id)', $tbldg, "document = '{$identifier}'", '', 1);
2585
                    }
2586
                    // check if file is not public
2587
                    $seclimit = $this->db->getValue($secrs);
2588
                }
2589
                if ($seclimit > 0) {
2590
                    // match found but not publicly accessible, send the visitor to the unauthorized_page
2591
                    $this->sendUnauthorizedPage();
2592
                    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...
2593
                } else {
2594
                    $this->sendErrorPage();
2595
                    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...
2596
                }
2597
            }
2598
            # 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...
2599
            $documentObject = $this->db->getRow($rs);
2600
2601
            if ($isPrepareResponse === 'prepareResponse') {
2602
                $this->documentObject = &$documentObject;
2603
            }
2604
            $out = $this->invokeEvent('OnLoadDocumentObject', compact('method', 'identifier', 'documentObject'));
2605
            if (is_array($out) && is_array($out[0])) {
2606
                $documentObject = $out[0];
2607
            }
2608
            if ($documentObject['template']) {
2609
                // load TVs and merge with document - Orig by Apodigm - Docvars
2610
                $rs = $this->db->select("tv.*, IF(tvc.value!='',tvc.value,tv.default_text) as value", $this->getFullTableName("site_tmplvars") . " tv
2611
                INNER JOIN " . $this->getFullTableName("site_tmplvar_templates") . " tvtpl ON tvtpl.tmplvarid = tv.id
2612
                LEFT JOIN " . $this->getFullTableName("site_tmplvar_contentvalues") . " tvc ON tvc.tmplvarid=tv.id AND tvc.contentid = '{$documentObject['id']}'", "tvtpl.templateid = '{$documentObject['template']}'");
2613
                $tmplvars = array();
2614
                while ($row = $this->db->getRow($rs)) {
2615
                    $tmplvars[$row['name']] = array(
2616
                        $row['name'],
2617
                        $row['value'],
2618
                        $row['display'],
2619
                        $row['display_params'],
2620
                        $row['type']
2621
                    );
2622
                }
2623
                $documentObject = array_merge($documentObject, $tmplvars);
2624
            }
2625
            $out = $this->invokeEvent('OnAfterLoadDocumentObject', compact('method', 'identifier', 'documentObject'));
2626
            if (is_array($out) && array_key_exists(0, $out) !== false && is_array($out[0])) {
2627
                $documentObject = $out[0];
2628
            }
2629
        }
2630
2631
        $this->tmpCache[__FUNCTION__][$cacheKey] = $documentObject;
2632
2633
        return $documentObject;
2634
    }
2635
2636
    /**
2637
     * Parse a source string.
2638
     *
2639
     * Handles most MODX tags. Exceptions include:
2640
     *   - uncached snippet tags [!...!]
2641
     *   - URL tags [~...~]
2642
     *
2643
     * @param string $source
2644
     * @return string
2645
     */
2646
    public function parseDocumentSource($source)
2647
    {
2648
        // set the number of times we are to parse the document source
2649
        $this->minParserPasses = empty ($this->minParserPasses) ? 2 : $this->minParserPasses;
2650
        $this->maxParserPasses = empty ($this->maxParserPasses) ? 10 : $this->maxParserPasses;
2651
        $passes = $this->minParserPasses;
2652
        for ($i = 0; $i < $passes; $i++) {
2653
            // get source length if this is the final pass
2654
            if ($i == ($passes - 1)) {
2655
                $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...
2656
            }
2657
            if ($this->dumpSnippets == 1) {
2658
                $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>";
2659
            }
2660
2661
            // invoke OnParseDocument event
2662
            $this->documentOutput = $source; // store source code so plugins can
2663
            $this->invokeEvent("OnParseDocument"); // work on it via $modx->documentOutput
2664
            $source = $this->documentOutput;
2665
2666
            if ($this->config['enable_at_syntax']) {
2667
                $source = $this->ignoreCommentedTagsContent($source);
2668
                $source = $this->mergeConditionalTagsContent($source);
2669
            }
2670
2671
            $source = $this->mergeSettingsContent($source);
2672
            $source = $this->mergeDocumentContent($source);
2673
            $source = $this->mergeChunkContent($source);
2674
            $source = $this->evalSnippets($source);
2675
            $source = $this->mergePlaceholderContent($source);
2676
2677
            if ($this->dumpSnippets == 1) {
2678
                $this->snippetsCode .= "</fieldset><br />";
2679
            }
2680
            if ($i == ($passes - 1) && $i < ($this->maxParserPasses - 1)) {
2681
                // check if source content was changed
2682
                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...
2683
                    $passes++;
2684
                } // if content change then increase passes because
2685
            } // we have not yet reached maxParserPasses
2686
        }
2687
        return $source;
2688
    }
2689
2690
    /**
2691
     * Starts the parsing operations.
2692
     *
2693
     * - connects to the db
2694
     * - gets the settings (including system_settings)
2695
     * - gets the document/resource identifier as in the query string
2696
     * - finally calls prepareResponse()
2697
     */
2698
    public function executeParser()
2699
    {
2700
2701
        //error_reporting(0);
2702
        set_error_handler(array(
2703
            & $this,
2704
            "phpError"
2705
        ), E_ALL);
2706
2707
        $this->db->connect();
2708
2709
        // get the settings
2710
        if (empty ($this->config)) {
2711
            $this->getSettings();
2712
        }
2713
2714
        $this->_IIS_furl_fix(); // IIS friendly url fix
2715
2716
        // check site settings
2717
        if ($this->checkSiteStatus()) {
2718
            // make sure the cache doesn't need updating
2719
            $this->updatePubStatus();
2720
2721
            // find out which document we need to display
2722
            $this->documentMethod = filter_input(INPUT_GET, 'q') ? 'alias' : 'id';
2723
            $this->documentIdentifier = $this->getDocumentIdentifier($this->documentMethod);
2724
        } else {
2725
            header('HTTP/1.0 503 Service Unavailable');
2726
            $this->systemCacheKey = 'unavailable';
2727
            if (!$this->config['site_unavailable_page']) {
2728
                // display offline message
2729
                $this->documentContent = $this->config['site_unavailable_message'];
2730
                $this->outputContent();
2731
                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...
2732
            } else {
2733
                // setup offline page document settings
2734
                $this->documentMethod = 'id';
2735
                $this->documentIdentifier = $this->config['site_unavailable_page'];
2736
            }
2737
        }
2738
2739
        if ($this->documentMethod == "alias") {
2740
            $this->documentIdentifier = $this->cleanDocumentIdentifier($this->documentIdentifier);
2741
2742
            // Check use_alias_path and check if $this->virtualDir is set to anything, then parse the path
2743
            if ($this->config['use_alias_path'] == 1) {
2744
                $alias = (strlen($this->virtualDir) > 0 ? $this->virtualDir . '/' : '') . $this->documentIdentifier;
2745
                if (isset($this->documentListing[$alias])) {
2746
                    $this->documentIdentifier = $this->documentListing[$alias];
2747
                } else {
2748
                    //@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...
2749
                    if ($this->config['aliaslistingfolder'] == 1) {
2750
                        $tbl_site_content = $this->getFullTableName('site_content');
2751
2752
                        $parentId = $this->getIdFromAlias($this->virtualDir);
2753
                        $parentId = ($parentId > 0) ? $parentId : '0';
2754
2755
                        $docAlias = $this->db->escape($this->documentIdentifier);
2756
2757
                        $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...
2758
                        if ($this->db->getRecordCount($rs) == 0) {
2759
                            $this->sendErrorPage();
2760
                        }
2761
                        $docId = $this->db->getValue($rs);
2762
2763
                        if (!$docId) {
2764
                            $alias = $this->q;
2765
                            if (!empty($this->config['friendly_url_suffix'])) {
2766
                                $pos = strrpos($alias, $this->config['friendly_url_suffix']);
2767
2768
                                if ($pos !== false) {
2769
                                    $alias = substr($alias, 0, $pos);
2770
                                }
2771
                            }
2772
                            $docId = $this->getIdFromAlias($alias);
2773
                        }
2774
2775
                        if ($docId > 0) {
2776
                            $this->documentIdentifier = $docId;
2777
                        } else {
2778
                            /*
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...
2779
                            $rs  = $this->db->select('id', $tbl_site_content, "deleted=0 and alias='{$docAlias}'");
2780
                            if($this->db->getRecordCount($rs)==0)
2781
                            {
2782
                                $rs  = $this->db->select('id', $tbl_site_content, "deleted=0 and id='{$docAlias}'");
2783
                            }
2784
                            $docId = $this->db->getValue($rs);
2785
2786
                            if ($docId > 0)
2787
                            {
2788
                                $this->documentIdentifier = $docId;
2789
2790
                            }else{
2791
                            */
2792
                            $this->sendErrorPage();
2793
                            //}
2794
                        }
2795
                    } else {
2796
                        $this->sendErrorPage();
2797
                    }
2798
                }
2799
            } else {
2800
                if (isset($this->documentListing[$this->documentIdentifier])) {
2801
                    $this->documentIdentifier = $this->documentListing[$this->documentIdentifier];
2802
                } else {
2803
                    $docAlias = $this->db->escape($this->documentIdentifier);
2804
                    $rs = $this->db->select('id', $this->getFullTableName('site_content'), "deleted=0 and alias='{$docAlias}'");
2805
                    $this->documentIdentifier = (int)$this->db->getValue($rs);
2806
                }
2807
            }
2808
            $this->documentMethod = 'id';
2809
        }
2810
2811
        //$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...
2812
        // invoke OnWebPageInit event
2813
        $this->invokeEvent("OnWebPageInit");
2814
        if ($this->config['seostrict'] === '1') {
2815
            $this->sendStrictURI();
2816
        }
2817
        $this->prepareResponse();
2818
    }
2819
2820
    /**
2821
     * @param $path
2822
     * @param null $suffix
2823
     * @return mixed
2824
     */
2825
    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...
2826
    {
2827
        $exp = explode('/', $path);
2828
        return str_replace($suffix, '', end($exp));
2829
    }
2830
2831
    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...
2832
    {
2833
        if ($this->config['friendly_urls'] != 1) {
2834
            return;
2835
        }
2836
2837
        if (strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') === false) {
2838
            return;
2839
        }
2840
2841
        $url = $_SERVER['QUERY_STRING'];
2842
        $err = substr($url, 0, 3);
2843
        if ($err !== '404' && $err !== '405') {
2844
            return;
2845
        }
2846
2847
        $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...
2848
        unset ($_GET[$k[0]]);
2849
        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...
2850
        $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...
2851
        $_SERVER['QUERY_STRING'] = $qp['query'];
2852
        if (!empty ($qp['query'])) {
2853
            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...
2854
            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...
2855
                $_REQUEST[$n] = $_GET[$n] = $v;
2856
            }
2857
        }
2858
        $_SERVER['PHP_SELF'] = $this->config['base_url'] . $qp['path'];
2859
        $this->q = $qp['path'];
2860
        return $qp['path'];
2861
    }
2862
2863
    /**
2864
     * The next step called at the end of executeParser()
2865
     *
2866
     * - checks cache
2867
     * - checks if document/resource is deleted/unpublished
2868
     * - checks if resource is a weblink and redirects if so
2869
     * - gets template and parses it
2870
     * - ensures that postProcess is called when PHP is finished
2871
     */
2872
    public function prepareResponse()
2873
    {
2874
        // we now know the method and identifier, let's check the cache
2875
2876
        if ($this->config['enable_cache'] == 2 && $this->isLoggedIn()) {
2877
            $this->config['enable_cache'] = 0;
2878
        }
2879
2880
        if ($this->config['enable_cache']) {
2881
            $this->documentContent = $this->getDocumentObjectFromCache($this->documentIdentifier, true);
2882
        } else {
2883
            $this->documentContent = '';
2884
        }
2885
2886
        if ($this->documentContent == '') {
2887
            // get document object from DB
2888
            $this->documentObject = $this->getDocumentObject($this->documentMethod, $this->documentIdentifier, 'prepareResponse');
2889
2890
            // write the documentName to the object
2891
            $this->documentName = &$this->documentObject['pagetitle'];
2892
2893
            // check if we should not hit this document
2894
            if ($this->documentObject['donthit'] == 1) {
2895
                $this->config['track_visitors'] = 0;
2896
            }
2897
2898
            if ($this->documentObject['deleted'] == 1) {
2899
                $this->sendErrorPage();
2900
            } // validation routines
2901
            elseif ($this->documentObject['published'] == 0) {
2902
                $this->_sendErrorForUnpubPage();
2903
            } elseif ($this->documentObject['type'] == 'reference') {
2904
                $this->_sendRedirectForRefPage($this->documentObject['content']);
2905
            }
2906
2907
            // get the template and start parsing!
2908
            if (!$this->documentObject['template']) {
2909
                $templateCode = '[*content*]';
2910
            } // use blank template
2911
            else {
2912
                $templateCode = $this->_getTemplateCodeFromDB($this->documentObject['template']);
2913
            }
2914
2915
            if (substr($templateCode, 0, 8) === '@INCLUDE') {
2916
                $templateCode = $this->atBindInclude($templateCode);
2917
            }
2918
2919
2920
            $this->documentContent = &$templateCode;
2921
2922
            // invoke OnLoadWebDocument event
2923
            $this->invokeEvent('OnLoadWebDocument');
2924
2925
            // Parse document source
2926
            $this->documentContent = $this->parseDocumentSource($templateCode);
2927
2928
            $this->documentGenerated = 1;
2929
        } else {
2930
            $this->documentGenerated = 0;
2931
        }
2932
2933
        if ($this->config['error_page'] == $this->documentIdentifier && $this->config['error_page'] != $this->config['site_start']) {
2934
            header('HTTP/1.0 404 Not Found');
2935
        }
2936
2937
        register_shutdown_function(array(
2938
            &$this,
2939
            'postProcess'
2940
        )); // tell PHP to call postProcess when it shuts down
2941
        $this->outputContent();
2942
        //$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...
2943
    }
2944
2945
    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...
2946
    {
2947
        // Can't view unpublished pages !$this->checkPreview()
2948
        if (!$this->hasPermission('view_unpublished')) {
2949
            $this->sendErrorPage();
2950
        } else {
2951
            // Inculde the necessary files to check document permissions
2952
            include_once(MODX_MANAGER_PATH . 'processors/user_documents_permissions.class.php');
2953
            $udperms = new udperms();
2954
            $udperms->user = $this->getLoginUserID();
2955
            $udperms->document = $this->documentIdentifier;
2956
            $udperms->role = $_SESSION['mgrRole'];
2957
            // Doesn't have access to this document
2958
            if (!$udperms->checkPermissions()) {
2959
                $this->sendErrorPage();
2960
            }
2961
        }
2962
    }
2963
2964
    /**
2965
     * @param $url
2966
     */
2967
    public function _sendRedirectForRefPage($url)
2968
    {
2969
        // check whether it's a reference
2970
        if (preg_match('@^[1-9][0-9]*$@', $url)) {
2971
            $url = $this->makeUrl($url); // if it's a bare document id
2972
        } elseif (strpos($url, '[~') !== false) {
2973
            $url = $this->rewriteUrls($url); // if it's an internal docid tag, process it
2974
        }
2975
        $this->sendRedirect($url, 0, '', 'HTTP/1.0 301 Moved Permanently');
2976
        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...
2977
    }
2978
2979
    /**
2980
     * @param $templateID
2981
     * @return mixed
2982
     */
2983
    public function _getTemplateCodeFromDB($templateID)
2984
    {
2985
        $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...
2986
        if ($this->db->getRecordCount($rs) == 1) {
2987
            return $this->db->getValue($rs);
2988
        } else {
2989
            $this->messageQuit('Incorrect number of templates returned from database');
2990
        }
2991
    }
2992
2993
    /**
2994
     * Returns an array of all parent record IDs for the id passed.
2995
     *
2996
     * @param int $id Docid to get parents for.
2997
     * @param int $height The maximum number of levels to go up, default 10.
2998
     * @return array
2999
     */
3000
    public function getParentIds($id, $height = 10)
3001
    {
3002
        $parents = array();
3003
        while ($id && $height--) {
3004
            $thisid = $id;
3005
            if ($this->config['aliaslistingfolder'] == 1) {
3006
                $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");
3007
                if (!$id || $id == '0') {
3008
                    break;
3009
                }
3010
            } else {
3011
                $id = $this->aliasListing[$id]['parent'];
3012
                if (!$id) {
3013
                    break;
3014
                }
3015
            }
3016
            $parents[$thisid] = $id;
3017
        }
3018
        return $parents;
3019
    }
3020
3021
    /**
3022
     * @param $id
3023
     * @param int $top
3024
     * @return mixed
3025
     */
3026
    public function getUltimateParentId($id, $top = 0)
3027
    {
3028
        $i = 0;
3029
        while ($id && $i < 20) {
3030
            if ($top == $this->aliasListing[$id]['parent']) {
3031
                break;
3032
            }
3033
            $id = $this->aliasListing[$id]['parent'];
3034
            $i++;
3035
        }
3036
        return $id;
3037
    }
3038
3039
    /**
3040
     * Returns an array of child IDs belonging to the specified parent.
3041
     *
3042
     * @param int $id The parent resource/document to start from
3043
     * @param int $depth How many levels deep to search for children, default: 10
3044
     * @param array $children Optional array of docids to merge with the result.
3045
     * @return array Contains the document Listing (tree) like the sitemap
3046
     */
3047
    public function getChildIds($id, $depth = 10, $children = array())
3048
    {
3049
3050
        $cacheKey = md5(print_r(func_get_args(), true));
3051
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3052
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3053
        }
3054
3055
        if ($this->config['aliaslistingfolder'] == 1) {
3056
3057
            $res = $this->db->select("id,alias,isfolder,parent", $this->getFullTableName('site_content'), "parent IN (" . $id . ") AND deleted = '0'");
3058
            $idx = array();
3059
            while ($row = $this->db->getRow($res)) {
3060
                $pAlias = '';
3061
                if (isset($this->aliasListing[$row['parent']])) {
3062
                    $pAlias .= !empty($this->aliasListing[$row['parent']]['path']) ? $this->aliasListing[$row['parent']]['path'] . '/' : '';
3063
                    $pAlias .= !empty($this->aliasListing[$row['parent']]['alias']) ? $this->aliasListing[$row['parent']]['alias'] . '/' : '';
3064
                };
3065
                $children[$pAlias . $row['alias']] = $row['id'];
3066
                if ($row['isfolder'] == 1) {
3067
                    $idx[] = $row['id'];
3068
                }
3069
            }
3070
            $depth--;
3071
            $idx = implode(',', $idx);
3072
            if (!empty($idx)) {
3073
                if ($depth) {
3074
                    $children = $this->getChildIds($idx, $depth, $children);
3075
                }
3076
            }
3077
            $this->tmpCache[__FUNCTION__][$cacheKey] = $children;
3078
            return $children;
3079
3080
        } else {
3081
3082
            // Initialise a static array to index parents->children
3083
            static $documentMap_cache = array();
3084
            if (!count($documentMap_cache)) {
3085
                foreach ($this->documentMap as $document) {
3086
                    foreach ($document as $p => $c) {
3087
                        $documentMap_cache[$p][] = $c;
3088
                    }
3089
                }
3090
            }
3091
3092
            // Get all the children for this parent node
3093
            if (isset($documentMap_cache[$id])) {
3094
                $depth--;
3095
3096
                foreach ($documentMap_cache[$id] as $childId) {
3097
                    $pkey = (strlen($this->aliasListing[$childId]['path']) ? "{$this->aliasListing[$childId]['path']}/" : '') . $this->aliasListing[$childId]['alias'];
3098
                    if (!strlen($pkey)) {
3099
                        $pkey = "{$childId}";
3100
                    }
3101
                    $children[$pkey] = $childId;
3102
3103
                    if ($depth && isset($documentMap_cache[$childId])) {
3104
                        $children += $this->getChildIds($childId, $depth);
3105
                    }
3106
                }
3107
            }
3108
            $this->tmpCache[__FUNCTION__][$cacheKey] = $children;
3109
            return $children;
3110
3111
        }
3112
    }
3113
3114
    /**
3115
     * Displays a javascript alert message in the web browser and quit
3116
     *
3117
     * @param string $msg Message to show
3118
     * @param string $url URL to redirect to
3119
     */
3120
    public function webAlertAndQuit($msg, $url = "")
3121
    {
3122
        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...
3123
        if($this->ajaxMode === true) $this->jsonResponse(array('error'=>$msg));
3124
        if (substr(strtolower($url), 0, 11) == "javascript:") {
3125
            $fnc = substr($url, 11);
3126
        } elseif ($url) {
3127
            $fnc = "window.location.href='" . addslashes($url) . "';";
3128
        } else {
3129
            $fnc = "history.back(-1);";
3130
        }
3131
        echo "<html><head>
3132
            <title>EVO :: Alert</title>
3133
            <meta http-equiv=\"Content-Type\" content=\"text/html; charset={$modx_manager_charset};\">
3134
            <script>
3135
                function __alertQuit() {
3136
                    alert('" . addslashes($msg) . "');
3137
                    {$fnc}
3138
                }
3139
                window.setTimeout('__alertQuit();',100);
3140
            </script>
3141
            </head><body>
3142
            <p>{$msg}</p>
3143
            </body></html>";
3144
        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...
3145
    }
3146
3147
    /**
3148
     * Returns true if user has the currect permission
3149
     *
3150
     * @param string $pm Permission name
3151
     * @return int
3152
     */
3153
    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...
3154
    {
3155
        $state = false;
3156
        $pms = $_SESSION['mgrPermissions'];
3157
        if ($pms) {
3158
            $state = ($pms[$pm] == 1);
3159
        }
3160
        return (int)$state;
3161
    }
3162
3163
    /**
3164
     * Returns true if element is locked
3165
     *
3166
     * @param int $type Types: 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3167
     * @param int $id Element- / Resource-id
3168
     * @param bool $includeThisUser true = Return also info about actual user
3169
     * @return array lock-details or null
3170
     */
3171
    public function elementIsLocked($type, $id, $includeThisUser = false)
3172
    {
3173
        $id = intval($id);
3174
        $type = intval($type);
3175
        if (!$type || !$id) {
3176
            return null;
3177
        }
3178
3179
        // Build lockedElements-Cache at first call
3180
        $this->buildLockedElementsCache();
3181
3182
        if (!$includeThisUser && $this->lockedElements[$type][$id]['sid'] == $this->sid) {
3183
            return null;
3184
        }
3185
3186
        if (isset($this->lockedElements[$type][$id])) {
3187
            return $this->lockedElements[$type][$id];
3188
        } else {
3189
            return null;
3190
        }
3191
    }
3192
3193
    /**
3194
     * Returns Locked Elements as Array
3195
     *
3196
     * @param int $type Types: 0=all, 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3197
     * @param bool $minimumDetails true =
3198
     * @return array|mixed|null
3199
     */
3200
    public function getLockedElements($type = 0, $minimumDetails = false)
3201
    {
3202
        $this->buildLockedElementsCache();
3203
3204
        if (!$minimumDetails) {
3205
            $lockedElements = $this->lockedElements;
3206
        } else {
3207
            // Minimum details for HTML / Ajax-requests
3208
            $lockedElements = array();
3209
            foreach ($this->lockedElements as $elType => $elements) {
3210
                foreach ($elements as $elId => $el) {
3211
                    $lockedElements[$elType][$elId] = array(
3212
                        'username' => $el['username'],
3213
                        'lasthit_df' => $el['lasthit_df'],
3214
                        'state' => $this->determineLockState($el['internalKey'])
3215
                    );
3216
                }
3217
            }
3218
        }
3219
3220
        if ($type == 0) {
3221
            return $lockedElements;
3222
        }
3223
3224
        $type = intval($type);
3225
        if (isset($lockedElements[$type])) {
3226
            return $lockedElements[$type];
3227
        } else {
3228
            return array();
3229
        }
3230
    }
3231
3232
    /**
3233
     * Builds the Locked Elements Cache once
3234
     */
3235
    public function buildLockedElementsCache()
3236
    {
3237
        if (is_null($this->lockedElements)) {
3238
            $this->lockedElements = array();
3239
            $this->cleanupExpiredLocks();
3240
3241
            $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...
3242
                LEFT JOIN {$this->getFullTableName('manager_users')} mu on ul.internalKey = mu.id");
3243
            while ($row = $this->db->getRow($rs)) {
3244
                $this->lockedElements[$row['elementType']][$row['elementId']] = array(
3245
                    'sid' => $row['sid'],
3246
                    'internalKey' => $row['internalKey'],
3247
                    'username' => $row['username'],
3248
                    'elementType' => $row['elementType'],
3249
                    'elementId' => $row['elementId'],
3250
                    'lasthit' => $row['lasthit'],
3251
                    'lasthit_df' => $this->toDateFormat($row['lasthit']),
3252
                    'state' => $this->determineLockState($row['sid'])
3253
                );
3254
            }
3255
        }
3256
    }
3257
3258
    /**
3259
     * Cleans up the active user locks table
3260
     */
3261
    public function cleanupExpiredLocks()
3262
    {
3263
        // Clean-up active_user_sessions first
3264
        $timeout = intval($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
3265
        $validSessionTimeLimit = $this->time - $timeout;
3266
        $this->db->delete($this->getFullTableName('active_user_sessions'), "lasthit < {$validSessionTimeLimit}");
3267
3268
        // Clean-up active_user_locks
3269
        $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...
3270
        $count = $this->db->getRecordCount($rs);
3271
        if ($count) {
3272
            $rs = $this->db->makeArray($rs);
3273
            $userSids = array();
3274
            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...
3275
                $userSids[] = $row['sid'];
3276
            }
3277
            $userSids = "'" . implode("','", $userSids) . "'";
3278
            $this->db->delete($this->getFullTableName('active_user_locks'), "sid NOT IN({$userSids})");
3279
        } else {
3280
            $this->db->delete($this->getFullTableName('active_user_locks'));
3281
        }
3282
3283
    }
3284
3285
    /**
3286
     * Cleans up the active users table
3287
     */
3288
    public function cleanupMultipleActiveUsers()
3289
    {
3290
        $timeout = 20 * 60; // Delete multiple user-sessions after 20min
3291
        $validSessionTimeLimit = $this->time - $timeout;
3292
3293
        $activeUserSids = array();
3294
        $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...
3295
        $count = $this->db->getRecordCount($rs);
3296
        if ($count) {
3297
            $rs = $this->db->makeArray($rs);
3298
            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...
3299
                $activeUserSids[] = $row['sid'];
3300
            }
3301
        }
3302
3303
        $rs = $this->db->select("sid,internalKey,lasthit", "{$this->getFullTableName('active_users')}", "", "lasthit DESC");
3304
        if ($this->db->getRecordCount($rs)) {
3305
            $rs = $this->db->makeArray($rs);
3306
            $internalKeyCount = array();
3307
            $deleteSids = '';
3308
            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...
3309
                if (!isset($internalKeyCount[$row['internalKey']])) {
3310
                    $internalKeyCount[$row['internalKey']] = 0;
3311
                }
3312
                $internalKeyCount[$row['internalKey']]++;
3313
3314
                if ($internalKeyCount[$row['internalKey']] > 1 && !in_array($row['sid'], $activeUserSids) && $row['lasthit'] < $validSessionTimeLimit) {
3315
                    $deleteSids .= $deleteSids == '' ? '' : ' OR ';
3316
                    $deleteSids .= "sid='{$row['sid']}'";
3317
                };
3318
3319
            }
3320
            if ($deleteSids) {
3321
                $this->db->delete($this->getFullTableName('active_users'), $deleteSids);
3322
            }
3323
        }
3324
3325
    }
3326
3327
    /**
3328
     * Determines state of a locked element acc. to user-permissions
3329
     *
3330
     * @param $sid
3331
     * @return int $state States: 0=No display, 1=viewing this element, 2=locked, 3=show unlock-button
3332
     * @internal param int $internalKey : ID of User who locked actual element
3333
     */
3334
    public function determineLockState($sid)
3335
    {
3336
        $state = 0;
3337
        if ($this->hasPermission('display_locks')) {
3338
            if ($sid == $this->sid) {
3339
                $state = 1;
3340
            } else {
3341
                if ($this->hasPermission('remove_locks')) {
3342
                    $state = 3;
3343
                } else {
3344
                    $state = 2;
3345
                }
3346
            }
3347
        }
3348
        return $state;
3349
    }
3350
3351
    /**
3352
     * Locks an element
3353
     *
3354
     * @param int $type Types: 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3355
     * @param int $id Element- / Resource-id
3356
     * @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...
3357
     */
3358
    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...
3359
    {
3360
        $userId = $this->isBackend() && $_SESSION['mgrInternalKey'] ? $_SESSION['mgrInternalKey'] : 0;
3361
        $type = intval($type);
3362
        $id = intval($id);
3363
        if (!$type || !$id || !$userId) {
3364
            return false;
3365
        }
3366
3367
        $sql = sprintf('REPLACE INTO %s (internalKey, elementType, elementId, lasthit, sid)
3368
                VALUES (%d, %d, %d, %d, \'%s\')', $this->getFullTableName('active_user_locks'), $userId, $type, $id, $this->time, $this->sid);
3369
        $this->db->query($sql);
3370
    }
3371
3372
    /**
3373
     * Unlocks an element
3374
     *
3375
     * @param int $type Types: 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3376
     * @param int $id Element- / Resource-id
3377
     * @param bool $includeAllUsers true = Deletes not only own user-locks
3378
     * @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...
3379
     */
3380
    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...
3381
    {
3382
        $userId = $this->isBackend() && $_SESSION['mgrInternalKey'] ? $_SESSION['mgrInternalKey'] : 0;
3383
        $type = intval($type);
3384
        $id = intval($id);
3385
        if (!$type || !$id) {
3386
            return false;
3387
        }
3388
3389
        if (!$includeAllUsers) {
3390
            $sql = sprintf('DELETE FROM %s WHERE internalKey = %d AND elementType = %d AND elementId = %d;', $this->getFullTableName('active_user_locks'), $userId, $type, $id);
3391
        } else {
3392
            $sql = sprintf('DELETE FROM %s WHERE elementType = %d AND elementId = %d;', $this->getFullTableName('active_user_locks'), $type, $id);
3393
        }
3394
        $this->db->query($sql);
3395
    }
3396
3397
    /**
3398
     * Updates table "active_user_sessions" with userid, lasthit, IP
3399
     */
3400
    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...
3401
    {
3402
        if (!$this->sid) {
3403
            return;
3404
        }
3405
3406
        // web users are stored with negative keys
3407
        $userId = $this->getLoginUserType() == 'manager' ? $this->getLoginUserID() : -$this->getLoginUserID();
3408
3409
        // Get user IP
3410 View Code Duplication
        if ($cip = getenv("HTTP_CLIENT_IP")) {
3411
            $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...
3412
        } elseif ($cip = getenv("HTTP_X_FORWARDED_FOR")) {
3413
            $ip = $cip;
3414
        } elseif ($cip = getenv("REMOTE_ADDR")) {
3415
            $ip = $cip;
3416
        } else {
3417
            $ip = "UNKNOWN";
3418
        }
3419
        $_SESSION['ip'] = $ip;
3420
3421
        $sql = sprintf('REPLACE INTO %s (internalKey, lasthit, ip, sid)
3422
            VALUES (%d, %d, \'%s\', \'%s\')', $this->getFullTableName('active_user_sessions'), $userId, $this->time, $ip, $this->sid);
3423
        $this->db->query($sql);
3424
    }
3425
3426
    /**
3427
     * Add an a alert message to the system event log
3428
     *
3429
     * @param int $evtid Event ID
3430
     * @param int $type Types: 1 = information, 2 = warning, 3 = error
3431
     * @param string $msg Message to be logged
3432
     * @param string $source source of the event (module, snippet name, etc.)
3433
     *                       Default: Parser
3434
     */
3435
    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...
3436
    {
3437
        $msg = $this->db->escape($msg);
3438
        if (strpos($GLOBALS['database_connection_charset'], 'utf8') === 0 && extension_loaded('mbstring')) {
3439
            $esc_source = mb_substr($source, 0, 50, "UTF-8");
3440
        } else {
3441
            $esc_source = substr($source, 0, 50);
3442
        }
3443
        $esc_source = $this->db->escape($esc_source);
3444
3445
        $LoginUserID = $this->getLoginUserID();
3446
        if ($LoginUserID == '') {
3447
            $LoginUserID = 0;
3448
        }
3449
3450
        $usertype = $this->isFrontend() ? 1 : 0;
3451
        $evtid = intval($evtid);
3452
        $type = intval($type);
3453
3454
        // Types: 1 = information, 2 = warning, 3 = error
3455
        if ($type < 1) {
3456
            $type = 1;
3457
        } elseif ($type > 3) {
3458
            $type = 3;
3459
        }
3460
3461
        $this->db->insert(array(
3462
            'eventid' => $evtid,
3463
            'type' => $type,
3464
            'createdon' => $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'],
3465
            'source' => $esc_source,
3466
            'description' => $msg,
3467
            'user' => $LoginUserID,
3468
            'usertype' => $usertype
3469
        ), $this->getFullTableName("event_log"));
3470
3471
        if (isset($this->config['send_errormail']) && $this->config['send_errormail'] !== '0') {
3472
            if ($this->config['send_errormail'] <= $type) {
3473
                $this->sendmail(array(
3474
                    'subject' => 'MODX System Error on ' . $this->config['site_name'],
3475
                    'body' => 'Source: ' . $source . ' - The details of the error could be seen in the MODX system events log.',
3476
                    'type' => 'text'
3477
                ));
3478
            }
3479
        }
3480
    }
3481
3482
    /**
3483
     * @param array $params
3484
     * @param string $msg
3485
     * @param array $files
3486
     * @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...
3487
     */
3488
    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...
3489
    {
3490
        if (isset($params) && is_string($params)) {
3491
            if (strpos($params, '=') === false) {
3492
                if (strpos($params, '@') !== false) {
3493
                    $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...
3494
                } else {
3495
                    $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...
3496
                }
3497
            } else {
3498
                $params_array = explode(',', $params);
3499
                foreach ($params_array as $k => $v) {
3500
                    $k = trim($k);
3501
                    $v = trim($v);
3502
                    $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...
3503
                }
3504
            }
3505
        } else {
3506
            $p = $params;
3507
            unset($params);
3508
        }
3509
        if (isset($p['sendto'])) {
3510
            $p['to'] = $p['sendto'];
3511
        }
3512
3513
        if (isset($p['to']) && preg_match('@^[0-9]+$@', $p['to'])) {
3514
            $userinfo = $this->getUserInfo($p['to']);
3515
            $p['to'] = $userinfo['email'];
3516
        }
3517
        if (isset($p['from']) && preg_match('@^[0-9]+$@', $p['from'])) {
3518
            $userinfo = $this->getUserInfo($p['from']);
3519
            $p['from'] = $userinfo['email'];
3520
            $p['fromname'] = $userinfo['username'];
3521
        }
3522
        if ($msg === '' && !isset($p['body'])) {
3523
            $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...
3524
        } elseif (is_string($msg) && 0 < strlen($msg)) {
3525
            $p['body'] = $msg;
3526
        }
3527
3528
        $this->loadExtension('MODxMailer');
3529
        $sendto = (!isset($p['to'])) ? $this->config['emailsender'] : $p['to'];
3530
        $sendto = explode(',', $sendto);
3531
        foreach ($sendto as $address) {
3532
            list($name, $address) = $this->mail->address_split($address);
3533
            $this->mail->AddAddress($address, $name);
3534
        }
3535 View Code Duplication
        if (isset($p['cc'])) {
3536
            $p['cc'] = explode(',', $p['cc']);
3537
            foreach ($p['cc'] as $address) {
3538
                list($name, $address) = $this->mail->address_split($address);
3539
                $this->mail->AddCC($address, $name);
3540
            }
3541
        }
3542 View Code Duplication
        if (isset($p['bcc'])) {
3543
            $p['bcc'] = explode(',', $p['bcc']);
3544
            foreach ($p['bcc'] as $address) {
3545
                list($name, $address) = $this->mail->address_split($address);
3546
                $this->mail->AddBCC($address, $name);
3547
            }
3548
        }
3549
        if (isset($p['from']) && strpos($p['from'], '<') !== false && substr($p['from'], -1) === '>') {
3550
            list($p['fromname'], $p['from']) = $this->mail->address_split($p['from']);
3551
        }
3552
        $this->mail->From = (!isset($p['from'])) ? $this->config['emailsender'] : $p['from'];
3553
        $this->mail->FromName = (!isset($p['fromname'])) ? $this->config['site_name'] : $p['fromname'];
3554
        $this->mail->Subject = (!isset($p['subject'])) ? $this->config['emailsubject'] : $p['subject'];
3555
        $this->mail->Body = $p['body'];
3556
        if (isset($p['type']) && $p['type'] == 'text') {
3557
            $this->mail->IsHTML(false);
3558
        }
3559
        if (!is_array($files)) {
3560
            $files = array();
3561
        }
3562
        foreach ($files as $f) {
3563
            if (file_exists(MODX_BASE_PATH . $f) && is_file(MODX_BASE_PATH . $f) && is_readable(MODX_BASE_PATH . $f)) {
3564
                $this->mail->AddAttachment(MODX_BASE_PATH . $f);
3565
            }
3566
        }
3567
        $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...
3568
        return $rs;
3569
    }
3570
3571
    /**
3572
     * @param string $target
3573
     * @param int $limit
3574
     * @param int $trim
3575
     */
3576
    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...
3577
    {
3578
        if ($limit < $trim) {
3579
            $trim = $limit;
3580
        }
3581
3582
        $table_name = $this->getFullTableName($target);
3583
        $count = $this->db->getValue($this->db->select('COUNT(id)', $table_name));
3584
        $over = $count - $limit;
3585
        if (0 < $over) {
3586
            $trim = ($over + $trim);
3587
            $this->db->delete($table_name, '', '', $trim);
3588
        }
3589
        $this->db->optimize($table_name);
3590
    }
3591
3592
    /**
3593
     * Returns true if we are currently in the manager backend
3594
     *
3595
     * @return boolean
3596
     */
3597
    public function isBackend()
3598
    {
3599
        return (defined('IN_MANAGER_MODE') && IN_MANAGER_MODE === true);
3600
    }
3601
3602
    /**
3603
     * Returns true if we are currently in the frontend
3604
     *
3605
     * @return boolean
3606
     */
3607
    public function isFrontend()
3608
    {
3609
        return ! $this->isBackend();
3610
    }
3611
3612
    /**
3613
     * Gets all child documents of the specified document, including those which are unpublished or deleted.
3614
     *
3615
     * @param int $id The Document identifier to start with
3616
     * @param string $sort Sort field
3617
     *                     Default: menuindex
3618
     * @param string $dir Sort direction, ASC and DESC is possible
3619
     *                    Default: ASC
3620
     * @param string $fields Default: id, pagetitle, description, parent, alias, menutitle
3621
     * @return array
3622
     */
3623 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...
3624
    {
3625
3626
        $cacheKey = md5(print_r(func_get_args(), true));
3627
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3628
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3629
        }
3630
3631
        $tblsc = $this->getFullTableName("site_content");
3632
        $tbldg = $this->getFullTableName("document_groups");
3633
        // modify field names to use sc. table reference
3634
        $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3635
        $sort = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
3636
        // get document groups for current user
3637
        if ($docgrp = $this->getUserDocGroups()) {
3638
            $docgrp = implode(",", $docgrp);
3639
        }
3640
        // build query
3641
        $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
3642
        $result = $this->db->select("DISTINCT {$fields}", "{$tblsc} sc
3643
                LEFT JOIN {$tbldg} dg on dg.document = sc.id", "sc.parent = '{$id}' AND ({$access}) GROUP BY sc.id", "{$sort} {$dir}");
3644
        $resourceArray = $this->db->makeArray($result);
3645
        $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
3646
        return $resourceArray;
3647
    }
3648
3649
    /**
3650
     * Gets all active child documents of the specified document, i.e. those which published and not deleted.
3651
     *
3652
     * @param int $id The Document identifier to start with
3653
     * @param string $sort Sort field
3654
     *                     Default: menuindex
3655
     * @param string $dir Sort direction, ASC and DESC is possible
3656
     *                    Default: ASC
3657
     * @param string $fields Default: id, pagetitle, description, parent, alias, menutitle
3658
     * @return array
3659
     */
3660 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...
3661
    {
3662
        $cacheKey = md5(print_r(func_get_args(), true));
3663
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3664
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3665
        }
3666
3667
        $tblsc = $this->getFullTableName("site_content");
3668
        $tbldg = $this->getFullTableName("document_groups");
3669
3670
        // modify field names to use sc. table reference
3671
        $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3672
        $sort = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
3673
        // get document groups for current user
3674
        if ($docgrp = $this->getUserDocGroups()) {
3675
            $docgrp = implode(",", $docgrp);
3676
        }
3677
        // build query
3678
        $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
3679
        $result = $this->db->select("DISTINCT {$fields}", "{$tblsc} sc
3680
                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}");
3681
        $resourceArray = $this->db->makeArray($result);
3682
3683
        $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
3684
3685
        return $resourceArray;
3686
    }
3687
3688
    /**
3689
     * getDocumentChildren
3690
     * @version 1.1.1 (2014-02-19)
3691
     *
3692
     * @desc Returns the children of the selected document/folder as an associative array.
3693
     *
3694
     * @param $parentid {integer} - The parent document identifier. Default: 0 (site root).
3695
     * @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.
3696
     * @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.
3697
     * @param $fields {comma separated string; '*'} - Comma separated list of document fields to get. Default: '*' (all fields).
3698
     * @param $where {string} - Where condition in SQL style. Should include a leading 'AND '. Default: ''.
3699
     * @param $sort {comma separated string} - Should be a comma-separated list of field names on which to sort. Default: 'menuindex'.
3700
     * @param $dir {'ASC'; 'DESC'} - Sort direction, ASC and DESC is possible. Default: 'ASC'.
3701
     * @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).
3702
     *
3703
     * @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...
3704
     */
3705
    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...
3706
    {
3707
3708
        $cacheKey = md5(print_r(func_get_args(), true));
3709
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3710
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3711
        }
3712
3713
        $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...
3714
        $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...
3715
3716
        if ($where != '') {
3717
            $where = 'AND ' . $where;
3718
        }
3719
3720
        // modify field names to use sc. table reference
3721
        $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3722
        $sort = ($sort == '') ? '' : 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
3723
3724
        // get document groups for current user
3725
        if ($docgrp = $this->getUserDocGroups()) {
3726
            $docgrp = implode(',', $docgrp);
3727
        }
3728
3729
        // build query
3730
        $access = ($this->isFrontend() ? 'sc.privateweb=0' : '1="' . $_SESSION['mgrRole'] . '" OR sc.privatemgr=0') . (!$docgrp ? '' : ' OR dg.document_group IN (' . $docgrp . ')');
3731
3732
        $tblsc = $this->getFullTableName('site_content');
3733
        $tbldg = $this->getFullTableName('document_groups');
3734
3735
        $result = $this->db->select("DISTINCT {$fields}", "{$tblsc} sc
3736
                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);
3737
3738
        $resourceArray = $this->db->makeArray($result);
3739
3740
        $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
3741
3742
        return $resourceArray;
3743
    }
3744
3745
    /**
3746
     * getDocuments
3747
     * @version 1.1.1 (2013-02-19)
3748
     *
3749
     * @desc Returns required documents (their fields).
3750
     *
3751
     * @param $ids {array; comma separated string} - Documents Ids to get. @required
3752
     * @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.
3753
     * @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.
3754
     * @param $fields {comma separated string; '*'} - Documents fields to get. Default: '*'.
3755
     * @param $where {string} - SQL WHERE clause. Default: ''.
3756
     * @param $sort {comma separated string} - A comma-separated list of field names to sort by. Default: 'menuindex'.
3757
     * @param $dir {'ASC'; 'DESC'} - Sorting direction. Default: 'ASC'.
3758
     * @param $limit {string} - SQL LIMIT (without 'LIMIT '). An empty string means no limit. Default: ''.
3759
     *
3760
     * @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...
3761
     */
3762
    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...
3763
    {
3764
3765
        $cacheKey = md5(print_r(func_get_args(), true));
3766
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3767
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3768
        }
3769
3770
        if (is_string($ids)) {
3771
            if (strpos($ids, ',') !== false) {
3772
                $ids = array_filter(array_map('intval', explode(',', $ids)));
3773
            } else {
3774
                $ids = array($ids);
3775
            }
3776
        }
3777
        if (count($ids) == 0) {
3778
            $this->tmpCache[__FUNCTION__][$cacheKey] = false;
3779
            return false;
3780
        } else {
3781
            // modify field names to use sc. table reference
3782
            $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3783
            $sort = ($sort == '') ? '' : 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
3784
            if ($where != '') {
3785
                $where = 'AND ' . $where;
3786
            }
3787
3788
            $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...
3789
            $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...
3790
3791
            // get document groups for current user
3792
            if ($docgrp = $this->getUserDocGroups()) {
3793
                $docgrp = implode(',', $docgrp);
3794
            }
3795
3796
            $access = ($this->isFrontend() ? 'sc.privateweb=0' : '1="' . $_SESSION['mgrRole'] . '" OR sc.privatemgr=0') . (!$docgrp ? '' : ' OR dg.document_group IN (' . $docgrp . ')');
3797
3798
            $tblsc = $this->getFullTableName('site_content');
3799
            $tbldg = $this->getFullTableName('document_groups');
3800
3801
            $result = $this->db->select("DISTINCT {$fields}", "{$tblsc} sc
3802
                    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);
3803
3804
            $resourceArray = $this->db->makeArray($result);
3805
3806
            $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
3807
3808
            return $resourceArray;
3809
        }
3810
    }
3811
3812
    /**
3813
     * getDocument
3814
     * @version 1.0.1 (2014-02-19)
3815
     *
3816
     * @desc Returns required fields of a document.
3817
     *
3818
     * @param int $id {integer}
3819
     * - Id of a document which data has to be gained. @required
3820
     * @param string $fields {comma separated string; '*'}
3821
     * - Comma separated list of document fields to get. Default: '*'.
3822
     * @param int $published {0; 1; 'all'}
3823
     * - 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.
3824
     * @param int $deleted {0; 1; 'all'}
3825
     * - 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.
3826
     * @return bool {array; false} - Result array with fields or false.
3827
     * - Result array with fields or false.
3828
     */
3829 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...
3830
    {
3831
        if ($id == 0) {
3832
            return false;
3833
        } else {
3834
            $docs = $this->getDocuments(array($id), $published, $deleted, $fields, '', '', '', 1);
3835
3836
            if ($docs != false) {
3837
                return $docs[0];
3838
            } else {
3839
                return false;
3840
            }
3841
        }
3842
    }
3843
3844
    /**
3845
     * @param string $field
3846
     * @param string $docid
3847
     * @return bool|mixed
3848
     */
3849
    public function getField($field = 'content', $docid = '')
3850
    {
3851
        if (empty($docid) && isset($this->documentIdentifier)) {
3852
            $docid = $this->documentIdentifier;
3853
        } elseif (!preg_match('@^[0-9]+$@', $docid)) {
3854
            $docid = $this->getIdFromAlias($docid);
3855
        }
3856
3857
        if (empty($docid)) {
3858
            return false;
3859
        }
3860
3861
        $cacheKey = md5(print_r(func_get_args(), true));
3862
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3863
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3864
        }
3865
3866
        $doc = $this->getDocumentObject('id', $docid);
3867
        if (is_array($doc[$field])) {
3868
            $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...
3869
            $content = $tvs[$field];
3870
        } else {
3871
            $content = $doc[$field];
3872
        }
3873
3874
        $this->tmpCache[__FUNCTION__][$cacheKey] = $content;
3875
3876
        return $content;
3877
    }
3878
3879
    /**
3880
     * Returns the page information as database row, the type of result is
3881
     * defined with the parameter $rowMode
3882
     *
3883
     * @param int $pageid The parent document identifier
3884
     *                    Default: -1 (no result)
3885
     * @param int $active Should we fetch only published and undeleted documents/resources?
3886
     *                     1 = yes, 0 = no
3887
     *                     Default: 1
3888
     * @param string $fields List of fields
3889
     *                       Default: id, pagetitle, description, alias
3890
     * @return boolean|array
3891
     */
3892
    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...
3893
    {
3894
3895
        $cacheKey = md5(print_r(func_get_args(), true));
3896
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3897
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3898
        }
3899
3900
        if ($pageid == 0) {
3901
            return false;
3902
        } else {
3903
            $tblsc = $this->getFullTableName("site_content");
3904
            $tbldg = $this->getFullTableName("document_groups");
3905
            $activeSql = $active == 1 ? "AND sc.published=1 AND sc.deleted=0" : "";
3906
            // modify field names to use sc. table reference
3907
            $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3908
            // get document groups for current user
3909
            if ($docgrp = $this->getUserDocGroups()) {
3910
                $docgrp = implode(",", $docgrp);
3911
            }
3912
            $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
3913
            $result = $this->db->select($fields, "{$tblsc} sc LEFT JOIN {$tbldg} dg on dg.document = sc.id", "(sc.id='{$pageid}' {$activeSql}) AND ({$access})", "", 1);
3914
            $pageInfo = $this->db->getRow($result);
3915
3916
            $this->tmpCache[__FUNCTION__][$cacheKey] = $pageInfo;
3917
3918
            return $pageInfo;
3919
        }
3920
    }
3921
3922
    /**
3923
     * Returns the parent document/resource of the given docid
3924
     *
3925
     * @param int $pid The parent docid. If -1, then fetch the current document/resource's parent
3926
     *                 Default: -1
3927
     * @param int $active Should we fetch only published and undeleted documents/resources?
3928
     *                     1 = yes, 0 = no
3929
     *                     Default: 1
3930
     * @param string $fields List of fields
3931
     *                       Default: id, pagetitle, description, alias
3932
     * @return boolean|array
3933
     */
3934
    public function getParent($pid = -1, $active = 1, $fields = 'id, pagetitle, description, alias, parent')
3935
    {
3936
        if ($pid == -1) {
3937
            $pid = $this->documentObject['parent'];
3938
            return ($pid == 0) ? false : $this->getPageInfo($pid, $active, $fields);
3939
        } else if ($pid == 0) {
3940
            return false;
3941
        } else {
3942
            // first get the child document
3943
            $child = $this->getPageInfo($pid, $active, "parent");
3944
            // now return the child's parent
3945
            $pid = ($child['parent']) ? $child['parent'] : 0;
3946
            return ($pid == 0) ? false : $this->getPageInfo($pid, $active, $fields);
3947
        }
3948
    }
3949
3950
    /**
3951
     * Returns the id of the current snippet.
3952
     *
3953
     * @return int
3954
     */
3955
    public function getSnippetId()
3956
    {
3957
        if ($this->currentSnippet) {
3958
            $tbl = $this->getFullTableName("site_snippets");
3959
            $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...
3960
            if ($snippetId = $this->db->getValue($rs)) {
3961
                return $snippetId;
3962
            }
3963
        }
3964
        return 0;
3965
    }
3966
3967
    /**
3968
     * Returns the name of the current snippet.
3969
     *
3970
     * @return string
3971
     */
3972
    public function getSnippetName()
3973
    {
3974
        return $this->currentSnippet;
3975
    }
3976
3977
    /**
3978
     * Clear the cache of MODX.
3979
     *
3980
     * @param string $type
3981
     * @param bool $report
3982
     * @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...
3983
     */
3984
    public function clearCache($type = '', $report = false)
3985
    {
3986
        $cache_dir = MODX_BASE_PATH . $this->getCacheFolder();
3987
        if (is_array($type)) {
3988
            foreach ($type as $_) {
3989
                $this->clearCache($_, $report);
3990
            }
3991
        } elseif ($type == 'full') {
3992
            include_once(MODX_MANAGER_PATH . 'processors/cache_sync.class.processor.php');
3993
            $sync = new synccache();
3994
            $sync->setCachepath($cache_dir);
3995
            $sync->setReport($report);
3996
            $sync->emptyCache();
3997
        } elseif (preg_match('@^[1-9][0-9]*$@', $type)) {
3998
            $key = ($this->config['cache_type'] == 2) ? $this->makePageCacheKey($type) : $type;
3999
            $file_name = "docid_" . $key . "_*.pageCache.php";
4000
            $cache_path = $cache_dir . $file_name;
4001
            $files = glob($cache_path);
4002
            $files[] = $cache_dir . "docid_" . $key . ".pageCache.php";
4003
            foreach ($files as $file) {
4004
                if (!is_file($file)) {
4005
                    continue;
4006
                }
4007
                unlink($file);
4008
            }
4009
        } else {
4010
            $files = glob($cache_dir . '*');
4011
            foreach ($files as $file) {
4012
                $name = basename($file);
4013
                if (strpos($name, '.pageCache.php') === false) {
4014
                    continue;
4015
                }
4016
                if (!is_file($file)) {
4017
                    continue;
4018
                }
4019
                unlink($file);
4020
            }
4021
        }
4022
    }
4023
4024
    /**
4025
     * makeUrl
4026
     *
4027
     * @desc Create an URL for the given document identifier. The url prefix and postfix are used, when “friendly_url” is active.
4028
     *
4029
     * @param $id {integer} - The document identifier. @required
4030
     * @param string $alias {string}
4031
     * - The alias name for the document. Default: ''.
4032
     * @param string $args {string}
4033
     * - The paramaters to add to the URL. Default: ''.
4034
     * @param string $scheme {string}
4035
     * - With full as valus, the site url configuration is used. Default: ''.
4036
     * @return mixed|string {string} - Result URL.
4037
     * - Result URL.
4038
     */
4039
    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...
4040
    {
4041
        $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...
4042
        $virtualDir = isset($this->config['virtual_dir']) ? $this->config['virtual_dir'] : '';
4043
        $f_url_prefix = $this->config['friendly_url_prefix'];
4044
        $f_url_suffix = $this->config['friendly_url_suffix'];
4045
4046
        if (!is_numeric($id)) {
4047
            $this->messageQuit("`{$id}` is not numeric and may not be passed to makeUrl()");
4048
        }
4049
4050
        if ($args !== '') {
4051
            // add ? or & to $args if missing
4052
            $args = ltrim($args, '?&');
4053
            $_ = 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...
4054
4055
            if ($_ === false && $this->config['friendly_urls'] == 1) {
4056
                $args = "?{$args}";
4057
            } else {
4058
                $args = "&{$args}";
4059
            }
4060
        }
4061
4062
        if ($id != $this->config['site_start']) {
4063
            if ($this->config['friendly_urls'] == 1 && $alias == '') {
4064
                $alias = $id;
4065
                $alPath = '';
4066
4067
                if ($this->config['friendly_alias_urls'] == 1) {
4068
4069
                    if ($this->config['aliaslistingfolder'] == 1) {
4070
                        $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...
4071
                    } else {
4072
                        $al = $this->aliasListing[$id];
4073
                    }
4074
4075
                    if ($al['isfolder'] === 1 && $this->config['make_folders'] === '1') {
4076
                        $f_url_suffix = '/';
4077
                    }
4078
4079
                    $alPath = !empty ($al['path']) ? $al['path'] . '/' : '';
4080
4081
                    if ($al && $al['alias']) {
4082
                        $alias = $al['alias'];
4083
                    }
4084
4085
                }
4086
4087
                $alias = $alPath . $f_url_prefix . $alias . $f_url_suffix;
4088
                $url = "{$alias}{$args}";
4089
            } else {
4090
                $url = "index.php?id={$id}{$args}";
4091
            }
4092
        } else {
4093
            $url = $args;
4094
        }
4095
4096
        $host = $this->config['base_url'];
4097
4098
        // check if scheme argument has been set
4099
        if ($scheme != '') {
4100
            // for backward compatibility - check if the desired scheme is different than the current scheme
4101
            if (is_numeric($scheme) && $scheme != $_SERVER['HTTPS']) {
4102
                $scheme = ($_SERVER['HTTPS'] ? 'http' : 'https');
4103
            }
4104
4105
            //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...
4106
            $host = $scheme == 'full' ? $this->config['site_url'] : $scheme . '://' . $_SERVER['HTTP_HOST'] . $host;
4107
        }
4108
4109
        //fix strictUrl by Bumkaka
4110
        if ($this->config['seostrict'] == '1') {
4111
            $url = $this->toAlias($url);
4112
        }
4113
4114
        if ($this->config['xhtml_urls']) {
4115
            $url = preg_replace("/&(?!amp;)/", "&amp;", $host . $virtualDir . $url);
4116
        } else {
4117
            $url = $host . $virtualDir . $url;
4118
        }
4119
4120
        $evtOut = $this->invokeEvent('OnMakeDocUrl', array(
4121
            'id' => $id,
4122
            'url' => $url
4123
        ));
4124
4125
        if (is_array($evtOut) && count($evtOut) > 0) {
4126
            $url = array_pop($evtOut);
4127
        }
4128
4129
        return $url;
4130
    }
4131
4132
    /**
4133
     * @param $id
4134
     * @return mixed
4135
     */
4136
    public function getAliasListing($id)
4137
    {
4138
        if (isset($this->aliasListing[$id])) {
4139
            $out = $this->aliasListing[$id];
4140
        } else {
4141
            $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...
4142
            if ($this->db->getRecordCount($q) == '1') {
4143
                $q = $this->db->getRow($q);
4144
                $this->aliasListing[$id] = array(
4145
                    'id' => (int)$q['id'],
4146
                    'alias' => $q['alias'] == '' ? $q['id'] : $q['alias'],
4147
                    'parent' => (int)$q['parent'],
4148
                    'isfolder' => (int)$q['isfolder'],
4149
                );
4150
                if ($this->aliasListing[$id]['parent'] > 0) {
4151
                    //fix alias_path_usage
4152
                    if ($this->config['use_alias_path'] == '1') {
4153
                        //&& $tmp['path'] != '' - fix error slash with epty path
4154
                        $tmp = $this->getAliasListing($this->aliasListing[$id]['parent']);
4155
                        $this->aliasListing[$id]['path'] = $tmp['path'] . ($tmp['alias_visible'] ? (($tmp['parent'] > 0 && $tmp['path'] != '') ? '/' : '') . $tmp['alias'] : '');
4156
                    } else {
4157
                        $this->aliasListing[$id]['path'] = '';
4158
                    }
4159
                }
4160
4161
                $out = $this->aliasListing[$id];
4162
            }
4163
        }
4164
        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...
4165
    }
4166
4167
    /**
4168
     * Returns an entry from the config
4169
     *
4170
     * Note: most code accesses the config array directly and we will continue to support this.
4171
     *
4172
     * @param string $name
4173
     * @return bool|string
4174
     */
4175
    public function getConfig($name = '')
4176
    {
4177
        if (!empty ($this->config[$name])) {
4178
            return $this->config[$name];
4179
        } else {
4180
            return false;
4181
        }
4182
    }
4183
4184
    /**
4185
     * Returns the MODX version information as version, branch, release date and full application name.
4186
     *
4187
     * @param null $data
4188
     * @return array
4189
     */
4190
4191
    public function getVersionData($data = null)
4192
    {
4193
        $out = array();
4194
        if (empty($this->version) || !is_array($this->version)) {
4195
            //include for compatibility modx version < 1.0.10
4196
            include MODX_MANAGER_PATH . "includes/version.inc.php";
4197
            $this->version = array();
4198
            $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...
4199
            $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...
4200
            $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...
4201
            $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...
4202
            $this->version['new_version'] = isset($this->config['newversiontext']) ? $this->config['newversiontext'] : '';
4203
        }
4204
        return (!is_null($data) && is_array($this->version) && isset($this->version[$data])) ? $this->version[$data] : $this->version;
4205
    }
4206
4207
    /**
4208
     * Executes a snippet.
4209
     *
4210
     * @param string $snippetName
4211
     * @param array $params Default: Empty array
4212
     * @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...
4213
     */
4214
    public function runSnippet($snippetName, $params = array())
4215
    {
4216
        if (isset ($this->snippetCache[$snippetName])) {
4217
            $snippet = $this->snippetCache[$snippetName];
4218
            $properties = !empty($this->snippetCache[$snippetName . "Props"]) ? $this->snippetCache[$snippetName . "Props"] : '';
4219
        } else { // not in cache so let's check the db
4220
            $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;";
4221
            $result = $this->db->query($sql);
4222
            if ($this->db->getRecordCount($result) == 1) {
4223
                $row = $this->db->getRow($result);
4224
                $snippet = $this->snippetCache[$snippetName] = $row['snippet'];
4225
                $mergedProperties = array_merge($this->parseProperties($row['properties']), $this->parseProperties($row['sharedproperties']));
4226
                $properties = $this->snippetCache[$snippetName . "Props"] = json_encode($mergedProperties);
4227
            } else {
4228
                $snippet = $this->snippetCache[$snippetName] = "return false;";
4229
                $properties = $this->snippetCache[$snippetName . "Props"] = '';
4230
            }
4231
        }
4232
        // load default params/properties
4233
        $parameters = $this->parseProperties($properties, $snippetName, 'snippet');
4234
        $parameters = array_merge($parameters, $params);
4235
4236
        // run snippet
4237
        return $this->evalSnippet($snippet, $parameters);
4238
    }
4239
4240
    /**
4241
     * Returns the chunk content for the given chunk name
4242
     *
4243
     * @param string $chunkName
4244
     * @return boolean|string
4245
     */
4246
    public function getChunk($chunkName)
4247
    {
4248
        $out = null;
4249
        if (empty($chunkName)) {
4250
            return $out;
4251
        }
4252
        if (isset ($this->chunkCache[$chunkName])) {
4253
            $out = $this->chunkCache[$chunkName];
4254
        } else if (stripos($chunkName, '@FILE') === 0) {
4255
            $out = $this->chunkCache[$chunkName] = $this->atBindFileContent($chunkName);
4256
        } else {
4257
            $where = sprintf("`name`='%s' AND disabled=0", $this->db->escape($chunkName));
4258
            $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...
4259
            if ($this->db->getRecordCount($rs) == 1) {
4260
                $row = $this->db->getRow($rs);
4261
                $out = $this->chunkCache[$chunkName] = $row['snippet'];
4262
            } else {
4263
                $out = $this->chunkCache[$chunkName] = null;
4264
            }
4265
        }
4266
        return $out;
4267
    }
4268
4269
    /**
4270
     * parseText
4271
     * @version 1.0 (2013-10-17)
4272
     *
4273
     * @desc Replaces placeholders in text with required values.
4274
     *
4275
     * @param string $tpl
4276
     * @param array $ph
4277
     * @param string $left
4278
     * @param string $right
4279
     * @param bool $execModifier
4280
     * @return mixed|string {string} - Parsed text.
4281
     * - Parsed text.
4282
     * @internal param $chunk {string} - String to parse. - String to parse. @required
4283
     * @internal param $chunkArr {array} - Array of values. Key — placeholder name, value — value. - Array of values. Key — placeholder name, value — value. @required
4284
     * @internal param $prefix {string} - Placeholders prefix. Default: '[+'. - Placeholders prefix. Default: '[+'.
4285
     * @internal param $suffix {string} - Placeholders suffix. Default: '+]'. - Placeholders suffix. Default: '+]'.
4286
     *
4287
     */
4288
    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...
4289
    {
4290
        if (!$ph) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $ph 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...
4291
            return $tpl;
4292
        }
4293
        if (!$tpl) {
4294
            return $tpl;
4295
        }
4296
4297 View Code Duplication
        if ($this->config['enable_at_syntax']) {
4298
            if (stripos($tpl, '<@LITERAL>') !== false) {
4299
                $tpl = $this->escapeLiteralTagsContent($tpl);
4300
            }
4301
        }
4302
4303
        $matches = $this->getTagsFromContent($tpl, $left, $right);
4304
        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...
4305
            return $tpl;
4306
        }
4307
4308
        foreach ($matches[1] as $i => $key) {
4309
4310
            if (strpos($key, ':') !== false && $execModifier) {
4311
                list($key, $modifiers) = $this->splitKeyAndFilter($key);
4312
            } else {
4313
                $modifiers = false;
4314
            }
4315
4316
            //          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...
4317
            if (!array_key_exists($key, $ph)) {
4318
                continue;
4319
            } //NULL values must be saved in placeholders, if we got them from database string
4320
4321
            $value = $ph[$key];
4322
4323
            $s = &$matches[0][$i];
4324
            if ($modifiers !== false) {
4325
                if (strpos($modifiers, $left) !== false) {
4326
                    $modifiers = $this->parseText($modifiers, $ph, $left, $right);
4327
                }
4328
                $value = $this->applyFilter($value, $modifiers, $key);
4329
            }
4330 View Code Duplication
            if (strpos($tpl, $s) !== false) {
4331
                $tpl = str_replace($s, $value, $tpl);
4332
            } elseif($this->debug) {
4333
                $this->addLog('parseText parse error', $_SERVER['REQUEST_URI'] . $s, 2);
4334
            }
4335
        }
4336
4337
        return $tpl;
4338
    }
4339
4340
    /**
4341
     * parseChunk
4342
     * @version 1.1 (2013-10-17)
4343
     *
4344
     * @desc Replaces placeholders in a chunk with required values.
4345
     *
4346
     * @param $chunkName {string} - Name of chunk to parse. @required
4347
     * @param $chunkArr {array} - Array of values. Key — placeholder name, value — value. @required
4348
     * @param string $prefix {string}
4349
     * - Placeholders prefix. Default: '{'.
4350
     * @param string $suffix {string}
4351
     * - Placeholders suffix. Default: '}'.
4352
     * @return bool|mixed|string {string; false} - Parsed chunk or false if $chunkArr is not array.
4353
     * - Parsed chunk or false if $chunkArr is not array.
4354
     */
4355
    public function parseChunk($chunkName, $chunkArr, $prefix = '{', $suffix = '}')
4356
    {
4357
        //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...
4358
        if (!is_array($chunkArr)) {
4359
            return false;
4360
        }
4361
4362
        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...
4363
    }
4364
4365
    /**
4366
     * getTpl
4367
     * get template for snippets
4368
     * @param $tpl {string}
4369
     * @return bool|string {string}
4370
     */
4371
    public function getTpl($tpl)
4372
    {
4373
        $template = $tpl;
4374
        if (preg_match("~^@([^:\s]+)[:\s]+(.+)$~", $tpl, $match)) {
4375
            $command = strtoupper($match[1]);
4376
            $template = $match[2];
4377
        }
4378
        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...
4379
            case 'CODE':
4380
                break;
4381
            case 'FILE':
4382
                $template = file_get_contents(MODX_BASE_PATH . $template);
4383
                break;
4384
            case 'CHUNK':
4385
                $template = $this->getChunk($template);
4386
                break;
4387
            case 'DOCUMENT':
4388
                $doc = $this->getDocument($template, 'content', 'all');
4389
                $template = $doc['content'];
4390
                break;
4391
            case 'SELECT':
4392
                $this->db->getValue($this->db->query("SELECT {$template}"));
4393
                break;
4394
            default:
4395
                if (!($template = $this->getChunk($tpl))) {
4396
                    $template = $tpl;
4397
                }
4398
        }
4399
        return $template;
4400
    }
4401
4402
    /**
4403
     * Returns the timestamp in the date format defined in $this->config['datetime_format']
4404
     *
4405
     * @param int $timestamp Default: 0
4406
     * @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.
4407
     * @return string
4408
     */
4409
    public function toDateFormat($timestamp = 0, $mode = '')
4410
    {
4411
        $timestamp = trim($timestamp);
4412
        if ($mode !== 'formatOnly' && empty($timestamp)) {
4413
            return '-';
4414
        }
4415
        $timestamp = intval($timestamp);
4416
4417
        switch ($this->config['datetime_format']) {
4418
            case 'YYYY/mm/dd':
4419
                $dateFormat = '%Y/%m/%d';
4420
                break;
4421
            case 'dd-mm-YYYY':
4422
                $dateFormat = '%d-%m-%Y';
4423
                break;
4424
            case 'mm/dd/YYYY':
4425
                $dateFormat = '%m/%d/%Y';
4426
                break;
4427
            /*
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...
4428
            case 'dd-mmm-YYYY':
4429
                $dateFormat = '%e-%b-%Y';
4430
                break;
4431
            */
4432
        }
4433
4434
        if (empty($mode)) {
4435
            $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...
4436
        } elseif ($mode == 'dateOnly') {
4437
            $strTime = strftime($dateFormat, $timestamp);
4438
        } elseif ($mode == 'formatOnly') {
4439
            $strTime = $dateFormat;
4440
        }
4441
        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...
4442
    }
4443
4444
    /**
4445
     * Make a timestamp from a string corresponding to the format in $this->config['datetime_format']
4446
     *
4447
     * @param string $str
4448
     * @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...
4449
     */
4450
    public function toTimeStamp($str)
4451
    {
4452
        $str = trim($str);
4453
        if (empty($str)) {
4454
            return '';
4455
        }
4456
4457
        switch ($this->config['datetime_format']) {
4458 View Code Duplication
            case 'YYYY/mm/dd':
4459
                if (!preg_match('/^[0-9]{4}\/[0-9]{2}\/[0-9]{2}[0-9 :]*$/', $str)) {
4460
                    return '';
4461
                }
4462
                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...
4463
                break;
4464 View Code Duplication
            case 'dd-mm-YYYY':
4465
                if (!preg_match('/^[0-9]{2}-[0-9]{2}-[0-9]{4}[0-9 :]*$/', $str)) {
4466
                    return '';
4467
                }
4468
                list ($d, $m, $Y, $H, $M, $S) = sscanf($str, '%2d-%2d-%4d %2d:%2d:%2d');
4469
                break;
4470 View Code Duplication
            case 'mm/dd/YYYY':
4471
                if (!preg_match('/^[0-9]{2}\/[0-9]{2}\/[0-9]{4}[0-9 :]*$/', $str)) {
4472
                    return '';
4473
                }
4474
                list ($m, $d, $Y, $H, $M, $S) = sscanf($str, '%2d/%2d/%4d %2d:%2d:%2d');
4475
                break;
4476
            /*
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...
4477
            case 'dd-mmm-YYYY':
4478
                if (!preg_match('/^[0-9]{2}-[0-9a-z]+-[0-9]{4}[0-9 :]*$/i', $str)) {return '';}
4479
                list ($m, $d, $Y, $H, $M, $S) = sscanf($str, '%2d-%3s-%4d %2d:%2d:%2d');
4480
                break;
4481
            */
4482
        }
4483
        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...
4484
            $H = 0;
4485
            $M = 0;
4486
            $S = 0;
4487
        }
4488
        $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...
4489
        $timeStamp = intval($timeStamp);
4490
        return $timeStamp;
4491
    }
4492
4493
    /**
4494
     * Get the TVs of a document's children. Returns an array where each element represents one child doc.
4495
     *
4496
     * Ignores deleted children. Gets all children - there is no where clause available.
4497
     *
4498
     * @param int $parentid The parent docid
4499
     *                 Default: 0 (site root)
4500
     * @param array $tvidnames . Which TVs to fetch - Can relate to the TV ids in the db (array elements should be numeric only)
4501
     *                                               or the TV names (array elements should be names only)
4502
     *                      Default: Empty array
4503
     * @param int $published Whether published or unpublished documents are in the result
4504
     *                      Default: 1
4505
     * @param string $docsort How to sort the result array (field)
4506
     *                      Default: menuindex
4507
     * @param ASC|string $docsortdir How to sort the result array (direction)
4508
     *                      Default: ASC
4509
     * @param string $tvfields Fields to fetch from site_tmplvars, default '*'
4510
     *                      Default: *
4511
     * @param string $tvsort How to sort each element of the result array i.e. how to sort the TVs (field)
4512
     *                      Default: rank
4513
     * @param string $tvsortdir How to sort each element of the result array i.e. how to sort the TVs (direction)
4514
     *                      Default: ASC
4515
     * @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...
4516
     */
4517
    public function getDocumentChildrenTVars($parentid = 0, $tvidnames = array(), $published = 1, $docsort = "menuindex", $docsortdir = "ASC", $tvfields = "*", $tvsort = "rank", $tvsortdir = "ASC")
4518
    {
4519
        $docs = $this->getDocumentChildren($parentid, $published, 0, '*', '', $docsort, $docsortdir);
4520
        if (!$docs) {
4521
            return false;
4522
        } else {
4523
            $result = array();
4524
            // get user defined template variables
4525
            if ($tvfields) {
4526
                $_ = 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...
4527
                foreach ($_ as $i => $v) {
4528
                    if ($v === 'value') {
4529
                        unset($_[$i]);
4530
                    } else {
4531
                        $_[$i] = 'tv.' . $v;
4532
                    }
4533
                }
4534
                $fields = join(',', $_);
4535
            } else {
4536
                $fields = "tv.*";
4537
            }
4538
4539
            if ($tvsort != '') {
4540
                $tvsort = 'tv.' . join(',tv.', array_filter(array_map('trim', explode(',', $tvsort))));
4541
            }
4542 View Code Duplication
            if ($tvidnames == "*") {
4543
                $query = "tv.id<>0";
4544
            } else {
4545
                $query = (is_numeric($tvidnames[0]) ? "tv.id" : "tv.name") . " IN ('" . join("','", $tvidnames) . "')";
4546
            }
4547
4548
            $this->getUserDocGroups();
4549
4550
            foreach ($docs as $doc) {
4551
4552
                $docid = $doc['id'];
4553
4554
                $rs = $this->db->select("{$fields}, IF(tvc.value!='',tvc.value,tv.default_text) as value ", "[+prefix+]site_tmplvars tv 
4555
                        INNER JOIN [+prefix+]site_tmplvar_templates tvtpl ON tvtpl.tmplvarid = tv.id
4556
                        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}" : ""));
4557
                $tvs = $this->db->makeArray($rs);
4558
4559
                // get default/built-in template variables
4560
                ksort($doc);
4561
                foreach ($doc as $key => $value) {
4562
                    if ($tvidnames == '*' || in_array($key, $tvidnames)) {
4563
                        $tvs[] = array('name' => $key, 'value' => $value);
4564
                    }
4565
                }
4566
                if (is_array($tvs) && count($tvs)) {
4567
                    $result[] = $tvs;
4568
                }
4569
            }
4570
            return $result;
4571
        }
4572
    }
4573
4574
    /**
4575
     * getDocumentChildrenTVarOutput
4576
     * @version 1.1 (2014-02-19)
4577
     *
4578
     * @desc Returns an array where each element represents one child doc and contains the result from getTemplateVarOutput().
4579
     *
4580
     * @param int $parentid {integer}
4581
     * - Id of parent document. Default: 0 (site root).
4582
     * @param array $tvidnames {array; '*'}
4583
     * - Which TVs to fetch. In the form expected by getTemplateVarOutput(). Default: array().
4584
     * @param int $published {0; 1; 'all'}
4585
     * - 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.
4586
     * @param string $sortBy {string}
4587
     * - How to sort the result array (field). Default: 'menuindex'.
4588
     * @param string $sortDir {'ASC'; 'DESC'}
4589
     * - How to sort the result array (direction). Default: 'ASC'.
4590
     * @param string $where {string}
4591
     * - SQL WHERE condition (use only document fields, not TV). Default: ''.
4592
     * @param string $resultKey {string; false}
4593
     * - Field, which values are keys into result array. Use the “false”, that result array keys just will be numbered. Default: 'id'.
4594
     * @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...
4595
     * - Result array, or false.
4596
     */
4597
    public function getDocumentChildrenTVarOutput($parentid = 0, $tvidnames = array(), $published = 1, $sortBy = 'menuindex', $sortDir = 'ASC', $where = '', $resultKey = 'id')
4598
    {
4599
        $docs = $this->getDocumentChildren($parentid, $published, 0, 'id', $where, $sortBy, $sortDir);
4600
4601
        if (!$docs) {
4602
            return false;
4603
        } else {
4604
            $result = array();
4605
4606
            $unsetResultKey = false;
4607
4608
            if ($resultKey !== false) {
4609
                if (is_array($tvidnames)) {
4610
                    if (count($tvidnames) != 0 && !in_array($resultKey, $tvidnames)) {
4611
                        $tvidnames[] = $resultKey;
4612
                        $unsetResultKey = true;
4613
                    }
4614
                } else if ($tvidnames != '*' && $tvidnames != $resultKey) {
4615
                    $tvidnames = array($tvidnames, $resultKey);
4616
                    $unsetResultKey = true;
4617
                }
4618
            }
4619
4620
            for ($i = 0; $i < count($docs); $i++) {
1 ignored issue
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
4621
                $tvs = $this->getTemplateVarOutput($tvidnames, $docs[$i]['id'], $published);
4622
4623
                if ($tvs) {
4624
                    if ($resultKey !== false && array_key_exists($resultKey, $tvs)) {
4625
                        $result[$tvs[$resultKey]] = $tvs;
4626
4627
                        if ($unsetResultKey) {
4628
                            unset($result[$tvs[$resultKey]][$resultKey]);
4629
                        }
4630
                    } else {
4631
                        $result[] = $tvs;
4632
                    }
4633
                }
4634
            }
4635
4636
            return $result;
4637
        }
4638
    }
4639
4640
    /**
4641
     * Modified by Raymond for TV - Orig Modified by Apodigm - DocVars
4642
     * Returns a single site_content field or TV record from the db.
4643
     *
4644
     * If a site content field the result is an associative array of 'name' and 'value'.
4645
     *
4646
     * If a TV the result is an array representing a db row including the fields specified in $fields.
4647
     *
4648
     * @param string $idname Can be a TV id or name
4649
     * @param string $fields Fields to fetch from site_tmplvars. Default: *
4650
     * @param string|type $docid Docid. Defaults to empty string which indicates the current document.
4651
     * @param int $published Whether published or unpublished documents are in the result
4652
     *                        Default: 1
4653
     * @return bool
4654
     */
4655 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...
4656
    {
4657
        if ($idname == "") {
4658
            return false;
4659
        } else {
4660
            $result = $this->getTemplateVars(array($idname), $fields, $docid, $published, "", ""); //remove sorting for speed
4661
            return ($result != false) ? $result[0] : false;
4662
        }
4663
    }
4664
4665
    /**
4666
     * getTemplateVars
4667
     * @version 1.0.1 (2014-02-19)
4668
     *
4669
     * @desc Returns an array of site_content field fields and/or TV records from the db.
4670
     * Elements representing a site content field consist of an associative array of 'name' and 'value'.
4671
     * Elements representing a TV consist of an array representing a db row including the fields specified in $fields.
4672
     *
4673
     * @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
4674
     * @param $fields {comma separated string; '*'} - Fields names in the TV table of MODx database. Default: '*'
4675
     * @param $docid {integer; ''} - Id of a document to get. Default: an empty string which indicates the current document.
4676
     * @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.
4677
     * @param $sort {comma separated string} - Fields of the TV table to sort by. Default: 'rank'.
4678
     * @param $dir {'ASC'; 'DESC'} - How to sort the result array (direction). Default: 'ASC'.
4679
     *
4680
     * @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...
4681
     */
4682
    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...
4683
    {
4684
        $cacheKey = md5(print_r(func_get_args(), true));
4685
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
4686
            return $this->tmpCache[__FUNCTION__][$cacheKey];
4687
        }
4688
4689
        if (($idnames != '*' && !is_array($idnames)) || empty($idnames) ) {
4690
            return false;
4691
        } else {
4692
4693
            // get document record
4694
            if ($docid == '') {
4695
                $docid = $this->documentIdentifier;
4696
                $docRow = $this->documentObject;
4697
            } else {
4698
                $docRow = $this->getDocument($docid, '*', $published);
4699
4700
                if (!$docRow) {
4701
                    $this->tmpCache[__FUNCTION__][$cacheKey] = false;
4702
                    return false;
4703
                }
4704
            }
4705
4706
            // get user defined template variables
4707
            $fields = ($fields == '') ? 'tv.*' : 'tv.' . implode(',tv.', array_filter(array_map('trim', explode(',', $fields))));
4708
            $sort = ($sort == '') ? '' : 'tv.' . implode(',tv.', array_filter(array_map('trim', explode(',', $sort))));
4709
4710 View Code Duplication
            if ($idnames == '*') {
4711
                $query = 'tv.id<>0';
4712
            } else {
4713
                $query = (is_numeric($idnames[0]) ? 'tv.id' : 'tv.name') . " IN ('" . implode("','", $idnames) . "')";
4714
            }
4715
4716
            $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...
4717
                    INNER JOIN " . $this->getFullTableName('site_tmplvar_templates') . " tvtpl ON tvtpl.tmplvarid = tv.id
4718
                    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}" : ""));
4719
4720
            $result = $this->db->makeArray($rs);
4721
4722
            // get default/built-in template variables
4723
            if(is_array($docRow)){
4724
                ksort($docRow);
4725
4726
                foreach ($docRow as $key => $value) {
4727
                    if ($idnames == '*' || in_array($key, $idnames)) {
4728
                        array_push($result, array(
4729
                            'name' => $key,
4730
                            'value' => $value
4731
                        ));
4732
                    }
4733
                }
4734
            }
4735
4736
            $this->tmpCache[__FUNCTION__][$cacheKey] = $result;
4737
4738
            return $result;
4739
        }
4740
    }
4741
4742
    /**
4743
     * getTemplateVarOutput
4744
     * @version 1.0.1 (2014-02-19)
4745
     *
4746
     * @desc Returns an associative array containing TV rendered output values.
4747
     *
4748
     * @param array $idnames {array; '*'}
4749
     * - 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
4750
     * @param string $docid {integer; ''}
4751
     * - Id of a document to get. Default: an empty string which indicates the current document.
4752
     * @param int $published {0; 1; 'all'}
4753
     * - 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.
4754
     * @param string $sep {string}
4755
     * - Separator that is used while concatenating in getTVDisplayFormat(). Default: ''.
4756
     * @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...
4757
     * - Result array, or false.
4758
     */
4759
    public function getTemplateVarOutput($idnames = array(), $docid = '', $published = 1, $sep = '')
4760
    {
4761
        if (is_array($idnames) && empty($idnames) ) {
4762
            return false;
4763
        } else {
4764
            $output = array();
4765
            $vars = ($idnames == '*' || is_array($idnames)) ? $idnames : array($idnames);
4766
4767
            $docid = intval($docid) ? intval($docid) : $this->documentIdentifier;
4768
            // remove sort for speed
4769
            $result = $this->getTemplateVars($vars, '*', $docid, $published, '', '');
4770
4771
            if ($result == false) {
4772
                return false;
4773
            } else {
4774
                $baspath = MODX_MANAGER_PATH . 'includes';
4775
                include_once $baspath . '/tmplvars.format.inc.php';
4776
                include_once $baspath . '/tmplvars.commands.inc.php';
4777
4778
                for ($i = 0; $i < count($result); $i++) {
1 ignored issue
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
4779
                    $row = $result[$i];
4780
4781
                    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...
4782
                        $output[$row['name']] = $row['value'];
4783
                    } else {
4784
                        $output[$row['name']] = getTVDisplayFormat($row['name'], $row['value'], $row['display'], $row['display_params'], $row['type'], $docid, $sep);
4785
                    }
4786
                }
4787
4788
                return $output;
4789
            }
4790
        }
4791
    }
4792
4793
    /**
4794
     * Returns the full table name based on db settings
4795
     *
4796
     * @param string $tbl Table name
4797
     * @return string Table name with prefix
4798
     */
4799
    public function getFullTableName($tbl)
4800
    {
4801
        return $this->db->config['dbase'] . ".`" . $this->db->config['table_prefix'] . $tbl . "`";
4802
    }
4803
4804
    /**
4805
     * Returns the placeholder value
4806
     *
4807
     * @param string $name Placeholder name
4808
     * @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...
4809
     */
4810
    public function getPlaceholder($name)
4811
    {
4812
        return isset($this->placeholders[$name]) ? $this->placeholders[$name] : null;
4813
    }
4814
4815
    /**
4816
     * Sets a value for a placeholder
4817
     *
4818
     * @param string $name The name of the placeholder
4819
     * @param string $value The value of the placeholder
4820
     */
4821
    public function setPlaceholder($name, $value)
4822
    {
4823
        $this->placeholders[$name] = $value;
4824
    }
4825
4826
    /**
4827
     * Set placeholders en masse via an array or object.
4828
     *
4829
     * @param object|array $subject
4830
     * @param string $prefix
4831
     */
4832
    public function toPlaceholders($subject, $prefix = '')
4833
    {
4834
        if (is_object($subject)) {
4835
            $subject = get_object_vars($subject);
4836
        }
4837
        if (is_array($subject)) {
4838
            foreach ($subject as $key => $value) {
4839
                $this->toPlaceholder($key, $value, $prefix);
4840
            }
4841
        }
4842
    }
4843
4844
    /**
4845
     * For use by toPlaceholders(); For setting an array or object element as placeholder.
4846
     *
4847
     * @param string $key
4848
     * @param object|array $value
4849
     * @param string $prefix
4850
     */
4851
    public function toPlaceholder($key, $value, $prefix = '')
4852
    {
4853
        if (is_array($value) || is_object($value)) {
4854
            $this->toPlaceholders($value, "{$prefix}{$key}.");
4855
        } else {
4856
            $this->setPlaceholder("{$prefix}{$key}", $value);
4857
        }
4858
    }
4859
4860
    /**
4861
     * Returns the manager relative URL/path with respect to the site root.
4862
     *
4863
     * @global string $base_url
4864
     * @return string The complete URL to the manager folder
4865
     */
4866
    public function getManagerPath()
4867
    {
4868
        return MODX_MANAGER_URL;
4869
    }
4870
4871
    /**
4872
     * Returns the cache relative URL/path with respect to the site root.
4873
     *
4874
     * @global string $base_url
4875
     * @return string The complete URL to the cache folder
4876
     */
4877
    public function getCachePath()
4878
    {
4879
        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...
4880
        $pth = $base_url . $this->getCacheFolder();
4881
        return $pth;
4882
    }
4883
4884
    /**
4885
     * Sends a message to a user's message box.
4886
     *
4887
     * @param string $type Type of the message
4888
     * @param string $to The recipient of the message
4889
     * @param string $from The sender of the message
4890
     * @param string $subject The subject of the message
4891
     * @param string $msg The message body
4892
     * @param int $private Whether it is a private message, or not
4893
     *                     Default : 0
4894
     */
4895
    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...
4896
    {
4897
        $private = ($private) ? 1 : 0;
4898 View Code Duplication
        if (!is_numeric($to)) {
4899
            // Query for the To ID
4900
            $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...
4901
            $to = $this->db->getValue($rs);
4902
        }
4903 View Code Duplication
        if (!is_numeric($from)) {
4904
            // Query for the From ID
4905
            $rs = $this->db->select('id', $this->getFullTableName("manager_users"), "username='{$from}'");
4906
            $from = $this->db->getValue($rs);
4907
        }
4908
        // insert a new message into user_messages
4909
        $this->db->insert(array(
4910
            'type' => $type,
4911
            'subject' => $subject,
4912
            'message' => $msg,
4913
            'sender' => $from,
4914
            'recipient' => $to,
4915
            'private' => $private,
4916
            'postdate' => $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'],
4917
            'messageread' => 0,
4918
        ), $this->getFullTableName('user_messages'));
4919
    }
4920
4921
    /**
4922
     * Returns current user id.
4923
     *
4924
     * @param string $context . Default is an empty string which indicates the method should automatically pick 'web (frontend) or 'mgr' (backend)
4925
     * @return string
4926
     */
4927 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...
4928
    {
4929
        $out = false;
4930
4931
        if (!empty($context)) {
4932
            if (is_scalar($context) && isset($_SESSION[$context . 'Validated'])) {
4933
                $out = $_SESSION[$context . 'InternalKey'];
4934
            }
4935
        } else {
4936
            switch (true) {
4937
                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...
4938
                    $out = $_SESSION['webInternalKey'];
4939
                    break;
4940
                }
4941
                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...
4942
                    $out = $_SESSION['mgrInternalKey'];
4943
                    break;
4944
                }
4945
            }
4946
        }
4947
        return $out;
4948
    }
4949
4950
    /**
4951
     * Returns current user name
4952
     *
4953
     * @param string $context . Default is an empty string which indicates the method should automatically pick 'web (frontend) or 'mgr' (backend)
4954
     * @return string
4955
     */
4956 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...
4957
    {
4958
        $out = false;
4959
4960
        if (!empty($context)) {
4961
            if (is_scalar($context) && isset($_SESSION[$context . 'Validated'])) {
4962
                $out = $_SESSION[$context . 'Shortname'];
4963
            }
4964
        } else {
4965
            switch (true) {
4966
                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...
4967
                    $out = $_SESSION['webShortname'];
4968
                    break;
4969
                }
4970
                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...
4971
                    $out = $_SESSION['mgrShortname'];
4972
                    break;
4973
                }
4974
            }
4975
        }
4976
        return $out;
4977
    }
4978
4979
    /**
4980
     * Returns current login user type - web or manager
4981
     *
4982
     * @return string
4983
     */
4984
    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...
4985
    {
4986
        if ($this->isFrontend() && isset ($_SESSION['webValidated'])) {
4987
            return 'web';
4988
        } elseif ($this->isBackend() && isset ($_SESSION['mgrValidated'])) {
4989
            return 'manager';
4990
        } else {
4991
            return '';
4992
        }
4993
    }
4994
4995
    /**
4996
     * Returns a user info record for the given manager user
4997
     *
4998
     * @param int $uid
4999
     * @return boolean|string
5000
     */
5001
    public function getUserInfo($uid)
5002
    {
5003
        if (isset($this->tmpCache[__FUNCTION__][$uid])) {
5004
            return $this->tmpCache[__FUNCTION__][$uid];
5005
        }
5006
5007
        $from = '[+prefix+]manager_users mu INNER JOIN [+prefix+]user_attributes mua ON mua.internalkey=mu.id';
5008
        $where = sprintf("mu.id='%s'", $this->db->escape($uid));
5009
        $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...
5010
5011
        if (!$this->db->getRecordCount($rs)) {
5012
            return $this->tmpCache[__FUNCTION__][$uid] = false;
5013
        }
5014
5015
        $row = $this->db->getRow($rs);
5016 View Code Duplication
        if (!isset($row['usertype']) || !$row['usertype']) {
5017
            $row['usertype'] = 'manager';
5018
        }
5019
5020
        $this->tmpCache[__FUNCTION__][$uid] = $row;
5021
5022
        return $row;
5023
    }
5024
5025
    /**
5026
     * Returns a record for the web user
5027
     *
5028
     * @param int $uid
5029
     * @return boolean|string
5030
     */
5031
    public function getWebUserInfo($uid)
5032
    {
5033
        $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...
5034
                INNER JOIN " . $this->getFullTableName("web_user_attributes") . " wua ON wua.internalkey=wu.id", "wu.id='{$uid}'");
5035
        if ($row = $this->db->getRow($rs)) {
5036 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...
5037
                $row["usertype"] = "web";
5038
            }
5039
            return $row;
5040
        }
5041
    }
5042
5043
    /**
5044
     * Returns an array of document groups that current user is assigned to.
5045
     * This function will first return the web user doc groups when running from
5046
     * frontend otherwise it will return manager user's docgroup.
5047
     *
5048
     * @param boolean $resolveIds Set to true to return the document group names
5049
     *                            Default: false
5050
     * @return string|array
5051
     */
5052
    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...
5053
    {
5054
        if ($this->isFrontend() && isset($_SESSION['webDocgroups']) && isset($_SESSION['webValidated'])) {
5055
            $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...
5056
            $dgn = isset($_SESSION['webDocgrpNames']) ? $_SESSION['webDocgrpNames'] : false;
5057
        } else if ($this->isBackend() && isset($_SESSION['mgrDocgroups']) && isset($_SESSION['mgrValidated'])) {
5058
            $dg = $_SESSION['mgrDocgroups'];
5059
            $dgn = isset($_SESSION['mgrDocgrpNames']) ? $_SESSION['mgrDocgrpNames'] : false;
5060
        } else {
5061
            $dg = '';
5062
        }
5063
        if (!$resolveIds) {
5064
            return $dg;
5065
        } else if (is_array($dgn)) {
5066
            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...
5067
        } else if (is_array($dg)) {
5068
            // resolve ids to names
5069
            $dgn = array();
5070
            $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...
5071
            while ($row = $this->db->getRow($ds)) {
5072
                $dgn[] = $row['name'];
5073
            }
5074
            // cache docgroup names to session
5075
            if ($this->isFrontend()) {
5076
                $_SESSION['webDocgrpNames'] = $dgn;
5077
            } else {
5078
                $_SESSION['mgrDocgrpNames'] = $dgn;
5079
            }
5080
            return $dgn;
5081
        }
5082
    }
5083
5084
    /**
5085
     * Change current web user's password
5086
     *
5087
     * @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...
5088
     * @param string $oldPwd
5089
     * @param string $newPwd
5090
     * @return string|boolean Returns true if successful, oterhwise return error
5091
     *                        message
5092
     */
5093
    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...
5094
    {
5095
        $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...
5096
        if ($_SESSION["webValidated"] == 1) {
5097
            $tbl = $this->getFullTableName("web_users");
5098
            $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...
5099
            if ($row = $this->db->getRow($ds)) {
5100
                if ($row["password"] == md5($oldPwd)) {
5101
                    if (strlen($newPwd) < 6) {
5102
                        return "Password is too short!";
5103
                    } elseif ($newPwd == "") {
5104
                        return "You didn't specify a password for this user!";
5105
                    } else {
5106
                        $this->db->update(array(
5107
                            'password' => $this->db->escape($newPwd),
5108
                        ), $tbl, "id='" . $this->getLoginUserID() . "'");
5109
                        // invoke OnWebChangePassword event
5110
                        $this->invokeEvent("OnWebChangePassword", array(
5111
                            "userid" => $row["id"],
5112
                            "username" => $row["username"],
5113
                            "userpassword" => $newPwd
5114
                        ));
5115
                        return true;
5116
                    }
5117
                } else {
5118
                    return "Incorrect password.";
5119
                }
5120
            }
5121
        }
5122
        return $rt;
5123
    }
5124
5125
    /**
5126
     * Returns true if the current web user is a member the specified groups
5127
     *
5128
     * @param array $groupNames
5129
     * @return boolean
5130
     */
5131
    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...
5132
    {
5133
        if (!is_array($groupNames)) {
5134
            return false;
5135
        }
5136
        // check cache
5137
        $grpNames = isset ($_SESSION['webUserGroupNames']) ? $_SESSION['webUserGroupNames'] : false;
5138
        if (!is_array($grpNames)) {
5139
            $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...
5140
                    INNER JOIN " . $this->getFullTableName("web_groups") . " wg ON wg.webgroup=wgn.id AND wg.webuser='" . $this->getLoginUserID() . "'");
5141
            $grpNames = $this->db->getColumn("name", $rs);
5142
            // save to cache
5143
            $_SESSION['webUserGroupNames'] = $grpNames;
5144
        }
5145
        foreach ($groupNames as $k => $v) {
5146
            if (in_array(trim($v), $grpNames)) {
5147
                return true;
5148
            }
5149
        }
5150
        return false;
5151
    }
5152
5153
    /**
5154
     * Registers Client-side CSS scripts - these scripts are loaded at inside
5155
     * the <head> tag
5156
     *
5157
     * @param string $src
5158
     * @param string $media Default: Empty string
5159
     * @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...
5160
     */
5161
    public function regClientCSS($src, $media = '')
5162
    {
5163
        if (empty($src) || isset ($this->loadedjscripts[$src])) {
5164
            return '';
5165
        }
5166
        $nextpos = max(array_merge(array(0), array_keys($this->sjscripts))) + 1;
5167
        $this->loadedjscripts[$src]['startup'] = true;
5168
        $this->loadedjscripts[$src]['version'] = '0';
5169
        $this->loadedjscripts[$src]['pos'] = $nextpos;
5170
        if (strpos(strtolower($src), "<style") !== false || strpos(strtolower($src), "<link") !== false) {
5171
            $this->sjscripts[$nextpos] = $src;
5172
        } else {
5173
            $this->sjscripts[$nextpos] = "\t" . '<link rel="stylesheet" type="text/css" href="' . $src . '" ' . ($media ? 'media="' . $media . '" ' : '') . '/>';
5174
        }
5175
    }
5176
5177
    /**
5178
     * Registers Startup Client-side JavaScript - these scripts are loaded at inside the <head> tag
5179
     *
5180
     * @param string $src
5181
     * @param array $options Default: 'name'=>'', 'version'=>'0', 'plaintext'=>false
5182
     */
5183
    public function regClientStartupScript($src, $options = array('name' => '', 'version' => '0', 'plaintext' => false))
5184
    {
5185
        $this->regClientScript($src, $options, true);
5186
    }
5187
5188
    /**
5189
     * Registers Client-side JavaScript these scripts are loaded at the end of the page unless $startup is true
5190
     *
5191
     * @param string $src
5192
     * @param array $options Default: 'name'=>'', 'version'=>'0', 'plaintext'=>false
5193
     * @param boolean $startup Default: false
5194
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null?

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

Loading history...
5195
     */
5196
    public function regClientScript($src, $options = array('name' => '', 'version' => '0', 'plaintext' => false), $startup = false)
5197
    {
5198
        if (empty($src)) {
5199
            return '';
5200
        } // nothing to register
5201
        if (!is_array($options)) {
5202
            if (is_bool($options))  // backward compatibility with old plaintext parameter
5203
            {
5204
                $options = array('plaintext' => $options);
5205
            } elseif (is_string($options)) // Also allow script name as 2nd param
5206
            {
5207
                $options = array('name' => $options);
5208
            } else {
5209
                $options = array();
5210
            }
5211
        }
5212
        $name = isset($options['name']) ? strtolower($options['name']) : '';
5213
        $version = isset($options['version']) ? $options['version'] : '0';
5214
        $plaintext = isset($options['plaintext']) ? $options['plaintext'] : false;
5215
        $key = !empty($name) ? $name : $src;
5216
        unset($overwritepos); // probably unnecessary--just making sure
5217
5218
        $useThisVer = true;
5219
        if (isset($this->loadedjscripts[$key])) { // a matching script was found
5220
            // if existing script is a startup script, make sure the candidate is also a startup script
5221
            if ($this->loadedjscripts[$key]['startup']) {
5222
                $startup = true;
5223
            }
5224
5225
            if (empty($name)) {
5226
                $useThisVer = false; // if the match was based on identical source code, no need to replace the old one
5227
            } else {
5228
                $useThisVer = version_compare($this->loadedjscripts[$key]['version'], $version, '<');
5229
            }
5230
5231
            if ($useThisVer) {
5232
                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...
5233
                    // remove old script from the bottom of the page (new one will be at the top)
5234
                    unset($this->jscripts[$this->loadedjscripts[$key]['pos']]);
5235
                } else {
5236
                    // overwrite the old script (the position may be important for dependent scripts)
5237
                    $overwritepos = $this->loadedjscripts[$key]['pos'];
5238
                }
5239
            } else { // Use the original version
5240
                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...
5241
                    // need to move the exisiting script to the head
5242
                    $version = $this->loadedjscripts[$key][$version];
5243
                    $src = $this->jscripts[$this->loadedjscripts[$key]['pos']];
5244
                    unset($this->jscripts[$this->loadedjscripts[$key]['pos']]);
5245
                } else {
5246
                    return ''; // the script is already in the right place
5247
                }
5248
            }
5249
        }
5250
5251
        if ($useThisVer && $plaintext != true && (strpos(strtolower($src), "<script") === false)) {
5252
            $src = "\t" . '<script type="text/javascript" src="' . $src . '"></script>';
5253
        }
5254
        if ($startup) {
5255
            $pos = isset($overwritepos) ? $overwritepos : max(array_merge(array(0), array_keys($this->sjscripts))) + 1;
5256
            $this->sjscripts[$pos] = $src;
5257
        } else {
5258
            $pos = isset($overwritepos) ? $overwritepos : max(array_merge(array(0), array_keys($this->jscripts))) + 1;
5259
            $this->jscripts[$pos] = $src;
5260
        }
5261
        $this->loadedjscripts[$key]['version'] = $version;
5262
        $this->loadedjscripts[$key]['startup'] = $startup;
5263
        $this->loadedjscripts[$key]['pos'] = $pos;
5264
    }
5265
5266
    /**
5267
     * Returns all registered JavaScripts
5268
     *
5269
     * @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...
5270
     */
5271
    public function regClientStartupHTMLBlock($html)
5272
    {
5273
        $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...
5274
    }
5275
5276
    /**
5277
     * Returns all registered startup scripts
5278
     *
5279
     * @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...
5280
     */
5281
    public function regClientHTMLBlock($html)
5282
    {
5283
        $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...
5284
    }
5285
5286
    /**
5287
     * Remove unwanted html tags and snippet, settings and tags
5288
     *
5289
     * @param string $html
5290
     * @param string $allowed Default: Empty string
5291
     * @return string
5292
     */
5293
    public function stripTags($html, $allowed = "")
5294
    {
5295
        $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...
5296
        $t = preg_replace('~\[\*(.*?)\*\]~', "", $t); //tv
5297
        $t = preg_replace('~\[\[(.*?)\]\]~', "", $t); //snippet
5298
        $t = preg_replace('~\[\!(.*?)\!\]~', "", $t); //snippet
5299
        $t = preg_replace('~\[\((.*?)\)\]~', "", $t); //settings
5300
        $t = preg_replace('~\[\+(.*?)\+\]~', "", $t); //placeholders
5301
        $t = preg_replace('~{{(.*?)}}~', "", $t); //chunks
5302
        return $t;
5303
    }
5304
5305
    /**
5306
     * Add an event listener to a plugin - only for use within the current execution cycle
5307
     *
5308
     * @param string $evtName
5309
     * @param string $pluginName
5310
     * @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...
5311
     */
5312
    public function addEventListener($evtName, $pluginName)
5313
    {
5314
        if (!$evtName || !$pluginName) {
5315
            return false;
5316
        }
5317
        if (!array_key_exists($evtName, $this->pluginEvent)) {
5318
            $this->pluginEvent[$evtName] = array();
5319
        }
5320
        return array_push($this->pluginEvent[$evtName], $pluginName); // return array count
5321
    }
5322
5323
    /**
5324
     * Remove event listener - only for use within the current execution cycle
5325
     *
5326
     * @param string $evtName
5327
     * @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...
5328
     */
5329
    public function removeEventListener($evtName)
5330
    {
5331
        if (!$evtName) {
5332
            return false;
5333
        }
5334
        unset ($this->pluginEvent[$evtName]);
5335
    }
5336
5337
    /**
5338
     * Remove all event listeners - only for use within the current execution cycle
5339
     */
5340
    public function removeAllEventListener()
5341
    {
5342
        unset ($this->pluginEvent);
5343
        $this->pluginEvent = array();
5344
    }
5345
5346
    /**
5347
     * Invoke an event.
5348
     *
5349
     * @param string $evtName
5350
     * @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.
5351
     * @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...
5352
     */
5353
    public function invokeEvent($evtName, $extParams = array())
5354
    {
5355
        if (!$evtName) {
5356
            return false;
5357
        }
5358
        if (!isset ($this->pluginEvent[$evtName])) {
5359
            return false;
5360
        }
5361
5362
        $results = null;
5363
        foreach ($this->pluginEvent[$evtName] as $pluginName) { // start for loop
5364
            if ($this->dumpPlugins) {
5365
                $eventtime = $this->getMicroTime();
5366
            }
5367
            // reset event object
5368
            $e = &$this->event;
5369
            $e->_resetEventObject();
5370
            $e->name = $evtName;
5371
            $e->activePlugin = $pluginName;
5372
5373
            // get plugin code
5374
            $_ = $this->getPluginCode($pluginName);
5375
            $pluginCode = $_['code'];
5376
            $pluginProperties = $_['props'];
5377
5378
            // load default params/properties
5379
            $parameter = $this->parseProperties($pluginProperties);
5380
            if (!is_array($parameter)) {
5381
                $parameter = array();
5382
            }
5383
            if (!empty($extParams)) {
5384
                $parameter = array_merge($parameter, $extParams);
5385
            }
5386
5387
            // eval plugin
5388
            $this->evalPlugin($pluginCode, $parameter);
5389
5390
            if (class_exists('PHxParser')) {
5391
                $this->config['enable_filter'] = 0;
5392
            }
5393
5394
            if ($this->dumpPlugins) {
5395
                $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...
5396
                $this->pluginsCode .= sprintf('<fieldset><legend><b>%s / %s</b> (%2.2f ms)</legend>', $evtName, $pluginName, $eventtime * 1000);
5397
                foreach ($parameter as $k => $v) {
5398
                    $this->pluginsCode .= "{$k} => " . print_r($v, true) . '<br>';
5399
                }
5400
                $this->pluginsCode .= '</fieldset><br />';
5401
                $this->pluginsTime["{$evtName} / {$pluginName}"] += $eventtime;
5402
            }
5403
            if ($e->_output != '') {
5404
                $results[] = $e->_output;
5405
            }
5406
            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...
5407
                break;
5408
            }
5409
        }
5410
5411
        $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...
5412
        return $results;
5413
    }
5414
5415
    /**
5416
     * Returns plugin-code and properties
5417
     *
5418
     * @param string $pluginName
5419
     * @return array Associative array consisting of 'code' and 'props'
5420
     */
5421
    public function getPluginCode($pluginName)
5422
    {
5423
        $plugin = array();
5424
        if (isset ($this->pluginCache[$pluginName])) {
5425
            $pluginCode = $this->pluginCache[$pluginName];
5426
            $pluginProperties = isset($this->pluginCache[$pluginName . "Props"]) ? $this->pluginCache[$pluginName . "Props"] : '';
5427
        } else {
5428
            $pluginName = $this->db->escape($pluginName);
5429
            $result = $this->db->select('name, plugincode, properties', $this->getFullTableName("site_plugins"), "name='{$pluginName}' AND disabled=0");
5430
            if ($row = $this->db->getRow($result)) {
5431
                $pluginCode = $this->pluginCache[$row['name']] = $row['plugincode'];
5432
                $pluginProperties = $this->pluginCache[$row['name'] . "Props"] = $row['properties'];
5433
            } else {
5434
                $pluginCode = $this->pluginCache[$pluginName] = "return false;";
5435
                $pluginProperties = '';
5436
            }
5437
        }
5438
        $plugin['code'] = $pluginCode;
5439
        $plugin['props'] = $pluginProperties;
5440
5441
        return $plugin;
5442
    }
5443
5444
    /**
5445
     * Parses a resource property string and returns the result as an array
5446
     *
5447
     * @param string $propertyString
5448
     * @param string|null $elementName
5449
     * @param string|null $elementType
5450
     * @return array Associative array in the form property name => property value
5451
     */
5452
    public function parseProperties($propertyString, $elementName = null, $elementType = null)
5453
    {
5454
        $propertyString = trim($propertyString);
5455
        $propertyString = str_replace('{}', '', $propertyString);
5456
        $propertyString = str_replace('} {', ',', $propertyString);
5457
        if (empty($propertyString)) {
5458
            return array();
5459
        }
5460
        if ($propertyString == '{}') {
5461
            return array();
5462
        }
5463
5464
        $jsonFormat = $this->isJson($propertyString, true);
5465
        $property = array();
5466
        // old format
5467
        if ($jsonFormat === false) {
5468
            $props = explode('&', $propertyString);
5469
            foreach ($props as $prop) {
5470
5471
                if (empty($prop)) {
5472
                    continue;
5473
                } elseif (strpos($prop, '=') === false) {
5474
                    $property[trim($prop)] = '';
5475
                    continue;
5476
                }
5477
5478
                $_ = explode('=', $prop, 2);
5479
                $key = trim($_[0]);
5480
                $p = explode(';', trim($_[1]));
5481
                switch ($p[1]) {
5482
                    case 'list':
5483
                    case 'list-multi':
5484
                    case 'checkbox':
5485
                    case 'radio':
5486
                        $value = !isset($p[3]) ? '' : $p[3];
5487
                        break;
5488
                    default:
5489
                        $value = !isset($p[2]) ? '' : $p[2];
5490
                }
5491
                if (!empty($key)) {
5492
                    $property[$key] = $value;
5493
                }
5494
            }
5495
            // new json-format
5496
        } else if (!empty($jsonFormat)) {
5497
            foreach ($jsonFormat as $key => $row) {
5498
                if (!empty($key)) {
5499
                    if (is_array($row)) {
5500
                        if (isset($row[0]['value'])) {
5501
                            $value = $row[0]['value'];
5502
                        }
5503
                    } else {
5504
                        $value = $row;
5505
                    }
5506
                    if (isset($value) && $value !== '') {
5507
                        $property[$key] = $value;
5508
                    }
5509
                }
5510
            }
5511
        }
5512
        if (!empty($elementName) && !empty($elementType)) {
5513
            $out = $this->invokeEvent('OnParseProperties', array(
5514
                'element' => $elementName,
5515
                'type' => $elementType,
5516
                'args' => $property
5517
            ));
5518
            if (is_array($out)) {
5519
                $out = array_pop($out);
5520
            }
5521
            if (is_array($out)) {
5522
                $property = $out;
5523
            }
5524
        }
5525
        return $property;
5526
    }
5527
5528
    /**
5529
     * Parses docBlock from a file and returns the result as an array
5530
     *
5531
     * @param string $element_dir
5532
     * @param string $filename
5533
     * @param boolean $escapeValues
5534
     * @return array Associative array in the form property name => property value
5535
     */
5536
    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...
5537
    {
5538
        $params = array();
5539
        $fullpath = $element_dir . '/' . $filename;
5540
        if (is_readable($fullpath)) {
5541
            $tpl = @fopen($fullpath, "r");
5542
            if ($tpl) {
5543
                $params['filename'] = $filename;
5544
                $docblock_start_found = false;
5545
                $name_found = false;
5546
                $description_found = false;
5547
                $docblock_end_found = false;
5548
                $arrayParams = array('author', 'documentation', 'reportissues', 'link');
5549
5550
                while (!feof($tpl)) {
5551
                    $line = fgets($tpl);
5552
                    $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...
5553
                    $docblock_start_found = $r['docblock_start_found'];
5554
                    $name_found = $r['name_found'];
5555
                    $description_found = $r['description_found'];
5556
                    $docblock_end_found = $r['docblock_end_found'];
5557
                    $param = $r['param'];
5558
                    $val = $r['val'];
5559
                    if (!$docblock_end_found) {
5560
                        break;
5561
                    }
5562
                    if (!$docblock_start_found || !$name_found || !$description_found || empty($param)) {
5563
                        continue;
5564
                    }
5565 View Code Duplication
                    if (!empty($param)) {
5566
                        if (in_array($param, $arrayParams)) {
5567
                            if (!isset($params[$param])) {
5568
                                $params[$param] = array();
5569
                            }
5570
                            $params[$param][] = $escapeValues ? $this->db->escape($val) : $val;
5571
                        } else {
5572
                            $params[$param] = $escapeValues ? $this->db->escape($val) : $val;
5573
                        }
5574
                    }
5575
                }
5576
                @fclose($tpl);
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
5577
            }
5578
        }
5579
        return $params;
5580
    }
5581
5582
    /**
5583
     * Parses docBlock from string and returns the result as an array
5584
     *
5585
     * @param string $string
5586
     * @param boolean $escapeValues
5587
     * @return array Associative array in the form property name => property value
5588
     */
5589
    public function parseDocBlockFromString($string, $escapeValues = false)
5590
    {
5591
        $params = array();
5592
        if (!empty($string)) {
5593
            $string = str_replace('\r\n', '\n', $string);
5594
            $exp = explode('\n', $string);
5595
            $docblock_start_found = false;
5596
            $name_found = false;
5597
            $description_found = false;
5598
            $docblock_end_found = false;
5599
            $arrayParams = array('author', 'documentation', 'reportissues', 'link');
5600
5601
            foreach ($exp as $line) {
5602
                $r = $this->parseDocBlockLine($line, $docblock_start_found, $name_found, $description_found, $docblock_end_found);
5603
                $docblock_start_found = $r['docblock_start_found'];
5604
                $name_found = $r['name_found'];
5605
                $description_found = $r['description_found'];
5606
                $docblock_end_found = $r['docblock_end_found'];
5607
                $param = $r['param'];
5608
                $val = $r['val'];
5609
                if (!$docblock_start_found) {
5610
                    continue;
5611
                }
5612
                if ($docblock_end_found) {
5613
                    break;
5614
                }
5615 View Code Duplication
                if (!empty($param)) {
5616
                    if (in_array($param, $arrayParams)) {
5617
                        if (!isset($params[$param])) {
5618
                            $params[$param] = array();
5619
                        }
5620
                        $params[$param][] = $escapeValues ? $this->db->escape($val) : $val;
5621
                    } else {
5622
                        $params[$param] = $escapeValues ? $this->db->escape($val) : $val;
5623
                    }
5624
                }
5625
            }
5626
        }
5627
        return $params;
5628
    }
5629
5630
    /**
5631
     * Parses docBlock of a component´s source-code and returns the result as an array
5632
     * (modified parseDocBlock() from modules/stores/setup.info.php by Bumkaka & Dmi3yy)
5633
     *
5634
     * @param string $line
5635
     * @param boolean $docblock_start_found
5636
     * @param boolean $name_found
5637
     * @param boolean $description_found
5638
     * @param boolean $docblock_end_found
5639
     * @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...
5640
     */
5641
    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...
5642
    {
5643
        $param = '';
5644
        $val = '';
5645
        $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...
5646
        if (!$docblock_start_found) {
5647
            // find docblock start
5648
            if (strpos($line, '/**') !== false) {
5649
                $docblock_start_found = true;
5650
            }
5651 View Code Duplication
        } elseif (!$name_found) {
5652
            // find name
5653
            if (preg_match("/^\s+\*\s+(.+)/", $line, $ma)) {
5654
                $param = 'name';
5655
                $val = trim($ma[1]);
5656
                $name_found = !empty($val);
5657
            }
5658
        } elseif (!$description_found) {
5659
            // find description
5660
            if (preg_match("/^\s+\*\s+(.+)/", $line, $ma)) {
5661
                $param = 'description';
5662
                $val = trim($ma[1]);
5663
                $description_found = !empty($val);
5664
            }
5665
        } else {
5666
            if (preg_match("/^\s+\*\s+\@([^\s]+)\s+(.+)/", $line, $ma)) {
5667
                $param = trim($ma[1]);
5668
                $val = trim($ma[2]);
5669 View Code Duplication
                if (!empty($param) && !empty($val)) {
5670
                    if ($param == 'internal') {
5671
                        $ma = null;
5672
                        if (preg_match("/\@([^\s]+)\s+(.+)/", $val, $ma)) {
5673
                            $param = trim($ma[1]);
5674
                            $val = trim($ma[2]);
5675
                        }
5676
                    }
5677
                }
5678
            } elseif (preg_match("/^\s*\*\/\s*$/", $line)) {
5679
                $docblock_end_found = true;
5680
            }
5681
        }
5682
        return array(
5683
            'docblock_start_found' => $docblock_start_found,
5684
            'name_found' => $name_found,
5685
            'description_found' => $description_found,
5686
            'docblock_end_found' => $docblock_end_found,
5687
            'param' => $param,
5688
            'val' => $val
5689
        );
5690
    }
5691
5692
    /**
5693
     * Renders docBlock-parameters into human readable list
5694
     *
5695
     * @param array $parsed
5696
     * @return string List in HTML-format
5697
     */
5698
    public function convertDocBlockIntoList($parsed)
5699
    {
5700
        global $_lang;
1 ignored issue
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...
5701
5702
        // Replace special placeholders & make URLs + Emails clickable
5703
        $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...
5704
        $regexUrl = "/((http|https|ftp|ftps)\:\/\/[^\/]+(\/[^\s]+[^,.?!:;\s])?)/";
5705
        $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';
5706
        $emailSubject = isset($parsed['name']) ? '?subject=' . $parsed['name'] : '';
5707
        $emailSubject .= isset($parsed['version']) ? ' v' . $parsed['version'] : '';
5708
        foreach ($parsed as $key => $val) {
5709
            if (is_array($val)) {
5710
                foreach ($val as $key2 => $val2) {
5711
                    $val2 = $this->parseText($val2, $ph);
5712 View Code Duplication
                    if (preg_match($regexUrl, $val2, $url)) {
5713
                        $val2 = preg_replace($regexUrl, "<a href=\"{$url[0]}\" target=\"_blank\">{$url[0]}</a> ", $val2);
5714
                    }
5715 View Code Duplication
                    if (preg_match($regexEmail, $val2, $url)) {
5716
                        $val2 = preg_replace($regexEmail, '<a href="mailto:\\1' . $emailSubject . '">\\1</a>', $val2);
5717
                    }
5718
                    $parsed[$key][$key2] = $val2;
5719
                }
5720
            } else {
5721
                $val = $this->parseText($val, $ph);
5722 View Code Duplication
                if (preg_match($regexUrl, $val, $url)) {
5723
                    $val = preg_replace($regexUrl, "<a href=\"{$url[0]}\" target=\"_blank\">{$url[0]}</a> ", $val);
5724
                }
5725 View Code Duplication
                if (preg_match($regexEmail, $val, $url)) {
5726
                    $val = preg_replace($regexEmail, '<a href="mailto:\\1' . $emailSubject . '">\\1</a>', $val);
5727
                }
5728
                $parsed[$key] = $val;
5729
            }
5730
        }
5731
5732
        $arrayParams = array(
5733
            'documentation' => $_lang['documentation'],
5734
            'reportissues' => $_lang['report_issues'],
5735
            'link' => $_lang['further_info'],
5736
            'author' => $_lang['author_infos']
5737
        );
5738
5739
        $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...
5740
        $list = isset($parsed['logo']) ? '<img src="' . $this->config['base_url'] . ltrim($parsed['logo'], "/") . '" style="float:right;max-width:100px;height:auto;" />' . $nl : '';
5741
        $list .= '<p>' . $nl;
5742
        $list .= isset($parsed['name']) ? '<strong>' . $parsed['name'] . '</strong><br/>' . $nl : '';
5743
        $list .= isset($parsed['description']) ? $parsed['description'] . $nl : '';
5744
        $list .= '</p><br/>' . $nl;
5745
        $list .= isset($parsed['version']) ? '<p><strong>' . $_lang['version'] . ':</strong> ' . $parsed['version'] . '</p>' . $nl : '';
5746
        $list .= isset($parsed['license']) ? '<p><strong>' . $_lang['license'] . ':</strong> ' . $parsed['license'] . '</p>' . $nl : '';
5747
        $list .= isset($parsed['lastupdate']) ? '<p><strong>' . $_lang['last_update'] . ':</strong> ' . $parsed['lastupdate'] . '</p>' . $nl : '';
5748
        $list .= '<br/>' . $nl;
5749
        $first = true;
5750
        foreach ($arrayParams as $param => $label) {
5751
            if (isset($parsed[$param])) {
5752
                if ($first) {
5753
                    $list .= '<p><strong>' . $_lang['references'] . '</strong></p>' . $nl;
5754
                    $list .= '<ul class="docBlockList">' . $nl;
5755
                    $first = false;
5756
                }
5757
                $list .= '    <li><strong>' . $label . '</strong>' . $nl;
5758
                $list .= '        <ul>' . $nl;
5759
                foreach ($parsed[$param] as $val) {
5760
                    $list .= '            <li>' . $val . '</li>' . $nl;
5761
                }
5762
                $list .= '        </ul></li>' . $nl;
5763
            }
5764
        }
5765
        $list .= !$first ? '</ul>' . $nl : '';
5766
5767
        return $list;
5768
    }
5769
5770
    /**
5771
     * @param string $string
5772
     * @return 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...
5773
     */
5774
    public function removeSanitizeSeed($string = '')
5775
    {
5776
        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...
5777
5778
        if (!$string || strpos($string, $sanitize_seed) === false) {
5779
            return $string;
5780
        }
5781
5782
        return str_replace($sanitize_seed, '', $string);
5783
    }
5784
5785
    /**
5786
     * @param string $content
5787
     * @return 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...
5788
     */
5789
    public function cleanUpMODXTags($content = '')
5790
    {
5791
        if ($this->minParserPasses < 1) {
5792
            return $content;
5793
        }
5794
5795
        $enable_filter = $this->config['enable_filter'];
5796
        $this->config['enable_filter'] = 1;
5797
        $_ = 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...
5798
        foreach ($_ as $brackets) {
5799
            list($left, $right) = explode(' ', $brackets);
5800
            if (strpos($content, $left) !== false) {
5801
                if ($left === '[*') {
5802
                    $content = $this->mergeDocumentContent($content);
5803
                } elseif ($left === '[(') {
5804
                    $content = $this->mergeSettingsContent($content);
5805
                } elseif ($left === '{{') {
5806
                    $content = $this->mergeChunkContent($content);
5807
                } elseif ($left === '[[') {
5808
                    $content = $this->evalSnippets($content);
5809
                }
5810
            }
5811
        }
5812
        foreach ($_ as $brackets) {
5813
            list($left, $right) = explode(' ', $brackets);
5814
            if (strpos($content, $left) !== false) {
5815
                $matches = $this->getTagsFromContent($content, $left, $right);
5816
                $content = str_replace($matches[0], '', $content);
5817
            }
5818
        }
5819
        $this->config['enable_filter'] = $enable_filter;
5820
        return $content;
5821
    }
5822
5823
    /**
5824
     * @param string $str
5825
     * @param string $allowable_tags
5826
     * @return string
5827
     */
5828
    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...
5829
    {
5830
        $str = strip_tags($str, $allowable_tags);
5831
        modx_sanitize_gpc($str);
5832
        return $str;
5833
    }
5834
5835
    /**
5836
     * @param $name
5837
     * @param $phpCode
5838
     */
5839
    public function addSnippet($name, $phpCode)
5840
    {
5841
        $this->snippetCache['#' . $name] = $phpCode;
5842
    }
5843
5844
    /**
5845
     * @param $name
5846
     * @param $text
5847
     */
5848
    public function addChunk($name, $text)
5849
    {
5850
        $this->chunkCache['#' . $name] = $text;
5851
    }
5852
5853
    /**
5854
     * @param string $phpcode
5855
     * @param string $evalmode
5856
     * @param string $safe_functions
5857
     * @return string|void
5858
     */
5859
    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...
5860
    {
5861
        if ($evalmode == '') {
5862
            $evalmode = $this->config['allow_eval'];
5863
        }
5864
        if ($safe_functions == '') {
5865
            $safe_functions = $this->config['safe_functions_at_eval'];
5866
        }
5867
5868
        modx_sanitize_gpc($phpcode);
5869
5870
        switch ($evalmode) {
5871
            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...
5872
                $isSafe = $this->isSafeCode($phpcode, $safe_functions);
5873
                break;
5874
            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...
5875
                $isSafe = $_POST ? $this->isSafeCode($phpcode, $safe_functions) : true;
5876
                break;
5877
            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...
5878
                $isSafe = true;
5879
                break; // Should debug only
5880
            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...
5881
            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...
5882
                return $phpcode;
5883
        }
5884
5885
        if (!$isSafe) {
5886
            $msg = $phpcode . "\n" . $this->currentSnippet . "\n" . print_r($_SERVER, true);
5887
            $title = sprintf('Unknown eval was executed (%s)', $this->htmlspecialchars(substr(trim($phpcode), 0, 50)));
5888
            $this->messageQuit($title, '', true, '', '', 'Parser', $msg);
5889
            return;
5890
        }
5891
5892
        ob_start();
5893
        $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...
5894
        $echo = ob_get_clean();
5895
5896
        if (is_array($return)) {
5897
            return 'array()';
5898
        }
5899
5900
        $output = $echo . $return;
5901
        modx_sanitize_gpc($output);
5902
        return $this->htmlspecialchars($output); // Maybe, all html tags are dangerous
5903
    }
5904
5905
    /**
5906
     * @param string $phpcode
5907
     * @param string $safe_functions
5908
     * @return bool
5909
     */
5910
    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...
5911
    { // 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...
5912
        if ($safe_functions == '') {
5913
            return false;
5914
        }
5915
5916
        $safe = explode(',', $safe_functions);
5917
5918
        $phpcode = rtrim($phpcode, ';') . ';';
5919
        $tokens = token_get_all('<?php ' . $phpcode);
5920
        foreach ($tokens as $i => $token) {
5921
            if (!is_array($token)) {
5922
                continue;
5923
            }
5924
            $tokens[$i]['token_name'] = token_name($token[0]);
5925
        }
5926
        foreach ($tokens as $token) {
5927
            if (!is_array($token)) {
5928
                continue;
5929
            }
5930
            switch ($token['token_name']) {
5931
                case 'T_STRING':
5932
                    if (!in_array($token[1], $safe)) {
5933
                        return false;
5934
                    }
5935
                    break;
5936
                case 'T_VARIABLE':
5937
                    if ($token[1] == '$GLOBALS') {
5938
                        return false;
5939
                    }
5940
                    break;
5941
                case 'T_EVAL':
5942
                    return false;
5943
            }
5944
        }
5945
        return true;
5946
    }
5947
5948
    /**
5949
     * @param string $str
5950
     * @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...
5951
     */
5952
    public function atBindFileContent($str = '')
5953
    {
5954
5955
        $search_path = array('assets/tvs/', 'assets/chunks/', 'assets/templates/', $this->config['rb_base_url'] . 'files/', '');
5956
5957
        if (stripos($str, '@FILE') !== 0) {
5958
            return $str;
5959
        }
5960 View Code Duplication
        if (strpos($str, "\n") !== false) {
5961
            $str = substr($str, 0, strpos("\n", $str));
5962
        }
5963
5964
        if ($this->getExtFromFilename($str) === '.php') {
5965
            return 'Could not retrieve PHP file.';
5966
        }
5967
5968
        $str = substr($str, 6);
5969
        $str = trim($str);
5970
        if (strpos($str, '\\') !== false) {
5971
            $str = str_replace('\\', '/', $str);
5972
        }
5973
        $str = ltrim($str, '/');
5974
5975
        $errorMsg = sprintf("Could not retrieve string '%s'.", $str);
5976
5977
        foreach ($search_path as $path) {
5978
            $file_path = MODX_BASE_PATH . $path . $str;
5979
            if (strpos($file_path, MODX_MANAGER_PATH) === 0) {
5980
                return $errorMsg;
5981
            } elseif (is_file($file_path)) {
5982
                break;
5983
            } else {
5984
                $file_path = false;
5985
            }
5986
        }
5987
5988
        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...
5989
            return $errorMsg;
5990
        }
5991
5992
        $content = (string)file_get_contents($file_path);
5993
        if ($content === false) {
5994
            return $errorMsg;
5995
        }
5996
5997
        return $content;
5998
    }
5999
6000
    /**
6001
     * @param $str
6002
     * @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...
6003
     */
6004
    public function getExtFromFilename($str)
6005
    {
6006
        $str = strtolower(trim($str));
6007
        $pos = strrpos($str, '.');
6008
        if ($pos === false) {
6009
            return false;
6010
        } else {
6011
            return substr($str, $pos);
6012
        }
6013
    }
6014
    /***************************************************************************************/
6015
    /* End of API functions                                       */
6016
    /***************************************************************************************/
6017
6018
    /**
6019
     * PHP error handler set by http://www.php.net/manual/en/function.set-error-handler.php
6020
     *
6021
     * Checks the PHP error and calls messageQuit() unless:
6022
     *  - error_reporting() returns 0, or
6023
     *  - the PHP error level is 0, or
6024
     *  - the PHP error level is 8 (E_NOTICE) and stopOnNotice is false
6025
     *
6026
     * @param int $nr The PHP error level as per http://www.php.net/manual/en/errorfunc.constants.php
6027
     * @param string $text Error message
6028
     * @param string $file File where the error was detected
6029
     * @param string $line Line number within $file
6030
     * @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...
6031
     */
6032
    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...
6033
    {
6034
        if (error_reporting() == 0 || $nr == 0) {
6035
            return true;
6036
        }
6037
        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...
6038
            switch ($nr) {
6039
                case E_NOTICE:
6040
                    if ($this->error_reporting <= 2) {
6041
                        return true;
6042
                    }
6043
                    $isError = false;
6044
                    $msg = 'PHP Minor Problem (this message show logged in only)';
6045
                    break;
6046
                case E_STRICT:
6047 View Code Duplication
                case E_DEPRECATED:
6048
                    if ($this->error_reporting <= 1) {
6049
                        return true;
6050
                    }
6051
                    $isError = true;
6052
                    $msg = 'PHP Strict Standards Problem';
6053
                    break;
6054 View Code Duplication
                default:
6055
                    if ($this->error_reporting === 0) {
6056
                        return true;
6057
                    }
6058
                    $isError = true;
6059
                    $msg = 'PHP Parse Error';
6060
            }
6061
        }
6062
        if (is_readable($file)) {
6063
            $source = file($file);
6064
            $source = $this->htmlspecialchars($source[$line - 1]);
6065
        } else {
6066
            $source = "";
6067
        } //Error $nr in $file at $line: <div><code>$source</code></div>
6068
6069
        $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...
6070
    }
6071
6072
    /**
6073
     * @param string $msg
6074
     * @param string $query
6075
     * @param bool $is_error
6076
     * @param string $nr
6077
     * @param string $file
6078
     * @param string $source
6079
     * @param string $text
6080
     * @param string $line
6081
     * @param string $output
6082
     * @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...
6083
     */
6084
    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...
6085
    {
6086
6087
        if (0 < $this->messageQuitCount) {
6088
            return;
6089
        }
6090
        $this->messageQuitCount++;
6091
6092
        if (!class_exists('makeTable')) {
6093
            include_once('extenders/maketable.class.php');
6094
        }
6095
        $MakeTable = new MakeTable();
6096
        $MakeTable->setTableClass('grid');
6097
        $MakeTable->setRowRegularClass('gridItem');
6098
        $MakeTable->setRowAlternateClass('gridAltItem');
6099
        $MakeTable->setColumnWidths(array('100px'));
6100
6101
        $table = array();
6102
6103
        $version = isset ($GLOBALS['modx_version']) ? $GLOBALS['modx_version'] : '';
6104
        $release_date = isset ($GLOBALS['release_date']) ? $GLOBALS['release_date'] : '';
6105
        $request_uri = "http://" . $_SERVER['HTTP_HOST'] . ($_SERVER["SERVER_PORT"] == 80 ? "" : (":" . $_SERVER["SERVER_PORT"])) . $_SERVER['REQUEST_URI'];
6106
        $request_uri = $this->htmlspecialchars($request_uri, ENT_QUOTES, $this->config['modx_charset']);
6107
        $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...
6108
        $referer = $this->htmlspecialchars($_SERVER['HTTP_REFERER'], ENT_QUOTES, $this->config['modx_charset']);
6109
        if ($is_error) {
6110
            $str = '<h2 style="color:red">&laquo; Evo Parse Error &raquo;</h2>';
6111
            if ($msg != 'PHP Parse Error') {
6112
                $str .= '<h3 style="color:red">' . $msg . '</h3>';
6113
            }
6114
        } else {
6115
            $str = '<h2 style="color:#003399">&laquo; Evo Debug/ stop message &raquo;</h2>';
6116
            $str .= '<h3 style="color:#003399">' . $msg . '</h3>';
6117
        }
6118
6119
        if (!empty ($query)) {
6120
            $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>';
6121
        }
6122
6123
        $errortype = array(
6124
            E_ERROR => "ERROR",
6125
            E_WARNING => "WARNING",
6126
            E_PARSE => "PARSING ERROR",
6127
            E_NOTICE => "NOTICE",
6128
            E_CORE_ERROR => "CORE ERROR",
6129
            E_CORE_WARNING => "CORE WARNING",
6130
            E_COMPILE_ERROR => "COMPILE ERROR",
6131
            E_COMPILE_WARNING => "COMPILE WARNING",
6132
            E_USER_ERROR => "USER ERROR",
6133
            E_USER_WARNING => "USER WARNING",
6134
            E_USER_NOTICE => "USER NOTICE",
6135
            E_STRICT => "STRICT NOTICE",
6136
            E_RECOVERABLE_ERROR => "RECOVERABLE ERROR",
6137
            E_DEPRECATED => "DEPRECATED",
6138
            E_USER_DEPRECATED => "USER DEPRECATED"
6139
        );
6140
6141
        if (!empty($nr) || !empty($file)) {
6142
            if ($text != '') {
6143
                $str .= '<div style="font-weight:bold;border:1px solid #ccc;padding:8px;color:#333;background-color:#ffffcd;margin-bottom:15px;">Error : ' . $text . '</div>';
6144
            }
6145
            if ($output != '') {
6146
                $str .= '<div style="font-weight:bold;border:1px solid #ccc;padding:8px;color:#333;background-color:#ffffcd;margin-bottom:15px;">' . $output . '</div>';
6147
            }
6148
            if ($nr !== '') {
6149
                $table[] = array('ErrorType[num]', $errortype [$nr] . "[" . $nr . "]");
6150
            }
6151
            if ($file) {
6152
                $table[] = array('File', $file);
6153
            }
6154
            if ($line) {
6155
                $table[] = array('Line', $line);
6156
            }
6157
6158
        }
6159
6160
        if ($source != '') {
6161
            $table[] = array("Source", $source);
6162
        }
6163
6164
        if (!empty($this->currentSnippet)) {
6165
            $table[] = array('Current Snippet', $this->currentSnippet);
6166
        }
6167
6168
        if (!empty($this->event->activePlugin)) {
6169
            $table[] = array('Current Plugin', $this->event->activePlugin . '(' . $this->event->name . ')');
6170
        }
6171
6172
        $str .= $MakeTable->create($table, array('Error information', ''));
6173
        $str .= "<br />";
6174
6175
        $table = array();
6176
        $table[] = array('REQUEST_URI', $request_uri);
6177
6178
        if ($this->manager->action) {
6179
            include_once(MODX_MANAGER_PATH . 'includes/actionlist.inc.php');
6180
            global $action_list;
1 ignored issue
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...
6181
            $actionName = (isset($action_list[$this->manager->action])) ? " - {$action_list[$this->manager->action]}" : '';
6182
6183
            $table[] = array('Manager action', $this->manager->action . $actionName);
6184
        }
6185
6186
        if (preg_match('@^[0-9]+@', $this->documentIdentifier)) {
6187
            $resource = $this->getDocumentObject('id', $this->documentIdentifier);
6188
            $url = $this->makeUrl($this->documentIdentifier, '', '', 'full');
6189
            $table[] = array('Resource', '[' . $this->documentIdentifier . '] <a href="' . $url . '" target="_blank">' . $resource['pagetitle'] . '</a>');
6190
        }
6191
        $table[] = array('Referer', $referer);
6192
        $table[] = array('User Agent', $ua);
6193
        $table[] = array('IP', $_SERVER['REMOTE_ADDR']);
6194
        $table[] = array('Current time', date("Y-m-d H:i:s", $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time']));
6195
        $str .= $MakeTable->create($table, array('Basic info', ''));
6196
        $str .= "<br />";
6197
6198
        $table = array();
6199
        $table[] = array('MySQL', '[^qt^] ([^q^] Requests)');
6200
        $table[] = array('PHP', '[^p^]');
6201
        $table[] = array('Total', '[^t^]');
6202
        $table[] = array('Memory', '[^m^]');
6203
        $str .= $MakeTable->create($table, array('Benchmarks', ''));
6204
        $str .= "<br />";
6205
6206
        $totalTime = ($this->getMicroTime() - $this->tstart);
6207
6208
        $mem = memory_get_peak_usage(true);
6209
        $total_mem = $mem - $this->mstart;
6210
        $total_mem = ($total_mem / 1024 / 1024) . ' mb';
6211
6212
        $queryTime = $this->queryTime;
6213
        $phpTime = $totalTime - $queryTime;
6214
        $queries = isset ($this->executedQueries) ? $this->executedQueries : 0;
6215
        $queryTime = sprintf("%2.4f s", $queryTime);
6216
        $totalTime = sprintf("%2.4f s", $totalTime);
6217
        $phpTime = sprintf("%2.4f s", $phpTime);
6218
6219
        $str = str_replace('[^q^]', $queries, $str);
6220
        $str = str_replace('[^qt^]', $queryTime, $str);
6221
        $str = str_replace('[^p^]', $phpTime, $str);
6222
        $str = str_replace('[^t^]', $totalTime, $str);
6223
        $str = str_replace('[^m^]', $total_mem, $str);
6224
6225
        if (isset($php_errormsg) && !empty($php_errormsg)) {
6226
            $str = "<b>{$php_errormsg}</b><br />\n{$str}";
6227
        }
6228
        $str .= $this->get_backtrace(debug_backtrace());
6229
        // Log error
6230
        if (!empty($this->currentSnippet)) {
6231
            $source = 'Snippet - ' . $this->currentSnippet;
6232
        } elseif (!empty($this->event->activePlugin)) {
6233
            $source = 'Plugin - ' . $this->event->activePlugin;
6234
        } elseif ($source !== '') {
6235
            $source = 'Parser - ' . $source;
6236
        } elseif ($query !== '') {
6237
            $source = 'SQL Query';
6238
        } else {
6239
            $source = 'Parser';
6240
        }
6241
        if ($msg) {
6242
            $source .= ' / ' . $msg;
6243
        }
6244
        if (isset($actionName) && !empty($actionName)) {
6245
            $source .= $actionName;
6246
        }
6247 View Code Duplication
        switch ($nr) {
6248
            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...
6249
            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...
6250
            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...
6251
            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...
6252
            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...
6253
                $error_level = 2;
6254
                break;
6255
            default:
6256
                $error_level = 3;
6257
        }
6258
        $this->logEvent(0, $error_level, $str, $source);
6259
6260
        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...
6261
            return true;
6262
        }
6263
        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...
6264
            return true;
6265
        }
6266
6267
        // Set 500 response header
6268
        if ($error_level !== 2) {
6269
            header('HTTP/1.1 500 Internal Server Error');
6270
        }
6271
6272
        // Display error
6273
        if (isset($_SESSION['mgrValidated'])) {
6274
            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>
6275
                 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
6276
                 <link rel="stylesheet" type="text/css" href="' . $this->config['site_manager_url'] . 'media/style/' . $this->config['manager_theme'] . '/style.css" />
6277
                 <style type="text/css">body { padding:10px; } td {font:inherit;}</style>
6278
                 </head><body>
6279
                 ' . $str . '</body></html>';
6280
6281
        } else {
6282
            echo 'Error';
6283
        }
6284
        ob_end_flush();
6285
        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...
6286
    }
6287
6288
    /**
6289
     * @param $backtrace
6290
     * @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...
6291
     */
6292
    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...
6293
    {
6294
        if (!class_exists('makeTable')) {
6295
            include_once('extenders/maketable.class.php');
6296
        }
6297
        $MakeTable = new MakeTable();
6298
        $MakeTable->setTableClass('grid');
6299
        $MakeTable->setRowRegularClass('gridItem');
6300
        $MakeTable->setRowAlternateClass('gridAltItem');
6301
        $table = array();
6302
        $backtrace = array_reverse($backtrace);
6303
        foreach ($backtrace as $key => $val) {
6304
            $key++;
6305
            if (substr($val['function'], 0, 11) === 'messageQuit') {
6306
                break;
6307
            } elseif (substr($val['function'], 0, 8) === 'phpError') {
6308
                break;
6309
            }
6310
            $path = str_replace('\\', '/', $val['file']);
6311
            if (strpos($path, MODX_BASE_PATH) === 0) {
6312
                $path = substr($path, strlen(MODX_BASE_PATH));
6313
            }
6314
            switch ($val['type']) {
6315
                case '->':
6316
                case '::':
6317
                    $functionName = $val['function'] = $val['class'] . $val['type'] . $val['function'];
6318
                    break;
6319
                default:
6320
                    $functionName = $val['function'];
6321
            }
6322
            $tmp = 1;
6323
            $_ = (!empty($val['args'])) ? count($val['args']) : 0;
6324
            $args = array_pad(array(), $_, '$var');
6325
            $args = implode(", ", $args);
6326
            $modx = &$this;
6327
            $args = preg_replace_callback('/\$var/', function () use ($modx, &$tmp, $val) {
6328
                $arg = $val['args'][$tmp - 1];
6329
                switch (true) {
6330
                    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...
6331
                        $out = 'NULL';
6332
                        break;
6333
                    }
6334
                    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...
6335
                        $out = $arg;
6336
                        break;
6337
                    }
6338
                    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...
6339
                        $out = strlen($arg) > 20 ? 'string $var' . $tmp : ("'" . $this->htmlspecialchars(str_replace("'", "\\'", $arg)) . "'");
6340
                        break;
6341
                    }
6342
                    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...
6343
                        $out = $arg ? 'TRUE' : 'FALSE';
6344
                        break;
6345
                    }
6346
                    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...
6347
                        $out = 'array $var' . $tmp;
6348
                        break;
6349
                    }
6350
                    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...
6351
                        $out = get_class($arg) . ' $var' . $tmp;
6352
                        break;
6353
                    }
6354
                    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...
6355
                        $out = '$var' . $tmp;
6356
                    }
6357
                }
6358
                $tmp++;
6359
                return $out;
6360
            }, $args);
6361
            $line = array(
6362
                "<strong>" . $functionName . "</strong>(" . $args . ")",
6363
                $path . " on line " . $val['line']
6364
            );
6365
            $table[] = array(implode("<br />", $line));
6366
        }
6367
        return $MakeTable->create($table, array('Backtrace'));
6368
    }
6369
6370
    /**
6371
     * @return string
6372
     */
6373
    public function getRegisteredClientScripts()
6374
    {
6375
        return implode("\n", $this->jscripts);
6376
    }
6377
6378
    /**
6379
     * @return string
6380
     */
6381
    public function getRegisteredClientStartupScripts()
6382
    {
6383
        return implode("\n", $this->sjscripts);
6384
    }
6385
6386
    /**
6387
     * Format alias to be URL-safe. Strip invalid characters.
6388
     *
6389
     * @param string $alias Alias to be formatted
6390
     * @return string Safe alias
6391
     */
6392
    public function stripAlias($alias)
6393
    {
6394
        // let add-ons overwrite the default behavior
6395
        $results = $this->invokeEvent('OnStripAlias', array('alias' => $alias));
6396
        if (!empty($results)) {
6397
            // if multiple plugins are registered, only the last one is used
6398
            return end($results);
6399
        } else {
6400
            // default behavior: strip invalid characters and replace spaces with dashes.
6401
            $alias = strip_tags($alias); // strip HTML
6402
            $alias = preg_replace('/[^\.A-Za-z0-9 _-]/', '', $alias); // strip non-alphanumeric characters
6403
            $alias = preg_replace('/\s+/', '-', $alias); // convert white-space to dash
6404
            $alias = preg_replace('/-+/', '-', $alias);  // convert multiple dashes to one
6405
            $alias = trim($alias, '-'); // trim excess
6406
            return $alias;
6407
        }
6408
    }
6409
6410
    /**
6411
     * @param $size
6412
     * @return string
6413
     */
6414
    public function nicesize($size)
6415
    {
6416
        $sizes = array('Tb' => 1099511627776, 'Gb' => 1073741824, 'Mb' => 1048576, 'Kb' => 1024, 'b' => 1);
6417
        $precisions = count($sizes) - 1;
6418
        foreach ($sizes as $unit => $bytes) {
6419
            if ($size >= $bytes) {
6420
                return number_format($size / $bytes, $precisions) . ' ' . $unit;
6421
            }
6422
            $precisions--;
6423
        }
6424
        return '0 b';
6425
    }
6426
6427
    /**
6428
     * @param $parentid
6429
     * @param $alias
6430
     * @return bool
6431
     */
6432
    public function getHiddenIdFromAlias($parentid, $alias)
6433
    {
6434
        $table = $this->getFullTableName('site_content');
6435
        $query = $this->db->query("SELECT sc.id, children.id AS child_id, children.alias, COUNT(children2.id) AS children_count 
6436
            FROM {$table} sc 
6437
            JOIN {$table} children ON children.parent = sc.id 
6438
            LEFT JOIN {$table} children2 ON children2.parent = children.id 
6439
            WHERE sc.parent = {$parentid} AND sc.alias_visible = '0' GROUP BY children.id;");
6440
6441
        while ($child = $this->db->getRow($query)) {
6442
            if ($child['alias'] == $alias || $child['child_id'] == $alias) {
6443
                return $child['child_id'];
6444
            }
6445
6446
            if ($child['children_count'] > 0) {
6447
                $id = $this->getHiddenIdFromAlias($child['id'], $alias);
6448
                if ($id) {
6449
                    return $id;
6450
                }
6451
            }
6452
        }
6453
6454
        return false;
6455
    }
6456
6457
    /**
6458
     * @param $alias
6459
     * @return bool|int
6460
     */
6461
    public function getIdFromAlias($alias)
6462
    {
6463
        if (isset($this->documentListing[$alias])) {
6464
            return $this->documentListing[$alias];
6465
        }
6466
6467
        $tbl_site_content = $this->getFullTableName('site_content');
6468
        if ($this->config['use_alias_path'] == 1) {
6469
            if ($alias == '.') {
6470
                return 0;
6471
            }
6472
6473
            if (strpos($alias, '/') !== false) {
6474
                $_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...
6475
            } else {
6476
                $_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...
6477
            }
6478
            $id = 0;
6479
6480
            foreach ($_a as $alias) {
6481
                if ($id === false) {
6482
                    break;
6483
                }
6484
                $alias = $this->db->escape($alias);
6485
                $rs = $this->db->select('id', $tbl_site_content, "deleted=0 and parent='{$id}' and alias='{$alias}'");
6486
                if ($this->db->getRecordCount($rs) == 0) {
6487
                    $rs = $this->db->select('id', $tbl_site_content, "deleted=0 and parent='{$id}' and id='{$alias}'");
6488
                }
6489
                $next = $this->db->getValue($rs);
6490
                $id = !$next ? $this->getHiddenIdFromAlias($id, $alias) : $next;
6491
            }
6492
        } else {
6493
            $rs = $this->db->select('id', $tbl_site_content, "deleted=0 and alias='{$alias}'", 'parent, menuindex');
6494
            $id = $this->db->getValue($rs);
6495
            if (!$id) {
6496
                $id = false;
6497
            }
6498
        }
6499
        return $id;
6500
    }
6501
6502
    /**
6503
     * @param string $str
6504
     * @return bool|mixed|string
6505
     */
6506
    public function atBindInclude($str = '')
6507
    {
6508
        if (strpos($str, '@INCLUDE') !== 0) {
6509
            return $str;
6510
        }
6511 View Code Duplication
        if (strpos($str, "\n") !== false) {
6512
            $str = substr($str, 0, strpos("\n", $str));
6513
        }
6514
6515
        $str = substr($str, 9);
6516
        $str = trim($str);
6517
        $str = str_replace('\\', '/', $str);
6518
        $str = ltrim($str, '/');
6519
6520
        $tpl_dir = 'assets/templates/';
6521
6522
        if (strpos($str, MODX_MANAGER_PATH) === 0) {
6523
            return false;
6524
        } elseif (is_file(MODX_BASE_PATH . $str)) {
6525
            $file_path = MODX_BASE_PATH . $str;
6526
        } elseif (is_file(MODX_BASE_PATH . "{$tpl_dir}{$str}")) {
6527
            $file_path = MODX_BASE_PATH . $tpl_dir . $str;
6528
        } else {
6529
            return false;
6530
        }
6531
6532
        if (!$file_path || !is_file($file_path)) {
6533
            return false;
6534
        }
6535
6536
        ob_start();
6537
        $modx = &$this;
6538
        $result = include($file_path);
6539
        if ($result === 1) {
6540
            $result = '';
6541
        }
6542
        $content = ob_get_clean();
6543
        if (!$content && $result) {
6544
            $content = $result;
6545
        }
6546
        return $content;
6547
    }
6548
6549
    // php compat
6550
6551
    /**
6552
     * @param $str
6553
     * @param int $flags
6554
     * @param string $encode
6555
     * @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...
6556
     */
6557
    public function htmlspecialchars($str, $flags = ENT_COMPAT, $encode = '')
6558
    {
6559
        $this->loadExtension('PHPCOMPAT');
6560
        return $this->phpcompat->htmlspecialchars($str, $flags, $encode);
6561
    }
6562
6563
    /**
6564
     * Sets ajaxMode to handle ajax-requests, error-msg etc correctly
6565
     *
6566
     * @param boolean $state State of ajaxMode
6567
     */
6568
    function setAjaxMode($state) {
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...
6569
        $this->ajaxMode = $state === true ? true : false;
6570
    }
6571
6572
    /**
6573
     * Send array as JSON-object and exit
6574
     *
6575
     * @param array $array Array to convert
6576
     */
6577
    function jsonResponse($array) {
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...
6578
        header('content-type: application/json');
6579
        exit(json_encode($array, JSON_FORCE_OBJECT | JSON_UNESCAPED_UNICODE));
0 ignored issues
show
Unused Code introduced by
The call to json_encode() has too many arguments starting with JSON_FORCE_OBJECT | JSON_UNESCAPED_UNICODE.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
Coding Style Compatibility introduced by
The method jsonResponse() 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...
6580
    }
6581
6582
    /**
6583
     * @param $string
6584
     * @param bool $returnData
6585
     * @return bool|mixed
6586
     */
6587
    public function isJson($string, $returnData = false)
6588
    {
6589
        $data = json_decode($string, true);
6590
        return (json_last_error() == JSON_ERROR_NONE) ? ($returnData ? $data : true) : false;
6591
    }
6592
6593
    /**
6594
     * @param $key
6595
     * @return array
6596
     */
6597
    public function splitKeyAndFilter($key)
6598
    {
6599
        if ($this->config['enable_filter'] == 1 && strpos($key, ':') !== false && stripos($key, '@FILE') !== 0) {
6600
            list($key, $modifiers) = explode(':', $key, 2);
6601
        } else {
6602
            $modifiers = false;
6603
        }
6604
6605
        $key = trim($key);
6606
        if ($modifiers !== false) {
6607
            $modifiers = trim($modifiers);
6608
        }
6609
6610
        return array($key, $modifiers);
6611
    }
6612
6613
    /**
6614
     * @param string $value
6615
     * @param bool $modifiers
6616
     * @param string $key
6617
     * @return string
6618
     */
6619
    public function applyFilter($value = '', $modifiers = false, $key = '')
6620
    {
6621
        if ($modifiers === false || $modifiers == 'raw') {
6622
            return $value;
6623
        }
6624
        if ($modifiers !== false) {
6625
            $modifiers = trim($modifiers);
6626
        }
6627
6628
        $this->loadExtension('MODIFIERS');
6629
        return $this->filter->phxFilter($key, $value, $modifiers);
6630
    }
6631
6632
    // End of class.
6633
6634
6635
    /**
6636
     * Get Clean Query String
6637
     *
6638
     * Fixes the issue where passing an array into the q get variable causes errors
6639
     *
6640
     */
6641
    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...
6642
    {
6643
        $q = $_GET['q'];
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $q. Configured minimum length is 3.

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

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