Completed
Pull Request — develop (#716)
by Agel_Nash
12:56
created

Core::executeParser()   F

Complexity

Conditions 20
Paths 1707

Size

Total Lines 127
Code Lines 68

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 20
eloc 68
nc 1707
nop 0
dl 0
loc 127
rs 2
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php namespace EvolutionCMS;
2
3
class Core implements Interfaces\CoreInterface
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...
4
{
5
    /**
6
     * This is New evolution
7
     * @var string
8
     */
9
    public $apiVersion = '1.0.0';
10
11
    /**
12
     * db object
13
     * @var \DBAPI
14
     * @see /manager/includes/extenders/ex_dbapi.inc.php
15
     * @example $this->loadExtension('DBAPI')
16
     */
17
    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...
18
19
    /**
20
     * @var \MODxMailer
21
     * @see /manager/includes/extenders/ex_modxmailer.inc.php
22
     * @example $this->loadExtension('MODxMailer');
23
     */
24
    public $mail;
25
26
    /**
27
     * @var \PHPCOMPAT
28
     * @see /manager/includes/extenders/ex_phpcompat.inc.php
29
     * @example $this->loadExtension('PHPCOMPAT');
30
     */
31
    public $phpcompat;
32
33
    /**
34
     * @var \MODIFIERS
35
     * @see /manager/includes/extenders/ex_modifiers.inc.php
36
     * @example $this->loadExtension('MODIFIERS');
37
     */
38
    public $filter;
39
40
    /**
41
     * @var \EXPORT_SITE
42
     * @see /manager/includes/extenders/ex_export_site.inc.php
43
     * @example $this->loadExtension('EXPORT_SITE');
44
     */
45
    public $export;
46
47
    /**
48
     * @var \MakeTable
49
     * @see /manager/includes/extenders/ex_maketable.inc.php
50
     * @example $this->loadExtension('makeTable');
51
     */
52
    public $table;
53
54
    /**
55
     * @var \ManagerAPI
56
     * @see /manager/includes/extenders/ex_managerapi.inc.php
57
     * @example $this->loadExtension('ManagerAPI');
58
     */
59
    public $manager;
60
61
    /**
62
     * @var \PasswordHash
63
     * @see manager/includes/extenders/ex_phpass.inc.php
64
     * @example $this->loadExtension('phpass');
65
     */
66
    public $phpass;
67
68
    /**
69
     * event object
70
     * @var Event
71
     */
72
73
    public $event;
74
    /**
75
     * event object
76
     * @var Event
77
     * @deprecated
78
     */
79
    public $Event;
80
81
    /**
82
     * @var array
83
     */
84
    public $pluginEvent = array();
85
86
    /**
87
     * @var array
88
     */
89
    public $config = array();
90
    /**
91
     * @var array
92
     */
93
    public $dbConfig = array();
94
    public $configGlobal = null; // contains backup of settings overwritten by user-settings
95
    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...
96
    public $result;
97
    public $sql;
98
    public $table_prefix;
99
    public $debug = false;
100
    public $documentIdentifier;
101
    public $documentMethod;
102
    public $documentGenerated;
103
    public $documentContent;
104
    public $documentOutput;
105
    public $tstart;
106
    public $mstart;
107
    public $minParserPasses;
108
    public $maxParserPasses;
109
    public $documentObject;
110
    public $templateObject;
111
    public $snippetObjects;
112
    public $stopOnNotice = false;
113
    public $executedQueries;
114
    public $queryTime;
115
    public $currentSnippet;
116
    public $documentName;
117
    public $aliases;
118
    public $visitor;
119
    public $entrypage;
120
    public $documentListing;
121
    /**
122
     * feed the parser the execution start time
123
     * @var bool
124
     */
125
    public $dumpSnippets = false;
126
    public $snippetsCode;
127
    public $snippetsTime = array();
128
    public $chunkCache;
129
    public $snippetCache;
130
    public $contentTypes;
131
    public $dumpSQL = false;
132
    public $queryCode;
133
    public $virtualDir;
134
    public $placeholders;
135
    public $sjscripts = array();
136
    public $jscripts = array();
137
    public $loadedjscripts = array();
138
    public $documentMap;
139
    public $forwards = 3;
140
    public $error_reporting = 1;
141
    public $dumpPlugins = false;
142
    public $pluginsCode;
143
    public $pluginsTime = array();
144
    public $pluginCache = array();
145
    public $aliasListing;
146
    public $lockedElements = null;
147
    public $tmpCache = array();
148
    private $version = array();
149
    public $extensions = array();
150
    public $cacheKey = null;
151
    public $recentUpdate = 0;
152
    public $useConditional = false;
153
    protected $systemCacheKey = null;
154
    public $snipLapCount = 0;
155
    public $messageQuitCount;
156
    public $time;
157
    public $sid;
158
    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...
159
    public $decoded_request_uri;
160
    /**
161
     * @var \OldFunctions
162
     */
163
    public $old;
164
165
    /**
166
     * Hold the class instance.
167
     * @var self
168
     */
169
    private static $instance = null;
170
171
    /**
172
     * Document constructor
173
     *
174
     * @return self
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...
175
     */
176
    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...
177
    {
178
        if ($this->isLoggedIn()) {
179
            ini_set('display_errors', 1);
180
        }
181
        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...
182
        if (substr(PHP_OS, 0, 3) === 'WIN' && $database_server === 'localhost') {
183
            $database_server = '127.0.0.1';
184
        }
185
        $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...
186
        $this->dbConfig = &$this->db->config; // alias for backward compatibility
187
        // events
188
        $this->event = new Event();
189
        $this->Event = &$this->event; //alias for backward compatibility
0 ignored issues
show
Deprecated Code introduced by
The property EvolutionCMS\Core::$Event has been deprecated.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
190
        // set track_errors ini variable
191
        @ ini_set("track_errors", "1"); // enable error tracking in $php_errormsg
192
        $this->time = $_SERVER['REQUEST_TIME']; // for having global timestamp
193
194
        $this->q = self::_getCleanQueryString();
195
    }
196
197
    final public function __clone()
198
    {
199
    }
200
    /**
201
     * @return self
202
     */
203
    public static function getInstance()
204
    {
205
        if (self::$instance === null) {
206
            self::$instance = new static();
207
        }
208
        return self::$instance;
209
    }
210
211
    /**
212
     * @param $method_name
213
     * @param $arguments
214
     * @return mixed
215
     */
216
    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...
217
    {
218
        include_once(MODX_MANAGER_PATH . 'includes/extenders/deprecated.functions.inc.php');
219
        if (method_exists($this->old, $method_name)) {
220
            $error_type = 1;
221
        } else {
222
            $error_type = 3;
223
        }
224
225
        if (!isset($this->config['error_reporting']) || 1 < $this->config['error_reporting']) {
226
            if ($error_type == 1) {
227
                $title = 'Call deprecated method';
228
                $msg = $this->htmlspecialchars("\$modx->{$method_name}() is deprecated function");
229
            } else {
230
                $title = 'Call undefined method';
231
                $msg = $this->htmlspecialchars("\$modx->{$method_name}() is undefined function");
232
            }
233
            $info = debug_backtrace();
234
            $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...
235
            if (!empty($this->currentSnippet)) {
236
                $m[] = 'Snippet - ' . $this->currentSnippet;
237
            } elseif (!empty($this->event->activePlugin)) {
238
                $m[] = 'Plugin - ' . $this->event->activePlugin;
239
            }
240
            $m[] = $this->decoded_request_uri;
241
            $m[] = str_replace('\\', '/', $info[0]['file']) . '(line:' . $info[0]['line'] . ')';
242
            $msg = implode('<br />', $m);
243
            $this->logEvent(0, $error_type, $msg, $title);
244
        }
245
        if (method_exists($this->old, $method_name)) {
246
            return call_user_func_array(array($this->old, $method_name), $arguments);
247
        }
248
    }
249
250
    /**
251
     * @param string $connector
252
     * @return bool
253
     */
254
    public function checkSQLconnect($connector = 'db')
255
    {
256
        $flag = false;
257
        if (is_scalar($connector) && !empty($connector) && isset($this->{$connector}) && $this->{$connector} instanceof DBAPI) {
0 ignored issues
show
Bug introduced by
The class EvolutionCMS\DBAPI does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
258
            $flag = (bool)$this->{$connector}->conn;
259
        }
260
        return $flag;
261
    }
262
263
    /**
264
     * Loads an extension from the extenders folder.
265
     * You can load any extension creating a boot file:
266
     * MODX_MANAGER_PATH."includes/extenders/ex_{$extname}.inc.php"
267
     * $extname - extension name in lowercase
268
     *
269
     * @param $extname
270
     * @param bool $reload
271
     * @return bool
272
     */
273
    public function loadExtension($extname, $reload = true)
274
    {
275
        $out = false;
276
        $flag = ($reload || !in_array($extname, $this->extensions));
277
        if ($this->checkSQLconnect('db') && $flag) {
278
            $evtOut = $this->invokeEvent('OnBeforeLoadExtension', array('name' => $extname, 'reload' => $reload));
279
            if (is_array($evtOut) && count($evtOut) > 0) {
280
                $out = array_pop($evtOut);
281
            }
282
        }
283
        if (!$out && $flag) {
284
            $extname = trim(str_replace(array('..', '/', '\\'), '', strtolower($extname)));
285
            $filename = MODX_MANAGER_PATH . "includes/extenders/ex_{$extname}.inc.php";
286
            $out = is_file($filename) ? include $filename : false;
287
        }
288
        if ($out && !in_array($extname, $this->extensions)) {
289
            $this->extensions[] = $extname;
290
        }
291
        return $out;
292
    }
293
294
    /**
295
     * Returns the current micro time
296
     *
297
     * @return float
298
     */
299
    public function getMicroTime()
300
    {
301
        list ($usec, $sec) = explode(' ', microtime());
302
        return ((float)$usec + (float)$sec);
303
    }
304
305
    /**
306
     * Redirect
307
     *
308
     * @param string $url
309
     * @param int $count_attempts
310
     * @param string $type $type
311
     * @param string $responseCode
312
     * @return bool|null
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use false|null.

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

Loading history...
313
     * @global string $base_url
314
     * @global string $site_url
315
     */
316
    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...
317
    {
318
        $header = '';
319
        if (empty ($url)) {
320
            return false;
321
        }
322
        if ($count_attempts == 1) {
323
            // append the redirect count string to the url
324
            $currentNumberOfRedirects = isset ($_REQUEST['err']) ? $_REQUEST['err'] : 0;
325
            if ($currentNumberOfRedirects > 3) {
326
                $this->messageQuit('Redirection attempt failed - please ensure the document you\'re trying to redirect to exists. <p>Redirection URL: <i>' . $url . '</i></p>');
327
            } else {
328
                $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...
329
                if (strpos($url, "?") > 0) {
330
                    $url .= "&err=$currentNumberOfRedirects";
331
                } else {
332
                    $url .= "?err=$currentNumberOfRedirects";
333
                }
334
            }
335
        }
336
        if ($type == 'REDIRECT_REFRESH') {
337
            $header = 'Refresh: 0;URL=' . $url;
338
        } elseif ($type == 'REDIRECT_META') {
339
            $header = '<META HTTP-EQUIV="Refresh" CONTENT="0; URL=' . $url . '" />';
340
            echo $header;
341
            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...
342
        } elseif ($type == 'REDIRECT_HEADER' || empty ($type)) {
343
            // check if url has /$base_url
344
            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...
345
            if (substr($url, 0, strlen($base_url)) == $base_url) {
346
                // append $site_url to make it work with Location:
347
                $url = $site_url . substr($url, strlen($base_url));
348
            }
349
            if (strpos($url, "\n") === false) {
350
                $header = 'Location: ' . $url;
351
            } else {
352
                $this->messageQuit('No newline allowed in redirect url.');
353
            }
354
        }
355
        if ($responseCode && (strpos($responseCode, '30') !== false)) {
356
            header($responseCode);
357
        }
358
359
        if(!empty($header)) {
360
            header($header);
361
        }
362
363
        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...
364
    }
365
366
    /**
367
     * Forward to another page
368
     *
369
     * @param int|string $id
370
     * @param string $responseCode
371
     */
372
    public function sendForward($id, $responseCode = '')
373
    {
374
        if ($this->forwards > 0) {
375
            $this->forwards = $this->forwards - 1;
376
            $this->documentIdentifier = $id;
377
            $this->documentMethod = 'id';
378
            if ($responseCode) {
379
                header($responseCode);
380
            }
381
            $this->prepareResponse();
382
            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...
383
        } else {
384
            $this->messageQuit("Internal Server Error id={$id}");
385
            header('HTTP/1.0 500 Internal Server Error');
386
            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...
387
        }
388
    }
389
390
    /**
391
     * Redirect to the error page, by calling sendForward(). This is called for example when the page was not found.
392
     * @param bool $noEvent
393
     */
394
    public function sendErrorPage($noEvent = false)
395
    {
396
        $this->systemCacheKey = 'notfound';
397
        if (!$noEvent) {
398
            // invoke OnPageNotFound event
399
            $this->invokeEvent('OnPageNotFound');
400
        }
401
        $url = $this->config['error_page'] ? $this->config['error_page'] : $this->config['site_start'];
402
403
        $this->sendForward($url, 'HTTP/1.0 404 Not Found');
404
        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...
405
    }
406
407
    /**
408
     * @param bool $noEvent
409
     */
410
    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...
411
    {
412
        // invoke OnPageUnauthorized event
413
        $_REQUEST['refurl'] = $this->documentIdentifier;
414
        $this->systemCacheKey = 'unauth';
415
        if (!$noEvent) {
416
            $this->invokeEvent('OnPageUnauthorized');
417
        }
418
        if ($this->config['unauthorized_page']) {
419
            $unauthorizedPage = $this->config['unauthorized_page'];
420
        } elseif ($this->config['error_page']) {
421
            $unauthorizedPage = $this->config['error_page'];
422
        } else {
423
            $unauthorizedPage = $this->config['site_start'];
424
        }
425
        $this->sendForward($unauthorizedPage, 'HTTP/1.1 401 Unauthorized');
426
        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...
427
    }
428
429
    /**
430
     * Get MODX settings including, but not limited to, the system_settings table
431
     */
432
    public function getSettings()
433
    {
434
        if (!isset($this->config['site_name'])) {
435
            $this->recoverySiteCache();
436
        }
437
438
        // setup default site id - new installation should generate a unique id for the site.
439
        if (!isset($this->config['site_id'])) {
440
            $this->config['site_id'] = "MzGeQ2faT4Dw06+U49x3";
441
        }
442
443
        // store base_url and base_path inside config array
444
        $this->config['base_url'] = MODX_BASE_URL;
445
        $this->config['base_path'] = MODX_BASE_PATH;
446
        $this->config['site_url'] = MODX_SITE_URL;
447
        $this->config['valid_hostnames'] = MODX_SITE_HOSTNAMES;
448
        $this->config['site_manager_url'] = MODX_MANAGER_URL;
449
        $this->config['site_manager_path'] = MODX_MANAGER_PATH;
450
        $this->error_reporting = $this->config['error_reporting'];
451
        $this->config['filemanager_path'] = str_replace('[(base_path)]', MODX_BASE_PATH, $this->config['filemanager_path']);
452
        $this->config['rb_base_dir'] = str_replace('[(base_path)]', MODX_BASE_PATH, $this->config['rb_base_dir']);
453
454
        if (!isset($this->config['enable_at_syntax'])) {
455
            $this->config['enable_at_syntax'] = 1;
456
        } // @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...
457
458
        // now merge user settings into evo-configuration
459
        $this->getUserSettings();
460
    }
461
462
    private function recoverySiteCache()
463
    {
464
        $site_cache_dir = MODX_BASE_PATH . $this->getCacheFolder();
465
        $site_cache_path = $site_cache_dir . 'siteCache.idx.php';
466
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
        $cache = new Cache();
475
        $cache->setCachepath($site_cache_dir);
476
        $cache->setReport(false);
477
        $cache->buildCache($this);
0 ignored issues
show
Documentation introduced by
$this is of type this<EvolutionCMS\Core>, but the function expects a object<EvolutionCMS\DocumentParser>.

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...
478
479
        clearstatcache();
480
        if (is_file($site_cache_path)) {
481
            include($site_cache_path);
482
        }
483
        if (isset($this->config['site_name'])) {
484
            return;
485
        }
486
487
        $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...
488
        while ($row = $this->db->getRow($rs)) {
489
            $this->config[$row['setting_name']] = $row['setting_value'];
490
        }
491
492
        if (!$this->config['enable_filter']) {
493
            return;
494
        }
495
496
        $where = "plugincode LIKE '%phx.parser.class.inc.php%OnParseDocument();%' AND disabled != 1";
497
        $rs = $this->db->select('id', '[+prefix+]site_plugins', $where);
498
        if ($this->db->getRecordCount($rs)) {
499
            $this->config['enable_filter'] = '0';
500
        }
501
    }
502
503
    /**
504
     * Get user settings and merge into MODX configuration
505
     * @return array
506
     */
507
    public function getUserSettings()
0 ignored issues
show
Coding Style introduced by
getUserSettings uses the super-global variable $_SESSION which is generally not recommended.

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

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

// Better
class Router
{
    private $host;

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

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

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

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
508
    {
509
        $tbl_web_user_settings = $this->getFullTableName('web_user_settings');
510
        $tbl_user_settings = $this->getFullTableName('user_settings');
511
512
        // load user setting if user is logged in
513
        $usrSettings = array();
514
        if ($id = $this->getLoginUserID()) {
515
            $usrType = $this->getLoginUserType();
516
            if (isset ($usrType) && $usrType == 'manager') {
517
                $usrType = 'mgr';
518
            }
519
520
            if ($usrType == 'mgr' && $this->isBackend()) {
521
                // invoke the OnBeforeManagerPageInit event, only if in backend
522
                $this->invokeEvent("OnBeforeManagerPageInit");
523
            }
524
525
            if (isset ($_SESSION[$usrType . 'UsrConfigSet'])) {
526
                $usrSettings = &$_SESSION[$usrType . 'UsrConfigSet'];
527
            } else {
528
                if ($usrType == 'web') {
529
                    $from = $tbl_web_user_settings;
530
                    $where = "webuser='{$id}'";
531
                } else {
532
                    $from = $tbl_user_settings;
533
                    $where = "user='{$id}'";
534
                }
535
536
                $which_browser_default = $this->configGlobal['which_browser'] ? $this->configGlobal['which_browser'] : $this->config['which_browser'];
537
538
                $result = $this->db->select('setting_name, setting_value', $from, $where);
539
                while ($row = $this->db->getRow($result)) {
540 View Code Duplication
                    if ($row['setting_name'] == 'which_browser' && $row['setting_value'] == 'default') {
541
                        $row['setting_value'] = $which_browser_default;
542
                    }
543
                    $usrSettings[$row['setting_name']] = $row['setting_value'];
544
                }
545
                if (isset ($usrType)) {
546
                    $_SESSION[$usrType . 'UsrConfigSet'] = $usrSettings;
547
                } // store user settings in session
548
            }
549
        }
550
        if ($this->isFrontend() && $mgrid = $this->getLoginUserID('mgr')) {
551
            $musrSettings = array();
552
            if (isset ($_SESSION['mgrUsrConfigSet'])) {
553
                $musrSettings = &$_SESSION['mgrUsrConfigSet'];
554
            } else {
555
                if ($result = $this->db->select('setting_name, setting_value', $tbl_user_settings, "user='{$mgrid}'")) {
556
                    while ($row = $this->db->getRow($result)) {
557
                        $musrSettings[$row['setting_name']] = $row['setting_value'];
558
                    }
559
                    $_SESSION['mgrUsrConfigSet'] = $musrSettings; // store user settings in session
560
                }
561
            }
562
            if (!empty ($musrSettings)) {
563
                $usrSettings = array_merge($musrSettings, $usrSettings);
564
            }
565
        }
566
        // save global values before overwriting/merging array
567
        foreach ($usrSettings as $param => $value) {
568
            if (isset($this->config[$param])) {
569
                $this->configGlobal[$param] = $this->config[$param];
570
            }
571
        }
572
573
        $this->config = array_merge($this->config, $usrSettings);
574
        $this->config['filemanager_path'] = str_replace('[(base_path)]', MODX_BASE_PATH, $this->config['filemanager_path']);
575
        $this->config['rb_base_dir'] = str_replace('[(base_path)]', MODX_BASE_PATH, $this->config['rb_base_dir']);
576
577
        return $usrSettings;
578
    }
579
580
    /**
581
     * Returns the document identifier of the current request
582
     *
583
     * @param string $method id and alias are allowed
584
     * @return int
585
     */
586
    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...
587
    {
588
        // function to test the query and find the retrieval method
589
        if ($method === 'alias') {
590
            return $this->db->escape($_REQUEST['q']);
591
        }
592
593
        $id_ = filter_input(INPUT_GET, 'id');
594
        if ($id_) {
595
            if (preg_match('@^[1-9][0-9]*$@', $id_)) {
596
                return $id_;
597
            } else {
598
                $this->sendErrorPage();
599
            }
600
        } elseif (strpos($_SERVER['REQUEST_URI'], 'index.php/') !== false) {
601
            $this->sendErrorPage();
602
        } else {
603
            return $this->config['site_start'];
604
        }
605
    }
606
607
    /**
608
     * Check for manager or webuser login session since v1.2
609
     *
610
     * @param string $context
611
     * @return bool
612
     */
613
    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...
614
    {
615
        if (substr($context, 0, 1) == 'm') {
616
            $_ = '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...
617
        } else {
618
            $_ = 'webValidated';
619
        }
620
621
        if (MODX_CLI || (isset($_SESSION[$_]) && !empty($_SESSION[$_]))) {
0 ignored issues
show
Coding Style introduced by
The if-else statement can be simplified to return MODX_CLI || isset... !empty($_SESSION[$_]);.
Loading history...
622
            return true;
623
        } else {
624
            return false;
625
        }
626
    }
627
628
    /**
629
     * Check for manager login session
630
     *
631
     * @return boolean
632
     */
633
    public function checkSession()
634
    {
635
        return $this->isLoggedin();
636
    }
637
638
    /**
639
     * Checks, if a the result is a preview
640
     *
641
     * @return boolean
642
     */
643
    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...
644
    {
645
        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...
646
            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...
647
                return true;
648
            } else {
649
                return false;
650
            }
651
        } else {
652
            return false;
653
        }
654
    }
655
656
    /**
657
     * check if site is offline
658
     *
659
     * @return boolean
660
     */
661
    public function checkSiteStatus()
662
    {
663
        if ($this->config['site_status']) {
664
            return true;
665
        }  // site online
666
        elseif ($this->isLoggedin()) {
667
            return true;
668
        }  // site offline but launched via the manager
669
        else {
670
            return false;
671
        } // site is offline
672
    }
673
674
    /**
675
     * Create a 'clean' document identifier with path information, friendly URL suffix and prefix.
676
     *
677
     * @param string $qOrig
678
     * @return string
679
     */
680
    public function cleanDocumentIdentifier($qOrig)
681
    {
682
        if (!$qOrig) {
683
            $qOrig = $this->config['site_start'];
684
        }
685
        $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...
686
687
        $pre = $this->config['friendly_url_prefix'];
688
        $suf = $this->config['friendly_url_suffix'];
689
        $pre = preg_quote($pre, '/');
690
        $suf = preg_quote($suf, '/');
691 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...
692
            $q = $_[1];
693
        }
694 View Code Duplication
        if ($suf && preg_match('@(.*)' . $suf . '$@', $q, $_)) {
695
            $q = $_[1];
696
        }
697
698
        /* First remove any / before or after */
699
        $q = trim($q, '/');
700
701
        /* Save path if any */
702
        /* FS#476 and FS#308: only return virtualDir if friendly paths are enabled */
703
        if ($this->config['use_alias_path'] == 1) {
704
            $_ = strrpos($q, '/');
705
            $this->virtualDir = $_ !== false ? substr($q, 0, $_) : '';
706
            if ($_ !== false) {
707
                $q = preg_replace('@.*/@', '', $q);
708
            }
709
        } else {
710
            $this->virtualDir = '';
711
        }
712
713
        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 */
714
            /* FS#476 and FS#308: check that id is valid in terms of virtualDir structure */
715
            if ($this->config['use_alias_path'] == 1) {
716
                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))))) {
717
                    $this->documentMethod = 'id';
718
                    return $q;
719
                } else { /* not a valid id in terms of virtualDir, treat as alias */
720
                    $this->documentMethod = 'alias';
721
                    return $q;
722
                }
723
            } else {
724
                $this->documentMethod = 'id';
725
                return $q;
726
            }
727
        } else { /* we didn't get an ID back, so instead we assume it's an alias */
728
            if ($this->config['friendly_alias_urls'] != 1) {
729
                $q = $qOrig;
730
            }
731
            $this->documentMethod = 'alias';
732
            return $q;
733
        }
734
    }
735
736
    /**
737
     * @return string
738
     */
739
    public function getCacheFolder()
740
    {
741
        return "assets/cache/";
742
    }
743
744
    /**
745
     * @param $key
746
     * @return string
747
     */
748
    public function getHashFile($key)
749
    {
750
        return $this->getCacheFolder() . "docid_" . $key . ".pageCache.php";
751
    }
752
753
    /**
754
     * @param $id
755
     * @return array|mixed|null|string
756
     */
757
    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...
758
        $hash = $id;
759
        $tmp = null;
760
        $params = array();
761
        if(!empty($this->systemCacheKey)){
762
            $hash = $this->systemCacheKey;
763
        }else {
764
            if (!empty($_GET)) {
765
                // Sort GET parameters so that the order of parameters on the HTTP request don't affect the generated cache ID.
766
                $params = $_GET;
767
                ksort($params);
768
                $hash .= '_'.md5(http_build_query($params));
769
            }
770
        }
771
        $evtOut = $this->invokeEvent("OnMakePageCacheKey", array ("hash" => $hash, "id" => $id, 'params' => $params));
772
        if (is_array($evtOut) && count($evtOut) > 0){
773
            $tmp = array_pop($evtOut);
774
        }
775
        return empty($tmp) ? $hash : $tmp;
776
    }
777
778
    /**
779
     * @param $id
780
     * @param bool $loading
781
     * @return string
782
     */
783
    public function checkCache($id, $loading = false)
784
    {
785
        return $this->getDocumentObjectFromCache($id, $loading);
786
    }
787
788
    /**
789
     * Check the cache for a specific document/resource
790
     *
791
     * @param int $id
792
     * @param bool $loading
793
     * @return string
794
     */
795
    public function getDocumentObjectFromCache($id, $loading = false)
796
    {
797
        $key = ($this->config['cache_type'] == 2) ? $this->makePageCacheKey($id) : $id;
798
        if ($loading) {
799
            $this->cacheKey = $key;
800
        }
801
802
        $cache_path = $this->getHashFile($key);
803
804
        if (!is_file($cache_path)) {
805
            $this->documentGenerated = 1;
806
            return '';
807
        }
808
        $content = file_get_contents($cache_path, false);
809
        if (substr($content, 0, 5) === '<?php') {
810
            $content = substr($content, strpos($content, '?>') + 2);
811
        } // remove php header
812
        $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...
813
        if (count($a) == 1) {
814
            $result = $a[0];
815
        } // return only document content
816
        else {
817
            $docObj = unserialize($a[0]); // rebuild document object
818
            // check page security
819
            if ($docObj['privateweb'] && isset ($docObj['__MODxDocGroups__'])) {
820
                $pass = false;
821
                $usrGrps = $this->getUserDocGroups();
822
                $docGrps = explode(',', $docObj['__MODxDocGroups__']);
823
                // check is user has access to doc groups
824
                if (is_array($usrGrps)) {
825
                    foreach ($usrGrps as $k => $v) {
826
                        if (!in_array($v, $docGrps)) {
827
                            continue;
828
                        }
829
                        $pass = true;
830
                        break;
831
                    }
832
                }
833
                // diplay error pages if user has no access to cached doc
834
                if (!$pass) {
835
                    if ($this->config['unauthorized_page']) {
836
                        // check if file is not public
837
                        $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...
838
                        $total = $this->db->getValue($rs);
839
                    } else {
840
                        $total = 0;
841
                    }
842
843
                    if ($total > 0) {
844
                        $this->sendUnauthorizedPage();
845
                    } else {
846
                        $this->sendErrorPage();
847
                    }
848
849
                    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...
850
                }
851
            }
852
            // Grab the Scripts
853
            if (isset($docObj['__MODxSJScripts__'])) {
854
                $this->sjscripts = $docObj['__MODxSJScripts__'];
855
            }
856
            if (isset($docObj['__MODxJScripts__'])) {
857
                $this->jscripts = $docObj['__MODxJScripts__'];
858
            }
859
860
            // Remove intermediate variables
861
            unset($docObj['__MODxDocGroups__'], $docObj['__MODxSJScripts__'], $docObj['__MODxJScripts__']);
862
863
            $this->documentObject = $docObj;
864
865
            $result = $a[1]; // return document content
866
        }
867
868
        $this->documentGenerated = 0;
869
        // invoke OnLoadWebPageCache  event
870
        $this->documentContent = $result;
871
        $this->invokeEvent('OnLoadWebPageCache');
872
        return $result;
873
    }
874
875
    /**
876
     * Final processing and output of the document/resource.
877
     *
878
     * - runs uncached snippets
879
     * - add javascript to <head>
880
     * - removes unused placeholders
881
     * - converts URL tags [~...~] to URLs
882
     *
883
     * @param boolean $noEvent Default: false
884
     */
885
    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...
886
    {
887
        $this->documentOutput = $this->documentContent;
888
889
        if ($this->documentGenerated == 1 && $this->documentObject['cacheable'] == 1 && $this->documentObject['type'] == 'document' && $this->documentObject['published'] == 1) {
890
            if (!empty($this->sjscripts)) {
891
                $this->documentObject['__MODxSJScripts__'] = $this->sjscripts;
892
            }
893
            if (!empty($this->jscripts)) {
894
                $this->documentObject['__MODxJScripts__'] = $this->jscripts;
895
            }
896
        }
897
898
        // check for non-cached snippet output
899
        if (strpos($this->documentOutput, '[!') > -1) {
900
            $this->recentUpdate = $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'];
901
902
            $this->documentOutput = str_replace('[!', '[[', $this->documentOutput);
903
            $this->documentOutput = str_replace('!]', ']]', $this->documentOutput);
904
905
            // Parse document source
906
            $this->documentOutput = $this->parseDocumentSource($this->documentOutput);
907
        }
908
909
        // Moved from prepareResponse() by sirlancelot
910
        // Insert Startup jscripts & CSS scripts into template - template must have a <head> tag
911
        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...
912
            // change to just before closing </head>
913
            // $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...
914
            $this->documentOutput = preg_replace("/(<\/head>)/i", $js . "\n\\1", $this->documentOutput);
915
        }
916
917
        // Insert jscripts & html block into template - template must have a </body> tag
918
        if ($js = $this->getRegisteredClientScripts()) {
919
            $this->documentOutput = preg_replace("/(<\/body>)/i", $js . "\n\\1", $this->documentOutput);
920
        }
921
        // End fix by sirlancelot
922
923
        $this->documentOutput = $this->cleanUpMODXTags($this->documentOutput);
924
925
        $this->documentOutput = $this->rewriteUrls($this->documentOutput);
926
927
        // send out content-type and content-disposition headers
928
        if (IN_PARSER_MODE == "true") {
929
            $type = !empty ($this->contentTypes[$this->documentIdentifier]) ? $this->contentTypes[$this->documentIdentifier] : "text/html";
930
            header('Content-Type: ' . $type . '; charset=' . $this->config['modx_charset']);
931
            //            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...
932
            //                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...
933
            if (!$this->checkPreview() && $this->documentObject['content_dispo'] == 1) {
934
                if ($this->documentObject['alias']) {
935
                    $name = $this->documentObject['alias'];
936
                } else {
937
                    // strip title of special characters
938
                    $name = $this->documentObject['pagetitle'];
939
                    $name = strip_tags($name);
940
                    $name = $this->cleanUpMODXTags($name);
941
                    $name = strtolower($name);
942
                    $name = preg_replace('/&.+?;/', '', $name); // kill entities
943
                    $name = preg_replace('/[^\.%a-z0-9 _-]/', '', $name);
944
                    $name = preg_replace('/\s+/', '-', $name);
945
                    $name = preg_replace('|-+|', '-', $name);
946
                    $name = trim($name, '-');
947
                }
948
                $header = 'Content-Disposition: attachment; filename=' . $name;
949
                header($header);
950
            }
951
        }
952
        $this->setConditional();
953
954
        $stats = $this->getTimerStats($this->tstart);
955
956
        $out =& $this->documentOutput;
957
        $out = str_replace("[^q^]", $stats['queries'], $out);
958
        $out = str_replace("[^qt^]", $stats['queryTime'], $out);
959
        $out = str_replace("[^p^]", $stats['phpTime'], $out);
960
        $out = str_replace("[^t^]", $stats['totalTime'], $out);
961
        $out = str_replace("[^s^]", $stats['source'], $out);
962
        $out = str_replace("[^m^]", $stats['phpMemory'], $out);
963
        //$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...
964
965
        // invoke OnWebPagePrerender event
966
        if (!$noEvent) {
967
            $evtOut = $this->invokeEvent('OnWebPagePrerender', array('documentOutput' => $this->documentOutput));
968
            if (is_array($evtOut) && count($evtOut) > 0) {
969
                $this->documentOutput = $evtOut['0'];
970
            }
971
        }
972
973
        $this->documentOutput = $this->removeSanitizeSeed($this->documentOutput);
974
975
        if (strpos($this->documentOutput, '\{') !== false) {
976
            $this->documentOutput = $this->RecoveryEscapedTags($this->documentOutput);
977
        } elseif (strpos($this->documentOutput, '\[') !== false) {
978
            $this->documentOutput = $this->RecoveryEscapedTags($this->documentOutput);
979
        }
980
981
        echo $this->documentOutput;
982
983
        if ($this->dumpSQL) {
984
            echo $this->queryCode;
985
        }
986
        if ($this->dumpSnippets) {
987
            $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...
988
            $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...
989
            foreach ($this->snippetsTime as $s => $v) {
990
                $t = $v['time'];
991
                $sname = $v['sname'];
992
                $sc .= sprintf("%s. %s (%s)<br>", $s, $sname, sprintf("%2.2f ms", $t)); // currentSnippet
993
                $tt += $t;
994
            }
995
            echo "<fieldset><legend><b>Snippets</b> (" . count($this->snippetsTime) . " / " . sprintf("%2.2f ms", $tt) . ")</legend>{$sc}</fieldset><br />";
996
            echo $this->snippetsCode;
997
        }
998
        if ($this->dumpPlugins) {
999
            $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...
1000
            $tt = 0;
1001
            foreach ($this->pluginsTime as $s => $t) {
1002
                $ps .= "$s (" . sprintf("%2.2f ms", $t * 1000) . ")<br>";
1003
                $tt += $t;
1004
            }
1005
            echo "<fieldset><legend><b>Plugins</b> (" . count($this->pluginsTime) . " / " . sprintf("%2.2f ms", $tt * 1000) . ")</legend>{$ps}</fieldset><br />";
1006
            echo $this->pluginsCode;
1007
        }
1008
1009
        ob_end_flush();
1010
    }
1011
1012
    /**
1013
     * @param $contents
1014
     * @return mixed
1015
     */
1016
    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...
1017
    {
1018
        list($sTags, $rTags) = $this->getTagsForEscape();
1019
        return str_replace($rTags, $sTags, $contents);
1020
    }
1021
1022
    /**
1023
     * @param string $tags
1024
     * @return array[]
1025
     */
1026
    public function getTagsForEscape($tags = '{{,}},[[,]],[!,!],[*,*],[(,)],[+,+],[~,~],[^,^]')
1027
    {
1028
        $srcTags = explode(',', $tags);
1029
        $repTags = array();
1030
        foreach ($srcTags as $tag) {
1031
            $repTags[] = '\\' . $tag[0] . '\\' . $tag[1];
1032
        }
1033
        return array($srcTags, $repTags);
1034
    }
1035
1036
    /**
1037
     * @param $tstart
1038
     * @return array
1039
     */
1040
    public function getTimerStats($tstart)
1041
    {
1042
        $stats = array();
1043
1044
        $stats['totalTime'] = ($this->getMicroTime() - $tstart);
1045
        $stats['queryTime'] = $this->queryTime;
1046
        $stats['phpTime'] = $stats['totalTime'] - $stats['queryTime'];
1047
1048
        $stats['queryTime'] = sprintf("%2.4f s", $stats['queryTime']);
1049
        $stats['totalTime'] = sprintf("%2.4f s", $stats['totalTime']);
1050
        $stats['phpTime'] = sprintf("%2.4f s", $stats['phpTime']);
1051
        $stats['source'] = $this->documentGenerated == 1 ? "database" : "cache";
1052
        $stats['queries'] = isset ($this->executedQueries) ? $this->executedQueries : 0;
1053
        $stats['phpMemory'] = (memory_get_peak_usage(true) / 1024 / 1024) . " mb";
1054
1055
        return $stats;
1056
    }
1057
1058
    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...
1059
    {
1060
        if (!empty($_POST) || (defined('MODX_API_MODE') && MODX_API_MODE) || $this->getLoginUserID('mgr') || !$this->useConditional || empty($this->recentUpdate)) {
1061
            return;
1062
        }
1063
        $last_modified = gmdate('D, d M Y H:i:s T', $this->recentUpdate);
1064
        $etag = md5($last_modified);
1065
        $HTTP_IF_MODIFIED_SINCE = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? $_SERVER['HTTP_IF_MODIFIED_SINCE'] : false;
1066
        $HTTP_IF_NONE_MATCH = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? $_SERVER['HTTP_IF_NONE_MATCH'] : false;
1067
        header('Pragma: no-cache');
1068
1069
        if ($HTTP_IF_MODIFIED_SINCE == $last_modified || strpos($HTTP_IF_NONE_MATCH, $etag) !== false) {
1070
            header('HTTP/1.1 304 Not Modified');
1071
            header('Content-Length: 0');
1072
            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...
1073
        } else {
1074
            header("Last-Modified: {$last_modified}");
1075
            header("ETag: '{$etag}'");
1076
        }
1077
    }
1078
1079
    /**
1080
     * Checks the publish state of page
1081
     */
1082
    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...
1083
    {
1084
        $cacheRefreshTime = 0;
1085
        $recent_update = 0;
1086
        @include(MODX_BASE_PATH . $this->getCacheFolder() . 'sitePublishing.idx.php');
1087
        $this->recentUpdate = $recent_update;
1088
1089
        $timeNow = $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'];
1090
        if ($timeNow < $cacheRefreshTime || $cacheRefreshTime == 0) {
1091
            return;
1092
        }
1093
1094
        // now, check for documents that need publishing
1095
        $field = array('published' => 1, 'publishedon' => $timeNow);
1096
        $where = "pub_date <= {$timeNow} AND pub_date!=0 AND published=0";
1097
        $result_pub = $this->db->select( 'id', '[+prefix+]site_content',  $where);
1098
        $this->db->update($field, '[+prefix+]site_content', $where);
1099 View Code Duplication
        if ($this->db->getRecordCount($result_pub) >= 1) { //Event unPublished doc
1100
            while ($row_pub = $this->db->getRow($result_pub)) {
1101
                $this->invokeEvent("OnDocUnPublished", array(
1102
                    "docid" => $row_pub['id']
1103
                ));
1104
            }
1105
        }
1106
1107
        // now, check for documents that need un-publishing
1108
        $field = array('published' => 0, 'publishedon' => 0);
1109
        $where = "unpub_date <= {$timeNow} AND unpub_date!=0 AND published=1";
1110
        $result_unpub = $this->db->select( 'id', '[+prefix+]site_content',  $where);
1111
        $this->db->update($field, '[+prefix+]site_content', $where);
1112 View Code Duplication
        if ($this->db->getRecordCount($result_unpub) >= 1) { //Event unPublished doc
1113
            while ($row_unpub = $this->db->getRow($result_unpub)) {
1114
                $this->invokeEvent("OnDocUnPublished", array(
1115
                    "docid" => $row_unpub['id']
1116
                ));
1117
            }
1118
        }
1119
1120
        $this->recentUpdate = $timeNow;
1121
1122
        // clear the cache
1123
        $this->clearCache('full');
1124
    }
1125
1126
    public function checkPublishStatus()
1127
    {
1128
        $this->updatePubStatus();
1129
    }
1130
1131
    /**
1132
     * Final jobs.
1133
     *
1134
     * - cache page
1135
     */
1136
    public function postProcess()
1137
    {
1138
        // if the current document was generated, cache it!
1139
        $cacheable = ($this->config['enable_cache'] && $this->documentObject['cacheable']) ? 1 : 0;
1140
        if ($cacheable && $this->documentGenerated && $this->documentObject['type'] == 'document' && $this->documentObject['published']) {
1141
            // invoke OnBeforeSaveWebPageCache event
1142
            $this->invokeEvent("OnBeforeSaveWebPageCache");
1143
1144
            if (!empty($this->cacheKey) && is_scalar($this->cacheKey)) {
1145
                // get and store document groups inside document object. Document groups will be used to check security on cache pages
1146
                $where = "document='{$this->documentIdentifier}'";
1147
                $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...
1148
                $docGroups = $this->db->getColumn('document_group', $rs);
1149
1150
                // Attach Document Groups and Scripts
1151
                if (is_array($docGroups)) {
1152
                    $this->documentObject['__MODxDocGroups__'] = implode(",", $docGroups);
1153
                }
1154
1155
                $docObjSerial = serialize($this->documentObject);
1156
                $cacheContent = $docObjSerial . "<!--__MODxCacheSpliter__-->" . $this->documentContent;
1157
                $page_cache_path = MODX_BASE_PATH . $this->getHashFile($this->cacheKey);
1158
                file_put_contents($page_cache_path, "<?php die('Unauthorized access.'); ?>$cacheContent");
1159
            }
1160
        }
1161
1162
        // Useful for example to external page counters/stats packages
1163
        $this->invokeEvent('OnWebPageComplete');
1164
1165
        // end post processing
1166
    }
1167
1168
    /**
1169
     * @param $content
1170
     * @param string $left
1171
     * @param string $right
1172
     * @return array
1173
     */
1174
    public function getTagsFromContent($content, $left = '[+', $right = '+]')
1175
    {
1176
        $_ = $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...
1177
        if (empty($_)) {
1178
            return array();
1179
        }
1180
        foreach ($_ as $v) {
1181
            $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...
1182
            $tags[1][] = $v;
1183
        }
1184
        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...
1185
    }
1186
1187
    /**
1188
     * @param $content
1189
     * @param string $left
1190
     * @param string $right
1191
     * @return array
1192
     */
1193
    public function _getTagsFromContent($content, $left = '[+', $right = '+]')
1194
    {
1195
        if (strpos($content, $left) === false) {
1196
            return array();
1197
        }
1198
        $spacer = md5('<<<EVO>>>');
1199
        if($left==='{{' && strpos($content,';}}')!==false)  $content = str_replace(';}}', sprintf(';}%s}',   $spacer),$content);
1200
        if($left==='{{' && strpos($content,'{{}}')!==false) $content = str_replace('{{}}',sprintf('{%$1s{}%$1s}',$spacer),$content);
1201
        if($left==='[[' && strpos($content,']]]]')!==false) $content = str_replace(']]]]',sprintf(']]%s]]',  $spacer),$content);
1202
        if($left==='[[' && strpos($content,']]]')!==false)  $content = str_replace(']]]', sprintf(']%s]]',   $spacer),$content);
1203
1204
        $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...
1205
        $pos[']]>'] = strpos($content, ']]>');
1206
1207
        if ($pos['<![CDATA['] !== false && $pos[']]>'] !== false) {
1208
            $content = substr($content, 0, $pos['<![CDATA[']) . substr($content, $pos[']]>'] + 3);
1209
        }
1210
1211
        $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...
1212
        $piece = array();
1213
        foreach ($lp as $lc => $lv) {
1214
            if ($lc !== 0) {
1215
                $piece[] = $left;
1216
            }
1217
            if (strpos($lv, $right) === false) {
1218
                $piece[] = $lv;
1219
            } else {
1220
                $rp = explode($right, $lv);
1221
                foreach ($rp as $rc => $rv) {
1222
                    if ($rc !== 0) {
1223
                        $piece[] = $right;
1224
                    }
1225
                    $piece[] = $rv;
1226
                }
1227
            }
1228
        }
1229
        $lc = 0;
1230
        $rc = 0;
1231
        $fetch = '';
1232
        $tags = array();
1233
        foreach ($piece as $v) {
1234
            if ($v === $left) {
1235
                if (0 < $lc) {
1236
                    $fetch .= $left;
1237
                }
1238
                $lc++;
1239
            } elseif ($v === $right) {
1240
                if ($lc === 0) {
1241
                    continue;
1242
                }
1243
                $rc++;
1244
                if ($lc === $rc) {
1245
                    // #1200 Enable modifiers in Wayfinder - add nested placeholders to $tags like for $fetch = "phx:input=`[+wf.linktext+]`:test"
1246
                    if (strpos($fetch, $left) !== false) {
1247
                        $nested = $this->_getTagsFromContent($fetch, $left, $right);
1248
                        foreach ($nested as $tag) {
1249
                            if (!in_array($tag, $tags)) {
1250
                                $tags[] = $tag;
1251
                            }
1252
                        }
1253
                    }
1254
1255
                    if (!in_array($fetch, $tags)) {  // Avoid double Matches
1256
                        $tags[] = $fetch; // Fetch
1257
                    };
1258
                    $fetch = ''; // and reset
1259
                    $lc = 0;
1260
                    $rc = 0;
1261
                } else {
1262
                    $fetch .= $right;
1263
                }
1264
            } else {
1265
                if (0 < $lc) {
1266
                    $fetch .= $v;
1267
                } else {
1268
                    continue;
1269
                }
1270
            }
1271
        }
1272
        foreach($tags as $i=>$tag) {
1273
            if(strpos($tag,$spacer)!==false) $tags[$i] = str_replace($spacer, '', $tag);
1274
        }
1275
        return $tags;
1276
    }
1277
1278
    /**
1279
     * Merge content fields and TVs
1280
     *
1281
     * @param $content
1282
     * @param bool $ph
1283
     * @return string
1284
     * @internal param string $template
1285
     */
1286
    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...
1287
    {
1288 View Code Duplication
        if ($this->config['enable_at_syntax']) {
1289
            if (stripos($content, '<@LITERAL>') !== false) {
1290
                $content = $this->escapeLiteralTagsContent($content);
1291
            }
1292
        }
1293
        if (strpos($content, '[*') === false) {
1294
            return $content;
1295
        }
1296
        if (!isset($this->documentIdentifier)) {
1297
            return $content;
1298
        }
1299
        if (!isset($this->documentObject) || empty($this->documentObject)) {
1300
            return $content;
1301
        }
1302
1303
        if (!$ph) {
1304
            $ph = $this->documentObject;
1305
        }
1306
1307
        $matches = $this->getTagsFromContent($content, '[*', '*]');
1308
        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...
1309
            return $content;
1310
        }
1311
1312
        foreach ($matches[1] as $i => $key) {
1313
            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...
1314
            if (substr($key, 0, 1) == '#') {
1315
                $key = substr($key, 1);
1316
            } // remove # for QuickEdit format
1317
1318
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1319
            if (strpos($key, '@') !== false) {
1320
                list($key, $context) = explode('@', $key, 2);
1321
            } else {
1322
                $context = false;
1323
            }
1324
1325
            // 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...
1326
            if ($context) {
1327
                $value = $this->_contextValue("{$key}@{$context}", $this->documentObject['parent']);
0 ignored issues
show
Documentation introduced by
$this->documentObject['parent'] is of type array|string, but the function expects a boolean|integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1328
            } else {
1329
                $value = isset($ph[$key]) ? $ph[$key] : '';
1330
            }
1331
1332 View Code Duplication
            if (is_array($value)) {
1333
                $value = getTVDisplayFormat($value[0], $value[1], $value[2], $value[3], $value[4]);
1334
            }
1335
1336
            $s = &$matches[0][$i];
1337
            if ($modifiers !== false) {
1338
                $value = $this->applyFilter($value, $modifiers, $key);
1339
            }
1340
1341 View Code Duplication
            if (strpos($content, $s) !== false) {
1342
                $content = str_replace($s, $value, $content);
1343
            } elseif($this->debug) {
1344
                $this->addLog('mergeDocumentContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1345
            }
1346
        }
1347
1348
        return $content;
1349
    }
1350
1351
    /**
1352
     * @param $key
1353
     * @param bool|int $parent
1354
     * @return bool|mixed|string
1355
     */
1356
    public function _contextValue($key, $parent = false)
1357
    {
1358
        if (preg_match('/@\d+\/u/', $key)) {
1359
            $key = str_replace(array('@', '/u'), array('@u(', ')'), $key);
1360
        }
1361
        list($key, $str) = explode('@', $key, 2);
1362
1363
        if (strpos($str, '(')) {
1364
            list($context, $option) = explode('(', $str, 2);
1365
        } else {
1366
            list($context, $option) = array($str, false);
1367
        }
1368
1369
        if ($option) {
1370
            $option = trim($option, ')(\'"`');
1371
        }
1372
1373
        switch (strtolower($context)) {
1374
            case 'site_start':
1375
                $docid = $this->config['site_start'];
1376
                break;
1377
            case 'parent':
1378
            case 'p':
1379
                $docid = $parent;
1380
                if ($docid == 0) {
1381
                    $docid = $this->config['site_start'];
1382
                }
1383
                break;
1384
            case 'ultimateparent':
1385
            case 'uparent':
1386
            case 'up':
1387
            case 'u':
1388 View Code Duplication
                if (strpos($str, '(') !== false) {
1389
                    $top = substr($str, strpos($str, '('));
1390
                    $top = trim($top, '()"\'');
1391
                } else {
1392
                    $top = 0;
1393
                }
1394
                $docid = $this->getUltimateParentId($this->documentIdentifier, $top);
1395
                break;
1396
            case 'alias':
1397
                $str = substr($str, strpos($str, '('));
1398
                $str = trim($str, '()"\'');
1399
                $docid = $this->getIdFromAlias($str);
1400
                break;
1401 View Code Duplication
            case 'prev':
1402
                if (!$option) {
1403
                    $option = 'menuindex,ASC';
1404
                } elseif (strpos($option, ',') === false) {
1405
                    $option .= ',ASC';
1406
                }
1407
                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...
1408
                $children = $this->getActiveChildren($parent, $by, $dir);
1409
                $find = false;
1410
                $prev = false;
1411
                foreach ($children as $row) {
1412
                    if ($row['id'] == $this->documentIdentifier) {
1413
                        $find = true;
1414
                        break;
1415
                    }
1416
                    $prev = $row;
1417
                }
1418
                if ($find) {
1419
                    if (isset($prev[$key])) {
1420
                        return $prev[$key];
1421
                    } else {
1422
                        $docid = $prev['id'];
1423
                    }
1424
                } else {
1425
                    $docid = '';
1426
                }
1427
                break;
1428 View Code Duplication
            case 'next':
1429
                if (!$option) {
1430
                    $option = 'menuindex,ASC';
1431
                } elseif (strpos($option, ',') === false) {
1432
                    $option .= ',ASC';
1433
                }
1434
                list($by, $dir) = explode(',', $option, 2);
1435
                $children = $this->getActiveChildren($parent, $by, $dir);
1436
                $find = false;
1437
                $next = false;
1438
                foreach ($children as $row) {
1439
                    if ($find) {
1440
                        $next = $row;
1441
                        break;
1442
                    }
1443
                    if ($row['id'] == $this->documentIdentifier) {
1444
                        $find = true;
1445
                    }
1446
                }
1447
                if ($find) {
1448
                    if (isset($next[$key])) {
1449
                        return $next[$key];
1450
                    } else {
1451
                        $docid = $next['id'];
1452
                    }
1453
                } else {
1454
                    $docid = '';
1455
                }
1456
                break;
1457
            default:
1458
                $docid = $str;
1459
        }
1460
        if (preg_match('@^[1-9][0-9]*$@', $docid)) {
1461
            $value = $this->getField($key, $docid);
1462
        } else {
1463
            $value = '';
1464
        }
1465
        return $value;
1466
    }
1467
1468
    /**
1469
     * Merge system settings
1470
     *
1471
     * @param $content
1472
     * @param bool|array $ph
1473
     * @return string
1474
     * @internal param string $template
1475
     */
1476
    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...
1477
    {
1478 View Code Duplication
        if ($this->config['enable_at_syntax']) {
1479
            if (stripos($content, '<@LITERAL>') !== false) {
1480
                $content = $this->escapeLiteralTagsContent($content);
1481
            }
1482
        }
1483
        if (strpos($content, '[(') === false) {
1484
            return $content;
1485
        }
1486
1487
        if (empty($ph)) {
1488
            $ph = $this->config;
1489
        }
1490
1491
        $matches = $this->getTagsFromContent($content, '[(', ')]');
1492
        if (empty($matches)) {
1493
            return $content;
1494
        }
1495
1496
        foreach ($matches[1] as $i => $key) {
1497
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1498
1499
            if (isset($ph[$key])) {
1500
                $value = $ph[$key];
1501
            } else {
1502
                continue;
1503
            }
1504
1505
            if ($modifiers !== false) {
1506
                $value = $this->applyFilter($value, $modifiers, $key);
1507
            }
1508
            $s = &$matches[0][$i];
1509 View Code Duplication
            if (strpos($content, $s) !== false) {
1510
                $content = str_replace($s, $value, $content);
1511
            } elseif($this->debug) {
1512
                $this->addLog('mergeSettingsContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1513
            }
1514
        }
1515
        return $content;
1516
    }
1517
1518
    /**
1519
     * Merge chunks
1520
     *
1521
     * @param string $content
1522
     * @param bool|array $ph
1523
     * @return string
1524
     */
1525
    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...
1526
    {
1527
        if ($this->config['enable_at_syntax']) {
1528
            if (strpos($content, '{{ ') !== false) {
1529
                $content = str_replace(array('{{ ', ' }}'), array('\{\{ ', ' \}\}'), $content);
1530
            }
1531
            if (stripos($content, '<@LITERAL>') !== false) {
1532
                $content = $this->escapeLiteralTagsContent($content);
1533
            }
1534
        }
1535
        if (strpos($content, '{{') === false) {
1536
            return $content;
1537
        }
1538
1539
        if (empty($ph)) {
1540
            $ph = $this->chunkCache;
1541
        }
1542
1543
        $matches = $this->getTagsFromContent($content, '{{', '}}');
1544
        if (empty($matches)) {
1545
            return $content;
1546
        }
1547
1548
        foreach ($matches[1] as $i => $key) {
1549
            $snip_call = $this->_split_snip_call($key);
1550
            $key = $snip_call['name'];
1551
            $params = $this->getParamsFromString($snip_call['params']);
1552
1553
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1554
1555
            if (!isset($ph[$key])) {
1556
                $ph[$key] = $this->getChunk($key);
1557
            }
1558
            $value = $ph[$key];
1559
1560
            if (is_null($value)) {
1561
                continue;
1562
            }
1563
1564
            $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 1551 can also be of type null; however, EvolutionCMS\Core::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...
1565
            $value = $this->mergePlaceholderContent($value, $params);  // parse page global placeholers
0 ignored issues
show
Bug introduced by
It seems like $params defined by $this->getParamsFromString($snip_call['params']) on line 1551 can also be of type null; however, EvolutionCMS\Core::mergePlaceholderContent() does only seem to accept boolean|array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1566
            if ($this->config['enable_at_syntax']) {
1567
                $value = $this->mergeConditionalTagsContent($value);
1568
            }
1569
            $value = $this->mergeDocumentContent($value);
1570
            $value = $this->mergeSettingsContent($value);
1571
            $value = $this->mergeChunkContent($value);
1572
1573
            if ($modifiers !== false) {
1574
                $value = $this->applyFilter($value, $modifiers, $key);
1575
            }
1576
1577
            $s = &$matches[0][$i];
1578 View Code Duplication
            if (strpos($content, $s) !== false) {
1579
                $content = str_replace($s, $value, $content);
1580
            } elseif($this->debug) {
1581
                $this->addLog('mergeChunkContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1582
            }
1583
        }
1584
        return $content;
1585
    }
1586
1587
    /**
1588
     * Merge placeholder values
1589
     *
1590
     * @param string $content
1591
     * @param bool|array $ph
1592
     * @return string
1593
     */
1594
    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...
1595
    {
1596
1597 View Code Duplication
        if ($this->config['enable_at_syntax']) {
1598
            if (stripos($content, '<@LITERAL>') !== false) {
1599
                $content = $this->escapeLiteralTagsContent($content);
1600
            }
1601
        }
1602
        if (strpos($content, '[+') === false) {
1603
            return $content;
1604
        }
1605
1606
        if (empty($ph)) {
1607
            $ph = $this->placeholders;
1608
        }
1609
1610
        if ($this->config['enable_at_syntax']) {
1611
            $content = $this->mergeConditionalTagsContent($content);
1612
        }
1613
1614
        $content = $this->mergeDocumentContent($content);
1615
        $content = $this->mergeSettingsContent($content);
1616
        $matches = $this->getTagsFromContent($content, '[+', '+]');
1617
        if (empty($matches)) {
1618
            return $content;
1619
        }
1620
        foreach ($matches[1] as $i => $key) {
1621
1622
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1623
1624
            if (isset($ph[$key])) {
1625
                $value = $ph[$key];
1626
            } elseif ($key === 'phx') {
1627
                $value = '';
1628
            } else {
1629
                continue;
1630
            }
1631
1632
            if ($modifiers !== false) {
1633
                $modifiers = $this->mergePlaceholderContent($modifiers);
1634
                $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...
1635
            }
1636
            $s = &$matches[0][$i];
1637 View Code Duplication
            if (strpos($content, $s) !== false) {
1638
                $content = str_replace($s, $value, $content);
1639
            } elseif($this->debug) {
1640
                $this->addLog('mergePlaceholderContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1641
            }
1642
        }
1643
        return $content;
1644
    }
1645
1646
    /**
1647
     * @param $content
1648
     * @param string $iftag
1649
     * @param string $elseiftag
1650
     * @param string $elsetag
1651
     * @param string $endiftag
1652
     * @return mixed|string
1653
     */
1654
    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...
1655
    {
1656
        if (strpos($content, '@IF') !== false) {
1657
            $content = $this->_prepareCTag($content, $iftag, $elseiftag, $elsetag, $endiftag);
1658
        }
1659
1660
        if (strpos($content, $iftag) === false) {
1661
            return $content;
1662
        }
1663
1664
        $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...
1665
        $content = str_replace(array('<?php', '<?=', '<?', '?>'), array("{$sp}b", "{$sp}p", "{$sp}s", "{$sp}e"), $content);
1666
1667
        $pieces = explode('<@IF:', $content);
1668 View Code Duplication
        foreach ($pieces as $i => $split) {
1669
            if ($i === 0) {
1670
                $content = $split;
1671
                continue;
1672
            }
1673
            list($cmd, $text) = explode('>', $split, 2);
1674
            $cmd = str_replace("'", "\'", $cmd);
1675
            $content .= "<?php if(\$this->_parseCTagCMD('" . $cmd . "')): ?>";
1676
            $content .= $text;
1677
        }
1678
        $pieces = explode('<@ELSEIF:', $content);
1679 View Code Duplication
        foreach ($pieces as $i => $split) {
1680
            if ($i === 0) {
1681
                $content = $split;
1682
                continue;
1683
            }
1684
            list($cmd, $text) = explode('>', $split, 2);
1685
            $cmd = str_replace("'", "\'", $cmd);
1686
            $content .= "<?php elseif(\$this->_parseCTagCMD('" . $cmd . "')): ?>";
1687
            $content .= $text;
1688
        }
1689
1690
        $content = str_replace(array('<@ELSE>', '<@ENDIF>'), array('<?php else:?>', '<?php endif;?>'), $content);
1691
        ob_start();
1692
        $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...
1693
        $content = ob_get_clean();
1694
        $content = str_replace(array("{$sp}b", "{$sp}p", "{$sp}s", "{$sp}e"), array('<?php', '<?=', '<?', '?>'), $content);
1695
1696
        return $content;
1697
    }
1698
1699
    /**
1700
     * @param $content
1701
     * @param string $iftag
1702
     * @param string $elseiftag
1703
     * @param string $elsetag
1704
     * @param string $endiftag
1705
     * @return mixed
1706
     */
1707
    private function _prepareCTag($content, $iftag = '<@IF:', $elseiftag = '<@ELSEIF:', $elsetag = '<@ELSE>', $endiftag = '<@ENDIF>')
1708
    {
1709
        if (strpos($content, '<!--@IF ') !== false) {
1710
            $content = str_replace('<!--@IF ', $iftag, $content);
1711
        } // for jp
1712
        if (strpos($content, '<!--@IF:') !== false) {
1713
            $content = str_replace('<!--@IF:', $iftag, $content);
1714
        }
1715
        if (strpos($content, $iftag) === false) {
1716
            return $content;
1717
        }
1718
        if (strpos($content, '<!--@ELSEIF:') !== false) {
1719
            $content = str_replace('<!--@ELSEIF:', $elseiftag, $content);
1720
        } // for jp
1721
        if (strpos($content, '<!--@ELSE-->') !== false) {
1722
            $content = str_replace('<!--@ELSE-->', $elsetag, $content);
1723
        }  // for jp
1724
        if (strpos($content, '<!--@ENDIF-->') !== false) {
1725
            $content = str_replace('<!--@ENDIF-->', $endiftag, $content);
1726
        }    // for jp
1727
        if (strpos($content, '<@ENDIF-->') !== false) {
1728
            $content = str_replace('<@ENDIF-->', $endiftag, $content);
1729
        }
1730
        $tags = array($iftag, $elseiftag, $elsetag, $endiftag);
1731
        $content = str_ireplace($tags, $tags, $content); // Change to capital letters
1732
        return $content;
1733
    }
1734
1735
    /**
1736
     * @param $cmd
1737
     * @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...
1738
     */
1739
    private function _parseCTagCMD($cmd)
1740
    {
1741
        $cmd = trim($cmd);
1742
        $reverse = substr($cmd, 0, 1) === '!' ? true : false;
1743
        if ($reverse) {
1744
            $cmd = ltrim($cmd, '!');
1745
        }
1746
        if (strpos($cmd, '[!') !== false) {
1747
            $cmd = str_replace(array('[!', '!]'), array('[[', ']]'), $cmd);
1748
        }
1749
        $safe = 0;
1750
        while ($safe < 20) {
1751
            $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...
1752
            if (strpos($cmd, '[*') !== false) {
1753
                $cmd = $this->mergeDocumentContent($cmd);
1754
            }
1755
            if (strpos($cmd, '[(') !== false) {
1756
                $cmd = $this->mergeSettingsContent($cmd);
1757
            }
1758
            if (strpos($cmd, '{{') !== false) {
1759
                $cmd = $this->mergeChunkContent($cmd);
1760
            }
1761
            if (strpos($cmd, '[[') !== false) {
1762
                $cmd = $this->evalSnippets($cmd);
1763
            }
1764
            if (strpos($cmd, '[+') !== false && strpos($cmd, '[[') === false) {
1765
                $cmd = $this->mergePlaceholderContent($cmd);
1766
            }
1767
            if ($bt === md5($cmd)) {
1768
                break;
1769
            }
1770
            $safe++;
1771
        }
1772
        $cmd = ltrim($cmd);
1773
        $cmd = rtrim($cmd, '-');
1774
        $cmd = str_ireplace(array(' and ', ' or '), array('&&', '||'), $cmd);
1775
1776
        if (!preg_match('@^[0-9]*$@', $cmd) && preg_match('@^[0-9<= \-\+\*/\(\)%!&|]*$@', $cmd)) {
1777
            $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...
1778
        } else {
1779
            $_ = 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...
1780
            foreach ($_ as $left) {
1781
                if (strpos($cmd, $left) !== false) {
1782
                    $cmd = 0;
1783
                    break;
1784
                }
1785
            }
1786
        }
1787
        $cmd = trim($cmd);
1788
        if (!preg_match('@^[0-9]+$@', $cmd)) {
1789
            $cmd = empty($cmd) ? 0 : 1;
1790
        } elseif ($cmd <= 0) {
1791
            $cmd = 0;
1792
        }
1793
1794
        if ($reverse) {
1795
            $cmd = !$cmd;
1796
        }
1797
1798
        return $cmd;
1799
    }
1800
1801
    /**
1802
     * Remove Comment-Tags from output like <!--@- Comment -@-->
1803
     * @param $content
1804
     * @param string $left
1805
     * @param string $right
1806
     * @return mixed
1807
     */
1808
    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...
1809
    {
1810
        if (strpos($content, $left) === false) {
1811
            return $content;
1812
        }
1813
1814
        $matches = $this->getTagsFromContent($content, $left, $right);
1815
        if (!empty($matches)) {
1816
            foreach ($matches[0] as $i => $v) {
1817
                $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...
1818
            }
1819
            $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...
1820
            if (strpos($content, $left) !== false) {
1821
                $content = str_replace($matches[0], '', $content);
1822
            }
1823
        }
1824
        return $content;
1825
    }
1826
1827
    /**
1828
     * @param $content
1829
     * @param string $left
1830
     * @param string $right
1831
     * @return mixed
1832
     */
1833
    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...
1834
    {
1835
        if (stripos($content, $left) === false) {
1836
            return $content;
1837
        }
1838
1839
        $matches = $this->getTagsFromContent($content, $left, $right);
1840
        if (empty($matches)) {
1841
            return $content;
1842
        }
1843
1844
        list($sTags, $rTags) = $this->getTagsForEscape();
1845
        foreach ($matches[1] as $i => $v) {
1846
            $v = str_ireplace($sTags, $rTags, $v);
1847
            $s = &$matches[0][$i];
1848 View Code Duplication
            if (strpos($content, $s) !== false) {
1849
                $content = str_replace($s, $v, $content);
1850
            } elseif($this->debug) {
1851
                $this->addLog('ignoreCommentedTagsContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1852
            }
1853
        }
1854
        return $content;
1855
    }
1856
1857
    /**
1858
     * Detect PHP error according to MODX error level
1859
     *
1860
     * @param integer $error PHP error level
1861
     * @return boolean Error detected
1862
     */
1863
1864
    public function detectError($error)
1865
    {
1866
        $detected = false;
1867
        if ($this->config['error_reporting'] == 99 && $error) {
1868
            $detected = true;
1869
        } elseif ($this->config['error_reporting'] == 2 && ($error & ~E_NOTICE)) {
1870
            $detected = true;
1871
        } elseif ($this->config['error_reporting'] == 1 && ($error & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT)) {
1872
            $detected = true;
1873
        }
1874
        return $detected;
1875
    }
1876
1877
    /**
1878
     * Run a plugin
1879
     *
1880
     * @param string $pluginCode Code to run
1881
     * @param array $params
1882
     */
1883
    public function evalPlugin($pluginCode, $params)
1884
    {
1885
        $modx = &$this;
1886
        $modx->event->params = &$params; // store params inside event object
1887
        if (is_array($params)) {
1888
            extract($params, EXTR_SKIP);
1889
        }
1890
        /* 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...
1891
        // 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.
1892
        // Related to https://github.com/modxcms/evolution/issues/1130
1893
        $lock_file_path = MODX_BASE_PATH . 'assets/cache/lock_' . str_replace(' ','-',strtolower($this->event->activePlugin)) . '.pageCache.php';
1894
        if($this->isBackend()) {
1895
            if(is_file($lock_file_path)) {
1896
                $msg = sprintf("Plugin parse error, Temporarily disabled '%s'.", $this->event->activePlugin);
1897
                $this->logEvent(0, 3, $msg, $msg);
1898
                return;
1899
            }
1900
            elseif(stripos($this->event->activePlugin,'ElementsInTree')===false) touch($lock_file_path);
1901
        }*/
1902
        ob_start();
1903
        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...
1904
        $msg = ob_get_contents();
1905
        ob_end_clean();
1906
        // When reached here, no fatal error occured so the lock should be removed.
1907
        /*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...
1908
1909 View Code Duplication
        if ((0 < $this->config['error_reporting']) && $msg && isset($php_errormsg)) {
1910
            $error_info = error_get_last();
1911
            if ($this->detectError($error_info['type'])) {
1912
                $msg = ($msg === false) ? 'ob_get_contents() error' : $msg;
1913
                $this->messageQuit('PHP Parse Error', '', true, $error_info['type'], $error_info['file'], 'Plugin', $error_info['message'], $error_info['line'], $msg);
1914
                if ($this->isBackend()) {
1915
                    $this->event->alert('An error occurred while loading. Please see the event log for more information.<p>' . $msg . '</p>');
1916
                }
1917
            }
1918
        } else {
1919
            echo $msg;
1920
        }
1921
        unset($modx->event->params);
1922
    }
1923
1924
    /**
1925
     * Run a snippet
1926
     *
1927
     * @param $phpcode
1928
     * @param array $params
1929
     * @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...
1930
     * @internal param string $snippet Code to run
1931
     */
1932
    public function evalSnippet($phpcode, $params)
1933
    {
1934
        $modx = &$this;
1935
        /*
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...
1936
        if(isset($params) && is_array($params)) {
1937
            foreach($params as $k=>$v) {
1938
                $v = strtolower($v);
1939
                if($v==='false')    $params[$k] = false;
1940
                elseif($v==='true') $params[$k] = true;
1941
            }
1942
        }*/
1943
        $modx->event->params = &$params; // store params inside event object
1944
        if (is_array($params)) {
1945
            extract($params, EXTR_SKIP);
1946
        }
1947
        ob_start();
1948
        if (strpos($phpcode, ';') !== false) {
1949
            $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...
1950
        } else {
1951
            $return = call_user_func_array($phpcode, array($params));
1952
        }
1953
        $echo = ob_get_contents();
1954
        ob_end_clean();
1955 View Code Duplication
        if ((0 < $this->config['error_reporting']) && isset($php_errormsg)) {
1956
            $error_info = error_get_last();
1957
            if ($this->detectError($error_info['type'])) {
1958
                $echo = ($echo === false) ? 'ob_get_contents() error' : $echo;
1959
                $this->messageQuit('PHP Parse Error', '', true, $error_info['type'], $error_info['file'], 'Snippet', $error_info['message'], $error_info['line'], $echo);
1960
                if ($this->isBackend()) {
1961
                    $this->event->alert('An error occurred while loading. Please see the event log for more information<p>' . $echo . $return . '</p>');
1962
                }
1963
            }
1964
        }
1965
        unset($modx->event->params);
1966
        if (is_array($return) || is_object($return)) {
1967
            return $return;
1968
        } else {
1969
            return $echo . $return;
1970
        }
1971
    }
1972
1973
    /**
1974
     * Run snippets as per the tags in $documentSource and replace the tags with the returned values.
1975
     *
1976
     * @param $content
1977
     * @return string
1978
     * @internal param string $documentSource
1979
     */
1980
    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...
1981
    {
1982
        if (strpos($content, '[[') === false) {
1983
            return $content;
1984
        }
1985
1986
        $matches = $this->getTagsFromContent($content, '[[', ']]');
1987
1988
        if (empty($matches)) {
1989
            return $content;
1990
        }
1991
1992
        $this->snipLapCount++;
1993
        if ($this->dumpSnippets) {
1994
            $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);
1995
        }
1996
1997
        foreach ($matches[1] as $i => $call) {
1998
            $s = &$matches[0][$i];
1999
            if (substr($call, 0, 2) === '$_') {
2000
                if (strpos($content, '_PHX_INTERNAL_') === false) {
2001
                    $value = $this->_getSGVar($call);
2002
                } else {
2003
                    $value = $s;
2004
                }
2005 View Code Duplication
                if (strpos($content, $s) !== false) {
2006
                    $content = str_replace($s, $value, $content);
2007
                } elseif($this->debug) {
2008
                    $this->addLog('evalSnippetsSGVar parse error', $_SERVER['REQUEST_URI'] . $s, 2);
2009
                }
2010
                continue;
2011
            }
2012
            $value = $this->_get_snip_result($call);
2013
            if (is_null($value)) {
2014
                continue;
2015
            }
2016
2017 View Code Duplication
            if (strpos($content, $s) !== false) {
2018
                $content = str_replace($s, $value, $content);
2019
            } elseif($this->debug) {
2020
                $this->addLog('evalSnippets parse error', $_SERVER['REQUEST_URI'] . $s, 2);
2021
            }
2022
        }
2023
2024
        if ($this->dumpSnippets) {
2025
            $this->snippetsCode .= '</fieldset><br />';
2026
        }
2027
2028
        return $content;
2029
    }
2030
2031
    /**
2032
     * @param $value
2033
     * @return mixed|string
2034
     */
2035
    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...
2036
    { // Get super globals
2037
        $key = $value;
2038
        $_ = $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...
2039
        $this->config['enable_filter'] = 1;
2040
        list($key, $modifiers) = $this->splitKeyAndFilter($key);
2041
        $this->config['enable_filter'] = $_;
2042
        $key = str_replace(array('(', ')'), array("['", "']"), $key);
2043
        $key = rtrim($key, ';');
2044
        if (strpos($key, '$_SESSION') !== false) {
2045
            $_ = $_SESSION;
2046
            $key = str_replace('$_SESSION', '$_', $key);
2047
            if (isset($_['mgrFormValues'])) {
2048
                unset($_['mgrFormValues']);
2049
            }
2050
            if (isset($_['token'])) {
2051
                unset($_['token']);
2052
            }
2053
        }
2054
        if (strpos($key, '[') !== false) {
2055
            $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...
2056
        } 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...
2057
            $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...
2058
        } else {
2059
            $value = '';
2060
        }
2061
        if ($modifiers !== false) {
2062
            $value = $this->applyFilter($value, $modifiers, $key);
2063
        }
2064
        return $value;
2065
    }
2066
2067
    /**
2068
     * @param $piece
2069
     * @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...
2070
     */
2071
    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...
2072
    {
2073
        if (ltrim($piece) !== $piece) {
2074
            return '';
2075
        }
2076
2077
        $eventtime = $this->dumpSnippets ? $this->getMicroTime() : 0;
2078
2079
        $snip_call = $this->_split_snip_call($piece);
2080
        $key = $snip_call['name'];
2081
2082
        list($key, $modifiers) = $this->splitKeyAndFilter($key);
2083
        $snip_call['name'] = $key;
2084
        $snippetObject = $this->_getSnippetObject($key);
2085
        if (is_null($snippetObject['content'])) {
2086
            return null;
2087
        }
2088
2089
        $this->currentSnippet = $snippetObject['name'];
2090
2091
        // current params
2092
        $params = $this->getParamsFromString($snip_call['params']);
2093
2094
        if (!isset($snippetObject['properties'])) {
2095
            $snippetObject['properties'] = array();
2096
        }
2097
        $default_params = $this->parseProperties($snippetObject['properties'], $this->currentSnippet, 'snippet');
2098
        $params = array_merge($default_params, $params);
2099
2100
        $value = $this->evalSnippet($snippetObject['content'], $params);
2101
        $this->currentSnippet = '';
2102
        if ($modifiers !== false) {
2103
            $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, EvolutionCMS\Core::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...
2104
        }
2105
2106
        if ($this->dumpSnippets) {
2107
            $eventtime = $this->getMicroTime() - $eventtime;
2108
            $eventtime = sprintf('%2.2f ms', $eventtime * 1000);
2109
            $code = str_replace("\t", '  ', $this->htmlspecialchars($value));
2110
            $piece = str_replace("\t", '  ', $this->htmlspecialchars($piece));
2111
            $print_r_params = str_replace("\t", '  ', $this->htmlspecialchars('$modx->event->params = ' . print_r($params, true)));
2112
            $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);
2113
            $this->snippetsTime[] = array('sname' => $key, 'time' => $eventtime);
2114
        }
2115
        return $value;
2116
    }
2117
2118
    /**
2119
     * @param string $string
2120
     * @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...
2121
     */
2122
    public function getParamsFromString($string = '')
2123
    {
2124
        if (empty($string)) {
2125
            return array();
2126
        }
2127
2128
        if (strpos($string, '&_PHX_INTERNAL_') !== false) {
2129
            $string = str_replace(array('&_PHX_INTERNAL_091_&', '&_PHX_INTERNAL_093_&'), array('[', ']'), $string);
2130
        }
2131
2132
        $_ = $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...
2133
        $this->documentOutput = $string;
2134
        $this->invokeEvent('OnBeforeParseParams');
2135
        $string = $this->documentOutput;
2136
        $this->documentOutput = $_;
2137
2138
        $_tmp = $string;
2139
        $_tmp = ltrim($_tmp, '?&');
2140
        $temp_params = array();
2141
        $key = '';
2142
        $value = null;
2143
        while ($_tmp !== '') {
2144
            $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...
2145
            $char = substr($_tmp, 0, 1);
2146
            $_tmp = substr($_tmp, 1);
2147
2148
            if ($char === '=') {
2149
                $_tmp = trim($_tmp);
2150
                $delim = substr($_tmp, 0, 1);
2151
                if (in_array($delim, array('"', "'", '`'))) {
2152
                    $null = null;
2153
                    //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...
2154
                    list($null, $value, $_tmp) = explode($delim, $_tmp, 3);
2155
                    unset($null);
2156
2157
                    if (substr(trim($_tmp), 0, 2) === '//') {
2158
                        $_tmp = strstr(trim($_tmp), "\n");
2159
                    }
2160
                    $i = 0;
2161
                    while ($delim === '`' && substr(trim($_tmp), 0, 1) !== '&' && 1 < substr_count($_tmp, '`')) {
2162
                        list($inner, $outer, $_tmp) = explode('`', $_tmp, 3);
2163
                        $value .= "`{$inner}`{$outer}";
2164
                        $i++;
2165
                        if (100 < $i) {
2166
                            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...
2167
                        }
2168
                    }
2169
                    if ($i && $delim === '`') {
2170
                        $value = rtrim($value, '`');
2171
                    }
2172
                } elseif (strpos($_tmp, '&') !== false) {
2173
                    list($value, $_tmp) = explode('&', $_tmp, 2);
2174
                    $value = trim($value);
2175
                } else {
2176
                    $value = $_tmp;
2177
                    $_tmp = '';
2178
                }
2179
            } elseif ($char === '&') {
2180
                if (trim($key) !== '') {
2181
                    $value = '1';
2182
                } else {
2183
                    continue;
2184
                }
2185
            } elseif ($_tmp === '') {
2186
                $key .= $char;
2187
                $value = '1';
2188
            } elseif ($key !== '' || trim($char) !== '') {
2189
                $key .= $char;
2190
            }
2191
2192
            if (isset($value) && !is_null($value)) {
2193
                if (strpos($key, 'amp;') !== false) {
2194
                    $key = str_replace('amp;', '', $key);
2195
                }
2196
                $key = trim($key);
2197 View Code Duplication
                if (strpos($value, '[!') !== false) {
2198
                    $value = str_replace(array('[!', '!]'), array('[[', ']]'), $value);
2199
                }
2200
                $value = $this->mergeDocumentContent($value);
2201
                $value = $this->mergeSettingsContent($value);
2202
                $value = $this->mergeChunkContent($value);
2203
                $value = $this->evalSnippets($value);
2204
                if (substr($value, 0, 6) !== '@CODE:') {
2205
                    $value = $this->mergePlaceholderContent($value);
2206
                }
2207
2208
                $temp_params[][$key] = $value;
2209
2210
                $key = '';
2211
                $value = null;
2212
2213
                $_tmp = ltrim($_tmp, " ,\t");
2214
                if (substr($_tmp, 0, 2) === '//') {
2215
                    $_tmp = strstr($_tmp, "\n");
2216
                }
2217
            }
2218
2219
            if ($_tmp === $bt) {
2220
                $key = trim($key);
2221
                if ($key !== '') {
2222
                    $temp_params[][$key] = '';
2223
                }
2224
                break;
2225
            }
2226
        }
2227
2228
        foreach ($temp_params as $p) {
2229
            $k = key($p);
2230
            if (substr($k, -2) === '[]') {
2231
                $k = substr($k, 0, -2);
2232
                $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...
2233
            } elseif (strpos($k, '[') !== false && substr($k, -1) === ']') {
2234
                list($k, $subk) = explode('[', $k, 2);
2235
                $subk = substr($subk, 0, -1);
2236
                $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...
2237
            } else {
2238
                $params[$k] = current($p);
2239
            }
2240
        }
2241
        return $params;
2242
    }
2243
2244
    /**
2245
     * @param $str
2246
     * @return bool|int
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use false|integer.

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

Loading history...
2247
     */
2248
    public function _getSplitPosition($str)
2249
    {
2250
        $closeOpt = false;
2251
        $maybePos = false;
2252
        $inFilter = false;
2253
        $pos = false;
2254
        $total = strlen($str);
2255
        for ($i = 0; $i < $total; $i++) {
2256
            $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...
2257
            $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...
2258
            if (!$inFilter) {
2259
                if ($c === ':') {
2260
                    $inFilter = true;
2261
                } elseif ($c === '?') {
2262
                    $pos = $i;
2263
                } elseif ($c === ' ') {
2264
                    $maybePos = $i;
2265
                } 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...
2266
                    $pos = $maybePos;
2267
                } elseif ($c === "\n") {
2268
                    $pos = $i;
2269
                } else {
2270
                    $pos = false;
2271
                }
2272
            } else {
2273
                if ($cc == $closeOpt) {
2274
                    $closeOpt = false;
2275
                } elseif ($c == $closeOpt) {
2276
                    $closeOpt = false;
2277
                } 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...
2278
                    continue;
2279
                } elseif ($cc === "('") {
2280
                    $closeOpt = "')";
2281
                } elseif ($cc === '("') {
2282
                    $closeOpt = '")';
2283
                } elseif ($cc === '(`') {
2284
                    $closeOpt = '`)';
2285
                } elseif ($c === '(') {
2286
                    $closeOpt = ')';
2287
                } elseif ($c === '?') {
2288
                    $pos = $i;
2289
                } elseif ($c === ' ' && strpos($str, '?') === false) {
2290
                    $pos = $i;
2291
                } else {
2292
                    $pos = false;
2293
                }
2294
            }
2295
            if ($pos) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $pos of type false|integer is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

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

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

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

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
2296
                break;
2297
            }
2298
        }
2299
        return $pos;
2300
    }
2301
2302
    /**
2303
     * @param $call
2304
     * @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...
2305
     */
2306
    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...
2307
    {
2308
        $spacer = md5('dummy');
2309 View Code Duplication
        if (strpos($call, ']]>') !== false) {
2310
            $call = str_replace(']]>', "]{$spacer}]>", $call);
2311
        }
2312
2313
        $splitPosition = $this->_getSplitPosition($call);
2314
2315
        if ($splitPosition !== false) {
2316
            $name = substr($call, 0, $splitPosition);
2317
            $params = substr($call, $splitPosition + 1);
2318
        } else {
2319
            $name = $call;
2320
            $params = '';
2321
        }
2322
2323
        $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...
2324 View Code Duplication
        if (strpos($params, $spacer) !== false) {
2325
            $params = str_replace("]{$spacer}]>", ']]>', $params);
2326
        }
2327
        $snip['params'] = ltrim($params, "?& \t\n");
2328
2329
        return $snip;
2330
    }
2331
2332
    /**
2333
     * @param $snip_name
2334
     * @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...
2335
     */
2336
    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...
2337
    {
2338
        if (isset($this->snippetCache[$snip_name])) {
2339
            $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...
2340
            $snippetObject['content'] = $this->snippetCache[$snip_name];
2341
            if (isset($this->snippetCache["{$snip_name}Props"])) {
2342
                if (!isset($this->snippetCache["{$snip_name}Props"])) {
2343
                    $this->snippetCache["{$snip_name}Props"] = '';
2344
                }
2345
                $snippetObject['properties'] = $this->snippetCache["{$snip_name}Props"];
2346
            }
2347
        } elseif (substr($snip_name, 0, 1) === '@' && isset($this->pluginEvent[trim($snip_name, '@')])) {
2348
            $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...
2349
            $snippetObject['content'] = sprintf('$rs=$this->invokeEvent("%s",$params);echo trim(implode("",$rs));', trim($snip_name, '@'));
2350
            $snippetObject['properties'] = '';
2351
        } else {
2352
            $where = sprintf("name='%s' AND disabled=0", $this->db->escape($snip_name));
2353
            $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...
2354
            $count = $this->db->getRecordCount($rs);
2355
            if (1 < $count) {
2356
                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...
2357
            }
2358
            if ($count) {
2359
                $row = $this->db->getRow($rs);
2360
                $snip_content = $row['snippet'];
2361
                $snip_prop = $row['properties'];
2362
            } else {
2363
                $snip_content = null;
2364
                $snip_prop = '';
2365
            }
2366
            $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...
2367
            $snippetObject['content'] = $snip_content;
2368
            $snippetObject['properties'] = $snip_prop;
2369
            $this->snippetCache[$snip_name] = $snip_content;
2370
            $this->snippetCache["{$snip_name}Props"] = $snip_prop;
2371
        }
2372
        return $snippetObject;
2373
    }
2374
2375
    /**
2376
     * @param $text
2377
     * @return mixed
2378
     */
2379
    public function toAlias($text)
2380
    {
2381
        $suff = $this->config['friendly_url_suffix'];
2382
        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);
2383
    }
2384
2385
    /**
2386
     * makeFriendlyURL
2387
     *
2388
     * @desc Create an URL.
2389
     *
2390
     * @param $pre {string} - Friendly URL Prefix. @required
2391
     * @param $suff {string} - Friendly URL Suffix. @required
2392
     * @param $alias {string} - Full document path. @required
2393
     * @param int $isfolder {0; 1}
2394
     * - Is it a folder? Default: 0.
2395
     * @param int $id {integer}
2396
     * - Document id. Default: 0.
2397
     * @return mixed|string {string} - Result URL.
2398
     * - Result URL.
2399
     */
2400
    public function makeFriendlyURL($pre, $suff, $alias, $isfolder = 0, $id = 0)
2401
    {
2402
        if ($id == $this->config['site_start'] && $this->config['seostrict'] === '1') {
2403
            $url = $this->config['base_url'];
2404
        } else {
2405
            $Alias = explode('/', $alias);
2406
            $alias = array_pop($Alias);
2407
            $dir = implode('/', $Alias);
2408
            unset($Alias);
2409
2410
            if ($this->config['make_folders'] === '1' && $isfolder == 1) {
2411
                $suff = '/';
2412
            }
2413
2414
            $url = ($dir != '' ? $dir . '/' : '') . $pre . $alias . $suff;
2415
        }
2416
2417
        $evtOut = $this->invokeEvent('OnMakeDocUrl', array(
2418
            'id' => $id,
2419
            'url' => $url
2420
        ));
2421
2422
        if (is_array($evtOut) && count($evtOut) > 0) {
2423
            $url = array_pop($evtOut);
2424
        }
2425
2426
        return $url;
2427
    }
2428
2429
    /**
2430
     * Convert URL tags [~...~] to URLs
2431
     *
2432
     * @param string $documentSource
2433
     * @return string
2434
     */
2435
    public function rewriteUrls($documentSource)
2436
    {
2437
        // rewrite the urls
2438
        if ($this->config['friendly_urls'] == 1) {
2439
            $aliases = array();
2440
            if (is_array($this->documentListing)) {
2441
                foreach ($this->documentListing as $path => $docid) { // This is big Loop on large site!
2442
                    $aliases[$docid] = $path;
2443
                    $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...
2444
                }
2445
            }
2446
2447
            if ($this->config['aliaslistingfolder'] == 1) {
2448
                preg_match_all('!\[\~([0-9]+)\~\]!ise', $documentSource, $match);
2449
                $ids = implode(',', array_unique($match['1']));
2450
                if ($ids) {
2451
                    $res = $this->db->select("id,alias,isfolder,parent,alias_visible", $this->getFullTableName('site_content'), "id IN (" . $ids . ") AND isfolder = '0'");
2452
                    while ($row = $this->db->getRow($res)) {
2453
                        if ($this->config['use_alias_path'] == '1' && $row['parent'] != 0) {
2454
                            $parent = $row['parent'];
2455
                            $path = $aliases[$parent];
2456
2457
                            while (isset($this->aliasListing[$parent]) && $this->aliasListing[$parent]['alias_visible'] == 0) {
2458
                                $path = $this->aliasListing[$parent]['path'];
2459
                                $parent = $this->aliasListing[$parent]['parent'];
2460
                            }
2461
2462
                            $aliases[$row['id']] = $path . '/' . $row['alias'];
2463
                        } else {
2464
                            $aliases[$row['id']] = $row['alias'];
2465
                        }
2466
                        $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...
2467
                    }
2468
                }
2469
            }
2470
            $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...
2471
            $isfriendly = ($this->config['friendly_alias_urls'] == 1 ? 1 : 0);
2472
            $pref = $this->config['friendly_url_prefix'];
2473
            $suff = $this->config['friendly_url_suffix'];
2474
            $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...
2475
                global $modx;
2476
                $thealias = $aliases[$m[1]];
2477
                $thefolder = $isfolder[$m[1]];
2478
                if ($isfriendly && isset($thealias)) {
2479
                    //found friendly url
2480
                    $out = ($modx->config['seostrict'] == '1' ? $modx->toAlias($modx->makeFriendlyURL($pref, $suff, $thealias, $thefolder, $m[1])) : $modx->makeFriendlyURL($pref, $suff, $thealias, $thefolder, $m[1]));
2481
                } else {
2482
                    //not found friendly url
2483
                    $out = $modx->makeFriendlyURL($pref, $suff, $m[1]);
2484
                }
2485
                return $out;
2486
            }, $documentSource);
2487
2488
        } else {
2489
            $in = '!\[\~([0-9]+)\~\]!is';
2490
            $out = "index.php?id=" . '\1';
2491
            $documentSource = preg_replace($in, $out, $documentSource);
2492
        }
2493
2494
        return $documentSource;
2495
    }
2496
2497
    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...
2498
    {
2499
        $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...
2500
        // FIX URLs
2501
        if (empty($this->documentIdentifier) || $this->config['seostrict'] == '0' || $this->config['friendly_urls'] == '0') {
2502
            return;
2503
        }
2504
        if ($this->config['site_status'] == 0) {
2505
            return;
2506
        }
2507
2508
        $scheme = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https' : 'http';
2509
        $len_base_url = strlen($this->config['base_url']);
2510
2511
        $url_path = $q;//LANG
2512
2513 View Code Duplication
        if (substr($url_path, 0, $len_base_url) === $this->config['base_url']) {
2514
            $url_path = substr($url_path, $len_base_url);
2515
        }
2516
2517
        $strictURL = $this->toAlias($this->makeUrl($this->documentIdentifier));
2518
2519 View Code Duplication
        if (substr($strictURL, 0, $len_base_url) === $this->config['base_url']) {
2520
            $strictURL = substr($strictURL, $len_base_url);
2521
        }
2522
        $http_host = $_SERVER['HTTP_HOST'];
2523
        $requestedURL = "{$scheme}://{$http_host}" . '/' . $q; //LANG
2524
2525
        $site_url = $this->config['site_url'];
2526
        $url_query_string = explode('?', $_SERVER['REQUEST_URI']);
2527
        // Strip conflicting id/q from query string
2528
        $qstring = !empty($url_query_string[1]) ? preg_replace("#(^|&)(q|id)=[^&]+#", '', $url_query_string[1]) : '';
2529
2530
        if ($this->documentIdentifier == $this->config['site_start']) {
2531
            if ($requestedURL != $this->config['site_url']) {
2532
                // Force redirect of site start
2533
                // $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...
2534
                if ($qstring) {
2535
                    $url = "{$site_url}?{$qstring}";
2536
                } else {
2537
                    $url = $site_url;
2538
                }
2539
                if ($this->config['base_url'] != $_SERVER['REQUEST_URI']) {
2540
                    if (empty($_POST)) {
2541
                        if (($this->config['base_url'] . '?' . $qstring) != $_SERVER['REQUEST_URI']) {
2542
                            $this->sendRedirect($url, 0, 'REDIRECT_HEADER', 'HTTP/1.0 301 Moved Permanently');
2543
                            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...
2544
                        }
2545
                    }
2546
                }
2547
            }
2548
        } elseif ($url_path != $strictURL && $this->documentIdentifier != $this->config['error_page']) {
2549
            // Force page redirect
2550
            //$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...
2551
            if (!empty($qstring)) {
2552
                $url = "{$site_url}{$strictURL}?{$qstring}";
2553
            } else {
2554
                $url = "{$site_url}{$strictURL}";
2555
            }
2556
            $this->sendRedirect($url, 0, 'REDIRECT_HEADER', 'HTTP/1.0 301 Moved Permanently');
2557
            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...
2558
        }
2559
        return;
2560
    }
2561
2562
    /**
2563
     * Get all db fields and TVs for a document/resource
2564
     *
2565
     * @param string $method
2566
     * @param mixed $identifier
2567
     * @param bool $isPrepareResponse
2568
     * @return array
2569
     */
2570
    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...
2571
    {
2572
2573
        $cacheKey = md5(print_r(func_get_args(), true));
2574
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
2575
            return $this->tmpCache[__FUNCTION__][$cacheKey];
2576
        }
2577
2578
        $tblsc = $this->getFullTableName("site_content");
2579
        $tbldg = $this->getFullTableName("document_groups");
2580
2581
        // allow alias to be full path
2582
        if ($method == 'alias') {
2583
            $identifier = $this->cleanDocumentIdentifier($identifier);
2584
            $method = $this->documentMethod;
2585
        }
2586
        if ($method == 'alias' && $this->config['use_alias_path'] && array_key_exists($identifier, $this->documentListing)) {
2587
            $method = 'id';
2588
            $identifier = $this->documentListing[$identifier];
2589
        }
2590
2591
        $out = $this->invokeEvent('OnBeforeLoadDocumentObject', compact('method', 'identifier'));
2592
        if (is_array($out) && is_array($out[0])) {
2593
            $documentObject = $out[0];
2594
        } else {
2595
            // get document groups for current user
2596
            if ($docgrp = $this->getUserDocGroups()) {
2597
                $docgrp = implode(",", $docgrp);
2598
            }
2599
            // get document
2600
            $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
2601
            $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...
2602
                LEFT JOIN {$tbldg} dg ON dg.document = sc.id", "sc.{$method} = '{$identifier}' AND ({$access})", "", 1);
2603
            if ($this->db->getRecordCount($rs) < 1) {
2604
                $seclimit = 0;
2605
                if ($this->config['unauthorized_page']) {
2606
                    // method may still be alias, while identifier is not full path alias, e.g. id not found above
2607
                    if ($method === 'alias') {
2608
                        $secrs = $this->db->select('count(dg.id)', "{$tbldg} as dg, {$tblsc} as sc", "dg.document = sc.id AND sc.alias = '{$identifier}'", '', 1);
2609
                    } else {
2610
                        $secrs = $this->db->select('count(id)', $tbldg, "document = '{$identifier}'", '', 1);
2611
                    }
2612
                    // check if file is not public
2613
                    $seclimit = $this->db->getValue($secrs);
2614
                }
2615
                if ($seclimit > 0) {
2616
                    // match found but not publicly accessible, send the visitor to the unauthorized_page
2617
                    $this->sendUnauthorizedPage();
2618
                    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...
2619
                } else {
2620
                    $this->sendErrorPage();
2621
                    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...
2622
                }
2623
            }
2624
            # 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...
2625
            $documentObject = $this->db->getRow($rs);
2626
2627
            if ($isPrepareResponse === 'prepareResponse') {
2628
                $this->documentObject = &$documentObject;
2629
            }
2630
            $out = $this->invokeEvent('OnLoadDocumentObject', compact('method', 'identifier', 'documentObject'));
2631
            if (is_array($out) && is_array($out[0])) {
2632
                $documentObject = $out[0];
2633
            }
2634
            if ($documentObject['template']) {
2635
                // load TVs and merge with document - Orig by Apodigm - Docvars
2636
                $rs = $this->db->select("tv.*, IF(tvc.value!='',tvc.value,tv.default_text) as value", $this->getFullTableName("site_tmplvars") . " tv
2637
                INNER JOIN " . $this->getFullTableName("site_tmplvar_templates") . " tvtpl ON tvtpl.tmplvarid = tv.id
2638
                LEFT JOIN " . $this->getFullTableName("site_tmplvar_contentvalues") . " tvc ON tvc.tmplvarid=tv.id AND tvc.contentid = '{$documentObject['id']}'", "tvtpl.templateid = '{$documentObject['template']}'");
2639
                $tmplvars = array();
2640
                while ($row = $this->db->getRow($rs)) {
2641
                    $tmplvars[$row['name']] = array(
2642
                        $row['name'],
2643
                        $row['value'],
2644
                        $row['display'],
2645
                        $row['display_params'],
2646
                        $row['type']
2647
                    );
2648
                }
2649
                $documentObject = array_merge($documentObject, $tmplvars);
2650
            }
2651
            $out = $this->invokeEvent('OnAfterLoadDocumentObject', compact('method', 'identifier', 'documentObject'));
2652
            if (is_array($out) && array_key_exists(0, $out) !== false && is_array($out[0])) {
2653
                $documentObject = $out[0];
2654
            }
2655
        }
2656
2657
        $this->tmpCache[__FUNCTION__][$cacheKey] = $documentObject;
2658
2659
        return $documentObject;
2660
    }
2661
2662
    /**
2663
     * Parse a source string.
2664
     *
2665
     * Handles most MODX tags. Exceptions include:
2666
     *   - uncached snippet tags [!...!]
2667
     *   - URL tags [~...~]
2668
     *
2669
     * @param string $source
2670
     * @return string
2671
     */
2672
    public function parseDocumentSource($source)
2673
    {
2674
        // set the number of times we are to parse the document source
2675
        $this->minParserPasses = empty ($this->minParserPasses) ? 2 : $this->minParserPasses;
2676
        $this->maxParserPasses = empty ($this->maxParserPasses) ? 10 : $this->maxParserPasses;
2677
        $passes = $this->minParserPasses;
2678
        for ($i = 0; $i < $passes; $i++) {
2679
            // get source length if this is the final pass
2680
            if ($i == ($passes - 1)) {
2681
                $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...
2682
            }
2683
            if ($this->dumpSnippets == 1) {
2684
                $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>";
2685
            }
2686
2687
            // invoke OnParseDocument event
2688
            $this->documentOutput = $source; // store source code so plugins can
2689
            $this->invokeEvent("OnParseDocument"); // work on it via $modx->documentOutput
2690
            $source = $this->documentOutput;
2691
2692
            if ($this->config['enable_at_syntax']) {
2693
                $source = $this->ignoreCommentedTagsContent($source);
2694
                $source = $this->mergeConditionalTagsContent($source);
2695
            }
2696
2697
            $source = $this->mergeSettingsContent($source);
2698
            $source = $this->mergeDocumentContent($source);
2699
            $source = $this->mergeChunkContent($source);
2700
            $source = $this->evalSnippets($source);
2701
            $source = $this->mergePlaceholderContent($source);
2702
2703
            if ($this->dumpSnippets == 1) {
2704
                $this->snippetsCode .= "</fieldset><br />";
2705
            }
2706
            if ($i == ($passes - 1) && $i < ($this->maxParserPasses - 1)) {
2707
                // check if source content was changed
2708
                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...
2709
                    $passes++;
2710
                } // if content change then increase passes because
2711
            } // we have not yet reached maxParserPasses
2712
        }
2713
        return $source;
2714
    }
2715
2716
    /**
2717
     * Starts the parsing operations.
2718
     *
2719
     * - connects to the db
2720
     * - gets the settings (including system_settings)
2721
     * - gets the document/resource identifier as in the query string
2722
     * - finally calls prepareResponse()
2723
     */
2724
    public function executeParser()
2725
    {
2726
        if(MODX_CLI) {
2727
            throw new \RuntimeException('Call DocumentParser::executeParser on CLI mode');
2728
        }
2729
2730
        //error_reporting(0);
2731
        set_error_handler(array(
2732
            & $this,
2733
            "phpError"
2734
        ), E_ALL);
2735
        $this->db->connect();
2736
2737
        // get the settings
2738
        if (empty ($this->config)) {
2739
            $this->getSettings();
2740
        }
2741
2742
        $this->_IIS_furl_fix(); // IIS friendly url fix
2743
2744
        // check site settings
2745
        if ($this->checkSiteStatus()) {
2746
            // make sure the cache doesn't need updating
2747
            $this->updatePubStatus();
2748
2749
            // find out which document we need to display
2750
            $this->documentMethod = filter_input(INPUT_GET, 'q') ? 'alias' : 'id';
2751
            $this->documentIdentifier = $this->getDocumentIdentifier($this->documentMethod);
2752
        } else {
2753
            header('HTTP/1.0 503 Service Unavailable');
2754
            $this->systemCacheKey = 'unavailable';
2755
            if (!$this->config['site_unavailable_page']) {
2756
                // display offline message
2757
                $this->documentContent = $this->config['site_unavailable_message'];
2758
                $this->outputContent();
2759
                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...
2760
            } else {
2761
                // setup offline page document settings
2762
                $this->documentMethod = 'id';
2763
                $this->documentIdentifier = $this->config['site_unavailable_page'];
2764
            }
2765
        }
2766
2767
        if ($this->documentMethod == "alias") {
2768
            $this->documentIdentifier = $this->cleanDocumentIdentifier($this->documentIdentifier);
2769
2770
            // Check use_alias_path and check if $this->virtualDir is set to anything, then parse the path
2771
            if ($this->config['use_alias_path'] == 1) {
2772
                $alias = (strlen($this->virtualDir) > 0 ? $this->virtualDir . '/' : '') . $this->documentIdentifier;
2773
                if (isset($this->documentListing[$alias])) {
2774
                    $this->documentIdentifier = $this->documentListing[$alias];
2775
                } else {
2776
                    //@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...
2777
                    if ($this->config['aliaslistingfolder'] == 1) {
2778
                        $tbl_site_content = $this->getFullTableName('site_content');
2779
2780
                        $parentId = $this->getIdFromAlias($this->virtualDir);
2781
                        $parentId = ($parentId > 0) ? $parentId : '0';
2782
2783
                        $docAlias = $this->db->escape($this->documentIdentifier);
2784
2785
                        $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...
2786
                        if ($this->db->getRecordCount($rs) == 0) {
2787
                            $this->sendErrorPage();
2788
                        }
2789
                        $docId = $this->db->getValue($rs);
2790
2791
                        if (!$docId) {
2792
                            $alias = $this->q;
2793
                            if (!empty($this->config['friendly_url_suffix'])) {
2794
                                $pos = strrpos($alias, $this->config['friendly_url_suffix']);
2795
2796
                                if ($pos !== false) {
2797
                                    $alias = substr($alias, 0, $pos);
2798
                                }
2799
                            }
2800
                            $docId = $this->getIdFromAlias($alias);
2801
                        }
2802
2803
                        if ($docId > 0) {
2804
                            $this->documentIdentifier = $docId;
2805
                        } else {
2806
                            /*
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...
2807
                            $rs  = $this->db->select('id', $tbl_site_content, "deleted=0 and alias='{$docAlias}'");
2808
                            if($this->db->getRecordCount($rs)==0)
2809
                            {
2810
                                $rs  = $this->db->select('id', $tbl_site_content, "deleted=0 and id='{$docAlias}'");
2811
                            }
2812
                            $docId = $this->db->getValue($rs);
2813
2814
                            if ($docId > 0)
2815
                            {
2816
                                $this->documentIdentifier = $docId;
2817
2818
                            }else{
2819
                            */
2820
                            $this->sendErrorPage();
2821
                            //}
2822
                        }
2823
                    } else {
2824
                        $this->sendErrorPage();
2825
                    }
2826
                }
2827
            } else {
2828
                if (isset($this->documentListing[$this->documentIdentifier])) {
2829
                    $this->documentIdentifier = $this->documentListing[$this->documentIdentifier];
2830
                } else {
2831
                    $docAlias = $this->db->escape($this->documentIdentifier);
2832
                    $rs = $this->db->select('id', $this->getFullTableName('site_content'), "deleted=0 and alias='{$docAlias}'");
2833
                    $this->documentIdentifier = (int)$this->db->getValue($rs);
2834
                }
2835
            }
2836
            $this->documentMethod = 'id';
2837
        }
2838
2839
        //$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...
2840
        // invoke OnWebPageInit event
2841
        $this->invokeEvent("OnWebPageInit");
2842
        // invoke OnLogPageView event
2843
        if ($this->config['track_visitors'] == 1) {
2844
            $this->invokeEvent("OnLogPageHit");
2845
        }
2846
        if ($this->config['seostrict'] === '1') {
2847
            $this->sendStrictURI();
2848
        }
2849
        $this->prepareResponse();
2850
    }
2851
2852
    /**
2853
     * @param $path
2854
     * @param null $suffix
2855
     * @return mixed
2856
     */
2857
    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...
2858
    {
2859
        $exp = explode('/', $path);
2860
        return str_replace($suffix, '', end($exp));
2861
    }
2862
2863
    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...
2864
    {
2865
        if ($this->config['friendly_urls'] != 1) {
2866
            return;
2867
        }
2868
2869
        if (strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') === false) {
2870
            return;
2871
        }
2872
2873
        $url = $_SERVER['QUERY_STRING'];
2874
        $err = substr($url, 0, 3);
2875
        if ($err !== '404' && $err !== '405') {
2876
            return;
2877
        }
2878
2879
        $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...
2880
        unset ($_GET[$k[0]]);
2881
        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...
2882
        $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...
2883
        $_SERVER['QUERY_STRING'] = $qp['query'];
2884
        if (!empty ($qp['query'])) {
2885
            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...
2886
            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...
2887
                $_REQUEST[$n] = $_GET[$n] = $v;
2888
            }
2889
        }
2890
        $_SERVER['PHP_SELF'] = $this->config['base_url'] . $qp['path'];
2891
        $this->q = $qp['path'];
2892
        return $qp['path'];
2893
    }
2894
2895
    /**
2896
     * The next step called at the end of executeParser()
2897
     *
2898
     * - checks cache
2899
     * - checks if document/resource is deleted/unpublished
2900
     * - checks if resource is a weblink and redirects if so
2901
     * - gets template and parses it
2902
     * - ensures that postProcess is called when PHP is finished
2903
     */
2904
    public function prepareResponse()
2905
    {
2906
        // we now know the method and identifier, let's check the cache
2907
2908
        if ($this->config['enable_cache'] == 2 && $this->isLoggedIn()) {
2909
            $this->config['enable_cache'] = 0;
2910
        }
2911
2912
        if ($this->config['enable_cache']) {
2913
            $this->documentContent = $this->getDocumentObjectFromCache($this->documentIdentifier, true);
2914
        } else {
2915
            $this->documentContent = '';
2916
        }
2917
2918
        if ($this->documentContent == '') {
2919
            // get document object from DB
2920
            $this->documentObject = $this->getDocumentObject($this->documentMethod, $this->documentIdentifier, 'prepareResponse');
2921
2922
            // write the documentName to the object
2923
            $this->documentName = &$this->documentObject['pagetitle'];
2924
2925
            // check if we should not hit this document
2926
            if ($this->documentObject['donthit'] == 1) {
2927
                $this->config['track_visitors'] = 0;
2928
            }
2929
2930
            if ($this->documentObject['deleted'] == 1) {
2931
                $this->sendErrorPage();
2932
            } // validation routines
2933
            elseif ($this->documentObject['published'] == 0) {
2934
                $this->_sendErrorForUnpubPage();
2935
            } elseif ($this->documentObject['type'] == 'reference') {
2936
                $this->_sendRedirectForRefPage($this->documentObject['content']);
2937
            }
2938
2939
            // get the template and start parsing!
2940
            if (!$this->documentObject['template']) {
2941
                $templateCode = '[*content*]';
2942
            } // use blank template
2943
            else {
2944
                $templateCode = $this->_getTemplateCodeFromDB($this->documentObject['template']);
2945
            }
2946
2947
            if (substr($templateCode, 0, 8) === '@INCLUDE') {
2948
                $templateCode = $this->atBindInclude($templateCode);
2949
            }
2950
2951
2952
            $this->documentContent = &$templateCode;
2953
2954
            // invoke OnLoadWebDocument event
2955
            $this->invokeEvent('OnLoadWebDocument');
2956
2957
            // Parse document source
2958
            $this->documentContent = $this->parseDocumentSource($templateCode);
2959
2960
            $this->documentGenerated = 1;
2961
        } else {
2962
            $this->documentGenerated = 0;
2963
        }
2964
2965
        if ($this->config['error_page'] == $this->documentIdentifier && $this->config['error_page'] != $this->config['site_start']) {
2966
            header('HTTP/1.0 404 Not Found');
2967
        }
2968
2969
        register_shutdown_function(array(
2970
            &$this,
2971
            'postProcess'
2972
        )); // tell PHP to call postProcess when it shuts down
2973
        $this->outputContent();
2974
        //$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...
2975
    }
2976
2977
    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...
2978
    {
2979
        // Can't view unpublished pages !$this->checkPreview()
2980
        if (!$this->hasPermission('view_unpublished')) {
2981
            $this->sendErrorPage();
2982
        } else {
2983
            $udperms = new Legacy\Permissions();
2984
            $udperms->user = $this->getLoginUserID();
0 ignored issues
show
Documentation Bug introduced by
The property $user was declared of type integer, but $this->getLoginUserID() is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
2985
            $udperms->document = $this->documentIdentifier;
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->documentIdentifier can also be of type string. However, the property $document is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
2986
            $udperms->role = $_SESSION['mgrRole'];
2987
            // Doesn't have access to this document
2988
            if (!$udperms->checkPermissions()) {
2989
                $this->sendErrorPage();
2990
            }
2991
        }
2992
    }
2993
2994
    /**
2995
     * @param $url
2996
     */
2997
    public function _sendRedirectForRefPage($url)
2998
    {
2999
        // check whether it's a reference
3000
        if (preg_match('@^[1-9][0-9]*$@', $url)) {
3001
            $url = $this->makeUrl($url); // if it's a bare document id
3002
        } elseif (strpos($url, '[~') !== false) {
3003
            $url = $this->rewriteUrls($url); // if it's an internal docid tag, process it
3004
        }
3005
        $this->sendRedirect($url, 0, '', 'HTTP/1.0 301 Moved Permanently');
3006
        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...
3007
    }
3008
3009
    /**
3010
     * @param $templateID
3011
     * @return mixed
3012
     */
3013
    public function _getTemplateCodeFromDB($templateID)
3014
    {
3015
        $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...
3016
        if ($this->db->getRecordCount($rs) == 1) {
3017
            return $this->db->getValue($rs);
3018
        } else {
3019
            $this->messageQuit('Incorrect number of templates returned from database');
3020
        }
3021
    }
3022
3023
    /**
3024
     * Returns an array of all parent record IDs for the id passed.
3025
     *
3026
     * @param int $id Docid to get parents for.
3027
     * @param int $height The maximum number of levels to go up, default 10.
3028
     * @return array
3029
     */
3030
    public function getParentIds($id, $height = 10)
3031
    {
3032
        $parents = array();
3033
        while ($id && $height--) {
3034
            $thisid = $id;
3035
            if ($this->config['aliaslistingfolder'] == 1) {
3036
                $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");
3037
                if (!$id || $id == '0') {
3038
                    break;
3039
                }
3040
            } else {
3041
                $id = $this->aliasListing[$id]['parent'];
3042
                if (!$id) {
3043
                    break;
3044
                }
3045
            }
3046
            $parents[$thisid] = $id;
3047
        }
3048
        return $parents;
3049
    }
3050
3051
    /**
3052
     * @param $id
3053
     * @param int $top
3054
     * @return mixed
3055
     */
3056
    public function getUltimateParentId($id, $top = 0)
3057
    {
3058
        $i = 0;
3059
        while ($id && $i < 20) {
3060
            if ($top == $this->aliasListing[$id]['parent']) {
3061
                break;
3062
            }
3063
            $id = $this->aliasListing[$id]['parent'];
3064
            $i++;
3065
        }
3066
        return $id;
3067
    }
3068
3069
    /**
3070
     * Returns an array of child IDs belonging to the specified parent.
3071
     *
3072
     * @param int $id The parent resource/document to start from
3073
     * @param int $depth How many levels deep to search for children, default: 10
3074
     * @param array $children Optional array of docids to merge with the result.
3075
     * @return array Contains the document Listing (tree) like the sitemap
3076
     */
3077
    public function getChildIds($id, $depth = 10, $children = array())
3078
    {
3079
3080
        $cacheKey = md5(print_r(func_get_args(), true));
3081
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3082
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3083
        }
3084
3085
        if ($this->config['aliaslistingfolder'] == 1) {
3086
3087
            $res = $this->db->select("id,alias,isfolder,parent", $this->getFullTableName('site_content'), "parent IN (" . $id . ") AND deleted = '0'");
3088
            $idx = array();
3089
            while ($row = $this->db->getRow($res)) {
3090
                $pAlias = '';
3091
                if (isset($this->aliasListing[$row['parent']])) {
3092
                    $pAlias .= !empty($this->aliasListing[$row['parent']]['path']) ? $this->aliasListing[$row['parent']]['path'] . '/' : '';
3093
                    $pAlias .= !empty($this->aliasListing[$row['parent']]['alias']) ? $this->aliasListing[$row['parent']]['alias'] . '/' : '';
3094
                };
3095
                $children[$pAlias . $row['alias']] = $row['id'];
3096
                if ($row['isfolder'] == 1) {
3097
                    $idx[] = $row['id'];
3098
                }
3099
            }
3100
            $depth--;
3101
            $idx = implode(',', $idx);
3102
            if (!empty($idx)) {
3103
                if ($depth) {
3104
                    $children = $this->getChildIds($idx, $depth, $children);
3105
                }
3106
            }
3107
            $this->tmpCache[__FUNCTION__][$cacheKey] = $children;
3108
            return $children;
3109
3110
        } else {
3111
3112
            // Initialise a static array to index parents->children
3113
            static $documentMap_cache = array();
3114
            if (!count($documentMap_cache)) {
3115
                foreach ($this->documentMap as $document) {
3116
                    foreach ($document as $p => $c) {
3117
                        $documentMap_cache[$p][] = $c;
3118
                    }
3119
                }
3120
            }
3121
3122
            // Get all the children for this parent node
3123
            if (isset($documentMap_cache[$id])) {
3124
                $depth--;
3125
3126
                foreach ($documentMap_cache[$id] as $childId) {
3127
                    $pkey = (strlen($this->aliasListing[$childId]['path']) ? "{$this->aliasListing[$childId]['path']}/" : '') . $this->aliasListing[$childId]['alias'];
3128
                    if (!strlen($pkey)) {
3129
                        $pkey = "{$childId}";
3130
                    }
3131
                    $children[$pkey] = $childId;
3132
3133
                    if ($depth && isset($documentMap_cache[$childId])) {
3134
                        $children += $this->getChildIds($childId, $depth);
3135
                    }
3136
                }
3137
            }
3138
            $this->tmpCache[__FUNCTION__][$cacheKey] = $children;
3139
            return $children;
3140
3141
        }
3142
    }
3143
3144
    /**
3145
     * Displays a javascript alert message in the web browser and quit
3146
     *
3147
     * @param string $msg Message to show
3148
     * @param string $url URL to redirect to
3149
     */
3150
    public function webAlertAndQuit($msg, $url = '')
3151
    {
3152
        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...
3153
        switch (true) {
3154
            case (0 === stripos($url, 'javascript:')):
3155
                $fnc = substr($url, 11);
3156
                break;
3157
            case $url === '#':
3158
                $fnc = '';
3159
                break;
3160
            case empty($url):
3161
                $fnc = 'history.back(-1);';
3162
                break;
3163
            default:
3164
                $fnc = "window.location.href='" . addslashes($url) . "';";
3165
        }
3166
3167
        echo "<html><head>
3168
            <title>MODX :: Alert</title>
3169
            <meta http-equiv=\"Content-Type\" content=\"text/html; charset={$modx_manager_charset};\">
3170
            <script>
3171
                function __alertQuit() {
3172
                    var el = document.querySelector('p');
3173
                    alert(el.innerHTML);
3174
                    el.remove();
3175
                    {$fnc}
3176
                }
3177
                window.setTimeout('__alertQuit();',100);
3178
            </script>
3179
            </head><body>
3180
            <p>{$msg}</p>
3181
            </body></html>";
3182
        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...
3183
    }
3184
3185
    /**
3186
     * Returns 1 if user has the currect permission
3187
     *
3188
     * @param string $pm Permission name
3189
     * @return int Why not bool?
3190
     */
3191
    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...
3192
    {
3193
        $state = 0;
3194
        $pms = $_SESSION['mgrPermissions'];
3195
        if ($pms) {
3196
            $state = ((bool)$pms[$pm] === true);
3197
        }
3198
        return (int)$state;
3199
    }
3200
3201
    /**
3202
     * Returns true if element is locked
3203
     *
3204
     * @param int $type Types: 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3205
     * @param int $id Element- / Resource-id
3206
     * @param bool $includeThisUser true = Return also info about actual user
3207
     * @return array lock-details or null
3208
     */
3209
    public function elementIsLocked($type, $id, $includeThisUser = false)
3210
    {
3211
        $id = (int)$id;
3212
        $type = (int)$type;
3213
        if (!$type || !$id) {
3214
            return null;
3215
        }
3216
3217
        // Build lockedElements-Cache at first call
3218
        $this->buildLockedElementsCache();
3219
3220
        if (!$includeThisUser && $this->lockedElements[$type][$id]['sid'] == $this->sid) {
3221
            return null;
3222
        }
3223
3224
        if (isset($this->lockedElements[$type][$id])) {
3225
            return $this->lockedElements[$type][$id];
3226
        } else {
3227
            return null;
3228
        }
3229
    }
3230
3231
    /**
3232
     * Returns Locked Elements as Array
3233
     *
3234
     * @param int $type Types: 0=all, 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3235
     * @param bool $minimumDetails true =
3236
     * @return array|mixed|null
3237
     */
3238
    public function getLockedElements($type = 0, $minimumDetails = false)
3239
    {
3240
        $this->buildLockedElementsCache();
3241
3242
        if (!$minimumDetails) {
3243
            $lockedElements = $this->lockedElements;
3244
        } else {
3245
            // Minimum details for HTML / Ajax-requests
3246
            $lockedElements = array();
3247
            foreach ($this->lockedElements as $elType => $elements) {
3248
                foreach ($elements as $elId => $el) {
3249
                    $lockedElements[$elType][$elId] = array(
3250
                        'username' => $el['username'],
3251
                        'lasthit_df' => $el['lasthit_df'],
3252
                        'state' => $this->determineLockState($el['internalKey'])
3253
                    );
3254
                }
3255
            }
3256
        }
3257
3258
        if ($type == 0) {
3259
            return $lockedElements;
3260
        }
3261
3262
        $type = (int)$type;
3263
        if (isset($lockedElements[$type])) {
3264
            return $lockedElements[$type];
3265
        } else {
3266
            return array();
3267
        }
3268
    }
3269
3270
    /**
3271
     * Builds the Locked Elements Cache once
3272
     */
3273
    public function buildLockedElementsCache()
3274
    {
3275
        if (is_null($this->lockedElements)) {
3276
            $this->lockedElements = array();
3277
            $this->cleanupExpiredLocks();
3278
3279
            $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...
3280
                LEFT JOIN {$this->getFullTableName('manager_users')} mu on ul.internalKey = mu.id");
3281
            while ($row = $this->db->getRow($rs)) {
3282
                $this->lockedElements[$row['elementType']][$row['elementId']] = array(
3283
                    'sid' => $row['sid'],
3284
                    'internalKey' => $row['internalKey'],
3285
                    'username' => $row['username'],
3286
                    'elementType' => $row['elementType'],
3287
                    'elementId' => $row['elementId'],
3288
                    'lasthit' => $row['lasthit'],
3289
                    'lasthit_df' => $this->toDateFormat($row['lasthit']),
3290
                    'state' => $this->determineLockState($row['sid'])
3291
                );
3292
            }
3293
        }
3294
    }
3295
3296
    /**
3297
     * Cleans up the active user locks table
3298
     */
3299
    public function cleanupExpiredLocks()
3300
    {
3301
        // Clean-up active_user_sessions first
3302
        $timeout = (int)$this->config['session_timeout'] < 2 ? 120 : $this->config['session_timeout'] * 60; // session.js pings every 10min, updateMail() in mainMenu pings every minute, so 2min is minimum
3303
        $validSessionTimeLimit = $this->time - $timeout;
3304
        $this->db->delete($this->getFullTableName('active_user_sessions'), "lasthit < {$validSessionTimeLimit}");
3305
3306
        // Clean-up active_user_locks
3307
        $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...
3308
        $count = $this->db->getRecordCount($rs);
3309
        if ($count) {
3310
            $rs = $this->db->makeArray($rs);
3311
            $userSids = array();
3312
            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...
3313
                $userSids[] = $row['sid'];
3314
            }
3315
            $userSids = "'" . implode("','", $userSids) . "'";
3316
            $this->db->delete($this->getFullTableName('active_user_locks'), "sid NOT IN({$userSids})");
3317
        } else {
3318
            $this->db->delete($this->getFullTableName('active_user_locks'));
3319
        }
3320
3321
    }
3322
3323
    /**
3324
     * Cleans up the active users table
3325
     */
3326
    public function cleanupMultipleActiveUsers()
3327
    {
3328
        $timeout = 20 * 60; // Delete multiple user-sessions after 20min
3329
        $validSessionTimeLimit = $this->time - $timeout;
3330
3331
        $activeUserSids = array();
3332
        $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...
3333
        $count = $this->db->getRecordCount($rs);
3334
        if ($count) {
3335
            $rs = $this->db->makeArray($rs);
3336
            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...
3337
                $activeUserSids[] = $row['sid'];
3338
            }
3339
        }
3340
3341
        $rs = $this->db->select("sid,internalKey,lasthit", "{$this->getFullTableName('active_users')}", "", "lasthit DESC");
3342
        if ($this->db->getRecordCount($rs)) {
3343
            $rs = $this->db->makeArray($rs);
3344
            $internalKeyCount = array();
3345
            $deleteSids = '';
3346
            foreach ($rs as $row) {
0 ignored issues
show
Bug introduced by
The expression $rs of type false|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

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

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

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

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

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

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

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

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

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

Loading history...
4389
     * - Parsed chunk or false if $chunkArr is not array.
4390
     */
4391
    public function parseChunk($chunkName, $chunkArr, $prefix = '{', $suffix = '}')
4392
    {
4393
        //TODO: Wouldn't it be more practical to return the contents of a chunk instead of false?
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
4394
        if (!is_array($chunkArr)) {
4395
            return false;
4396
        }
4397
4398
        return $this->parseText($this->getChunk($chunkName), $chunkArr, $prefix, $suffix);
0 ignored issues
show
Bug introduced by
It seems like $this->getChunk($chunkName) targeting EvolutionCMS\Core::getChunk() can also be of type boolean; however, EvolutionCMS\Core::parseText() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
4399
    }
4400
4401
    /**
4402
     * getTpl
4403
     * get template for snippets
4404
     * @param $tpl {string}
4405
     * @return bool|string {string}
4406
     */
4407
    public function getTpl($tpl)
4408
    {
4409
        $template = $tpl;
4410
        if (preg_match("/^@([^:\s]+)[:\s]+(.+)$/s", trim($tpl), $match)) {
4411
            $command = strtoupper($match[1]);
4412
            $template = $match[2];
4413
        }
4414
        switch ($command) {
0 ignored issues
show
Bug introduced by
The variable $command does not seem to be defined for all execution paths leading up to this point.

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

Let’s take a look at an example:

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

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

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

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

Available Fixes

  1. Check for existence of the variable explicitly:

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

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

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

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

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

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

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

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

Let’s take a look at an example:

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

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

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

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

Available Fixes

  1. Check for existence of the variable explicitly:

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

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

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

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

Let’s take a look at an example:

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

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

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

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

Available Fixes

  1. Check for existence of the variable explicitly:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Let’s take a look at an example:

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

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

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

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

Available Fixes

  1. Check for existence of the variable explicitly:

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

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

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

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

Let’s take a look at an example:

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

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

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

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

Available Fixes

  1. Check for existence of the variable explicitly:

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

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

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

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

Let’s take a look at an example:

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

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

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

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

Available Fixes

  1. Check for existence of the variable explicitly:

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

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

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

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

Let’s take a look at an example:

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

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

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

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

Available Fixes

  1. Check for existence of the variable explicitly:

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

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

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

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

Let’s take a look at an example:

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

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

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

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

Available Fixes

  1. Check for existence of the variable explicitly:

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

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

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

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

Let’s take a look at an example:

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

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

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

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

Available Fixes

  1. Check for existence of the variable explicitly:

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

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

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

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

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

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

Loading history...
4563
                foreach ($_ as $i => $v) {
4564
                    if ($v === 'value') {
4565
                        unset($_[$i]);
4566
                    } else {
4567
                        $_[$i] = 'tv.' . $v;
4568
                    }
4569
                }
4570
                $fields = implode(',', $_);
4571
            } else {
4572
                $fields = "tv.*";
4573
            }
4574
4575
            if ($tvsort != '') {
4576
                $tvsort = 'tv.' . implode(',tv.', array_filter(array_map('trim', explode(',', $tvsort))));
4577
            }
4578 View Code Duplication
            if ($tvidnames == "*") {
4579
                $query = "tv.id<>0";
4580
            } else {
4581
                $query = (is_numeric($tvidnames[0]) ? "tv.id" : "tv.name") . " IN ('" . implode("','", $tvidnames) . "')";
4582
            }
4583
4584
            $this->getUserDocGroups();
4585
4586
            foreach ($docs as $doc) {
4587
4588
                $docid = $doc['id'];
4589
4590
                $rs = $this->db->select("{$fields}, IF(tvc.value!='',tvc.value,tv.default_text) as value ", "[+prefix+]site_tmplvars tv
4591
                        INNER JOIN [+prefix+]site_tmplvar_templates tvtpl ON tvtpl.tmplvarid = tv.id
4592
                        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}" : ""));
4593
                $tvs = $this->db->makeArray($rs);
4594
4595
                // get default/built-in template variables
4596
                ksort($doc);
4597
                foreach ($doc as $key => $value) {
4598
                    if ($tvidnames == '*' || in_array($key, $tvidnames)) {
4599
                        $tvs[] = array('name' => $key, 'value' => $value);
4600
                    }
4601
                }
4602
                if (is_array($tvs) && count($tvs)) {
4603
                    $result[] = $tvs;
4604
                }
4605
            }
4606
            return $result;
4607
        }
4608
    }
4609
4610
    /**
4611
     * getDocumentChildrenTVarOutput
4612
     * @version 1.1 (2014-02-19)
4613
     *
4614
     * @desc Returns an array where each element represents one child doc and contains the result from getTemplateVarOutput().
4615
     *
4616
     * @param int $parentid {integer}
4617
     * - Id of parent document. Default: 0 (site root).
4618
     * @param array $tvidnames {array; '*'}
4619
     * - Which TVs to fetch. In the form expected by getTemplateVarOutput(). Default: array().
4620
     * @param int $published {0; 1; 'all'}
4621
     * - 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.
4622
     * @param string $sortBy {string}
4623
     * - How to sort the result array (field). Default: 'menuindex'.
4624
     * @param string $sortDir {'ASC'; 'DESC'}
4625
     * - How to sort the result array (direction). Default: 'ASC'.
4626
     * @param string $where {string}
4627
     * - SQL WHERE condition (use only document fields, not TV). Default: ''.
4628
     * @param string $resultKey {string; false}
4629
     * - Field, which values are keys into result array. Use the “false”, that result array keys just will be numbered. Default: 'id'.
4630
     * @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...
4631
     * - Result array, or false.
4632
     */
4633
    public function getDocumentChildrenTVarOutput($parentid = 0, $tvidnames = array(), $published = 1, $sortBy = 'menuindex', $sortDir = 'ASC', $where = '', $resultKey = 'id')
4634
    {
4635
        $docs = $this->getDocumentChildren($parentid, $published, 0, 'id', $where, $sortBy, $sortDir);
4636
4637
        if (!$docs) {
4638
            return false;
4639
        } else {
4640
            $result = array();
4641
4642
            $unsetResultKey = false;
4643
4644
            if ($resultKey !== false) {
4645
                if (is_array($tvidnames)) {
4646
                    if (count($tvidnames) != 0 && !in_array($resultKey, $tvidnames)) {
4647
                        $tvidnames[] = $resultKey;
4648
                        $unsetResultKey = true;
4649
                    }
4650
                } else if ($tvidnames != '*' && $tvidnames != $resultKey) {
4651
                    $tvidnames = array($tvidnames, $resultKey);
4652
                    $unsetResultKey = true;
4653
                }
4654
            }
4655
4656
            for ($i = 0; $i < count($docs); $i++) {
4657
                $tvs = $this->getTemplateVarOutput($tvidnames, $docs[$i]['id'], $published);
4658
4659
                if ($tvs) {
4660
                    if ($resultKey !== false && array_key_exists($resultKey, $tvs)) {
4661
                        $result[$tvs[$resultKey]] = $tvs;
4662
4663
                        if ($unsetResultKey) {
4664
                            unset($result[$tvs[$resultKey]][$resultKey]);
4665
                        }
4666
                    } else {
4667
                        $result[] = $tvs;
4668
                    }
4669
                }
4670
            }
4671
4672
            return $result;
4673
        }
4674
    }
4675
4676
    /**
4677
     * Modified by Raymond for TV - Orig Modified by Apodigm - DocVars
4678
     * Returns a single site_content field or TV record from the db.
4679
     *
4680
     * If a site content field the result is an associative array of 'name' and 'value'.
4681
     *
4682
     * If a TV the result is an array representing a db row including the fields specified in $fields.
4683
     *
4684
     * @param string $idname Can be a TV id or name
4685
     * @param string $fields Fields to fetch from site_tmplvars. Default: *
4686
     * @param string|type $docid Docid. Defaults to empty string which indicates the current document.
4687
     * @param int $published Whether published or unpublished documents are in the result
4688
     *                        Default: 1
4689
     * @return bool
4690
     */
4691 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...
4692
    {
4693
        if ($idname == "") {
4694
            return false;
4695
        } else {
4696
            $result = $this->getTemplateVars(array($idname), $fields, $docid, $published, "", ""); //remove sorting for speed
4697
            return ($result != false) ? $result[0] : false;
4698
        }
4699
    }
4700
4701
    /**
4702
     * getTemplateVars
4703
     * @version 1.0.1 (2014-02-19)
4704
     *
4705
     * @desc Returns an array of site_content field fields and/or TV records from the db.
4706
     * Elements representing a site content field consist of an associative array of 'name' and 'value'.
4707
     * Elements representing a TV consist of an array representing a db row including the fields specified in $fields.
4708
     *
4709
     * @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
4710
     * @param $fields {comma separated string; '*'} - Fields names in the TV table of MODx database. Default: '*'
4711
     * @param $docid {integer; ''} - Id of a document to get. Default: an empty string which indicates the current document.
4712
     * @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.
4713
     * @param $sort {comma separated string} - Fields of the TV table to sort by. Default: 'rank'.
4714
     * @param $dir {'ASC'; 'DESC'} - How to sort the result array (direction). Default: 'ASC'.
4715
     *
4716
     * @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...
4717
     */
4718
    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...
4719
    {
4720
        $cacheKey = md5(print_r(func_get_args(), true));
4721
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
4722
            return $this->tmpCache[__FUNCTION__][$cacheKey];
4723
        }
4724
4725
        if (($idnames != '*' && !is_array($idnames)) || empty($idnames) ) {
4726
            return false;
4727
        } else {
4728
4729
            // get document record
4730
            if ($docid == '') {
4731
                $docid = $this->documentIdentifier;
4732
                $docRow = $this->documentObject;
4733
            } else {
4734
                $docRow = $this->getDocument($docid, '*', $published);
4735
4736
                if (!$docRow) {
4737
                    $this->tmpCache[__FUNCTION__][$cacheKey] = false;
4738
                    return false;
4739
                }
4740
            }
4741
4742
            // get user defined template variables
4743
            $fields = ($fields == '') ? 'tv.*' : 'tv.' . implode(',tv.', array_filter(array_map('trim', explode(',', $fields))));
4744
            $sort = ($sort == '') ? '' : 'tv.' . implode(',tv.', array_filter(array_map('trim', explode(',', $sort))));
4745
4746 View Code Duplication
            if ($idnames == '*') {
4747
                $query = 'tv.id<>0';
4748
            } else {
4749
                $query = (is_numeric($idnames[0]) ? 'tv.id' : 'tv.name') . " IN ('" . implode("','", $idnames) . "')";
4750
            }
4751
4752
            $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...
4753
                    INNER JOIN " . $this->getFullTableName('site_tmplvar_templates') . " tvtpl ON tvtpl.tmplvarid = tv.id
4754
                    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}" : ""));
4755
4756
            $result = $this->db->makeArray($rs);
4757
4758
            // get default/built-in template variables
4759
            if(is_array($docRow)){
4760
                ksort($docRow);
4761
4762
                foreach ($docRow as $key => $value) {
4763
                    if ($idnames == '*' || in_array($key, $idnames)) {
4764
                        array_push($result, array(
4765
                            'name' => $key,
4766
                            'value' => $value
4767
                        ));
4768
                    }
4769
                }
4770
            }
4771
4772
            $this->tmpCache[__FUNCTION__][$cacheKey] = $result;
4773
4774
            return $result;
4775
        }
4776
    }
4777
4778
    /**
4779
     * getTemplateVarOutput
4780
     * @version 1.0.1 (2014-02-19)
4781
     *
4782
     * @desc Returns an associative array containing TV rendered output values.
4783
     *
4784
     * @param array $idnames {array; '*'}
4785
     * - 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
4786
     * @param string $docid {integer; ''}
4787
     * - Id of a document to get. Default: an empty string which indicates the current document.
4788
     * @param int $published {0; 1; 'all'}
4789
     * - 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.
4790
     * @param string $sep {string}
4791
     * - Separator that is used while concatenating in getTVDisplayFormat(). Default: ''.
4792
     * @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...
4793
     * - Result array, or false.
4794
     */
4795
    public function getTemplateVarOutput($idnames = array(), $docid = '', $published = 1, $sep = '')
4796
    {
4797
        if (is_array($idnames) && empty($idnames) ) {
4798
            return false;
4799
        } else {
4800
            $output = array();
4801
            $vars = ($idnames == '*' || is_array($idnames)) ? $idnames : array($idnames);
4802
4803
            $docid = (int)$docid > 0 ? (int)$docid : $this->documentIdentifier;
4804
            // remove sort for speed
4805
            $result = $this->getTemplateVars($vars, '*', $docid, $published, '', '');
4806
4807
            if ($result == false) {
4808
                return false;
4809
            } else {
4810
                for ($i = 0; $i < count($result); $i++) {
4811
                    $row = $result[$i];
4812
4813
                    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...
4814
                        $output[$row['name']] = $row['value'];
4815
                    } else {
4816
                        $output[$row['name']] = getTVDisplayFormat($row['name'], $row['value'], $row['display'], $row['display_params'], $row['type'], $docid, $sep);
4817
                    }
4818
                }
4819
4820
                return $output;
4821
            }
4822
        }
4823
    }
4824
4825
    /**
4826
     * Returns the full table name based on db settings
4827
     *
4828
     * @param string $tbl Table name
4829
     * @return string Table name with prefix
4830
     */
4831
    public function getFullTableName($tbl)
4832
    {
4833
        return $this->db->config['dbase'] . ".`" . $this->db->config['table_prefix'] . $tbl . "`";
4834
    }
4835
4836
    /**
4837
     * Returns the placeholder value
4838
     *
4839
     * @param string $name Placeholder name
4840
     * @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...
4841
     */
4842
    public function getPlaceholder($name)
4843
    {
4844
        return isset($this->placeholders[$name]) ? $this->placeholders[$name] : null;
4845
    }
4846
4847
    /**
4848
     * Sets a value for a placeholder
4849
     *
4850
     * @param string $name The name of the placeholder
4851
     * @param string $value The value of the placeholder
4852
     */
4853
    public function setPlaceholder($name, $value)
4854
    {
4855
        $this->placeholders[$name] = $value;
4856
    }
4857
4858
    /**
4859
     * Set placeholders en masse via an array or object.
4860
     *
4861
     * @param object|array $subject
4862
     * @param string $prefix
4863
     */
4864
    public function toPlaceholders($subject, $prefix = '')
4865
    {
4866
        if (is_object($subject)) {
4867
            $subject = get_object_vars($subject);
4868
        }
4869
        if (is_array($subject)) {
4870
            foreach ($subject as $key => $value) {
4871
                $this->toPlaceholder($key, $value, $prefix);
4872
            }
4873
        }
4874
    }
4875
4876
    /**
4877
     * For use by toPlaceholders(); For setting an array or object element as placeholder.
4878
     *
4879
     * @param string $key
4880
     * @param object|array $value
4881
     * @param string $prefix
4882
     */
4883
    public function toPlaceholder($key, $value, $prefix = '')
4884
    {
4885
        if (is_array($value) || is_object($value)) {
4886
            $this->toPlaceholders($value, "{$prefix}{$key}.");
4887
        } else {
4888
            $this->setPlaceholder("{$prefix}{$key}", $value);
4889
        }
4890
    }
4891
4892
    /**
4893
     * Returns the manager relative URL/path with respect to the site root.
4894
     *
4895
     * @global string $base_url
4896
     * @return string The complete URL to the manager folder
4897
     */
4898
    public function getManagerPath()
4899
    {
4900
        return MODX_MANAGER_URL;
4901
    }
4902
4903
    /**
4904
     * Returns the cache relative URL/path with respect to the site root.
4905
     *
4906
     * @global string $base_url
4907
     * @return string The complete URL to the cache folder
4908
     */
4909
    public function getCachePath()
4910
    {
4911
        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...
4912
        $pth = $base_url . $this->getCacheFolder();
4913
        return $pth;
4914
    }
4915
4916
    /**
4917
     * Sends a message to a user's message box.
4918
     *
4919
     * @param string $type Type of the message
4920
     * @param string $to The recipient of the message
4921
     * @param string $from The sender of the message
4922
     * @param string $subject The subject of the message
4923
     * @param string $msg The message body
4924
     * @param int $private Whether it is a private message, or not
4925
     *                     Default : 0
4926
     */
4927
    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...
4928
    {
4929
        $private = ($private) ? 1 : 0;
4930 View Code Duplication
        if (!is_numeric($to)) {
4931
            // Query for the To ID
4932
            $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...
4933
            $to = $this->db->getValue($rs);
4934
        }
4935 View Code Duplication
        if (!is_numeric($from)) {
4936
            // Query for the From ID
4937
            $rs = $this->db->select('id', $this->getFullTableName("manager_users"), "username='{$from}'");
4938
            $from = $this->db->getValue($rs);
4939
        }
4940
        // insert a new message into user_messages
4941
        $this->db->insert(array(
4942
            'type' => $type,
4943
            'subject' => $subject,
4944
            'message' => $msg,
4945
            'sender' => $from,
4946
            'recipient' => $to,
4947
            'private' => $private,
4948
            'postdate' => $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'],
4949
            'messageread' => 0,
4950
        ), $this->getFullTableName('user_messages'));
4951
    }
4952
4953
    /**
4954
     * Returns current user id.
4955
     *
4956
     * @param string $context . Default is an empty string which indicates the method should automatically pick 'web (frontend) or 'mgr' (backend)
4957
     * @return string
4958
     */
4959 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...
4960
    {
4961
        $out = false;
4962
4963
        if (!empty($context)) {
4964
            if (is_scalar($context) && isset($_SESSION[$context . 'Validated'])) {
4965
                $out = $_SESSION[$context . 'InternalKey'];
4966
            }
4967
        } else {
4968
            switch (true) {
4969
                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...
4970
                    $out = $_SESSION['webInternalKey'];
4971
                    break;
4972
                }
4973
                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...
4974
                    $out = $_SESSION['mgrInternalKey'];
4975
                    break;
4976
                }
4977
            }
4978
        }
4979
        return $out;
4980
    }
4981
4982
    /**
4983
     * Returns current user name
4984
     *
4985
     * @param string $context . Default is an empty string which indicates the method should automatically pick 'web (frontend) or 'mgr' (backend)
4986
     * @return string
4987
     */
4988 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...
4989
    {
4990
        $out = false;
4991
4992
        if (!empty($context)) {
4993
            if (is_scalar($context) && isset($_SESSION[$context . 'Validated'])) {
4994
                $out = $_SESSION[$context . 'Shortname'];
4995
            }
4996
        } else {
4997
            switch (true) {
4998
                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...
4999
                    $out = $_SESSION['webShortname'];
5000
                    break;
5001
                }
5002
                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...
5003
                    $out = $_SESSION['mgrShortname'];
5004
                    break;
5005
                }
5006
            }
5007
        }
5008
        return $out;
5009
    }
5010
5011
    /**
5012
     * Returns current login user type - web or manager
5013
     *
5014
     * @return string
5015
     */
5016
    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...
5017
    {
5018
        if ($this->isFrontend() && isset ($_SESSION['webValidated'])) {
5019
            return 'web';
5020
        } elseif ($this->isBackend() && isset ($_SESSION['mgrValidated'])) {
5021
            return 'manager';
5022
        } else {
5023
            return '';
5024
        }
5025
    }
5026
5027
    /**
5028
     * Returns a user info record for the given manager user
5029
     *
5030
     * @param int $uid
5031
     * @return boolean|string
5032
     */
5033
    public function getUserInfo($uid)
5034
    {
5035
        if (isset($this->tmpCache[__FUNCTION__][$uid])) {
5036
            return $this->tmpCache[__FUNCTION__][$uid];
5037
        }
5038
5039
        $from = '[+prefix+]manager_users mu INNER JOIN [+prefix+]user_attributes mua ON mua.internalkey=mu.id';
5040
        $where = sprintf("mu.id='%s'", $this->db->escape($uid));
5041
        $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...
5042
5043
        if (!$this->db->getRecordCount($rs)) {
5044
            return $this->tmpCache[__FUNCTION__][$uid] = false;
5045
        }
5046
5047
        $row = $this->db->getRow($rs);
5048 View Code Duplication
        if (!isset($row['usertype']) || !$row['usertype']) {
5049
            $row['usertype'] = 'manager';
5050
        }
5051
5052
        $this->tmpCache[__FUNCTION__][$uid] = $row;
5053
5054
        return $row;
5055
    }
5056
5057
    /**
5058
     * Returns a record for the web user
5059
     *
5060
     * @param int $uid
5061
     * @return boolean|string
5062
     */
5063
    public function getWebUserInfo($uid)
5064
    {
5065
        $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...
5066
                INNER JOIN " . $this->getFullTableName("web_user_attributes") . " wua ON wua.internalkey=wu.id", "wu.id='{$uid}'");
5067
        if ($row = $this->db->getRow($rs)) {
5068 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...
5069
                $row["usertype"] = "web";
5070
            }
5071
            return $row;
5072
        }
5073
    }
5074
5075
    /**
5076
     * Returns an array of document groups that current user is assigned to.
5077
     * This function will first return the web user doc groups when running from
5078
     * frontend otherwise it will return manager user's docgroup.
5079
     *
5080
     * @param boolean $resolveIds Set to true to return the document group names
5081
     *                            Default: false
5082
     * @return string|array
5083
     */
5084
    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...
5085
    {
5086
        if ($this->isFrontend() && isset($_SESSION['webDocgroups']) && isset($_SESSION['webValidated'])) {
5087
            $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...
5088
            $dgn = isset($_SESSION['webDocgrpNames']) ? $_SESSION['webDocgrpNames'] : false;
5089
        } else if ($this->isBackend() && isset($_SESSION['mgrDocgroups']) && isset($_SESSION['mgrValidated'])) {
5090
            $dg = $_SESSION['mgrDocgroups'];
5091
            $dgn = isset($_SESSION['mgrDocgrpNames']) ? $_SESSION['mgrDocgrpNames'] : false;
5092
        } else {
5093
            $dg = '';
5094
        }
5095
        if (!$resolveIds) {
5096
            return $dg;
5097
        } else if (is_array($dgn)) {
5098
            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...
5099
        } else if (is_array($dg)) {
5100
            // resolve ids to names
5101
            $dgn = array();
5102
            $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...
5103
            while ($row = $this->db->getRow($ds)) {
5104
                $dgn[] = $row['name'];
5105
            }
5106
            // cache docgroup names to session
5107
            if ($this->isFrontend()) {
5108
                $_SESSION['webDocgrpNames'] = $dgn;
5109
            } else {
5110
                $_SESSION['mgrDocgrpNames'] = $dgn;
5111
            }
5112
            return $dgn;
5113
        }
5114
    }
5115
5116
    /**
5117
     * Change current web user's password
5118
     *
5119
     * @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...
5120
     * @param string $oldPwd
5121
     * @param string $newPwd
5122
     * @return string|boolean Returns true if successful, oterhwise return error
5123
     *                        message
5124
     */
5125
    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...
5126
    {
5127
        $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...
5128
        if ($_SESSION["webValidated"] == 1) {
5129
            $tbl = $this->getFullTableName("web_users");
5130
            $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...
5131
            if ($row = $this->db->getRow($ds)) {
5132
                if ($row["password"] == md5($oldPwd)) {
5133
                    if (strlen($newPwd) < 6) {
5134
                        return "Password is too short!";
5135
                    } elseif ($newPwd == "") {
5136
                        return "You didn't specify a password for this user!";
5137
                    } else {
5138
                        $this->db->update(array(
5139
                            'password' => $this->db->escape($newPwd),
5140
                        ), $tbl, "id='" . $this->getLoginUserID() . "'");
5141
                        // invoke OnWebChangePassword event
5142
                        $this->invokeEvent("OnWebChangePassword", array(
5143
                            "userid" => $row["id"],
5144
                            "username" => $row["username"],
5145
                            "userpassword" => $newPwd
5146
                        ));
5147
                        return true;
5148
                    }
5149
                } else {
5150
                    return "Incorrect password.";
5151
                }
5152
            }
5153
        }
5154
        return $rt;
5155
    }
5156
5157
    /**
5158
     * Returns true if the current web user is a member the specified groups
5159
     *
5160
     * @param array $groupNames
5161
     * @return boolean
5162
     */
5163
    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...
5164
    {
5165
        if (!is_array($groupNames)) {
5166
            return false;
5167
        }
5168
        // check cache
5169
        $grpNames = isset ($_SESSION['webUserGroupNames']) ? $_SESSION['webUserGroupNames'] : false;
5170
        if (!is_array($grpNames)) {
5171
            $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...
5172
                    INNER JOIN " . $this->getFullTableName("web_groups") . " wg ON wg.webgroup=wgn.id AND wg.webuser='" . $this->getLoginUserID() . "'");
5173
            $grpNames = $this->db->getColumn("name", $rs);
5174
            // save to cache
5175
            $_SESSION['webUserGroupNames'] = $grpNames;
5176
        }
5177
        foreach ($groupNames as $k => $v) {
5178
            if (in_array(trim($v), $grpNames)) {
5179
                return true;
5180
            }
5181
        }
5182
        return false;
5183
    }
5184
5185
    /**
5186
     * Registers Client-side CSS scripts - these scripts are loaded at inside
5187
     * the <head> tag
5188
     *
5189
     * @param string $src
5190
     * @param string $media Default: Empty string
5191
     * @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...
5192
     */
5193
    public function regClientCSS($src, $media = '')
5194
    {
5195
        if (empty($src) || isset ($this->loadedjscripts[$src])) {
5196
            return '';
5197
        }
5198
        $nextpos = max(array_merge(array(0), array_keys($this->sjscripts))) + 1;
5199
        $this->loadedjscripts[$src]['startup'] = true;
5200
        $this->loadedjscripts[$src]['version'] = '0';
5201
        $this->loadedjscripts[$src]['pos'] = $nextpos;
5202
        if (strpos(strtolower($src), "<style") !== false || strpos(strtolower($src), "<link") !== false) {
5203
            $this->sjscripts[$nextpos] = $src;
5204
        } else {
5205
            $this->sjscripts[$nextpos] = "\t" . '<link rel="stylesheet" type="text/css" href="' . $src . '" ' . ($media ? 'media="' . $media . '" ' : '') . '/>';
5206
        }
5207
    }
5208
5209
    /**
5210
     * Registers Startup Client-side JavaScript - these scripts are loaded at inside the <head> tag
5211
     *
5212
     * @param string $src
5213
     * @param array $options Default: 'name'=>'', 'version'=>'0', 'plaintext'=>false
5214
     */
5215
    public function regClientStartupScript($src, $options = array('name' => '', 'version' => '0', 'plaintext' => false))
5216
    {
5217
        $this->regClientScript($src, $options, true);
5218
    }
5219
5220
    /**
5221
     * Registers Client-side JavaScript these scripts are loaded at the end of the page unless $startup is true
5222
     *
5223
     * @param string $src
5224
     * @param array $options Default: 'name'=>'', 'version'=>'0', 'plaintext'=>false
5225
     * @param boolean $startup Default: false
5226
     * @return string
5227
     */
5228
    public function regClientScript($src, $options = array('name' => '', 'version' => '0', 'plaintext' => false), $startup = false)
5229
    {
5230
        if (empty($src)) {
5231
            return '';
5232
        } // nothing to register
5233
        if (!is_array($options)) {
5234
            if (is_bool($options))  // backward compatibility with old plaintext parameter
5235
            {
5236
                $options = array('plaintext' => $options);
5237
            } elseif (is_string($options)) // Also allow script name as 2nd param
5238
            {
5239
                $options = array('name' => $options);
5240
            } else {
5241
                $options = array();
5242
            }
5243
        }
5244
        $name = isset($options['name']) ? strtolower($options['name']) : '';
5245
        $version = isset($options['version']) ? $options['version'] : '0';
5246
        $plaintext = isset($options['plaintext']) ? $options['plaintext'] : false;
5247
        $key = !empty($name) ? $name : $src;
5248
        unset($overwritepos); // probably unnecessary--just making sure
5249
5250
        $useThisVer = true;
5251
        if (isset($this->loadedjscripts[$key])) { // a matching script was found
5252
            // if existing script is a startup script, make sure the candidate is also a startup script
5253
            if ($this->loadedjscripts[$key]['startup']) {
5254
                $startup = true;
5255
            }
5256
5257
            if (empty($name)) {
5258
                $useThisVer = false; // if the match was based on identical source code, no need to replace the old one
5259
            } else {
5260
                $useThisVer = version_compare($this->loadedjscripts[$key]['version'], $version, '<');
5261
            }
5262
5263
            if ($useThisVer) {
5264
                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...
5265
                    // remove old script from the bottom of the page (new one will be at the top)
5266
                    unset($this->jscripts[$this->loadedjscripts[$key]['pos']]);
5267
                } else {
5268
                    // overwrite the old script (the position may be important for dependent scripts)
5269
                    $overwritepos = $this->loadedjscripts[$key]['pos'];
5270
                }
5271
            } else { // Use the original version
5272
                if ($startup == true && $this->loadedjscripts[$key]['startup'] == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
5273
                    // need to move the exisiting script to the head
5274
                    $version = $this->loadedjscripts[$key][$version];
5275
                    $src = $this->jscripts[$this->loadedjscripts[$key]['pos']];
5276
                    unset($this->jscripts[$this->loadedjscripts[$key]['pos']]);
5277
                } else {
5278
                    return ''; // the script is already in the right place
5279
                }
5280
            }
5281
        }
5282
5283
        if ($useThisVer && $plaintext != true && (strpos(strtolower($src), "<script") === false)) {
5284
            $src = "\t" . '<script type="text/javascript" src="' . $src . '"></script>';
5285
        }
5286
        if ($startup) {
5287
            $pos = isset($overwritepos) ? $overwritepos : max(array_merge(array(0), array_keys($this->sjscripts))) + 1;
5288
            $this->sjscripts[$pos] = $src;
5289
        } else {
5290
            $pos = isset($overwritepos) ? $overwritepos : max(array_merge(array(0), array_keys($this->jscripts))) + 1;
5291
            $this->jscripts[$pos] = $src;
5292
        }
5293
        $this->loadedjscripts[$key]['version'] = $version;
5294
        $this->loadedjscripts[$key]['startup'] = $startup;
5295
        $this->loadedjscripts[$key]['pos'] = $pos;
5296
        return '';
5297
    }
5298
5299
    /**
5300
     * Returns all registered JavaScripts
5301
     *
5302
     * @return string
5303
     */
5304
    public function regClientStartupHTMLBlock($html)
5305
    {
5306
        return $this->regClientScript($html, true, true);
0 ignored issues
show
Documentation introduced by
true is of type boolean, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
5307
    }
5308
5309
    /**
5310
     * Returns all registered startup scripts
5311
     *
5312
     * @return string
5313
     */
5314
    public function regClientHTMLBlock($html)
5315
    {
5316
        return $this->regClientScript($html, true);
0 ignored issues
show
Documentation introduced by
true is of type boolean, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
5317
    }
5318
5319
    /**
5320
     * Remove unwanted html tags and snippet, settings and tags
5321
     *
5322
     * @param string $html
5323
     * @param string $allowed Default: Empty string
5324
     * @return string
5325
     */
5326
    public function stripTags($html, $allowed = "")
5327
    {
5328
        $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...
5329
        $t = preg_replace('~\[\*(.*?)\*\]~', "", $t); //tv
5330
        $t = preg_replace('~\[\[(.*?)\]\]~', "", $t); //snippet
5331
        $t = preg_replace('~\[\!(.*?)\!\]~', "", $t); //snippet
5332
        $t = preg_replace('~\[\((.*?)\)\]~', "", $t); //settings
5333
        $t = preg_replace('~\[\+(.*?)\+\]~', "", $t); //placeholders
5334
        $t = preg_replace('~{{(.*?)}}~', "", $t); //chunks
5335
        return $t;
5336
    }
5337
5338
    /**
5339
     * Add an event listener to a plugin - only for use within the current execution cycle
5340
     *
5341
     * @param string $evtName
5342
     * @param string $pluginName
5343
     * @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...
5344
     */
5345
    public function addEventListener($evtName, $pluginName)
5346
    {
5347
        if (!$evtName || !$pluginName) {
5348
            return false;
5349
        }
5350
        if (!array_key_exists($evtName, $this->pluginEvent)) {
5351
            $this->pluginEvent[$evtName] = array();
5352
        }
5353
        return array_push($this->pluginEvent[$evtName], $pluginName); // return array count
5354
    }
5355
5356
    /**
5357
     * Remove event listener - only for use within the current execution cycle
5358
     *
5359
     * @param string $evtName
5360
     * @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...
5361
     */
5362
    public function removeEventListener($evtName)
5363
    {
5364
        if (!$evtName) {
5365
            return false;
5366
        }
5367
        unset ($this->pluginEvent[$evtName]);
5368
    }
5369
5370
    /**
5371
     * Remove all event listeners - only for use within the current execution cycle
5372
     */
5373
    public function removeAllEventListener()
5374
    {
5375
        unset ($this->pluginEvent);
5376
        $this->pluginEvent = array();
5377
    }
5378
5379
    /**
5380
     * Invoke an event.
5381
     *
5382
     * @param string $evtName
5383
     * @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.
5384
     * @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...
5385
     */
5386
    public function invokeEvent($evtName, $extParams = array())
5387
    {
5388
        if (!$evtName) {
5389
            return false;
5390
        }
5391
        if (!isset ($this->pluginEvent[$evtName])) {
5392
            return false;
5393
        }
5394
5395
        $results = null;
5396
        foreach ($this->pluginEvent[$evtName] as $pluginName) { // start for loop
5397
            if ($this->dumpPlugins) {
5398
                $eventtime = $this->getMicroTime();
5399
            }
5400
            // reset event object
5401
            $e = &$this->event;
5402
            $e->_resetEventObject();
5403
            $e->name = $evtName;
5404
            $e->activePlugin = $pluginName;
5405
5406
            // get plugin code
5407
            $_ = $this->getPluginCode($pluginName);
5408
            $pluginCode = $_['code'];
5409
            $pluginProperties = $_['props'];
5410
5411
            // load default params/properties
5412
            $parameter = $this->parseProperties($pluginProperties);
5413
            if (!is_array($parameter)) {
5414
                $parameter = array();
5415
            }
5416
            if (!empty($extParams)) {
5417
                $parameter = array_merge($parameter, $extParams);
5418
            }
5419
5420
            // eval plugin
5421
            $this->evalPlugin($pluginCode, $parameter);
5422
5423
            if (class_exists('PHxParser')) {
5424
                $this->config['enable_filter'] = 0;
5425
            }
5426
5427
            if ($this->dumpPlugins) {
5428
                $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...
5429
                $this->pluginsCode .= sprintf('<fieldset><legend><b>%s / %s</b> (%2.2f ms)</legend>', $evtName, $pluginName, $eventtime * 1000);
5430
                foreach ($parameter as $k => $v) {
5431
                    $this->pluginsCode .= "{$k} => " . print_r($v, true) . '<br>';
5432
                }
5433
                $this->pluginsCode .= '</fieldset><br />';
5434
                $this->pluginsTime["{$evtName} / {$pluginName}"] += $eventtime;
5435
            }
5436
            if ($e->getOutput() != '') {
5437
                $results[] = $e->getOutput();
5438
            }
5439
            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...
5440
                break;
5441
            }
5442
        }
5443
5444
        $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...
5445
        return $results;
5446
    }
5447
5448
    /**
5449
     * Returns plugin-code and properties
5450
     *
5451
     * @param string $pluginName
5452
     * @return array Associative array consisting of 'code' and 'props'
5453
     */
5454
    public function getPluginCode($pluginName)
5455
    {
5456
        $plugin = array();
5457
        if (isset ($this->pluginCache[$pluginName])) {
5458
            $pluginCode = $this->pluginCache[$pluginName];
5459
            $pluginProperties = isset($this->pluginCache[$pluginName . "Props"]) ? $this->pluginCache[$pluginName . "Props"] : '';
5460
        } else {
5461
            $pluginName = $this->db->escape($pluginName);
5462
            $result = $this->db->select('name, plugincode, properties', $this->getFullTableName("site_plugins"), "name='{$pluginName}' AND disabled=0");
5463
            if ($row = $this->db->getRow($result)) {
5464
                $pluginCode = $this->pluginCache[$row['name']] = $row['plugincode'];
5465
                $pluginProperties = $this->pluginCache[$row['name'] . "Props"] = $row['properties'];
5466
            } else {
5467
                $pluginCode = $this->pluginCache[$pluginName] = "return false;";
5468
                $pluginProperties = '';
5469
            }
5470
        }
5471
        $plugin['code'] = $pluginCode;
5472
        $plugin['props'] = $pluginProperties;
5473
5474
        return $plugin;
5475
    }
5476
5477
    /**
5478
     * Parses a resource property string and returns the result as an array
5479
     *
5480
     * @param string|array $propertyString
5481
     * @param string|null $elementName
5482
     * @param string|null $elementType
5483
     * @return array Associative array in the form property name => property value
5484
     */
5485
    public function parseProperties($propertyString, $elementName = null, $elementType = null)
5486
    {
5487
        if(\is_scalar($propertyString)) {
5488
            $property = array();
5489
            $propertyString = trim($propertyString);
5490
            $propertyString = str_replace('{}', '', $propertyString);
5491
            $propertyString = str_replace('} {', ',', $propertyString);
5492
            if (!empty($propertyString) && $propertyString != '{}') {
5493
                $jsonFormat = $this->isJson($propertyString, true);
5494
                // old format
5495
                if ($jsonFormat === false) {
5496
                    $props = explode('&', $propertyString);
5497
                    foreach ($props as $prop) {
5498
5499
                        if (empty($prop)) {
5500
                            continue;
5501
                        } elseif (strpos($prop, '=') === false) {
5502
                            $property[trim($prop)] = '';
5503
                            continue;
5504
                        }
5505
5506
                        $_ = explode('=', $prop, 2);
5507
                        $key = trim($_[0]);
5508
                        $p = explode(';', trim($_[1]));
5509
                        switch ($p[1]) {
5510
                            case 'list':
5511
                            case 'list-multi':
5512
                            case 'checkbox':
5513
                            case 'radio':
5514
                                $value = !isset($p[3]) ? '' : $p[3];
5515
                                break;
5516
                            default:
5517
                                $value = !isset($p[2]) ? '' : $p[2];
5518
                        }
5519
                        if (!empty($key)) {
5520
                            $property[$key] = $value;
5521
                        }
5522
                    }
5523
                    // new json-format
5524
                } else if (!empty($jsonFormat)) {
5525
                    foreach ($jsonFormat as $key => $row) {
5526
                        if (!empty($key)) {
5527 View Code Duplication
                            if (is_array($row)) {
5528
                                if (isset($row[0]['value'])) {
5529
                                    $value = $row[0]['value'];
5530
                                }
5531
                            } else {
5532
                                $value = $row;
5533
                            }
5534
                            if (isset($value) && $value !== '') {
5535
                                $property[$key] = $value;
5536
                            }
5537
                        }
5538
                    }
5539
                }
5540
            }
5541
        }
5542
        elseif(\is_array($propertyString)) {
5543
            $property = $propertyString;
5544
        }
5545
        if (!empty($elementName) && !empty($elementType)) {
5546
            $out = $this->invokeEvent('OnParseProperties', array(
5547
                'element' => $elementName,
5548
                'type' => $elementType,
5549
                'args' => $property
0 ignored issues
show
Bug introduced by
The variable $property 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...
5550
            ));
5551
            if (is_array($out)) {
5552
                $out = array_pop($out);
5553
            }
5554
            if (is_array($out)) {
5555
                $property = $out;
5556
            }
5557
        }
5558
5559
        return $property;
5560
    }
5561
5562
    /**
5563
     * Parses docBlock from a file and returns the result as an array
5564
     *
5565
     * @param string $element_dir
5566
     * @param string $filename
5567
     * @param boolean $escapeValues
5568
     * @return array Associative array in the form property name => property value
5569
     */
5570
    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...
5571
    {
5572
        $params = array();
5573
        $fullpath = $element_dir . '/' . $filename;
5574
        if (is_readable($fullpath)) {
5575
            $tpl = @fopen($fullpath, "r");
5576
            if ($tpl) {
5577
                $params['filename'] = $filename;
5578
                $docblock_start_found = false;
5579
                $name_found = false;
5580
                $description_found = false;
5581
                $docblock_end_found = false;
5582
                $arrayParams = array('author', 'documentation', 'reportissues', 'link');
5583
5584
                while (!feof($tpl)) {
5585
                    $line = fgets($tpl);
5586
                    $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...
5587
                    $docblock_start_found = $r['docblock_start_found'];
5588
                    $name_found = $r['name_found'];
5589
                    $description_found = $r['description_found'];
5590
                    $docblock_end_found = $r['docblock_end_found'];
5591
                    $param = $r['param'];
5592
                    $val = $r['val'];
5593
                    if (!$docblock_end_found) {
5594
                        break;
5595
                    }
5596
                    if (!$docblock_start_found || !$name_found || !$description_found || empty($param)) {
5597
                        continue;
5598
                    }
5599 View Code Duplication
                    if (!empty($param)) {
5600
                        if (in_array($param, $arrayParams)) {
5601
                            if (!isset($params[$param])) {
5602
                                $params[$param] = array();
5603
                            }
5604
                            $params[$param][] = $escapeValues ? $this->db->escape($val) : $val;
5605
                        } else {
5606
                            $params[$param] = $escapeValues ? $this->db->escape($val) : $val;
5607
                        }
5608
                    }
5609
                }
5610
                @fclose($tpl);
5611
            }
5612
        }
5613
        return $params;
5614
    }
5615
5616
    /**
5617
     * Parses docBlock from string and returns the result as an array
5618
     *
5619
     * @param string $string
5620
     * @param boolean $escapeValues
5621
     * @return array Associative array in the form property name => property value
5622
     */
5623
    public function parseDocBlockFromString($string, $escapeValues = false)
5624
    {
5625
        $params = array();
5626
        if (!empty($string)) {
5627
            $string = str_replace('\r\n', '\n', $string);
5628
            $exp = explode('\n', $string);
5629
            $docblock_start_found = false;
5630
            $name_found = false;
5631
            $description_found = false;
5632
            $docblock_end_found = false;
5633
            $arrayParams = array('author', 'documentation', 'reportissues', 'link');
5634
5635
            foreach ($exp as $line) {
5636
                $r = $this->parseDocBlockLine($line, $docblock_start_found, $name_found, $description_found, $docblock_end_found);
5637
                $docblock_start_found = $r['docblock_start_found'];
5638
                $name_found = $r['name_found'];
5639
                $description_found = $r['description_found'];
5640
                $docblock_end_found = $r['docblock_end_found'];
5641
                $param = $r['param'];
5642
                $val = $r['val'];
5643
                if (!$docblock_start_found) {
5644
                    continue;
5645
                }
5646
                if ($docblock_end_found) {
5647
                    break;
5648
                }
5649 View Code Duplication
                if (!empty($param)) {
5650
                    if (in_array($param, $arrayParams)) {
5651
                        if (!isset($params[$param])) {
5652
                            $params[$param] = array();
5653
                        }
5654
                        $params[$param][] = $escapeValues ? $this->db->escape($val) : $val;
5655
                    } else {
5656
                        $params[$param] = $escapeValues ? $this->db->escape($val) : $val;
5657
                    }
5658
                }
5659
            }
5660
        }
5661
        return $params;
5662
    }
5663
5664
    /**
5665
     * Parses docBlock of a component´s source-code and returns the result as an array
5666
     * (modified parseDocBlock() from modules/stores/setup.info.php by Bumkaka & Dmi3yy)
5667
     *
5668
     * @param string $line
5669
     * @param boolean $docblock_start_found
5670
     * @param boolean $name_found
5671
     * @param boolean $description_found
5672
     * @param boolean $docblock_end_found
5673
     * @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...
5674
     */
5675
    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...
5676
    {
5677
        $param = '';
5678
        $val = '';
5679
        $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...
5680
        if (!$docblock_start_found) {
5681
            // find docblock start
5682
            if (strpos($line, '/**') !== false) {
5683
                $docblock_start_found = true;
5684
            }
5685
        } elseif (!$name_found) {
5686
            // find name
5687
            if (preg_match("/^\s+\*\s+(.+)/", $line, $ma)) {
5688
                $param = 'name';
5689
                $val = trim($ma[1]);
5690
                $name_found = !empty($val);
5691
            }
5692
        } elseif (!$description_found) {
5693
            // find description
5694
            if (preg_match("/^\s+\*\s+(.+)/", $line, $ma)) {
5695
                $param = 'description';
5696
                $val = trim($ma[1]);
5697
                $description_found = !empty($val);
5698
            }
5699
        } else {
5700
            if (preg_match("/^\s+\*\s+\@([^\s]+)\s+(.+)/", $line, $ma)) {
5701
                $param = trim($ma[1]);
5702
                $val = trim($ma[2]);
5703
                if (!empty($param) && !empty($val)) {
5704
                    if ($param == 'internal') {
5705
                        $ma = null;
5706
                        if (preg_match("/\@([^\s]+)\s+(.+)/", $val, $ma)) {
5707
                            $param = trim($ma[1]);
5708
                            $val = trim($ma[2]);
5709
                        }
5710
                    }
5711
                }
5712
            } elseif (preg_match("/^\s*\*\/\s*$/", $line)) {
5713
                $docblock_end_found = true;
5714
            }
5715
        }
5716
        return array(
5717
            'docblock_start_found' => $docblock_start_found,
5718
            'name_found' => $name_found,
5719
            'description_found' => $description_found,
5720
            'docblock_end_found' => $docblock_end_found,
5721
            'param' => $param,
5722
            'val' => $val
5723
        );
5724
    }
5725
5726
    /**
5727
     * Renders docBlock-parameters into human readable list
5728
     *
5729
     * @param array $parsed
5730
     * @return string List in HTML-format
5731
     */
5732
    public function convertDocBlockIntoList($parsed)
5733
    {
5734
        global $_lang;
5735
5736
        // Replace special placeholders & make URLs + Emails clickable
5737
        $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...
5738
        $regexUrl = "/((http|https|ftp|ftps)\:\/\/[^\/]+(\/[^\s]+[^,.?!:;\s])?)/";
5739
        $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';
5740
        $emailSubject = isset($parsed['name']) ? '?subject=' . $parsed['name'] : '';
5741
        $emailSubject .= isset($parsed['version']) ? ' v' . $parsed['version'] : '';
5742
        foreach ($parsed as $key => $val) {
5743
            if (is_array($val)) {
5744
                foreach ($val as $key2 => $val2) {
5745
                    $val2 = $this->parseText($val2, $ph);
5746 View Code Duplication
                    if (preg_match($regexUrl, $val2, $url)) {
5747
                        $val2 = preg_replace($regexUrl, "<a href=\"{$url[0]}\" target=\"_blank\">{$url[0]}</a> ", $val2);
5748
                    }
5749 View Code Duplication
                    if (preg_match($regexEmail, $val2, $url)) {
5750
                        $val2 = preg_replace($regexEmail, '<a href="mailto:\\1' . $emailSubject . '">\\1</a>', $val2);
5751
                    }
5752
                    $parsed[$key][$key2] = $val2;
5753
                }
5754
            } else {
5755
                $val = $this->parseText($val, $ph);
5756 View Code Duplication
                if (preg_match($regexUrl, $val, $url)) {
5757
                    $val = preg_replace($regexUrl, "<a href=\"{$url[0]}\" target=\"_blank\">{$url[0]}</a> ", $val);
5758
                }
5759 View Code Duplication
                if (preg_match($regexEmail, $val, $url)) {
5760
                    $val = preg_replace($regexEmail, '<a href="mailto:\\1' . $emailSubject . '">\\1</a>', $val);
5761
                }
5762
                $parsed[$key] = $val;
5763
            }
5764
        }
5765
5766
        $arrayParams = array(
5767
            'documentation' => $_lang['documentation'],
5768
            'reportissues' => $_lang['report_issues'],
5769
            'link' => $_lang['further_info'],
5770
            'author' => $_lang['author_infos']
5771
        );
5772
5773
        $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...
5774
        $list = isset($parsed['logo']) ? '<img src="' . $this->config['base_url'] . ltrim($parsed['logo'], "/") . '" style="float:right;max-width:100px;height:auto;" />' . $nl : '';
5775
        $list .= '<p>' . $nl;
5776
        $list .= isset($parsed['name']) ? '<strong>' . $parsed['name'] . '</strong><br/>' . $nl : '';
5777
        $list .= isset($parsed['description']) ? $parsed['description'] . $nl : '';
5778
        $list .= '</p><br/>' . $nl;
5779
        $list .= isset($parsed['version']) ? '<p><strong>' . $_lang['version'] . ':</strong> ' . $parsed['version'] . '</p>' . $nl : '';
5780
        $list .= isset($parsed['license']) ? '<p><strong>' . $_lang['license'] . ':</strong> ' . $parsed['license'] . '</p>' . $nl : '';
5781
        $list .= isset($parsed['lastupdate']) ? '<p><strong>' . $_lang['last_update'] . ':</strong> ' . $parsed['lastupdate'] . '</p>' . $nl : '';
5782
        $list .= '<br/>' . $nl;
5783
        $first = true;
5784
        foreach ($arrayParams as $param => $label) {
5785
            if (isset($parsed[$param])) {
5786
                if ($first) {
5787
                    $list .= '<p><strong>' . $_lang['references'] . '</strong></p>' . $nl;
5788
                    $list .= '<ul class="docBlockList">' . $nl;
5789
                    $first = false;
5790
                }
5791
                $list .= '    <li><strong>' . $label . '</strong>' . $nl;
5792
                $list .= '        <ul>' . $nl;
5793
                foreach ($parsed[$param] as $val) {
5794
                    $list .= '            <li>' . $val . '</li>' . $nl;
5795
                }
5796
                $list .= '        </ul></li>' . $nl;
5797
            }
5798
        }
5799
        $list .= !$first ? '</ul>' . $nl : '';
5800
5801
        return $list;
5802
    }
5803
5804
    /**
5805
     * @param string $string
5806
     * @return string
5807
     */
5808
    public function removeSanitizeSeed($string = '')
5809
    {
5810
        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...
5811
5812
        if (!$string || strpos($string, $sanitize_seed) === false) {
5813
            return $string;
5814
        }
5815
5816
        return str_replace($sanitize_seed, '', $string);
5817
    }
5818
5819
    /**
5820
     * @param string $content
5821
     * @return string
5822
     */
5823
    public function cleanUpMODXTags($content = '')
5824
    {
5825
        if ($this->minParserPasses < 1) {
5826
            return $content;
5827
        }
5828
5829
        $enable_filter = $this->config['enable_filter'];
5830
        $this->config['enable_filter'] = 1;
5831
        $_ = 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...
5832
        foreach ($_ as $brackets) {
5833
            list($left, $right) = explode(' ', $brackets);
5834
            if (strpos($content, $left) !== false) {
5835
                if ($left === '[*') {
5836
                    $content = $this->mergeDocumentContent($content);
5837
                } elseif ($left === '[(') {
5838
                    $content = $this->mergeSettingsContent($content);
5839
                } elseif ($left === '{{') {
5840
                    $content = $this->mergeChunkContent($content);
5841
                } elseif ($left === '[[') {
5842
                    $content = $this->evalSnippets($content);
5843
                }
5844
            }
5845
        }
5846
        foreach ($_ as $brackets) {
5847
            list($left, $right) = explode(' ', $brackets);
5848
            if (strpos($content, $left) !== false) {
5849
                $matches = $this->getTagsFromContent($content, $left, $right);
5850
                $content = str_replace($matches[0], '', $content);
5851
            }
5852
        }
5853
        $this->config['enable_filter'] = $enable_filter;
5854
        return $content;
5855
    }
5856
5857
    /**
5858
     * @param string $str
5859
     * @param string $allowable_tags
5860
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be array|string? Also, consider making the array more specific, something like array<String>, or String[].

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

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
5861
     */
5862
    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...
5863
    {
5864
        $str = strip_tags($str, $allowable_tags);
5865
        modx_sanitize_gpc($str);
5866
        return $str;
5867
    }
5868
5869
    /**
5870
     * @param string $name
5871
     * @param string $phpCode
5872
     * @param string $namespace
5873
     * @param array defaultParams
5874
     */
5875
    public function addSnippet($name, $phpCode, $namespace = '#', array $defaultParams = array())
5876
    {
5877
        $this->snippetCache[$namespace . $name] = $phpCode;
5878
        $this->snippetCache[$namespace . $name . 'Props'] = $defaultParams;
5879
    }
5880
5881
    /**
5882
     * @param string $name
5883
     * @param string $text
5884
     * @param string $namespace
5885
     */
5886
    public function addChunk($name, $text, $namespace = '#')
5887
    {
5888
        $this->chunkCache[$namespace . $name] = $text;
5889
    }
5890
5891
    /**
5892
     * @param $type
5893
     * @param $scanPath
5894
     * @param array $ext
5895
     * @return array
5896
     * @throws \Exception
5897
     */
5898
    public function findElements($type, $scanPath, array $ext)
5899
    {
5900
        $out = array();
5901
5902
        if (! is_dir($scanPath) || empty($ext)) {
5903
            return $out;
5904
        }
5905
        $iterator = new \RecursiveIteratorIterator(
5906
            new \RecursiveDirectoryIterator($scanPath, \RecursiveDirectoryIterator::SKIP_DOTS),
5907
            \RecursiveIteratorIterator::SELF_FIRST
5908
        );
5909
        foreach ($iterator as $item) {
5910
            /**
5911
             * @var \SplFileInfo $item
5912
             */
5913
            if ($item->isFile() && $item->isReadable() && \in_array($item->getExtension(), $ext)) {
5914
                $name = $item->getBasename('.' . $item->getExtension());
5915
                $path = str_replace($scanPath, '', $item->getPath() . '/');
5916
                if (!empty($path)) {
5917
                    $name = str_replace('/', '\\', $path) . $name;
5918
                }
5919
                switch ($type) {
5920
                    case 'chunk':
5921
                        $out[$name] = file_get_contents($item->getRealPath());
5922
                        break;
5923
                    case 'snippet':
5924
                        $out[$name] = "return require '" . $item->getRealPath() . "';";
5925
                        break;
5926
                    default:
5927
                        throw new \Exception;
5928
                }
5929
            }
5930
        }
5931
5932
        return $out;
5933
    }
5934
5935
    /**
5936
     * @param string $phpcode
5937
     * @param string $evalmode
5938
     * @param string $safe_functions
5939
     * @return string|void
0 ignored issues
show
Documentation introduced by
Should the return type not be array|string|null? Also, consider making the array more specific, something like array<String>, or String[].

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

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
5940
     */
5941
    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...
5942
    {
5943
        if ($evalmode == '') {
5944
            $evalmode = $this->config['allow_eval'];
5945
        }
5946
        if ($safe_functions == '') {
5947
            $safe_functions = $this->config['safe_functions_at_eval'];
5948
        }
5949
5950
        modx_sanitize_gpc($phpcode);
5951
5952
        switch ($evalmode) {
5953
            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...
5954
                $isSafe = $this->isSafeCode($phpcode, $safe_functions);
0 ignored issues
show
Bug introduced by
It seems like $phpcode can also be of type array; however, EvolutionCMS\Core::isSafeCode() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
5955
                break;
5956
            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...
5957
                $isSafe = $_POST ? $this->isSafeCode($phpcode, $safe_functions) : true;
0 ignored issues
show
Bug introduced by
It seems like $phpcode can also be of type array; however, EvolutionCMS\Core::isSafeCode() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
5958
                break;
5959
            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...
5960
                $isSafe = true;
5961
                break; // Should debug only
5962
            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...
5963
            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...
5964
                return $phpcode;
5965
        }
5966
5967
        if (!$isSafe) {
5968
            $msg = $phpcode . "\n" . $this->currentSnippet . "\n" . print_r($_SERVER, true);
5969
            $title = sprintf('Unknown eval was executed (%s)', $this->htmlspecialchars(substr(trim($phpcode), 0, 50)));
5970
            $this->messageQuit($title, '', true, '', '', 'Parser', $msg);
5971
            return;
5972
        }
5973
5974
        ob_start();
5975
        $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...
5976
        $echo = ob_get_clean();
5977
5978
        if (is_array($return)) {
5979
            return 'array()';
5980
        }
5981
5982
        $output = $echo . $return;
5983
        modx_sanitize_gpc($output);
5984
        return $this->htmlspecialchars($output); // Maybe, all html tags are dangerous
5985
    }
5986
5987
    /**
5988
     * @param string $phpcode
5989
     * @param string $safe_functions
5990
     * @return bool
5991
     */
5992
    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...
5993
    { // 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...
5994
        if ($safe_functions == '') {
5995
            return false;
5996
        }
5997
5998
        $safe = explode(',', $safe_functions);
5999
6000
        $phpcode = rtrim($phpcode, ';') . ';';
6001
        $tokens = token_get_all('<?php ' . $phpcode);
6002
        foreach ($tokens as $i => $token) {
6003
            if (!is_array($token)) {
6004
                continue;
6005
            }
6006
            $tokens[$i]['token_name'] = token_name($token[0]);
6007
        }
6008
        foreach ($tokens as $token) {
6009
            if (!is_array($token)) {
6010
                continue;
6011
            }
6012
            switch ($token['token_name']) {
6013
                case 'T_STRING':
6014
                    if (!in_array($token[1], $safe)) {
6015
                        return false;
6016
                    }
6017
                    break;
6018
                case 'T_VARIABLE':
6019
                    if ($token[1] == '$GLOBALS') {
6020
                        return false;
6021
                    }
6022
                    break;
6023
                case 'T_EVAL':
6024
                    return false;
6025
            }
6026
        }
6027
        return true;
6028
    }
6029
6030
    /**
6031
     * @param string $str
6032
     * @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...
6033
     */
6034
    public function atBindFileContent($str = '')
6035
    {
6036
6037
        $search_path = array('assets/tvs/', 'assets/chunks/', 'assets/templates/', $this->config['rb_base_url'] . 'files/', '');
6038
6039
        if (stripos($str, '@FILE') !== 0) {
6040
            return $str;
6041
        }
6042 View Code Duplication
        if (strpos($str, "\n") !== false) {
6043
            $str = substr($str, 0, strpos("\n", $str));
6044
        }
6045
6046
        if ($this->getExtFromFilename($str) === '.php') {
6047
            return 'Could not retrieve PHP file.';
6048
        }
6049
6050
        $str = substr($str, 6);
6051
        $str = trim($str);
6052
        if (strpos($str, '\\') !== false) {
6053
            $str = str_replace('\\', '/', $str);
6054
        }
6055
        $str = ltrim($str, '/');
6056
6057
        $errorMsg = sprintf("Could not retrieve string '%s'.", $str);
6058
6059
        foreach ($search_path as $path) {
6060
            $file_path = MODX_BASE_PATH . $path . $str;
6061
            if (strpos($file_path, MODX_MANAGER_PATH) === 0) {
6062
                return $errorMsg;
6063
            } elseif (is_file($file_path)) {
6064
                break;
6065
            } else {
6066
                $file_path = false;
6067
            }
6068
        }
6069
6070
        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...
6071
            return $errorMsg;
6072
        }
6073
6074
        $content = (string)file_get_contents($file_path);
6075
        if ($content === false) {
6076
            return $errorMsg;
6077
        }
6078
6079
        return $content;
6080
    }
6081
6082
    /**
6083
     * @param $str
6084
     * @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...
6085
     */
6086
    public function getExtFromFilename($str)
6087
    {
6088
        $str = strtolower(trim($str));
6089
        $pos = strrpos($str, '.');
6090
        if ($pos === false) {
6091
            return false;
6092
        } else {
6093
            return substr($str, $pos);
6094
        }
6095
    }
6096
    /***************************************************************************************/
6097
    /* End of API functions                                       */
6098
    /***************************************************************************************/
6099
6100
    /**
6101
     * PHP error handler set by http://www.php.net/manual/en/function.set-error-handler.php
6102
     *
6103
     * Checks the PHP error and calls messageQuit() unless:
6104
     *  - error_reporting() returns 0, or
6105
     *  - the PHP error level is 0, or
6106
     *  - the PHP error level is 8 (E_NOTICE) and stopOnNotice is false
6107
     *
6108
     * @param int $nr The PHP error level as per http://www.php.net/manual/en/errorfunc.constants.php
6109
     * @param string $text Error message
6110
     * @param string $file File where the error was detected
6111
     * @param string $line Line number within $file
6112
     * @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...
6113
     */
6114
    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...
6115
    {
6116
        if (error_reporting() == 0 || $nr == 0) {
6117
            return true;
6118
        }
6119
        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...
6120
            switch ($nr) {
6121
                case E_NOTICE:
6122
                    if ($this->error_reporting <= 2) {
6123
                        return true;
6124
                    }
6125
                    $isError = false;
6126
                    $msg = 'PHP Minor Problem (this message show logged in only)';
6127
                    break;
6128
                case E_STRICT:
6129 View Code Duplication
                case E_DEPRECATED:
6130
                    if ($this->error_reporting <= 1) {
6131
                        return true;
6132
                    }
6133
                    $isError = true;
6134
                    $msg = 'PHP Strict Standards Problem';
6135
                    break;
6136 View Code Duplication
                default:
6137
                    if ($this->error_reporting === 0) {
6138
                        return true;
6139
                    }
6140
                    $isError = true;
6141
                    $msg = 'PHP Parse Error';
6142
            }
6143
        }
6144
        if (is_readable($file)) {
6145
            $source = file($file);
6146
            $source = $this->htmlspecialchars($source[$line - 1]);
6147
        } else {
6148
            $source = "";
6149
        } //Error $nr in $file at $line: <div><code>$source</code></div>
6150
6151
        $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...
6152
    }
6153
6154
    /**
6155
     * @param string $msg
6156
     * @param string $query
6157
     * @param bool $is_error
6158
     * @param string $nr
6159
     * @param string $file
6160
     * @param string $source
6161
     * @param string $text
6162
     * @param string $line
6163
     * @param string $output
6164
     * @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...
6165
     */
6166
    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...
6167
    {
6168
6169
        if (0 < $this->messageQuitCount) {
6170
            return;
6171
        }
6172
        $this->messageQuitCount++;
6173
        $MakeTable = new Support\MakeTable();
6174
        $MakeTable->setTableClass('grid');
6175
        $MakeTable->setRowRegularClass('gridItem');
6176
        $MakeTable->setRowAlternateClass('gridAltItem');
6177
        $MakeTable->setColumnWidths(array('100px'));
6178
6179
        $table = array();
6180
6181
        $version = isset ($GLOBALS['modx_version']) ? $GLOBALS['modx_version'] : '';
6182
        $release_date = isset ($GLOBALS['release_date']) ? $GLOBALS['release_date'] : '';
6183
        $request_uri = "http://" . $_SERVER['HTTP_HOST'] . ($_SERVER["SERVER_PORT"] == 80 ? "" : (":" . $_SERVER["SERVER_PORT"])) . $_SERVER['REQUEST_URI'];
6184
        $request_uri = $this->htmlspecialchars($request_uri, ENT_QUOTES, $this->config['modx_charset']);
6185
        $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...
6186
        $referer = $this->htmlspecialchars($_SERVER['HTTP_REFERER'], ENT_QUOTES, $this->config['modx_charset']);
6187
        if ($is_error) {
6188
            $str = '<h2 style="color:red">&laquo; Evo Parse Error &raquo;</h2>';
6189
            if ($msg != 'PHP Parse Error') {
6190
                $str .= '<h3 style="color:red">' . $msg . '</h3>';
6191
            }
6192
        } else {
6193
            $str = '<h2 style="color:#003399">&laquo; Evo Debug/ stop message &raquo;</h2>';
6194
            $str .= '<h3 style="color:#003399">' . $msg . '</h3>';
6195
        }
6196
6197
        if (!empty ($query)) {
6198
            $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>';
6199
        }
6200
6201
        $errortype = array(
6202
            E_ERROR => "ERROR",
6203
            E_WARNING => "WARNING",
6204
            E_PARSE => "PARSING ERROR",
6205
            E_NOTICE => "NOTICE",
6206
            E_CORE_ERROR => "CORE ERROR",
6207
            E_CORE_WARNING => "CORE WARNING",
6208
            E_COMPILE_ERROR => "COMPILE ERROR",
6209
            E_COMPILE_WARNING => "COMPILE WARNING",
6210
            E_USER_ERROR => "USER ERROR",
6211
            E_USER_WARNING => "USER WARNING",
6212
            E_USER_NOTICE => "USER NOTICE",
6213
            E_STRICT => "STRICT NOTICE",
6214
            E_RECOVERABLE_ERROR => "RECOVERABLE ERROR",
6215
            E_DEPRECATED => "DEPRECATED",
6216
            E_USER_DEPRECATED => "USER DEPRECATED"
6217
        );
6218
6219
        if (!empty($nr) || !empty($file)) {
6220
            if ($text != '') {
6221
                $str .= '<div style="font-weight:bold;border:1px solid #ccc;padding:8px;color:#333;background-color:#ffffcd;margin-bottom:15px;">Error : ' . $text . '</div>';
6222
            }
6223
            if ($output != '') {
6224
                $str .= '<div style="font-weight:bold;border:1px solid #ccc;padding:8px;color:#333;background-color:#ffffcd;margin-bottom:15px;">' . $output . '</div>';
6225
            }
6226
            if ($nr !== '') {
6227
                $table[] = array('ErrorType[num]', $errortype [$nr] . "[" . $nr . "]");
6228
            }
6229
            if ($file) {
6230
                $table[] = array('File', $file);
6231
            }
6232
            if ($line) {
6233
                $table[] = array('Line', $line);
6234
            }
6235
6236
        }
6237
6238
        if ($source != '') {
6239
            $table[] = array("Source", $source);
6240
        }
6241
6242
        if (!empty($this->currentSnippet)) {
6243
            $table[] = array('Current Snippet', $this->currentSnippet);
6244
        }
6245
6246
        if (!empty($this->event->activePlugin)) {
6247
            $table[] = array('Current Plugin', $this->event->activePlugin . '(' . $this->event->name . ')');
6248
        }
6249
6250
        $str .= $MakeTable->create($table, array('Error information', ''));
6251
        $str .= "<br />";
6252
6253
        $table = array();
6254
        $table[] = array('REQUEST_URI', $request_uri);
6255
6256
        if ($this->manager->action) {
6257
            include_once(MODX_MANAGER_PATH . 'includes/actionlist.inc.php');
6258
            global $action_list;
6259
            $actionName = (isset($action_list[$this->manager->action])) ? " - {$action_list[$this->manager->action]}" : '';
6260
6261
            $table[] = array('Manager action', $this->manager->action . $actionName);
6262
        }
6263
6264
        if (preg_match('@^[0-9]+@', $this->documentIdentifier)) {
6265
            $resource = $this->getDocumentObject('id', $this->documentIdentifier);
6266
            $url = $this->makeUrl($this->documentIdentifier, '', '', 'full');
6267
            $table[] = array('Resource', '[' . $this->documentIdentifier . '] <a href="' . $url . '" target="_blank">' . $resource['pagetitle'] . '</a>');
6268
        }
6269
        $table[] = array('Referer', $referer);
6270
        $table[] = array('User Agent', $ua);
6271
        $table[] = array('IP', $_SERVER['REMOTE_ADDR']);
6272
        $table[] = array('Current time', date("Y-m-d H:i:s", $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time']));
6273
        $str .= $MakeTable->create($table, array('Basic info', ''));
6274
        $str .= "<br />";
6275
6276
        $table = array();
6277
        $table[] = array('MySQL', '[^qt^] ([^q^] Requests)');
6278
        $table[] = array('PHP', '[^p^]');
6279
        $table[] = array('Total', '[^t^]');
6280
        $table[] = array('Memory', '[^m^]');
6281
        $str .= $MakeTable->create($table, array('Benchmarks', ''));
6282
        $str .= "<br />";
6283
6284
        $totalTime = ($this->getMicroTime() - $this->tstart);
6285
6286
        $mem = memory_get_peak_usage(true);
6287
        $total_mem = $mem - $this->mstart;
6288
        $total_mem = ($total_mem / 1024 / 1024) . ' mb';
6289
6290
        $queryTime = $this->queryTime;
6291
        $phpTime = $totalTime - $queryTime;
6292
        $queries = isset ($this->executedQueries) ? $this->executedQueries : 0;
6293
        $queryTime = sprintf("%2.4f s", $queryTime);
6294
        $totalTime = sprintf("%2.4f s", $totalTime);
6295
        $phpTime = sprintf("%2.4f s", $phpTime);
6296
6297
        $str = str_replace('[^q^]', $queries, $str);
6298
        $str = str_replace('[^qt^]', $queryTime, $str);
6299
        $str = str_replace('[^p^]', $phpTime, $str);
6300
        $str = str_replace('[^t^]', $totalTime, $str);
6301
        $str = str_replace('[^m^]', $total_mem, $str);
6302
6303
        if (isset($php_errormsg) && !empty($php_errormsg)) {
6304
            $str = "<b>{$php_errormsg}</b><br />\n{$str}";
6305
        }
6306
        $str .= $this->get_backtrace(debug_backtrace());
6307
        // Log error
6308
        if (!empty($this->currentSnippet)) {
6309
            $source = 'Snippet - ' . $this->currentSnippet;
6310
        } elseif (!empty($this->event->activePlugin)) {
6311
            $source = 'Plugin - ' . $this->event->activePlugin;
6312
        } elseif ($source !== '') {
6313
            $source = 'Parser - ' . $source;
6314
        } elseif ($query !== '') {
6315
            $source = 'SQL Query';
6316
        } else {
6317
            $source = 'Parser';
6318
        }
6319
        if ($msg) {
6320
            $source .= ' / ' . $msg;
6321
        }
6322
        if (isset($actionName) && !empty($actionName)) {
6323
            $source .= $actionName;
6324
        }
6325 View Code Duplication
        switch ($nr) {
6326
            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...
6327
            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...
6328
            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...
6329
            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...
6330
            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...
6331
                $error_level = 2;
6332
                break;
6333
            default:
6334
                $error_level = 3;
6335
        }
6336
        $this->logEvent(0, $error_level, $str, $source);
6337
6338
        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...
6339
            return true;
6340
        }
6341
        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...
6342
            return true;
6343
        }
6344
6345
        // Set 500 response header
6346
        if ($error_level !== 2) {
6347
            header('HTTP/1.1 500 Internal Server Error');
6348
        }
6349
6350
        // Display error
6351
        if (isset($_SESSION['mgrValidated'])) {
6352
            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>
6353
                 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
6354
                 <link rel="stylesheet" type="text/css" href="' . $this->config['site_manager_url'] . 'media/style/' . $this->config['manager_theme'] . '/style.css" />
6355
                 <style type="text/css">body { padding:10px; } td {font:inherit;}</style>
6356
                 </head><body>
6357
                 ' . $str . '</body></html>';
6358
6359
        } else {
6360
            echo 'Error';
6361
        }
6362
        ob_end_flush();
6363
        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...
6364
    }
6365
6366
    /**
6367
     * @param $backtrace
6368
     * @return string
6369
     */
6370
    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...
6371
    {
6372
        $MakeTable = new Support\MakeTable();
6373
        $MakeTable->setTableClass('grid');
6374
        $MakeTable->setRowRegularClass('gridItem');
6375
        $MakeTable->setRowAlternateClass('gridAltItem');
6376
        $table = array();
6377
        $backtrace = array_reverse($backtrace);
6378
        foreach ($backtrace as $key => $val) {
6379
            $key++;
6380
            if (substr($val['function'], 0, 11) === 'messageQuit') {
6381
                break;
6382
            } elseif (substr($val['function'], 0, 8) === 'phpError') {
6383
                break;
6384
            }
6385
            $path = str_replace('\\', '/', $val['file']);
6386
            if (strpos($path, MODX_BASE_PATH) === 0) {
6387
                $path = substr($path, strlen(MODX_BASE_PATH));
6388
            }
6389
            switch ($val['type']) {
6390
                case '->':
6391
                case '::':
6392
                    $functionName = $val['function'] = $val['class'] . $val['type'] . $val['function'];
6393
                    break;
6394
                default:
6395
                    $functionName = $val['function'];
6396
            }
6397
            $tmp = 1;
6398
            $_ = (!empty($val['args'])) ? count($val['args']) : 0;
6399
            $args = array_pad(array(), $_, '$var');
6400
            $args = implode(", ", $args);
6401
            $modx = &$this;
6402
            $args = preg_replace_callback('/\$var/', function () use ($modx, &$tmp, $val) {
6403
                $arg = $val['args'][$tmp - 1];
6404
                switch (true) {
6405
                    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...
6406
                        $out = 'NULL';
6407
                        break;
6408
                    }
6409
                    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...
6410
                        $out = $arg;
6411
                        break;
6412
                    }
6413
                    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...
6414
                        $out = strlen($arg) > 20 ? 'string $var' . $tmp : ("'" . $this->htmlspecialchars(str_replace("'", "\\'", $arg)) . "'");
6415
                        break;
6416
                    }
6417
                    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...
6418
                        $out = $arg ? 'TRUE' : 'FALSE';
6419
                        break;
6420
                    }
6421
                    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...
6422
                        $out = 'array $var' . $tmp;
6423
                        break;
6424
                    }
6425
                    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...
6426
                        $out = get_class($arg) . ' $var' . $tmp;
6427
                        break;
6428
                    }
6429
                    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...
6430
                        $out = '$var' . $tmp;
6431
                    }
6432
                }
6433
                $tmp++;
6434
                return $out;
6435
            }, $args);
6436
            $line = array(
6437
                "<strong>" . $functionName . "</strong>(" . $args . ")",
6438
                $path . " on line " . $val['line']
6439
            );
6440
            $table[] = array(implode("<br />", $line));
6441
        }
6442
        return $MakeTable->create($table, array('Backtrace'));
6443
    }
6444
6445
    /**
6446
     * @return string
6447
     */
6448
    public function getRegisteredClientScripts()
6449
    {
6450
        return implode("\n", $this->jscripts);
6451
    }
6452
6453
    /**
6454
     * @return string
6455
     */
6456
    public function getRegisteredClientStartupScripts()
6457
    {
6458
        return implode("\n", $this->sjscripts);
6459
    }
6460
6461
    /**
6462
     * Format alias to be URL-safe. Strip invalid characters.
6463
     *
6464
     * @param string $alias Alias to be formatted
6465
     * @return string Safe alias
6466
     */
6467
    public function stripAlias($alias)
6468
    {
6469
        // let add-ons overwrite the default behavior
6470
        $results = $this->invokeEvent('OnStripAlias', array('alias' => $alias));
6471
        if (!empty($results)) {
6472
            // if multiple plugins are registered, only the last one is used
6473
            return end($results);
6474
        } else {
6475
            // default behavior: strip invalid characters and replace spaces with dashes.
6476
            $alias = strip_tags($alias); // strip HTML
6477
            $alias = preg_replace('/[^\.A-Za-z0-9 _-]/', '', $alias); // strip non-alphanumeric characters
6478
            $alias = preg_replace('/\s+/', '-', $alias); // convert white-space to dash
6479
            $alias = preg_replace('/-+/', '-', $alias);  // convert multiple dashes to one
6480
            $alias = trim($alias, '-'); // trim excess
6481
            return $alias;
6482
        }
6483
    }
6484
6485
    /**
6486
     * @param $size
6487
     * @return string
6488
     */
6489
    public function nicesize($size)
6490
    {
6491
        $sizes = array('Tb' => 1099511627776, 'Gb' => 1073741824, 'Mb' => 1048576, 'Kb' => 1024, 'b' => 1);
6492
        $precisions = count($sizes) - 1;
6493
        foreach ($sizes as $unit => $bytes) {
6494
            if ($size >= $bytes) {
6495
                return number_format($size / $bytes, $precisions) . ' ' . $unit;
6496
            }
6497
            $precisions--;
6498
        }
6499
        return '0 b';
6500
    }
6501
6502
    /**
6503
     * @param $parentid
6504
     * @param $alias
6505
     * @return bool
6506
     */
6507
    public function getHiddenIdFromAlias($parentid, $alias)
6508
    {
6509
        $table = $this->getFullTableName('site_content');
6510
        $query = $this->db->query("SELECT sc.id, children.id AS child_id, children.alias, COUNT(children2.id) AS children_count
6511
            FROM {$table} sc
6512
            JOIN {$table} children ON children.parent = sc.id
6513
            LEFT JOIN {$table} children2 ON children2.parent = children.id
6514
            WHERE sc.parent = {$parentid} AND sc.alias_visible = '0' GROUP BY children.id;");
6515
6516
        while ($child = $this->db->getRow($query)) {
6517
            if ($child['alias'] == $alias || $child['child_id'] == $alias) {
6518
                return $child['child_id'];
6519
            }
6520
6521
            if ($child['children_count'] > 0) {
6522
                $id = $this->getHiddenIdFromAlias($child['id'], $alias);
6523
                if ($id) {
6524
                    return $id;
6525
                }
6526
            }
6527
        }
6528
6529
        return false;
6530
    }
6531
6532
    /**
6533
     * @param $alias
6534
     * @return bool|int
6535
     */
6536
    public function getIdFromAlias($alias)
6537
    {
6538
        if (isset($this->documentListing[$alias])) {
6539
            return $this->documentListing[$alias];
6540
        }
6541
6542
        $tbl_site_content = $this->getFullTableName('site_content');
6543
        if ($this->config['use_alias_path'] == 1) {
6544
            if ($alias == '.') {
6545
                return 0;
6546
            }
6547
6548
            if (strpos($alias, '/') !== false) {
6549
                $_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...
6550
            } else {
6551
                $_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...
6552
            }
6553
            $id = 0;
6554
6555
            foreach ($_a as $alias) {
6556
                if ($id === false) {
6557
                    break;
6558
                }
6559
                $alias = $this->db->escape($alias);
6560
                $rs = $this->db->select('id', $tbl_site_content, "deleted=0 and parent='{$id}' and alias='{$alias}'");
6561
                if ($this->db->getRecordCount($rs) == 0) {
6562
                    $rs = $this->db->select('id', $tbl_site_content, "deleted=0 and parent='{$id}' and id='{$alias}'");
6563
                }
6564
                $next = $this->db->getValue($rs);
6565
                $id = !$next ? $this->getHiddenIdFromAlias($id, $alias) : $next;
6566
            }
6567
        } else {
6568
            $rs = $this->db->select('id', $tbl_site_content, "deleted=0 and alias='{$alias}'", 'parent, menuindex');
6569
            $id = $this->db->getValue($rs);
6570
            if (!$id) {
6571
                $id = false;
6572
            }
6573
        }
6574
        return $id;
6575
    }
6576
6577
    /**
6578
     * @param string $str
6579
     * @return bool|mixed|string
6580
     */
6581
    public function atBindInclude($str = '')
6582
    {
6583
        if (strpos($str, '@INCLUDE') !== 0) {
6584
            return $str;
6585
        }
6586 View Code Duplication
        if (strpos($str, "\n") !== false) {
6587
            $str = substr($str, 0, strpos("\n", $str));
6588
        }
6589
6590
        $str = substr($str, 9);
6591
        $str = trim($str);
6592
        $str = str_replace('\\', '/', $str);
6593
        $str = ltrim($str, '/');
6594
6595
        $tpl_dir = 'assets/templates/';
6596
6597
        if (strpos($str, MODX_MANAGER_PATH) === 0) {
6598
            return false;
6599
        } elseif (is_file(MODX_BASE_PATH . $str)) {
6600
            $file_path = MODX_BASE_PATH . $str;
6601
        } elseif (is_file(MODX_BASE_PATH . "{$tpl_dir}{$str}")) {
6602
            $file_path = MODX_BASE_PATH . $tpl_dir . $str;
6603
        } else {
6604
            return false;
6605
        }
6606
6607
        if (!$file_path || !is_file($file_path)) {
6608
            return false;
6609
        }
6610
6611
        ob_start();
6612
        $modx = &$this;
6613
        $result = include($file_path);
6614
        if ($result === 1) {
6615
            $result = '';
6616
        }
6617
        $content = ob_get_clean();
6618
        if (!$content && $result) {
6619
            $content = $result;
6620
        }
6621
        return $content;
6622
    }
6623
6624
    // php compat
6625
6626
    /**
6627
     * @param $str
6628
     * @param int $flags
6629
     * @param string $encode
6630
     * @return mixed
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use null|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...
6631
     */
6632
    public function htmlspecialchars($str, $flags = ENT_COMPAT, $encode = '')
6633
    {
6634
        $this->loadExtension('PHPCOMPAT');
6635
        return $this->phpcompat->htmlspecialchars($str, $flags, $encode);
6636
    }
6637
6638
    /**
6639
     * @param $string
6640
     * @param bool $returnData
6641
     * @return bool|mixed
6642
     */
6643
    public function isJson($string, $returnData = false)
6644
    {
6645
        $data = json_decode($string, true);
6646
        return (json_last_error() == JSON_ERROR_NONE) ? ($returnData ? $data : true) : false;
6647
    }
6648
6649
    /**
6650
     * @param $key
6651
     * @return array
6652
     */
6653
    public function splitKeyAndFilter($key)
6654
    {
6655
        if ($this->config['enable_filter'] == 1 && strpos($key, ':') !== false && stripos($key, '@FILE') !== 0) {
6656
            list($key, $modifiers) = explode(':', $key, 2);
6657
        } else {
6658
            $modifiers = false;
6659
        }
6660
6661
        $key = trim($key);
6662
        if ($modifiers !== false) {
6663
            $modifiers = trim($modifiers);
6664
        }
6665
6666
        return array($key, $modifiers);
6667
    }
6668
6669
    /**
6670
     * @param string $value
6671
     * @param bool $modifiers
6672
     * @param string $key
6673
     * @return string
6674
     */
6675
    public function applyFilter($value = '', $modifiers = false, $key = '')
6676
    {
6677
        if ($modifiers === false || $modifiers == 'raw') {
6678
            return $value;
6679
        }
6680
        if ($modifiers !== false) {
6681
            $modifiers = trim($modifiers);
6682
        }
6683
6684
        $this->loadExtension('MODIFIERS');
6685
        return $this->filter->phxFilter($key, $value, $modifiers);
0 ignored issues
show
Bug introduced by
It seems like $modifiers defined by parameter $modifiers on line 6675 can also be of type false; however, MODIFIERS::phxFilter() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
6686
    }
6687
6688
    // End of class.
6689
6690
6691
    /**
6692
     * Get Clean Query String
6693
     *
6694
     * Fixes the issue where passing an array into the q get variable causes errors
6695
     *
6696
     */
6697
    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...
6698
    {
6699
        $q = MODX_CLI ? null : $_GET['q'];
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $q. Configured minimum length is 3.

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

Loading history...
6700
6701
        //Return null if the query doesn't exist
6702
        if (empty($q)) {
6703
            return null;
6704
        }
6705
6706
        //If we have a string, return it
6707
        if (is_string($q)) {
6708
            return $q;
6709
        }
6710
6711
        //If we have an array, return the first element
6712
        if (is_array($q)) {
6713
            return $q[0];
6714
        }
6715
    }
6716
6717
    /**
6718
     * @param string $title
6719
     * @param string $msg
6720
     * @param int $type
6721
     */
6722
    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...
6723
    {
6724
        if ($title === '') {
6725
            $title = 'no title';
6726
        }
6727
        if (is_array($msg)) {
6728
            $msg = '<pre>' . print_r($msg, true) . '</pre>';
6729
        } elseif ($msg === '') {
6730
            $msg = $_SERVER['REQUEST_URI'];
6731
        }
6732
        $this->logEvent(0, $type, $msg, $title);
6733
    }
6734
6735
}
6736