Completed
Pull Request — develop (#709)
by Agel_Nash
06:07
created

SystemEvent::setOutput()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

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

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

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

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

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

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

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

namespace YourVendor;

class YourClass { }

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

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

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

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

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

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

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

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

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

Please refer to the PHP core documentation on constructors.

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

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

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

// Better
class Router
{
    private $host;

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

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

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

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

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

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

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

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

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

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

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

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

Let’s take a look at a few examples:

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

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


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

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

Logical Operators are used for Control-Flow

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

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

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

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

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

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

Loading history...
196
        $this->dbConfig = &$this->db->config; // alias for backward compatibility
197
        // events
198
        $this->event = new SystemEvent();
199
        $this->Event = &$this->event; //alias for backward compatibility
200
        // set track_errors ini variable
201
        @ ini_set("track_errors", "1"); // enable error tracking in $php_errormsg
202
        $this->time = $_SERVER['REQUEST_TIME']; // for having global timestamp
203
204
        $this->q = self::_getCleanQueryString();
205
    }
206
207
    final public function __clone()
208
    {
209
    }
210
    /**
211
     * @return DocumentParser
212
     */
213
    public static function getInstance()
214
    {
215
        if (self::$instance === null) {
216
            self::$instance = new DocumentParser();
217
        }
218
        return self::$instance;
219
    }
220
221
    /**
222
     * @param $method_name
223
     * @param $arguments
224
     * @return mixed
225
     */
226
    function __call($method_name, $arguments)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

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

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

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

Loading history...
227
    {
228
        include_once(MODX_MANAGER_PATH . 'includes/extenders/deprecated.functions.inc.php');
229
        if (method_exists($this->old, $method_name)) {
230
            $error_type = 1;
231
        } else {
232
            $error_type = 3;
233
        }
234
235
        if (!isset($this->config['error_reporting']) || 1 < $this->config['error_reporting']) {
236
            if ($error_type == 1) {
237
                $title = 'Call deprecated method';
238
                $msg = $this->htmlspecialchars("\$modx->{$method_name}() is deprecated function");
239
            } else {
240
                $title = 'Call undefined method';
241
                $msg = $this->htmlspecialchars("\$modx->{$method_name}() is undefined function");
242
            }
243
            $info = debug_backtrace();
244
            $m[] = $msg;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$m was never initialized. Although not strictly required by PHP, it is generally a good practice to add $m = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

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

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

Loading history...
245
            if (!empty($this->currentSnippet)) {
246
                $m[] = 'Snippet - ' . $this->currentSnippet;
247
            } elseif (!empty($this->event->activePlugin)) {
248
                $m[] = 'Plugin - ' . $this->event->activePlugin;
249
            }
250
            $m[] = $this->decoded_request_uri;
251
            $m[] = str_replace('\\', '/', $info[0]['file']) . '(line:' . $info[0]['line'] . ')';
252
            $msg = implode('<br />', $m);
253
            $this->logEvent(0, $error_type, $msg, $title);
254
        }
255
        if (method_exists($this->old, $method_name)) {
256
            return call_user_func_array(array($this->old, $method_name), $arguments);
257
        }
258
    }
259
260
    /**
261
     * @param string $connector
262
     * @return bool
263
     */
264
    public function checkSQLconnect($connector = 'db')
265
    {
266
        $flag = false;
267
        if (is_scalar($connector) && !empty($connector) && isset($this->{$connector}) && $this->{$connector} instanceof DBAPI) {
268
            $flag = (bool)$this->{$connector}->conn;
269
        }
270
        return $flag;
271
    }
272
273
    /**
274
     * Loads an extension from the extenders folder.
275
     * You can load any extension creating a boot file:
276
     * MODX_MANAGER_PATH."includes/extenders/ex_{$extname}.inc.php"
277
     * $extname - extension name in lowercase
278
     *
279
     * @param $extname
280
     * @param bool $reload
281
     * @return bool
282
     */
283
    public function loadExtension($extname, $reload = true)
284
    {
285
        $out = false;
286
        $flag = ($reload || !in_array($extname, $this->extensions));
287
        if ($this->checkSQLconnect('db') && $flag) {
288
            $evtOut = $this->invokeEvent('OnBeforeLoadExtension', array('name' => $extname, 'reload' => $reload));
289
            if (is_array($evtOut) && count($evtOut) > 0) {
290
                $out = array_pop($evtOut);
291
            }
292
        }
293
        if (!$out && $flag) {
294
            $extname = trim(str_replace(array('..', '/', '\\'), '', strtolower($extname)));
295
            $filename = MODX_MANAGER_PATH . "includes/extenders/ex_{$extname}.inc.php";
296
            $out = is_file($filename) ? include $filename : false;
297
        }
298
        if ($out && !in_array($extname, $this->extensions)) {
299
            $this->extensions[] = $extname;
300
        }
301
        return $out;
302
    }
303
304
    /**
305
     * Returns the current micro time
306
     *
307
     * @return float
308
     */
309
    public function getMicroTime()
310
    {
311
        list ($usec, $sec) = explode(' ', microtime());
312
        return ((float)$usec + (float)$sec);
313
    }
314
315
    /**
316
     * Redirect
317
     *
318
     * @param string $url
319
     * @param int $count_attempts
320
     * @param string $type $type
321
     * @param string $responseCode
322
     * @return bool|null
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use false|null.

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

Loading history...
323
     * @global string $base_url
324
     * @global string $site_url
325
     */
326
    public function sendRedirect($url, $count_attempts = 0, $type = '', $responseCode = '')
0 ignored issues
show
Coding Style introduced by
sendRedirect uses the super-global variable $_REQUEST which is generally not recommended.

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

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

// Better
class Router
{
    private $host;

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

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

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

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style Naming introduced by
The parameter $count_attempts is not named in camelCase.

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

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

Loading history...
327
    {
328
        $header = '';
329
        if (empty ($url)) {
330
            return false;
331
        }
332
        if ($count_attempts == 1) {
333
            // append the redirect count string to the url
334
            $currentNumberOfRedirects = isset ($_REQUEST['err']) ? $_REQUEST['err'] : 0;
335
            if ($currentNumberOfRedirects > 3) {
336
                $this->messageQuit('Redirection attempt failed - please ensure the document you\'re trying to redirect to exists. <p>Redirection URL: <i>' . $url . '</i></p>');
337
            } else {
338
                $currentNumberOfRedirects += 1;
0 ignored issues
show
Coding Style introduced by
Increment operators should be used where possible; found "$currentNumberOfRedirects += 1;" but expected "$currentNumberOfRedirects++"
Loading history...
339
                if (strpos($url, "?") > 0) {
340
                    $url .= "&err=$currentNumberOfRedirects";
341
                } else {
342
                    $url .= "?err=$currentNumberOfRedirects";
343
                }
344
            }
345
        }
346
        if ($type == 'REDIRECT_REFRESH') {
347
            $header = 'Refresh: 0;URL=' . $url;
348
        } elseif ($type == 'REDIRECT_META') {
349
            $header = '<META HTTP-EQUIV="Refresh" CONTENT="0; URL=' . $url . '" />';
350
            echo $header;
351
            exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method sendRedirect() contains an exit expression.

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

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

Loading history...
352
        } elseif ($type == 'REDIRECT_HEADER' || empty ($type)) {
353
            // check if url has /$base_url
354
            global $base_url, $site_url;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

    public function myFunction() {
        // Do something
    }
}
Loading history...
355
            if (substr($url, 0, strlen($base_url)) == $base_url) {
356
                // append $site_url to make it work with Location:
357
                $url = $site_url . substr($url, strlen($base_url));
358
            }
359
            if (strpos($url, "\n") === false) {
360
                $header = 'Location: ' . $url;
361
            } else {
362
                $this->messageQuit('No newline allowed in redirect url.');
363
            }
364
        }
365
        if ($responseCode && (strpos($responseCode, '30') !== false)) {
366
            header($responseCode);
367
        }
368
369
        if(!empty($header)) {
370
            header($header);
371
        }
372
373
        exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method sendRedirect() contains an exit expression.

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

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

Loading history...
374
    }
375
376
    /**
377
     * Forward to another page
378
     *
379
     * @param int|string $id
380
     * @param string $responseCode
381
     */
382
    public function sendForward($id, $responseCode = '')
383
    {
384
        if ($this->forwards > 0) {
385
            $this->forwards = $this->forwards - 1;
386
            $this->documentIdentifier = $id;
387
            $this->documentMethod = 'id';
388
            if ($responseCode) {
389
                header($responseCode);
390
            }
391
            $this->prepareResponse();
392
            exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method sendForward() contains an exit expression.

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

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

Loading history...
393
        } else {
394
            $this->messageQuit("Internal Server Error id={$id}");
395
            header('HTTP/1.0 500 Internal Server Error');
396
            die('<h1>ERROR: Too many forward attempts!</h1><p>The request could not be completed due to too many unsuccessful forward attempts.</p>');
0 ignored issues
show
Coding Style Compatibility introduced by
The method sendForward() contains an exit expression.

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

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

Loading history...
397
        }
398
    }
399
400
    /**
401
     * Redirect to the error page, by calling sendForward(). This is called for example when the page was not found.
402
     * @param bool $noEvent
403
     */
404
    public function sendErrorPage($noEvent = false)
405
    {
406
        $this->systemCacheKey = 'notfound';
407
        if (!$noEvent) {
408
            // invoke OnPageNotFound event
409
            $this->invokeEvent('OnPageNotFound');
410
        }
411
        $url = $this->config['error_page'] ? $this->config['error_page'] : $this->config['site_start'];
412
413
        $this->sendForward($url, 'HTTP/1.0 404 Not Found');
414
        exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method sendErrorPage() contains an exit expression.

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

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

Loading history...
415
    }
416
417
    /**
418
     * @param bool $noEvent
419
     */
420
    public function sendUnauthorizedPage($noEvent = false)
0 ignored issues
show
Coding Style introduced by
sendUnauthorizedPage uses the super-global variable $_REQUEST which is generally not recommended.

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

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

// Better
class Router
{
    private $host;

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

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

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

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
421
    {
422
        // invoke OnPageUnauthorized event
423
        $_REQUEST['refurl'] = $this->documentIdentifier;
424
        $this->systemCacheKey = 'unauth';
425
        if (!$noEvent) {
426
            $this->invokeEvent('OnPageUnauthorized');
427
        }
428
        if ($this->config['unauthorized_page']) {
429
            $unauthorizedPage = $this->config['unauthorized_page'];
430
        } elseif ($this->config['error_page']) {
431
            $unauthorizedPage = $this->config['error_page'];
432
        } else {
433
            $unauthorizedPage = $this->config['site_start'];
434
        }
435
        $this->sendForward($unauthorizedPage, 'HTTP/1.1 401 Unauthorized');
436
        exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method sendUnauthorizedPage() contains an exit expression.

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

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

Loading history...
437
    }
438
439
    /**
440
     * Get MODX settings including, but not limited to, the system_settings table
441
     */
442
    public function getSettings()
443
    {
444
        if (!isset($this->config['site_name'])) {
445
            $this->recoverySiteCache();
446
        }
447
448
        // setup default site id - new installation should generate a unique id for the site.
449
        if (!isset($this->config['site_id'])) {
450
            $this->config['site_id'] = "MzGeQ2faT4Dw06+U49x3";
451
        }
452
453
        // store base_url and base_path inside config array
454
        $this->config['base_url'] = MODX_BASE_URL;
455
        $this->config['base_path'] = MODX_BASE_PATH;
456
        $this->config['site_url'] = MODX_SITE_URL;
457
        $this->config['valid_hostnames'] = MODX_SITE_HOSTNAMES;
458
        $this->config['site_manager_url'] = MODX_MANAGER_URL;
459
        $this->config['site_manager_path'] = MODX_MANAGER_PATH;
460
        $this->error_reporting = $this->config['error_reporting'];
461
        $this->config['filemanager_path'] = str_replace('[(base_path)]', MODX_BASE_PATH, $this->config['filemanager_path']);
462
        $this->config['rb_base_dir'] = str_replace('[(base_path)]', MODX_BASE_PATH, $this->config['rb_base_dir']);
463
464
        if (!isset($this->config['enable_at_syntax'])) {
465
            $this->config['enable_at_syntax'] = 1;
466
        } // @TODO: This line is temporary, should be remove in next version
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
467
468
        // now merge user settings into evo-configuration
469
        $this->getUserSettings();
470
    }
471
472
    private function recoverySiteCache()
473
    {
474
        $site_cache_dir = MODX_BASE_PATH . $this->getCacheFolder();
475
        $site_cache_path = $site_cache_dir . 'siteCache.idx.php';
476
477
        if (is_file($site_cache_path)) {
478
            include($site_cache_path);
479
        }
480
        if (isset($this->config['site_name'])) {
481
            return;
482
        }
483
484
        include_once(MODX_MANAGER_PATH . 'processors/cache_sync.class.processor.php');
485
        $cache = new synccache();
486
        $cache->setCachepath($site_cache_dir);
487
        $cache->setReport(false);
488
        $cache->buildCache($this);
489
490
        clearstatcache();
491
        if (is_file($site_cache_path)) {
492
            include($site_cache_path);
493
        }
494
        if (isset($this->config['site_name'])) {
495
            return;
496
        }
497
498
        $rs = $this->db->select('setting_name, setting_value', '[+prefix+]system_settings');
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rs. Configured minimum length is 3.

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

Loading history...
499
        while ($row = $this->db->getRow($rs)) {
500
            $this->config[$row['setting_name']] = $row['setting_value'];
501
        }
502
503
        if (!$this->config['enable_filter']) {
504
            return;
505
        }
506
507
        $where = "plugincode LIKE '%phx.parser.class.inc.php%OnParseDocument();%' AND disabled != 1";
508
        $rs = $this->db->select('id', '[+prefix+]site_plugins', $where);
509
        if ($this->db->getRecordCount($rs)) {
510
            $this->config['enable_filter'] = '0';
511
        }
512
    }
513
514
    /**
515
     * Get user settings and merge into MODX configuration
516
     * @return array
517
     */
518
    public function getUserSettings()
0 ignored issues
show
Coding Style introduced by
getUserSettings uses the super-global variable $_SESSION which is generally not recommended.

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

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

// Better
class Router
{
    private $host;

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

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

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

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
519
    {
520
        $tbl_web_user_settings = $this->getFullTableName('web_user_settings');
521
        $tbl_user_settings = $this->getFullTableName('user_settings');
522
523
        // load user setting if user is logged in
524
        $usrSettings = array();
525
        if ($id = $this->getLoginUserID()) {
526
            $usrType = $this->getLoginUserType();
527
            if (isset ($usrType) && $usrType == 'manager') {
528
                $usrType = 'mgr';
529
            }
530
531
            if ($usrType == 'mgr' && $this->isBackend()) {
532
                // invoke the OnBeforeManagerPageInit event, only if in backend
533
                $this->invokeEvent("OnBeforeManagerPageInit");
534
            }
535
536
            if (isset ($_SESSION[$usrType . 'UsrConfigSet'])) {
537
                $usrSettings = &$_SESSION[$usrType . 'UsrConfigSet'];
538
            } else {
539
                if ($usrType == 'web') {
540
                    $from = $tbl_web_user_settings;
541
                    $where = "webuser='{$id}'";
542
                } else {
543
                    $from = $tbl_user_settings;
544
                    $where = "user='{$id}'";
545
                }
546
547
                $which_browser_default = $this->configGlobal['which_browser'] ? $this->configGlobal['which_browser'] : $this->config['which_browser'];
548
549
                $result = $this->db->select('setting_name, setting_value', $from, $where);
550
                while ($row = $this->db->getRow($result)) {
551 View Code Duplication
                    if ($row['setting_name'] == 'which_browser' && $row['setting_value'] == 'default') {
552
                        $row['setting_value'] = $which_browser_default;
553
                    }
554
                    $usrSettings[$row['setting_name']] = $row['setting_value'];
555
                }
556
                if (isset ($usrType)) {
557
                    $_SESSION[$usrType . 'UsrConfigSet'] = $usrSettings;
558
                } // store user settings in session
559
            }
560
        }
561
        if ($this->isFrontend() && $mgrid = $this->getLoginUserID('mgr')) {
562
            $musrSettings = array();
563
            if (isset ($_SESSION['mgrUsrConfigSet'])) {
564
                $musrSettings = &$_SESSION['mgrUsrConfigSet'];
565
            } else {
566
                if ($result = $this->db->select('setting_name, setting_value', $tbl_user_settings, "user='{$mgrid}'")) {
567
                    while ($row = $this->db->getRow($result)) {
568
                        $musrSettings[$row['setting_name']] = $row['setting_value'];
569
                    }
570
                    $_SESSION['mgrUsrConfigSet'] = $musrSettings; // store user settings in session
571
                }
572
            }
573
            if (!empty ($musrSettings)) {
574
                $usrSettings = array_merge($musrSettings, $usrSettings);
575
            }
576
        }
577
        // save global values before overwriting/merging array
578
        foreach ($usrSettings as $param => $value) {
579
            if (isset($this->config[$param])) {
580
                $this->configGlobal[$param] = $this->config[$param];
581
            }
582
        }
583
584
        $this->config = array_merge($this->config, $usrSettings);
585
        $this->config['filemanager_path'] = str_replace('[(base_path)]', MODX_BASE_PATH, $this->config['filemanager_path']);
586
        $this->config['rb_base_dir'] = str_replace('[(base_path)]', MODX_BASE_PATH, $this->config['rb_base_dir']);
587
588
        return $usrSettings;
589
    }
590
591
    /**
592
     * Returns the document identifier of the current request
593
     *
594
     * @param string $method id and alias are allowed
595
     * @return int
596
     */
597
    public function getDocumentIdentifier($method)
0 ignored issues
show
Coding Style introduced by
getDocumentIdentifier uses the super-global variable $_REQUEST which is generally not recommended.

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

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

// Better
class Router
{
    private $host;

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

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

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

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

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

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

// Better
class Router
{
    private $host;

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

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

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

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
598
    {
599
        // function to test the query and find the retrieval method
600
        if ($method === 'alias') {
601
            return $this->db->escape($_REQUEST['q']);
602
        }
603
604
        $id_ = filter_input(INPUT_GET, 'id');
605
        if ($id_) {
606
            if (preg_match('@^[1-9][0-9]*$@', $id_)) {
607
                return $id_;
608
            } else {
609
                $this->sendErrorPage();
610
            }
611
        } elseif (strpos($_SERVER['REQUEST_URI'], 'index.php/') !== false) {
612
            $this->sendErrorPage();
613
        } else {
614
            return $this->config['site_start'];
615
        }
616
    }
617
618
    /**
619
     * Check for manager or webuser login session since v1.2
620
     *
621
     * @param string $context
622
     * @return bool
623
     */
624
    public function isLoggedIn($context = 'mgr')
0 ignored issues
show
Coding Style introduced by
isLoggedIn uses the super-global variable $_SESSION which is generally not recommended.

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

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

// Better
class Router
{
    private $host;

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

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

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

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
625
    {
626
        if (substr($context, 0, 1) == 'm') {
627
            $_ = 'mgrValidated';
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $_. Configured minimum length is 3.

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

Loading history...
628
        } else {
629
            $_ = 'webValidated';
630
        }
631
632
        if (MODX_CLI || (isset($_SESSION[$_]) && !empty($_SESSION[$_]))) {
0 ignored issues
show
Coding Style introduced by
The if-else statement can be simplified to return (bool) (MODX_CLI ...!empty($_SESSION[$_]));.
Loading history...
633
            return true;
634
        } else {
635
            return false;
636
        }
637
    }
638
639
    /**
640
     * Check for manager login session
641
     *
642
     * @return boolean
643
     */
644
    public function checkSession()
645
    {
646
        return $this->isLoggedin();
647
    }
648
649
    /**
650
     * Checks, if a the result is a preview
651
     *
652
     * @return boolean
653
     */
654
    public function checkPreview()
0 ignored issues
show
Coding Style introduced by
checkPreview uses the super-global variable $_REQUEST which is generally not recommended.

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

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

// Better
class Router
{
    private $host;

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

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

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

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
655
    {
656
        if ($this->isLoggedIn() == true) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

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

Loading history...
657
            if (isset ($_REQUEST['z']) && $_REQUEST['z'] == 'manprev') {
0 ignored issues
show
Coding Style introduced by
The if-else statement can be simplified to return isset($_REQUEST['...UEST['z'] == 'manprev';.
Loading history...
658
                return true;
659
            } else {
660
                return false;
661
            }
662
        } else {
663
            return false;
664
        }
665
    }
666
667
    /**
668
     * check if site is offline
669
     *
670
     * @return boolean
671
     */
672
    public function checkSiteStatus()
673
    {
674
        if ($this->config['site_status']) {
675
            return true;
676
        }  // site online
677
        elseif ($this->isLoggedin()) {
678
            return true;
679
        }  // site offline but launched via the manager
680
        else {
681
            return false;
682
        } // site is offline
683
    }
684
685
    /**
686
     * Create a 'clean' document identifier with path information, friendly URL suffix and prefix.
687
     *
688
     * @param string $qOrig
689
     * @return string
690
     */
691
    public function cleanDocumentIdentifier($qOrig)
692
    {
693
        if (!$qOrig) {
694
            $qOrig = $this->config['site_start'];
695
        }
696
        $q = $qOrig;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $q. Configured minimum length is 3.

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

Loading history...
697
698
        $pre = $this->config['friendly_url_prefix'];
699
        $suf = $this->config['friendly_url_suffix'];
700
        $pre = preg_quote($pre, '/');
701
        $suf = preg_quote($suf, '/');
702 View Code Duplication
        if ($pre && preg_match('@^' . $pre . '(.*)$@', $q, $_)) {
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $_. Configured minimum length is 3.

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

Loading history...
703
            $q = $_[1];
704
        }
705 View Code Duplication
        if ($suf && preg_match('@(.*)' . $suf . '$@', $q, $_)) {
706
            $q = $_[1];
707
        }
708
709
        /* First remove any / before or after */
710
        $q = trim($q, '/');
711
712
        /* Save path if any */
713
        /* FS#476 and FS#308: only return virtualDir if friendly paths are enabled */
714
        if ($this->config['use_alias_path'] == 1) {
715
            $_ = strrpos($q, '/');
716
            $this->virtualDir = $_ !== false ? substr($q, 0, $_) : '';
717
            if ($_ !== false) {
718
                $q = preg_replace('@.*/@', '', $q);
719
            }
720
        } else {
721
            $this->virtualDir = '';
722
        }
723
724
        if (preg_match('@^[1-9][0-9]*$@', $q) && !isset($this->documentListing[$q])) { /* we got an ID returned, check to make sure it's not an alias */
725
            /* FS#476 and FS#308: check that id is valid in terms of virtualDir structure */
726
            if ($this->config['use_alias_path'] == 1) {
727
                if (($this->virtualDir != '' && !isset($this->documentListing[$this->virtualDir . '/' . $q]) || ($this->virtualDir == '' && !isset($this->documentListing[$q]))) && (($this->virtualDir != '' && isset($this->documentListing[$this->virtualDir]) && in_array($q, $this->getChildIds($this->documentListing[$this->virtualDir], 1))) || ($this->virtualDir == '' && in_array($q, $this->getChildIds(0, 1))))) {
728
                    $this->documentMethod = 'id';
729
                    return $q;
730
                } else { /* not a valid id in terms of virtualDir, treat as alias */
731
                    $this->documentMethod = 'alias';
732
                    return $q;
733
                }
734
            } else {
735
                $this->documentMethod = 'id';
736
                return $q;
737
            }
738
        } else { /* we didn't get an ID back, so instead we assume it's an alias */
739
            if ($this->config['friendly_alias_urls'] != 1) {
740
                $q = $qOrig;
741
            }
742
            $this->documentMethod = 'alias';
743
            return $q;
744
        }
745
    }
746
747
    /**
748
     * @return string
749
     */
750
    public function getCacheFolder()
751
    {
752
        return "assets/cache/";
753
    }
754
755
    /**
756
     * @param $key
757
     * @return string
758
     */
759
    public function getHashFile($key)
760
    {
761
        return $this->getCacheFolder() . "docid_" . $key . ".pageCache.php";
762
    }
763
764
    /**
765
     * @param $id
766
     * @return array|mixed|null|string
767
     */
768
    public function makePageCacheKey($id){
0 ignored issues
show
Coding Style introduced by
makePageCacheKey uses the super-global variable $_GET which is generally not recommended.

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

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

// Better
class Router
{
    private $host;

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

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

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

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

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

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

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

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

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

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

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

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

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

// Better
class Router
{
    private $host;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// Better
class Router
{
    private $host;

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

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

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

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

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

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

// Better
class Router
{
    private $host;

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

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

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

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

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

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

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

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

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

// Better
class Router
{
    private $host;

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

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

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

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1094
    {
1095
        $cacheRefreshTime = 0;
1096
        $recent_update = 0;
1097
        @include(MODX_BASE_PATH . $this->getCacheFolder() . 'sitePublishing.idx.php');
1098
        $this->recentUpdate = $recent_update;
1099
1100
        $timeNow = $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'];
1101
        if ($timeNow < $cacheRefreshTime || $cacheRefreshTime == 0) {
1102
            return;
1103
        }
1104
1105
        // now, check for documents that need publishing
1106
        $field = array('published' => 1, 'publishedon' => $timeNow);
1107
        $where = "pub_date <= {$timeNow} AND pub_date!=0 AND published=0";
1108
        $result_pub = $this->db->select( 'id', '[+prefix+]site_content',  $where);
1109
        $this->db->update($field, '[+prefix+]site_content', $where);
1110 View Code Duplication
        if ($this->db->getRecordCount($result_pub) >= 1) { //Event unPublished doc
1111
            while ($row_pub = $this->db->getRow($result_pub)) {
1112
                $this->invokeEvent("OnDocUnPublished", array(
1113
                    "docid" => $row_pub['id']
1114
                ));
1115
            }
1116
        }
1117
1118
        // now, check for documents that need un-publishing
1119
        $field = array('published' => 0, 'publishedon' => 0);
1120
        $where = "unpub_date <= {$timeNow} AND unpub_date!=0 AND published=1";
1121
        $result_unpub = $this->db->select( 'id', '[+prefix+]site_content',  $where);
1122
        $this->db->update($field, '[+prefix+]site_content', $where);
1123 View Code Duplication
        if ($this->db->getRecordCount($result_unpub) >= 1) { //Event unPublished doc
1124
            while ($row_unpub = $this->db->getRow($result_unpub)) {
1125
                $this->invokeEvent("OnDocUnPublished", array(
1126
                    "docid" => $row_unpub['id']
1127
                ));
1128
            }
1129
        }
1130
1131
        $this->recentUpdate = $timeNow;
1132
1133
        // clear the cache
1134
        $this->clearCache('full');
1135
    }
1136
1137
    public function checkPublishStatus()
1138
    {
1139
        $this->updatePubStatus();
1140
    }
1141
1142
    /**
1143
     * Final jobs.
1144
     *
1145
     * - cache page
1146
     */
1147
    public function postProcess()
1148
    {
1149
        // if the current document was generated, cache it!
1150
        $cacheable = ($this->config['enable_cache'] && $this->documentObject['cacheable']) ? 1 : 0;
1151
        if ($cacheable && $this->documentGenerated && $this->documentObject['type'] == 'document' && $this->documentObject['published']) {
1152
            // invoke OnBeforeSaveWebPageCache event
1153
            $this->invokeEvent("OnBeforeSaveWebPageCache");
1154
1155
            if (!empty($this->cacheKey) && is_scalar($this->cacheKey)) {
1156
                // get and store document groups inside document object. Document groups will be used to check security on cache pages
1157
                $where = "document='{$this->documentIdentifier}'";
1158
                $rs = $this->db->select('document_group', '[+prefix+]document_groups', $where);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rs. Configured minimum length is 3.

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

Loading history...
1159
                $docGroups = $this->db->getColumn('document_group', $rs);
1160
1161
                // Attach Document Groups and Scripts
1162
                if (is_array($docGroups)) {
1163
                    $this->documentObject['__MODxDocGroups__'] = implode(",", $docGroups);
1164
                }
1165
1166
                $docObjSerial = serialize($this->documentObject);
1167
                $cacheContent = $docObjSerial . "<!--__MODxCacheSpliter__-->" . $this->documentContent;
1168
                $page_cache_path = MODX_BASE_PATH . $this->getHashFile($this->cacheKey);
1169
                file_put_contents($page_cache_path, "<?php die('Unauthorized access.'); ?>$cacheContent");
1170
            }
1171
        }
1172
1173
        // Useful for example to external page counters/stats packages
1174
        $this->invokeEvent('OnWebPageComplete');
1175
1176
        // end post processing
1177
    }
1178
1179
    /**
1180
     * @param $content
1181
     * @param string $left
1182
     * @param string $right
1183
     * @return array
1184
     */
1185
    public function getTagsFromContent($content, $left = '[+', $right = '+]')
1186
    {
1187
        $_ = $this->_getTagsFromContent($content, $left, $right);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $_. Configured minimum length is 3.

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

Loading history...
1188
        if (empty($_)) {
1189
            return array();
1190
        }
1191
        foreach ($_ as $v) {
1192
            $tags[0][] = "{$left}{$v}{$right}";
0 ignored issues
show
Coding Style Comprehensibility introduced by
$tags was never initialized. Although not strictly required by PHP, it is generally a good practice to add $tags = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
1193
            $tags[1][] = $v;
1194
        }
1195
        return $tags;
0 ignored issues
show
Bug introduced by
The variable $tags does not seem to be defined for all execution paths leading up to this point.

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

Let’s take a look at an example:

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

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

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

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

Available Fixes

  1. Check for existence of the variable explicitly:

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

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

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1196
    }
1197
1198
    /**
1199
     * @param $content
1200
     * @param string $left
1201
     * @param string $right
1202
     * @return array
1203
     */
1204
    public function _getTagsFromContent($content, $left = '[+', $right = '+]')
1205
    {
1206
        if (strpos($content, $left) === false) {
1207
            return array();
1208
        }
1209
        $spacer = md5('<<<EVO>>>');
1210
        if($left==='{{' && strpos($content,';}}')!==false)  $content = str_replace(';}}', sprintf(';}%s}',   $spacer),$content);
1211
        if($left==='{{' && strpos($content,'{{}}')!==false) $content = str_replace('{{}}',sprintf('{%$1s{}%$1s}',$spacer),$content);
1212
        if($left==='[[' && strpos($content,']]]]')!==false) $content = str_replace(']]]]',sprintf(']]%s]]',  $spacer),$content);
1213
        if($left==='[[' && strpos($content,']]]')!==false)  $content = str_replace(']]]', sprintf(']%s]]',   $spacer),$content);
1214
1215
        $pos['<![CDATA['] = strpos($content, '<![CDATA[');
0 ignored issues
show
Coding Style Comprehensibility introduced by
$pos was never initialized. Although not strictly required by PHP, it is generally a good practice to add $pos = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
1216
        $pos[']]>'] = strpos($content, ']]>');
1217
1218
        if ($pos['<![CDATA['] !== false && $pos[']]>'] !== false) {
1219
            $content = substr($content, 0, $pos['<![CDATA[']) . substr($content, $pos[']]>'] + 3);
1220
        }
1221
1222
        $lp = explode($left, $content);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $lp. Configured minimum length is 3.

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

Loading history...
1223
        $piece = array();
1224
        foreach ($lp as $lc => $lv) {
1225
            if ($lc !== 0) {
1226
                $piece[] = $left;
1227
            }
1228
            if (strpos($lv, $right) === false) {
1229
                $piece[] = $lv;
1230
            } else {
1231
                $rp = explode($right, $lv);
1232
                foreach ($rp as $rc => $rv) {
1233
                    if ($rc !== 0) {
1234
                        $piece[] = $right;
1235
                    }
1236
                    $piece[] = $rv;
1237
                }
1238
            }
1239
        }
1240
        $lc = 0;
1241
        $rc = 0;
1242
        $fetch = '';
1243
        $tags = array();
1244
        foreach ($piece as $v) {
1245
            if ($v === $left) {
1246
                if (0 < $lc) {
1247
                    $fetch .= $left;
1248
                }
1249
                $lc++;
1250
            } elseif ($v === $right) {
1251
                if ($lc === 0) {
1252
                    continue;
1253
                }
1254
                $rc++;
1255
                if ($lc === $rc) {
1256
                    // #1200 Enable modifiers in Wayfinder - add nested placeholders to $tags like for $fetch = "phx:input=`[+wf.linktext+]`:test"
1257
                    if (strpos($fetch, $left) !== false) {
1258
                        $nested = $this->_getTagsFromContent($fetch, $left, $right);
1259
                        foreach ($nested as $tag) {
1260
                            if (!in_array($tag, $tags)) {
1261
                                $tags[] = $tag;
1262
                            }
1263
                        }
1264
                    }
1265
1266
                    if (!in_array($fetch, $tags)) {  // Avoid double Matches
1267
                        $tags[] = $fetch; // Fetch
1268
                    };
1269
                    $fetch = ''; // and reset
1270
                    $lc = 0;
1271
                    $rc = 0;
1272
                } else {
1273
                    $fetch .= $right;
1274
                }
1275
            } else {
1276
                if (0 < $lc) {
1277
                    $fetch .= $v;
1278
                } else {
1279
                    continue;
1280
                }
1281
            }
1282
        }
1283
        foreach($tags as $i=>$tag) {
1284
            if(strpos($tag,$spacer)!==false) $tags[$i] = str_replace($spacer, '', $tag);
1285
        }
1286
        return $tags;
1287
    }
1288
1289
    /**
1290
     * Merge content fields and TVs
1291
     *
1292
     * @param $content
1293
     * @param bool $ph
1294
     * @return string
1295
     * @internal param string $template
1296
     */
1297
    public function mergeDocumentContent($content, $ph = false)
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ph. Configured minimum length is 3.

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

Loading history...
Coding Style introduced by
mergeDocumentContent uses the super-global variable $_SERVER which is generally not recommended.

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

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

// Better
class Router
{
    private $host;

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

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

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

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1298
    {
1299 View Code Duplication
        if ($this->config['enable_at_syntax']) {
1300
            if (stripos($content, '<@LITERAL>') !== false) {
1301
                $content = $this->escapeLiteralTagsContent($content);
1302
            }
1303
        }
1304
        if (strpos($content, '[*') === false) {
1305
            return $content;
1306
        }
1307
        if (!isset($this->documentIdentifier)) {
1308
            return $content;
1309
        }
1310
        if (!isset($this->documentObject) || empty($this->documentObject)) {
1311
            return $content;
1312
        }
1313
1314
        if (!$ph) {
1315
            $ph = $this->documentObject;
1316
        }
1317
1318
        $matches = $this->getTagsFromContent($content, '[*', '*]');
1319
        if (!$matches) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $matches of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1320
            return $content;
1321
        }
1322
1323
        foreach ($matches[1] as $i => $key) {
1324
            if(strpos($key,'[+')!==false) continue; // Allow chunk {{chunk?&param=`xxx`}} with [*tv_name_[+param+]*] as content
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
1325
            if (substr($key, 0, 1) == '#') {
1326
                $key = substr($key, 1);
1327
            } // remove # for QuickEdit format
1328
1329
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1330
            if (strpos($key, '@') !== false) {
1331
                list($key, $context) = explode('@', $key, 2);
1332
            } else {
1333
                $context = false;
1334
            }
1335
1336
            // if(!isset($ph[$key]) && !$context) continue; // #1218 TVs/PHs will not be rendered if custom_meta_title is not assigned to template like [*custom_meta_title:ne:then=`[*custom_meta_title*]`:else=`[*pagetitle*]`*]
0 ignored issues
show
Unused Code Comprehensibility introduced by
77% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
1337
            if ($context) {
1338
                $value = $this->_contextValue("{$key}@{$context}", $this->documentObject['parent']);
0 ignored issues
show
Documentation introduced by
$this->documentObject['parent'] is of type array|string, but the function expects a boolean|integer.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1339
            } else {
1340
                $value = isset($ph[$key]) ? $ph[$key] : '';
1341
            }
1342
1343
            if (is_array($value)) {
1344
                include_once(MODX_MANAGER_PATH . 'includes/tmplvars.format.inc.php');
1345
                include_once(MODX_MANAGER_PATH . 'includes/tmplvars.commands.inc.php');
1346
                $value = getTVDisplayFormat($value[0], $value[1], $value[2], $value[3], $value[4]);
1347
            }
1348
1349
            $s = &$matches[0][$i];
1350
            if ($modifiers !== false) {
1351
                $value = $this->applyFilter($value, $modifiers, $key);
1352
            }
1353
1354 View Code Duplication
            if (strpos($content, $s) !== false) {
1355
                $content = str_replace($s, $value, $content);
1356
            } elseif($this->debug) {
1357
                $this->addLog('mergeDocumentContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1358
            }
1359
        }
1360
1361
        return $content;
1362
    }
1363
1364
    /**
1365
     * @param $key
1366
     * @param bool|int $parent
1367
     * @return bool|mixed|string
1368
     */
1369
    public function _contextValue($key, $parent = false)
1370
    {
1371
        if (preg_match('/@\d+\/u/', $key)) {
1372
            $key = str_replace(array('@', '/u'), array('@u(', ')'), $key);
1373
        }
1374
        list($key, $str) = explode('@', $key, 2);
1375
1376
        if (strpos($str, '(')) {
1377
            list($context, $option) = explode('(', $str, 2);
1378
        } else {
1379
            list($context, $option) = array($str, false);
1380
        }
1381
1382
        if ($option) {
1383
            $option = trim($option, ')(\'"`');
1384
        }
1385
1386
        switch (strtolower($context)) {
1387
            case 'site_start':
1388
                $docid = $this->config['site_start'];
1389
                break;
1390
            case 'parent':
1391
            case 'p':
1392
                $docid = $parent;
1393
                if ($docid == 0) {
1394
                    $docid = $this->config['site_start'];
1395
                }
1396
                break;
1397
            case 'ultimateparent':
1398
            case 'uparent':
1399
            case 'up':
1400
            case 'u':
1401 View Code Duplication
                if (strpos($str, '(') !== false) {
1402
                    $top = substr($str, strpos($str, '('));
1403
                    $top = trim($top, '()"\'');
1404
                } else {
1405
                    $top = 0;
1406
                }
1407
                $docid = $this->getUltimateParentId($this->documentIdentifier, $top);
1408
                break;
1409
            case 'alias':
1410
                $str = substr($str, strpos($str, '('));
1411
                $str = trim($str, '()"\'');
1412
                $docid = $this->getIdFromAlias($str);
1413
                break;
1414 View Code Duplication
            case 'prev':
1415
                if (!$option) {
1416
                    $option = 'menuindex,ASC';
1417
                } elseif (strpos($option, ',') === false) {
1418
                    $option .= ',ASC';
1419
                }
1420
                list($by, $dir) = explode(',', $option, 2);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $by. Configured minimum length is 3.

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

Loading history...
1421
                $children = $this->getActiveChildren($parent, $by, $dir);
1422
                $find = false;
1423
                $prev = false;
1424
                foreach ($children as $row) {
1425
                    if ($row['id'] == $this->documentIdentifier) {
1426
                        $find = true;
1427
                        break;
1428
                    }
1429
                    $prev = $row;
1430
                }
1431
                if ($find) {
1432
                    if (isset($prev[$key])) {
1433
                        return $prev[$key];
1434
                    } else {
1435
                        $docid = $prev['id'];
1436
                    }
1437
                } else {
1438
                    $docid = '';
1439
                }
1440
                break;
1441 View Code Duplication
            case 'next':
1442
                if (!$option) {
1443
                    $option = 'menuindex,ASC';
1444
                } elseif (strpos($option, ',') === false) {
1445
                    $option .= ',ASC';
1446
                }
1447
                list($by, $dir) = explode(',', $option, 2);
1448
                $children = $this->getActiveChildren($parent, $by, $dir);
1449
                $find = false;
1450
                $next = false;
1451
                foreach ($children as $row) {
1452
                    if ($find) {
1453
                        $next = $row;
1454
                        break;
1455
                    }
1456
                    if ($row['id'] == $this->documentIdentifier) {
1457
                        $find = true;
1458
                    }
1459
                }
1460
                if ($find) {
1461
                    if (isset($next[$key])) {
1462
                        return $next[$key];
1463
                    } else {
1464
                        $docid = $next['id'];
1465
                    }
1466
                } else {
1467
                    $docid = '';
1468
                }
1469
                break;
1470
            default:
1471
                $docid = $str;
1472
        }
1473
        if (preg_match('@^[1-9][0-9]*$@', $docid)) {
1474
            $value = $this->getField($key, $docid);
1475
        } else {
1476
            $value = '';
1477
        }
1478
        return $value;
1479
    }
1480
1481
    /**
1482
     * Merge system settings
1483
     *
1484
     * @param $content
1485
     * @param bool|array $ph
1486
     * @return string
1487
     * @internal param string $template
1488
     */
1489
    public function mergeSettingsContent($content, $ph = false)
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ph. Configured minimum length is 3.

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

Loading history...
Coding Style introduced by
mergeSettingsContent uses the super-global variable $_SERVER which is generally not recommended.

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

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

// Better
class Router
{
    private $host;

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

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

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

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1490
    {
1491 View Code Duplication
        if ($this->config['enable_at_syntax']) {
1492
            if (stripos($content, '<@LITERAL>') !== false) {
1493
                $content = $this->escapeLiteralTagsContent($content);
1494
            }
1495
        }
1496
        if (strpos($content, '[(') === false) {
1497
            return $content;
1498
        }
1499
1500
        if (empty($ph)) {
1501
            $ph = $this->config;
1502
        }
1503
1504
        $matches = $this->getTagsFromContent($content, '[(', ')]');
1505
        if (empty($matches)) {
1506
            return $content;
1507
        }
1508
1509
        foreach ($matches[1] as $i => $key) {
1510
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1511
1512
            if (isset($ph[$key])) {
1513
                $value = $ph[$key];
1514
            } else {
1515
                continue;
1516
            }
1517
1518
            if ($modifiers !== false) {
1519
                $value = $this->applyFilter($value, $modifiers, $key);
1520
            }
1521
            $s = &$matches[0][$i];
1522 View Code Duplication
            if (strpos($content, $s) !== false) {
1523
                $content = str_replace($s, $value, $content);
1524
            } elseif($this->debug) {
1525
                $this->addLog('mergeSettingsContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1526
            }
1527
        }
1528
        return $content;
1529
    }
1530
1531
    /**
1532
     * Merge chunks
1533
     *
1534
     * @param string $content
1535
     * @param bool|array $ph
1536
     * @return string
1537
     */
1538
    public function mergeChunkContent($content, $ph = false)
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ph. Configured minimum length is 3.

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

Loading history...
Coding Style introduced by
mergeChunkContent uses the super-global variable $_SERVER which is generally not recommended.

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

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

// Better
class Router
{
    private $host;

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

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

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

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1539
    {
1540
        if ($this->config['enable_at_syntax']) {
1541
            if (strpos($content, '{{ ') !== false) {
1542
                $content = str_replace(array('{{ ', ' }}'), array('\{\{ ', ' \}\}'), $content);
1543
            }
1544
            if (stripos($content, '<@LITERAL>') !== false) {
1545
                $content = $this->escapeLiteralTagsContent($content);
1546
            }
1547
        }
1548
        if (strpos($content, '{{') === false) {
1549
            return $content;
1550
        }
1551
1552
        if (empty($ph)) {
1553
            $ph = $this->chunkCache;
1554
        }
1555
1556
        $matches = $this->getTagsFromContent($content, '{{', '}}');
1557
        if (empty($matches)) {
1558
            return $content;
1559
        }
1560
1561
        foreach ($matches[1] as $i => $key) {
1562
            $snip_call = $this->_split_snip_call($key);
1563
            $key = $snip_call['name'];
1564
            $params = $this->getParamsFromString($snip_call['params']);
1565
1566
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1567
1568
            if (!isset($ph[$key])) {
1569
                $ph[$key] = $this->getChunk($key);
1570
            }
1571
            $value = $ph[$key];
1572
1573
            if (is_null($value)) {
1574
                continue;
1575
            }
1576
1577
            $value = $this->parseText($value, $params); // parse local scope placeholers for ConditionalTags
0 ignored issues
show
Bug introduced by
It seems like $params defined by $this->getParamsFromString($snip_call['params']) on line 1564 can also be of type null; however, DocumentParser::parseText() does only seem to accept array, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
1578
            $value = $this->mergePlaceholderContent($value, $params);  // parse page global placeholers
0 ignored issues
show
Bug introduced by
It seems like $params defined by $this->getParamsFromString($snip_call['params']) on line 1564 can also be of type null; however, DocumentParser::mergePlaceholderContent() does only seem to accept boolean|array, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
1579
            if ($this->config['enable_at_syntax']) {
1580
                $value = $this->mergeConditionalTagsContent($value);
1581
            }
1582
            $value = $this->mergeDocumentContent($value);
1583
            $value = $this->mergeSettingsContent($value);
1584
            $value = $this->mergeChunkContent($value);
1585
1586
            if ($modifiers !== false) {
1587
                $value = $this->applyFilter($value, $modifiers, $key);
1588
            }
1589
1590
            $s = &$matches[0][$i];
1591 View Code Duplication
            if (strpos($content, $s) !== false) {
1592
                $content = str_replace($s, $value, $content);
1593
            } elseif($this->debug) {
1594
                $this->addLog('mergeChunkContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1595
            }
1596
        }
1597
        return $content;
1598
    }
1599
1600
    /**
1601
     * Merge placeholder values
1602
     *
1603
     * @param string $content
1604
     * @param bool|array $ph
1605
     * @return string
1606
     */
1607
    public function mergePlaceholderContent($content, $ph = false)
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ph. Configured minimum length is 3.

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

Loading history...
Coding Style introduced by
mergePlaceholderContent uses the super-global variable $_SERVER which is generally not recommended.

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

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

// Better
class Router
{
    private $host;

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

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

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

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1608
    {
1609
1610 View Code Duplication
        if ($this->config['enable_at_syntax']) {
1611
            if (stripos($content, '<@LITERAL>') !== false) {
1612
                $content = $this->escapeLiteralTagsContent($content);
1613
            }
1614
        }
1615
        if (strpos($content, '[+') === false) {
1616
            return $content;
1617
        }
1618
1619
        if (empty($ph)) {
1620
            $ph = $this->placeholders;
1621
        }
1622
1623
        if ($this->config['enable_at_syntax']) {
1624
            $content = $this->mergeConditionalTagsContent($content);
1625
        }
1626
1627
        $content = $this->mergeDocumentContent($content);
1628
        $content = $this->mergeSettingsContent($content);
1629
        $matches = $this->getTagsFromContent($content, '[+', '+]');
1630
        if (empty($matches)) {
1631
            return $content;
1632
        }
1633
        foreach ($matches[1] as $i => $key) {
1634
1635
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1636
1637
            if (isset($ph[$key])) {
1638
                $value = $ph[$key];
1639
            } elseif ($key === 'phx') {
1640
                $value = '';
1641
            } else {
1642
                continue;
1643
            }
1644
1645
            if ($modifiers !== false) {
1646
                $modifiers = $this->mergePlaceholderContent($modifiers);
1647
                $value = $this->applyFilter($value, $modifiers, $key);
0 ignored issues
show
Documentation introduced by
$modifiers is of type string, but the function expects a boolean.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1648
            }
1649
            $s = &$matches[0][$i];
1650 View Code Duplication
            if (strpos($content, $s) !== false) {
1651
                $content = str_replace($s, $value, $content);
1652
            } elseif($this->debug) {
1653
                $this->addLog('mergePlaceholderContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1654
            }
1655
        }
1656
        return $content;
1657
    }
1658
1659
    /**
1660
     * @param $content
1661
     * @param string $iftag
1662
     * @param string $elseiftag
1663
     * @param string $elsetag
1664
     * @param string $endiftag
1665
     * @return mixed|string
1666
     */
1667
    public function mergeConditionalTagsContent($content, $iftag = '<@IF:', $elseiftag = '<@ELSEIF:', $elsetag = '<@ELSE>', $endiftag = '<@ENDIF>')
0 ignored issues
show
Coding Style introduced by
mergeConditionalTagsContent uses the super-global variable $_SERVER which is generally not recommended.

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

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

// Better
class Router
{
    private $host;

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

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

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

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1668
    {
1669
        if (strpos($content, '@IF') !== false) {
1670
            $content = $this->_prepareCTag($content, $iftag, $elseiftag, $elsetag, $endiftag);
1671
        }
1672
1673
        if (strpos($content, $iftag) === false) {
1674
            return $content;
1675
        }
1676
1677
        $sp = '#' . md5('ConditionalTags' . $_SERVER['REQUEST_TIME']) . '#';
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $sp. Configured minimum length is 3.

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

Loading history...
1678
        $content = str_replace(array('<?php', '<?=', '<?', '?>'), array("{$sp}b", "{$sp}p", "{$sp}s", "{$sp}e"), $content);
1679
1680
        $pieces = explode('<@IF:', $content);
1681 View Code Duplication
        foreach ($pieces as $i => $split) {
1682
            if ($i === 0) {
1683
                $content = $split;
1684
                continue;
1685
            }
1686
            list($cmd, $text) = explode('>', $split, 2);
1687
            $cmd = str_replace("'", "\'", $cmd);
1688
            $content .= "<?php if(\$this->_parseCTagCMD('" . $cmd . "')): ?>";
1689
            $content .= $text;
1690
        }
1691
        $pieces = explode('<@ELSEIF:', $content);
1692 View Code Duplication
        foreach ($pieces as $i => $split) {
1693
            if ($i === 0) {
1694
                $content = $split;
1695
                continue;
1696
            }
1697
            list($cmd, $text) = explode('>', $split, 2);
1698
            $cmd = str_replace("'", "\'", $cmd);
1699
            $content .= "<?php elseif(\$this->_parseCTagCMD('" . $cmd . "')): ?>";
1700
            $content .= $text;
1701
        }
1702
1703
        $content = str_replace(array('<@ELSE>', '<@ENDIF>'), array('<?php else:?>', '<?php endif;?>'), $content);
1704
        ob_start();
1705
        $content = eval('?>' . $content);
0 ignored issues
show
Unused Code introduced by
$content is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
Coding Style introduced by
It is generally not recommended to use eval unless absolutely required.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
1706
        $content = ob_get_clean();
1707
        $content = str_replace(array("{$sp}b", "{$sp}p", "{$sp}s", "{$sp}e"), array('<?php', '<?=', '<?', '?>'), $content);
1708
1709
        return $content;
1710
    }
1711
1712
    /**
1713
     * @param $content
1714
     * @param string $iftag
1715
     * @param string $elseiftag
1716
     * @param string $elsetag
1717
     * @param string $endiftag
1718
     * @return mixed
1719
     */
1720
    private function _prepareCTag($content, $iftag = '<@IF:', $elseiftag = '<@ELSEIF:', $elsetag = '<@ELSE>', $endiftag = '<@ENDIF>')
1721
    {
1722
        if (strpos($content, '<!--@IF ') !== false) {
1723
            $content = str_replace('<!--@IF ', $iftag, $content);
1724
        } // for jp
1725
        if (strpos($content, '<!--@IF:') !== false) {
1726
            $content = str_replace('<!--@IF:', $iftag, $content);
1727
        }
1728
        if (strpos($content, $iftag) === false) {
1729
            return $content;
1730
        }
1731
        if (strpos($content, '<!--@ELSEIF:') !== false) {
1732
            $content = str_replace('<!--@ELSEIF:', $elseiftag, $content);
1733
        } // for jp
1734
        if (strpos($content, '<!--@ELSE-->') !== false) {
1735
            $content = str_replace('<!--@ELSE-->', $elsetag, $content);
1736
        }  // for jp
1737
        if (strpos($content, '<!--@ENDIF-->') !== false) {
1738
            $content = str_replace('<!--@ENDIF-->', $endiftag, $content);
1739
        }    // for jp
1740
        if (strpos($content, '<@ENDIF-->') !== false) {
1741
            $content = str_replace('<@ENDIF-->', $endiftag, $content);
1742
        }
1743
        $tags = array($iftag, $elseiftag, $elsetag, $endiftag);
1744
        $content = str_ireplace($tags, $tags, $content); // Change to capital letters
1745
        return $content;
1746
    }
1747
1748
    /**
1749
     * @param $cmd
1750
     * @return mixed|string
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use integer|string|boolean.

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

Loading history...
1751
     */
1752
    private function _parseCTagCMD($cmd)
1753
    {
1754
        $cmd = trim($cmd);
1755
        $reverse = substr($cmd, 0, 1) === '!' ? true : false;
1756
        if ($reverse) {
1757
            $cmd = ltrim($cmd, '!');
1758
        }
1759
        if (strpos($cmd, '[!') !== false) {
1760
            $cmd = str_replace(array('[!', '!]'), array('[[', ']]'), $cmd);
1761
        }
1762
        $safe = 0;
1763
        while ($safe < 20) {
1764
            $bt = md5($cmd);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $bt. Configured minimum length is 3.

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

Loading history...
1765
            if (strpos($cmd, '[*') !== false) {
1766
                $cmd = $this->mergeDocumentContent($cmd);
1767
            }
1768
            if (strpos($cmd, '[(') !== false) {
1769
                $cmd = $this->mergeSettingsContent($cmd);
1770
            }
1771
            if (strpos($cmd, '{{') !== false) {
1772
                $cmd = $this->mergeChunkContent($cmd);
1773
            }
1774
            if (strpos($cmd, '[[') !== false) {
1775
                $cmd = $this->evalSnippets($cmd);
1776
            }
1777
            if (strpos($cmd, '[+') !== false && strpos($cmd, '[[') === false) {
1778
                $cmd = $this->mergePlaceholderContent($cmd);
1779
            }
1780
            if ($bt === md5($cmd)) {
1781
                break;
1782
            }
1783
            $safe++;
1784
        }
1785
        $cmd = ltrim($cmd);
1786
        $cmd = rtrim($cmd, '-');
1787
        $cmd = str_ireplace(array(' and ', ' or '), array('&&', '||'), $cmd);
1788
1789
        if (!preg_match('@^[0-9]*$@', $cmd) && preg_match('@^[0-9<= \-\+\*/\(\)%!&|]*$@', $cmd)) {
1790
            $cmd = eval("return {$cmd};");
0 ignored issues
show
Coding Style introduced by
It is generally not recommended to use eval unless absolutely required.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
1791
        } else {
1792
            $_ = explode(',', '[*,[(,{{,[[,[!,[+');
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $_. Configured minimum length is 3.

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

Loading history...
1793
            foreach ($_ as $left) {
1794
                if (strpos($cmd, $left) !== false) {
1795
                    $cmd = 0;
1796
                    break;
1797
                }
1798
            }
1799
        }
1800
        $cmd = trim($cmd);
1801
        if (!preg_match('@^[0-9]+$@', $cmd)) {
1802
            $cmd = empty($cmd) ? 0 : 1;
1803
        } elseif ($cmd <= 0) {
1804
            $cmd = 0;
1805
        }
1806
1807
        if ($reverse) {
1808
            $cmd = !$cmd;
1809
        }
1810
1811
        return $cmd;
1812
    }
1813
1814
    /**
1815
     * Remove Comment-Tags from output like <!--@- Comment -@-->
1816
     * @param $content
1817
     * @param string $left
1818
     * @param string $right
1819
     * @return mixed
1820
     */
1821
    function ignoreCommentedTagsContent($content, $left = '<!--@-', $right = '-@-->')
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1822
    {
1823
        if (strpos($content, $left) === false) {
1824
            return $content;
1825
        }
1826
1827
        $matches = $this->getTagsFromContent($content, $left, $right);
1828
        if (!empty($matches)) {
1829
            foreach ($matches[0] as $i => $v) {
1830
                $addBreakMatches[$i] = $v . "\n";
0 ignored issues
show
Coding Style Comprehensibility introduced by
$addBreakMatches was never initialized. Although not strictly required by PHP, it is generally a good practice to add $addBreakMatches = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
1831
            }
1832
            $content = str_replace($addBreakMatches, '', $content);
0 ignored issues
show
Bug introduced by
The variable $addBreakMatches does not seem to be defined for all execution paths leading up to this point.

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

Let’s take a look at an example:

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

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

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

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

Available Fixes

  1. Check for existence of the variable explicitly:

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

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

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1833
            if (strpos($content, $left) !== false) {
1834
                $content = str_replace($matches[0], '', $content);
1835
            }
1836
        }
1837
        return $content;
1838
    }
1839
1840
    /**
1841
     * @param $content
1842
     * @param string $left
1843
     * @param string $right
1844
     * @return mixed
1845
     */
1846
    public function escapeLiteralTagsContent($content, $left = '<@LITERAL>', $right = '<@ENDLITERAL>')
0 ignored issues
show
Coding Style introduced by
escapeLiteralTagsContent uses the super-global variable $_SERVER which is generally not recommended.

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

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

// Better
class Router
{
    private $host;

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

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

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

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1847
    {
1848
        if (stripos($content, $left) === false) {
1849
            return $content;
1850
        }
1851
1852
        $matches = $this->getTagsFromContent($content, $left, $right);
1853
        if (empty($matches)) {
1854
            return $content;
1855
        }
1856
1857
        list($sTags, $rTags) = $this->getTagsForEscape();
1858
        foreach ($matches[1] as $i => $v) {
1859
            $v = str_ireplace($sTags, $rTags, $v);
1860
            $s = &$matches[0][$i];
1861 View Code Duplication
            if (strpos($content, $s) !== false) {
1862
                $content = str_replace($s, $v, $content);
1863
            } elseif($this->debug) {
1864
                $this->addLog('ignoreCommentedTagsContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1865
            }
1866
        }
1867
        return $content;
1868
    }
1869
1870
    /**
1871
     * Detect PHP error according to MODX error level
1872
     *
1873
     * @param integer $error PHP error level
1874
     * @return boolean Error detected
1875
     */
1876
1877
    public function detectError($error)
1878
    {
1879
        $detected = false;
1880
        if ($this->config['error_reporting'] == 99 && $error) {
1881
            $detected = true;
1882
        } elseif ($this->config['error_reporting'] == 2 && ($error & ~E_NOTICE)) {
1883
            $detected = true;
1884
        } elseif ($this->config['error_reporting'] == 1 && ($error & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT)) {
1885
            $detected = true;
1886
        }
1887
        return $detected;
1888
    }
1889
1890
    /**
1891
     * Run a plugin
1892
     *
1893
     * @param string $pluginCode Code to run
1894
     * @param array $params
1895
     */
1896
    public function evalPlugin($pluginCode, $params)
1897
    {
1898
        $modx = &$this;
1899
        $modx->event->params = &$params; // store params inside event object
1900
        if (is_array($params)) {
1901
            extract($params, EXTR_SKIP);
1902
        }
1903
        /* if uncomment incorrect work plugin, cant understend where use this code and for what?
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
1904
        // This code will avoid further execution of plugins in case they cause a fatal-error. clearCache() will delete those locks to allow execution of locked plugins again.
1905
        // Related to https://github.com/modxcms/evolution/issues/1130
1906
        $lock_file_path = MODX_BASE_PATH . 'assets/cache/lock_' . str_replace(' ','-',strtolower($this->event->activePlugin)) . '.pageCache.php';
1907
        if($this->isBackend()) {
1908
            if(is_file($lock_file_path)) {
1909
                $msg = sprintf("Plugin parse error, Temporarily disabled '%s'.", $this->event->activePlugin);
1910
                $this->logEvent(0, 3, $msg, $msg);
1911
                return;
1912
            }
1913
            elseif(stripos($this->event->activePlugin,'ElementsInTree')===false) touch($lock_file_path);
1914
        }*/
1915
        ob_start();
1916
        eval($pluginCode);
0 ignored issues
show
Coding Style introduced by
It is generally not recommended to use eval unless absolutely required.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
1917
        $msg = ob_get_contents();
1918
        ob_end_clean();
1919
        // When reached here, no fatal error occured so the lock should be removed.
1920
        /*if(is_file($lock_file_path)) unlink($lock_file_path);*/
0 ignored issues
show
Unused Code Comprehensibility introduced by
77% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
1921
1922 View Code Duplication
        if ((0 < $this->config['error_reporting']) && $msg && isset($php_errormsg)) {
1923
            $error_info = error_get_last();
1924
            if ($this->detectError($error_info['type'])) {
1925
                $msg = ($msg === false) ? 'ob_get_contents() error' : $msg;
1926
                $this->messageQuit('PHP Parse Error', '', true, $error_info['type'], $error_info['file'], 'Plugin', $error_info['message'], $error_info['line'], $msg);
1927
                if ($this->isBackend()) {
1928
                    $this->event->alert('An error occurred while loading. Please see the event log for more information.<p>' . $msg . '</p>');
1929
                }
1930
            }
1931
        } else {
1932
            echo $msg;
1933
        }
1934
        unset($modx->event->params);
1935
    }
1936
1937
    /**
1938
     * Run a snippet
1939
     *
1940
     * @param $phpcode
1941
     * @param array $params
1942
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be array|object|string? Also, consider making the array more specific, something like array<String>, or String[].

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

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

Loading history...
1943
     * @internal param string $snippet Code to run
1944
     */
1945
    public function evalSnippet($phpcode, $params)
1946
    {
1947
        $modx = &$this;
1948
        /*
0 ignored issues
show
Unused Code Comprehensibility introduced by
65% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
1949
        if(isset($params) && is_array($params)) {
1950
            foreach($params as $k=>$v) {
1951
                $v = strtolower($v);
1952
                if($v==='false')    $params[$k] = false;
1953
                elseif($v==='true') $params[$k] = true;
1954
            }
1955
        }*/
1956
        $modx->event->params = &$params; // store params inside event object
1957
        if (is_array($params)) {
1958
            extract($params, EXTR_SKIP);
1959
        }
1960
        ob_start();
1961
        if (strpos($phpcode, ';') !== false) {
1962
            $return = eval($phpcode);
0 ignored issues
show
Coding Style introduced by
It is generally not recommended to use eval unless absolutely required.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
1963
        } else {
1964
            $return = call_user_func_array($phpcode, array($params));
1965
        }
1966
        $echo = ob_get_contents();
1967
        ob_end_clean();
1968 View Code Duplication
        if ((0 < $this->config['error_reporting']) && isset($php_errormsg)) {
1969
            $error_info = error_get_last();
1970
            if ($this->detectError($error_info['type'])) {
1971
                $echo = ($echo === false) ? 'ob_get_contents() error' : $echo;
1972
                $this->messageQuit('PHP Parse Error', '', true, $error_info['type'], $error_info['file'], 'Snippet', $error_info['message'], $error_info['line'], $echo);
1973
                if ($this->isBackend()) {
1974
                    $this->event->alert('An error occurred while loading. Please see the event log for more information<p>' . $echo . $return . '</p>');
1975
                }
1976
            }
1977
        }
1978
        unset($modx->event->params);
1979
        if (is_array($return) || is_object($return)) {
1980
            return $return;
1981
        } else {
1982
            return $echo . $return;
1983
        }
1984
    }
1985
1986
    /**
1987
     * Run snippets as per the tags in $documentSource and replace the tags with the returned values.
1988
     *
1989
     * @param $content
1990
     * @return string
1991
     * @internal param string $documentSource
1992
     */
1993
    public function evalSnippets($content)
0 ignored issues
show
Coding Style introduced by
evalSnippets uses the super-global variable $_SERVER which is generally not recommended.

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

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

// Better
class Router
{
    private $host;

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

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

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

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1994
    {
1995
        if (strpos($content, '[[') === false) {
1996
            return $content;
1997
        }
1998
1999
        $matches = $this->getTagsFromContent($content, '[[', ']]');
2000
2001
        if (empty($matches)) {
2002
            return $content;
2003
        }
2004
2005
        $this->snipLapCount++;
2006
        if ($this->dumpSnippets) {
2007
            $this->snippetsCode .= sprintf('<fieldset><legend><b style="color: #821517;">PARSE PASS %s</b></legend><p>The following snippets (if any) were parsed during this pass.</p>', $this->snipLapCount);
2008
        }
2009
2010
        foreach ($matches[1] as $i => $call) {
2011
            $s = &$matches[0][$i];
2012
            if (substr($call, 0, 2) === '$_') {
2013
                if (strpos($content, '_PHX_INTERNAL_') === false) {
2014
                    $value = $this->_getSGVar($call);
2015
                } else {
2016
                    $value = $s;
2017
                }
2018 View Code Duplication
                if (strpos($content, $s) !== false) {
2019
                    $content = str_replace($s, $value, $content);
2020
                } elseif($this->debug) {
2021
                    $this->addLog('evalSnippetsSGVar parse error', $_SERVER['REQUEST_URI'] . $s, 2);
2022
                }
2023
                continue;
2024
            }
2025
            $value = $this->_get_snip_result($call);
2026
            if (is_null($value)) {
2027
                continue;
2028
            }
2029
2030 View Code Duplication
            if (strpos($content, $s) !== false) {
2031
                $content = str_replace($s, $value, $content);
2032
            } elseif($this->debug) {
2033
                $this->addLog('evalSnippets parse error', $_SERVER['REQUEST_URI'] . $s, 2);
2034
            }
2035
        }
2036
2037
        if ($this->dumpSnippets) {
2038
            $this->snippetsCode .= '</fieldset><br />';
2039
        }
2040
2041
        return $content;
2042
    }
2043
2044
    /**
2045
     * @param $value
2046
     * @return mixed|string
2047
     */
2048
    public function _getSGVar($value)
0 ignored issues
show
Coding Style introduced by
_getSGVar uses the super-global variable $_SESSION which is generally not recommended.

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

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

// Better
class Router
{
    private $host;

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

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

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

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
2049
    { // Get super globals
2050
        $key = $value;
2051
        $_ = $this->config['enable_filter'];
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $_. Configured minimum length is 3.

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

Loading history...
2052
        $this->config['enable_filter'] = 1;
2053
        list($key, $modifiers) = $this->splitKeyAndFilter($key);
2054
        $this->config['enable_filter'] = $_;
2055
        $key = str_replace(array('(', ')'), array("['", "']"), $key);
2056
        $key = rtrim($key, ';');
2057
        if (strpos($key, '$_SESSION') !== false) {
2058
            $_ = $_SESSION;
2059
            $key = str_replace('$_SESSION', '$_', $key);
2060
            if (isset($_['mgrFormValues'])) {
2061
                unset($_['mgrFormValues']);
2062
            }
2063
            if (isset($_['token'])) {
2064
                unset($_['token']);
2065
            }
2066
        }
2067
        if (strpos($key, '[') !== false) {
2068
            $value = $key ? eval("return {$key};") : '';
0 ignored issues
show
Coding Style introduced by
It is generally not recommended to use eval unless absolutely required.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
2069
        } elseif (0 < eval("return count({$key});")) {
0 ignored issues
show
Coding Style introduced by
It is generally not recommended to use eval unless absolutely required.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
2070
            $value = eval("return print_r({$key},true);");
0 ignored issues
show
Coding Style introduced by
It is generally not recommended to use eval unless absolutely required.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
2071
        } else {
2072
            $value = '';
2073
        }
2074
        if ($modifiers !== false) {
2075
            $value = $this->applyFilter($value, $modifiers, $key);
2076
        }
2077
        return $value;
2078
    }
2079
2080
    /**
2081
     * @param $piece
2082
     * @return null|string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null|array|object? Also, consider making the array more specific, something like array<String>, or String[].

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

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

Loading history...
2083
     */
2084
    private function _get_snip_result($piece)
0 ignored issues
show
Coding Style introduced by
This method is not in camel caps format.

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

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

Loading history...
2085
    {
2086
        if (ltrim($piece) !== $piece) {
2087
            return '';
2088
        }
2089
2090
        $eventtime = $this->dumpSnippets ? $this->getMicroTime() : 0;
2091
2092
        $snip_call = $this->_split_snip_call($piece);
2093
        $key = $snip_call['name'];
2094
2095
        list($key, $modifiers) = $this->splitKeyAndFilter($key);
2096
        $snip_call['name'] = $key;
2097
        $snippetObject = $this->_getSnippetObject($key);
2098
        if (is_null($snippetObject['content'])) {
2099
            return null;
2100
        }
2101
2102
        $this->currentSnippet = $snippetObject['name'];
2103
2104
        // current params
2105
        $params = $this->getParamsFromString($snip_call['params']);
2106
2107
        if (!isset($snippetObject['properties'])) {
2108
            $snippetObject['properties'] = '';
2109
        }
2110
        $default_params = $this->parseProperties($snippetObject['properties'], $this->currentSnippet, 'snippet');
2111
        $params = array_merge($default_params, $params);
2112
2113
        $value = $this->evalSnippet($snippetObject['content'], $params);
2114
        $this->currentSnippet = '';
2115
        if ($modifiers !== false) {
2116
            $value = $this->applyFilter($value, $modifiers, $key);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type array or object; however, DocumentParser::applyFilter() does only seem to accept string, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
2117
        }
2118
2119
        if ($this->dumpSnippets) {
2120
            $eventtime = $this->getMicroTime() - $eventtime;
2121
            $eventtime = sprintf('%2.2f ms', $eventtime * 1000);
2122
            $code = str_replace("\t", '  ', $this->htmlspecialchars($value));
2123
            $piece = str_replace("\t", '  ', $this->htmlspecialchars($piece));
2124
            $print_r_params = str_replace("\t", '  ', $this->htmlspecialchars('$modx->event->params = ' . print_r($params, true)));
2125
            $this->snippetsCode .= sprintf('<fieldset style="margin:1em;"><legend><b>%s</b>(%s)</legend><pre style="white-space: pre-wrap;background-color:#fff;width:90%%;">[[%s]]</pre><pre style="white-space: pre-wrap;background-color:#fff;width:90%%;">%s</pre><pre style="white-space: pre-wrap;background-color:#fff;width:90%%;">%s</pre></fieldset>', $snippetObject['name'], $eventtime, $piece, $print_r_params, $code);
2126
            $this->snippetsTime[] = array('sname' => $key, 'time' => $eventtime);
2127
        }
2128
        return $value;
2129
    }
2130
2131
    /**
2132
     * @param string $string
2133
     * @return array
0 ignored issues
show
Documentation introduced by
Should the return type not be array|null? Also, consider making the array more specific, something like array<String>, or String[].

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

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

Loading history...
2134
     */
2135
    public function getParamsFromString($string = '')
2136
    {
2137
        if (empty($string)) {
2138
            return array();
2139
        }
2140
2141
        if (strpos($string, '&_PHX_INTERNAL_') !== false) {
2142
            $string = str_replace(array('&_PHX_INTERNAL_091_&', '&_PHX_INTERNAL_093_&'), array('[', ']'), $string);
2143
        }
2144
2145
        $_ = $this->documentOutput;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $_. Configured minimum length is 3.

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

Loading history...
2146
        $this->documentOutput = $string;
2147
        $this->invokeEvent('OnBeforeParseParams');
2148
        $string = $this->documentOutput;
2149
        $this->documentOutput = $_;
2150
2151
        $_tmp = $string;
2152
        $_tmp = ltrim($_tmp, '?&');
2153
        $temp_params = array();
2154
        $key = '';
2155
        $value = null;
2156
        while ($_tmp !== '') {
2157
            $bt = $_tmp;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $bt. Configured minimum length is 3.

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

Loading history...
2158
            $char = substr($_tmp, 0, 1);
2159
            $_tmp = substr($_tmp, 1);
2160
2161
            if ($char === '=') {
2162
                $_tmp = trim($_tmp);
2163
                $delim = substr($_tmp, 0, 1);
2164
                if (in_array($delim, array('"', "'", '`'))) {
2165
                    $null = null;
2166
                    //list(, $value, $_tmp)
0 ignored issues
show
Unused Code Comprehensibility introduced by
78% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
2167
                    list($null, $value, $_tmp) = explode($delim, $_tmp, 3);
2168
                    unset($null);
2169
2170
                    if (substr(trim($_tmp), 0, 2) === '//') {
2171
                        $_tmp = strstr(trim($_tmp), "\n");
2172
                    }
2173
                    $i = 0;
2174
                    while ($delim === '`' && substr(trim($_tmp), 0, 1) !== '&' && 1 < substr_count($_tmp, '`')) {
2175
                        list($inner, $outer, $_tmp) = explode('`', $_tmp, 3);
2176
                        $value .= "`{$inner}`{$outer}";
2177
                        $i++;
2178
                        if (100 < $i) {
2179
                            exit('The nest of values are hard to read. Please use three different quotes.');
0 ignored issues
show
Coding Style Compatibility introduced by
The method getParamsFromString() contains an exit expression.

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

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

Loading history...
2180
                        }
2181
                    }
2182
                    if ($i && $delim === '`') {
2183
                        $value = rtrim($value, '`');
2184
                    }
2185
                } elseif (strpos($_tmp, '&') !== false) {
2186
                    list($value, $_tmp) = explode('&', $_tmp, 2);
2187
                    $value = trim($value);
2188
                } else {
2189
                    $value = $_tmp;
2190
                    $_tmp = '';
2191
                }
2192
            } elseif ($char === '&') {
2193
                if (trim($key) !== '') {
2194
                    $value = '1';
2195
                } else {
2196
                    continue;
2197
                }
2198
            } elseif ($_tmp === '') {
2199
                $key .= $char;
2200
                $value = '1';
2201
            } elseif ($key !== '' || trim($char) !== '') {
2202
                $key .= $char;
2203
            }
2204
2205
            if (isset($value) && !is_null($value)) {
2206
                if (strpos($key, 'amp;') !== false) {
2207
                    $key = str_replace('amp;', '', $key);
2208
                }
2209
                $key = trim($key);
2210 View Code Duplication
                if (strpos($value, '[!') !== false) {
2211
                    $value = str_replace(array('[!', '!]'), array('[[', ']]'), $value);
2212
                }
2213
                $value = $this->mergeDocumentContent($value);
2214
                $value = $this->mergeSettingsContent($value);
2215
                $value = $this->mergeChunkContent($value);
2216
                $value = $this->evalSnippets($value);
2217
                if (substr($value, 0, 6) !== '@CODE:') {
2218
                    $value = $this->mergePlaceholderContent($value);
2219
                }
2220
2221
                $temp_params[][$key] = $value;
2222
2223
                $key = '';
2224
                $value = null;
2225
2226
                $_tmp = ltrim($_tmp, " ,\t");
2227
                if (substr($_tmp, 0, 2) === '//') {
2228
                    $_tmp = strstr($_tmp, "\n");
2229
                }
2230
            }
2231
2232
            if ($_tmp === $bt) {
2233
                $key = trim($key);
2234
                if ($key !== '') {
2235
                    $temp_params[][$key] = '';
2236
                }
2237
                break;
2238
            }
2239
        }
2240
2241
        foreach ($temp_params as $p) {
2242
            $k = key($p);
2243
            if (substr($k, -2) === '[]') {
2244
                $k = substr($k, 0, -2);
2245
                $params[$k][] = current($p);
0 ignored issues
show
Coding Style Comprehensibility introduced by
$params was never initialized. Although not strictly required by PHP, it is generally a good practice to add $params = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
2246
            } elseif (strpos($k, '[') !== false && substr($k, -1) === ']') {
2247
                list($k, $subk) = explode('[', $k, 2);
2248
                $subk = substr($subk, 0, -1);
2249
                $params[$k][$subk] = current($p);
0 ignored issues
show
Bug introduced by
The variable $params does not seem to be defined for all execution paths leading up to this point.

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

Let’s take a look at an example:

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

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

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

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

Available Fixes

  1. Check for existence of the variable explicitly:

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

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

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

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

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

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

class Id
{
    public $id;

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

}

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

$account_id = false;

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

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
3001
            $udperms->role = $_SESSION['mgrRole'];
3002
            // Doesn't have access to this document
3003
            if (!$udperms->checkPermissions()) {
3004
                $this->sendErrorPage();
3005
            }
3006
        }
3007
    }
3008
3009
    /**
3010
     * @param $url
3011
     */
3012
    public function _sendRedirectForRefPage($url)
3013
    {
3014
        // check whether it's a reference
3015
        if (preg_match('@^[1-9][0-9]*$@', $url)) {
3016
            $url = $this->makeUrl($url); // if it's a bare document id
3017
        } elseif (strpos($url, '[~') !== false) {
3018
            $url = $this->rewriteUrls($url); // if it's an internal docid tag, process it
3019
        }
3020
        $this->sendRedirect($url, 0, '', 'HTTP/1.0 301 Moved Permanently');
3021
        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...
3022
    }
3023
3024
    /**
3025
     * @param $templateID
3026
     * @return mixed
3027
     */
3028
    public function _getTemplateCodeFromDB($templateID)
3029
    {
3030
        $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...
3031
        if ($this->db->getRecordCount($rs) == 1) {
3032
            return $this->db->getValue($rs);
3033
        } else {
3034
            $this->messageQuit('Incorrect number of templates returned from database');
3035
        }
3036
    }
3037
3038
    /**
3039
     * Returns an array of all parent record IDs for the id passed.
3040
     *
3041
     * @param int $id Docid to get parents for.
3042
     * @param int $height The maximum number of levels to go up, default 10.
3043
     * @return array
3044
     */
3045
    public function getParentIds($id, $height = 10)
3046
    {
3047
        $parents = array();
3048
        while ($id && $height--) {
3049
            $thisid = $id;
3050
            if ($this->config['aliaslistingfolder'] == 1) {
3051
                $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");
3052
                if (!$id || $id == '0') {
3053
                    break;
3054
                }
3055
            } else {
3056
                $id = $this->aliasListing[$id]['parent'];
3057
                if (!$id) {
3058
                    break;
3059
                }
3060
            }
3061
            $parents[$thisid] = $id;
3062
        }
3063
        return $parents;
3064
    }
3065
3066
    /**
3067
     * @param $id
3068
     * @param int $top
3069
     * @return mixed
3070
     */
3071
    public function getUltimateParentId($id, $top = 0)
3072
    {
3073
        $i = 0;
3074
        while ($id && $i < 20) {
3075
            if ($top == $this->aliasListing[$id]['parent']) {
3076
                break;
3077
            }
3078
            $id = $this->aliasListing[$id]['parent'];
3079
            $i++;
3080
        }
3081
        return $id;
3082
    }
3083
3084
    /**
3085
     * Returns an array of child IDs belonging to the specified parent.
3086
     *
3087
     * @param int $id The parent resource/document to start from
3088
     * @param int $depth How many levels deep to search for children, default: 10
3089
     * @param array $children Optional array of docids to merge with the result.
3090
     * @return array Contains the document Listing (tree) like the sitemap
3091
     */
3092
    public function getChildIds($id, $depth = 10, $children = array())
3093
    {
3094
3095
        $cacheKey = md5(print_r(func_get_args(), true));
3096
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3097
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3098
        }
3099
3100
        if ($this->config['aliaslistingfolder'] == 1) {
3101
3102
            $res = $this->db->select("id,alias,isfolder,parent", $this->getFullTableName('site_content'), "parent IN (" . $id . ") AND deleted = '0'");
3103
            $idx = array();
3104
            while ($row = $this->db->getRow($res)) {
3105
                $pAlias = '';
3106
                if (isset($this->aliasListing[$row['parent']])) {
3107
                    $pAlias .= !empty($this->aliasListing[$row['parent']]['path']) ? $this->aliasListing[$row['parent']]['path'] . '/' : '';
3108
                    $pAlias .= !empty($this->aliasListing[$row['parent']]['alias']) ? $this->aliasListing[$row['parent']]['alias'] . '/' : '';
3109
                };
3110
                $children[$pAlias . $row['alias']] = $row['id'];
3111
                if ($row['isfolder'] == 1) {
3112
                    $idx[] = $row['id'];
3113
                }
3114
            }
3115
            $depth--;
3116
            $idx = implode(',', $idx);
3117
            if (!empty($idx)) {
3118
                if ($depth) {
3119
                    $children = $this->getChildIds($idx, $depth, $children);
3120
                }
3121
            }
3122
            $this->tmpCache[__FUNCTION__][$cacheKey] = $children;
3123
            return $children;
3124
3125
        } else {
3126
3127
            // Initialise a static array to index parents->children
3128
            static $documentMap_cache = array();
3129
            if (!count($documentMap_cache)) {
3130
                foreach ($this->documentMap as $document) {
3131
                    foreach ($document as $p => $c) {
3132
                        $documentMap_cache[$p][] = $c;
3133
                    }
3134
                }
3135
            }
3136
3137
            // Get all the children for this parent node
3138
            if (isset($documentMap_cache[$id])) {
3139
                $depth--;
3140
3141
                foreach ($documentMap_cache[$id] as $childId) {
3142
                    $pkey = (strlen($this->aliasListing[$childId]['path']) ? "{$this->aliasListing[$childId]['path']}/" : '') . $this->aliasListing[$childId]['alias'];
3143
                    if (!strlen($pkey)) {
3144
                        $pkey = "{$childId}";
3145
                    }
3146
                    $children[$pkey] = $childId;
3147
3148
                    if ($depth && isset($documentMap_cache[$childId])) {
3149
                        $children += $this->getChildIds($childId, $depth);
3150
                    }
3151
                }
3152
            }
3153
            $this->tmpCache[__FUNCTION__][$cacheKey] = $children;
3154
            return $children;
3155
3156
        }
3157
    }
3158
3159
    /**
3160
     * Displays a javascript alert message in the web browser and quit
3161
     *
3162
     * @param string $msg Message to show
3163
     * @param string $url URL to redirect to
3164
     */
3165
    public function webAlertAndQuit($msg, $url = "")
3166
    {
3167
        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...
3168
        if (substr(strtolower($url), 0, 11) == "javascript:") {
3169
            $fnc = substr($url, 11);
3170
        } elseif ($url) {
3171
            $fnc = "window.location.href='" . addslashes($url) . "';";
3172
        } else {
3173
            $fnc = "history.back(-1);";
3174
        }
3175
        echo "<html><head>
3176
            <title>MODX :: Alert</title>
3177
            <meta http-equiv=\"Content-Type\" content=\"text/html; charset={$modx_manager_charset};\">
3178
            <script>
3179
                function __alertQuit() {
3180
                    alert('" . addslashes($msg) . "');
3181
                    {$fnc}
3182
                }
3183
                window.setTimeout('__alertQuit();',100);
3184
            </script>
3185
            </head><body>
3186
            <p>{$msg}</p>
3187
            </body></html>";
3188
        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...
3189
    }
3190
3191
    /**
3192
     * Returns 1 if user has the currect permission
3193
     *
3194
     * @param string $pm Permission name
3195
     * @return int Why not bool?
3196
     */
3197
    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...
3198
    {
3199
        $state = 0;
3200
        $pms = $_SESSION['mgrPermissions'];
3201
        if ($pms) {
3202
            $state = ((bool)$pms[$pm] === true);
3203
        }
3204
        return (int)$state;
3205
    }
3206
3207
    /**
3208
     * Returns true if element is locked
3209
     *
3210
     * @param int $type Types: 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3211
     * @param int $id Element- / Resource-id
3212
     * @param bool $includeThisUser true = Return also info about actual user
3213
     * @return array lock-details or null
3214
     */
3215
    public function elementIsLocked($type, $id, $includeThisUser = false)
3216
    {
3217
        $id = (int)$id;
3218
        $type = (int)$type;
3219
        if (!$type || !$id) {
3220
            return null;
3221
        }
3222
3223
        // Build lockedElements-Cache at first call
3224
        $this->buildLockedElementsCache();
3225
3226
        if (!$includeThisUser && $this->lockedElements[$type][$id]['sid'] == $this->sid) {
3227
            return null;
3228
        }
3229
3230
        if (isset($this->lockedElements[$type][$id])) {
3231
            return $this->lockedElements[$type][$id];
3232
        } else {
3233
            return null;
3234
        }
3235
    }
3236
3237
    /**
3238
     * Returns Locked Elements as Array
3239
     *
3240
     * @param int $type Types: 0=all, 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3241
     * @param bool $minimumDetails true =
3242
     * @return array|mixed|null
3243
     */
3244
    public function getLockedElements($type = 0, $minimumDetails = false)
3245
    {
3246
        $this->buildLockedElementsCache();
3247
3248
        if (!$minimumDetails) {
3249
            $lockedElements = $this->lockedElements;
3250
        } else {
3251
            // Minimum details for HTML / Ajax-requests
3252
            $lockedElements = array();
3253
            foreach ($this->lockedElements as $elType => $elements) {
3254
                foreach ($elements as $elId => $el) {
3255
                    $lockedElements[$elType][$elId] = array(
3256
                        'username' => $el['username'],
3257
                        'lasthit_df' => $el['lasthit_df'],
3258
                        'state' => $this->determineLockState($el['internalKey'])
3259
                    );
3260
                }
3261
            }
3262
        }
3263
3264
        if ($type == 0) {
3265
            return $lockedElements;
3266
        }
3267
3268
        $type = (int)$type;
3269
        if (isset($lockedElements[$type])) {
3270
            return $lockedElements[$type];
3271
        } else {
3272
            return array();
3273
        }
3274
    }
3275
3276
    /**
3277
     * Builds the Locked Elements Cache once
3278
     */
3279
    public function buildLockedElementsCache()
3280
    {
3281
        if (is_null($this->lockedElements)) {
3282
            $this->lockedElements = array();
3283
            $this->cleanupExpiredLocks();
3284
3285
            $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...
3286
                LEFT JOIN {$this->getFullTableName('manager_users')} mu on ul.internalKey = mu.id");
3287
            while ($row = $this->db->getRow($rs)) {
3288
                $this->lockedElements[$row['elementType']][$row['elementId']] = array(
3289
                    'sid' => $row['sid'],
3290
                    'internalKey' => $row['internalKey'],
3291
                    'username' => $row['username'],
3292
                    'elementType' => $row['elementType'],
3293
                    'elementId' => $row['elementId'],
3294
                    'lasthit' => $row['lasthit'],
3295
                    'lasthit_df' => $this->toDateFormat($row['lasthit']),
3296
                    'state' => $this->determineLockState($row['sid'])
3297
                );
3298
            }
3299
        }
3300
    }
3301
3302
    /**
3303
     * Cleans up the active user locks table
3304
     */
3305
    public function cleanupExpiredLocks()
3306
    {
3307
        // Clean-up active_user_sessions first
3308
        $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
3309
        $validSessionTimeLimit = $this->time - $timeout;
3310
        $this->db->delete($this->getFullTableName('active_user_sessions'), "lasthit < {$validSessionTimeLimit}");
3311
3312
        // Clean-up active_user_locks
3313
        $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...
3314
        $count = $this->db->getRecordCount($rs);
3315
        if ($count) {
3316
            $rs = $this->db->makeArray($rs);
3317
            $userSids = array();
3318
            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...
3319
                $userSids[] = $row['sid'];
3320
            }
3321
            $userSids = "'" . implode("','", $userSids) . "'";
3322
            $this->db->delete($this->getFullTableName('active_user_locks'), "sid NOT IN({$userSids})");
3323
        } else {
3324
            $this->db->delete($this->getFullTableName('active_user_locks'));
3325
        }
3326
3327
    }
3328
3329
    /**
3330
     * Cleans up the active users table
3331
     */
3332
    public function cleanupMultipleActiveUsers()
3333
    {
3334
        $timeout = 20 * 60; // Delete multiple user-sessions after 20min
3335
        $validSessionTimeLimit = $this->time - $timeout;
3336
3337
        $activeUserSids = array();
3338
        $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...
3339
        $count = $this->db->getRecordCount($rs);
3340
        if ($count) {
3341
            $rs = $this->db->makeArray($rs);
3342
            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...
3343
                $activeUserSids[] = $row['sid'];
3344
            }
3345
        }
3346
3347
        $rs = $this->db->select("sid,internalKey,lasthit", "{$this->getFullTableName('active_users')}", "", "lasthit DESC");
3348
        if ($this->db->getRecordCount($rs)) {
3349
            $rs = $this->db->makeArray($rs);
3350
            $internalKeyCount = array();
3351
            $deleteSids = '';
3352
            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...
3353
                if (!isset($internalKeyCount[$row['internalKey']])) {
3354
                    $internalKeyCount[$row['internalKey']] = 0;
3355
                }
3356
                $internalKeyCount[$row['internalKey']]++;
3357
3358
                if ($internalKeyCount[$row['internalKey']] > 1 && !in_array($row['sid'], $activeUserSids) && $row['lasthit'] < $validSessionTimeLimit) {
3359
                    $deleteSids .= $deleteSids == '' ? '' : ' OR ';
3360
                    $deleteSids .= "sid='{$row['sid']}'";
3361
                };
3362
3363
            }
3364
            if ($deleteSids) {
3365
                $this->db->delete($this->getFullTableName('active_users'), $deleteSids);
3366
            }
3367
        }
3368
3369
    }
3370
3371
    /**
3372
     * Determines state of a locked element acc. to user-permissions
3373
     *
3374
     * @param $sid
3375
     * @return int $state States: 0=No display, 1=viewing this element, 2=locked, 3=show unlock-button
3376
     * @internal param int $internalKey : ID of User who locked actual element
3377
     */
3378
    public function determineLockState($sid)
3379
    {
3380
        $state = 0;
3381
        if ($this->hasPermission('display_locks')) {
3382
            if ($sid == $this->sid) {
3383
                $state = 1;
3384
            } else {
3385
                if ($this->hasPermission('remove_locks')) {
3386
                    $state = 3;
3387
                } else {
3388
                    $state = 2;
3389
                }
3390
            }
3391
        }
3392
        return $state;
3393
    }
3394
3395
    /**
3396
     * Locks an element
3397
     *
3398
     * @param int $type Types: 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3399
     * @param int $id Element- / Resource-id
3400
     * @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...
3401
     */
3402
    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...
3403
    {
3404
        $userId = $this->isBackend() && $_SESSION['mgrInternalKey'] ? $_SESSION['mgrInternalKey'] : 0;
3405
        $type = (int)$type;
3406
        $id = (int)$id;
3407
        if (!$type || !$id || !$userId) {
3408
            return false;
3409
        }
3410
3411
        $sql = sprintf('REPLACE INTO %s (internalKey, elementType, elementId, lasthit, sid)
3412
                VALUES (%d, %d, %d, %d, \'%s\')', $this->getFullTableName('active_user_locks'), $userId, $type, $id, $this->time, $this->sid);
3413
        $this->db->query($sql);
3414
    }
3415
3416
    /**
3417
     * Unlocks an element
3418
     *
3419
     * @param int $type Types: 1=template, 2=tv, 3=chunk, 4=snippet, 5=plugin, 6=module, 7=resource, 8=role
3420
     * @param int $id Element- / Resource-id
3421
     * @param bool $includeAllUsers true = Deletes not only own user-locks
3422
     * @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...
3423
     */
3424
    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...
3425
    {
3426
        $userId = $this->isBackend() && $_SESSION['mgrInternalKey'] ? $_SESSION['mgrInternalKey'] : 0;
3427
        $type = (int)$type;
3428
        $id = (int)$id;
3429
        if (!$type || !$id) {
3430
            return false;
3431
        }
3432
3433
        if (!$includeAllUsers) {
3434
            $sql = sprintf('DELETE FROM %s WHERE internalKey = %d AND elementType = %d AND elementId = %d;', $this->getFullTableName('active_user_locks'), $userId, $type, $id);
3435
        } else {
3436
            $sql = sprintf('DELETE FROM %s WHERE elementType = %d AND elementId = %d;', $this->getFullTableName('active_user_locks'), $type, $id);
3437
        }
3438
        $this->db->query($sql);
3439
    }
3440
3441
    /**
3442
     * Updates table "active_user_sessions" with userid, lasthit, IP
3443
     */
3444
    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...
3445
    {
3446
        if (!$this->sid) {
3447
            return;
3448
        }
3449
3450
        // web users are stored with negative keys
3451
        $userId = $this->getLoginUserType() == 'manager' ? $this->getLoginUserID() : -$this->getLoginUserID();
3452
3453
        // Get user IP
3454 View Code Duplication
        if ($cip = getenv("HTTP_CLIENT_IP")) {
3455
            $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...
3456
        } elseif ($cip = getenv("HTTP_X_FORWARDED_FOR")) {
3457
            $ip = $cip;
3458
        } elseif ($cip = getenv("REMOTE_ADDR")) {
3459
            $ip = $cip;
3460
        } else {
3461
            $ip = "UNKNOWN";
3462
        }
3463
        $_SESSION['ip'] = $ip;
3464
3465
        $sql = sprintf('REPLACE INTO %s (internalKey, lasthit, ip, sid)
3466
            VALUES (%d, %d, \'%s\', \'%s\')', $this->getFullTableName('active_user_sessions'), $userId, $this->time, $ip, $this->sid);
3467
        $this->db->query($sql);
3468
    }
3469
3470
    /**
3471
     * Add an a alert message to the system event log
3472
     *
3473
     * @param int $evtid Event ID
3474
     * @param int $type Types: 1 = information, 2 = warning, 3 = error
3475
     * @param string $msg Message to be logged
3476
     * @param string $source source of the event (module, snippet name, etc.)
3477
     *                       Default: Parser
3478
     */
3479
    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...
3480
    {
3481
        $msg = $this->db->escape($msg);
3482
        if (strpos($GLOBALS['database_connection_charset'], 'utf8') === 0 && extension_loaded('mbstring')) {
3483
            $esc_source = mb_substr($source, 0, 50, "UTF-8");
3484
        } else {
3485
            $esc_source = substr($source, 0, 50);
3486
        }
3487
        $esc_source = $this->db->escape($esc_source);
3488
3489
        $LoginUserID = $this->getLoginUserID();
3490
        if ($LoginUserID == '') {
3491
            $LoginUserID = 0;
3492
        }
3493
3494
        $usertype = $this->isFrontend() ? 1 : 0;
3495
        $evtid = (int)$evtid;
3496
        $type = (int)$type;
3497
3498
        // Types: 1 = information, 2 = warning, 3 = error
3499
        if ($type < 1) {
3500
            $type = 1;
3501
        } elseif ($type > 3) {
3502
            $type = 3;
3503
        }
3504
3505
        $this->db->insert(array(
3506
            'eventid' => $evtid,
3507
            'type' => $type,
3508
            'createdon' => $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'],
3509
            'source' => $esc_source,
3510
            'description' => $msg,
3511
            'user' => $LoginUserID,
3512
            'usertype' => $usertype
3513
        ), $this->getFullTableName("event_log"));
3514
3515
        if (isset($this->config['send_errormail']) && $this->config['send_errormail'] !== '0') {
3516
            if ($this->config['send_errormail'] <= $type) {
3517
                $this->sendmail(array(
3518
                    'subject' => 'MODX System Error on ' . $this->config['site_name'],
3519
                    'body' => 'Source: ' . $source . ' - The details of the error could be seen in the MODX system events log.',
3520
                    'type' => 'text'
3521
                ));
3522
            }
3523
        }
3524
    }
3525
3526
    /**
3527
     * @param array $params
3528
     * @param string $msg
3529
     * @param array $files
3530
     * @return mixed
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use boolean.

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

Loading history...
3531
     */
3532
    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...
3533
    {
3534
        if (isset($params) && is_string($params)) {
3535
            if (strpos($params, '=') === false) {
3536
                if (strpos($params, '@') !== false) {
3537
                    $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...
3538
                } else {
3539
                    $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...
3540
                }
3541
            } else {
3542
                $params_array = explode(',', $params);
3543
                foreach ($params_array as $k => $v) {
3544
                    $k = trim($k);
3545
                    $v = trim($v);
3546
                    $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...
3547
                }
3548
            }
3549
        } else {
3550
            $p = $params;
3551
            unset($params);
3552
        }
3553
        if (isset($p['sendto'])) {
3554
            $p['to'] = $p['sendto'];
3555
        }
3556
3557
        if (isset($p['to']) && preg_match('@^[0-9]+$@', $p['to'])) {
3558
            $userinfo = $this->getUserInfo($p['to']);
3559
            $p['to'] = $userinfo['email'];
3560
        }
3561
        if (isset($p['from']) && preg_match('@^[0-9]+$@', $p['from'])) {
3562
            $userinfo = $this->getUserInfo($p['from']);
3563
            $p['from'] = $userinfo['email'];
3564
            $p['fromname'] = $userinfo['username'];
3565
        }
3566
        if ($msg === '' && !isset($p['body'])) {
3567
            $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...
3568
        } elseif (is_string($msg) && 0 < strlen($msg)) {
3569
            $p['body'] = $msg;
3570
        }
3571
3572
        $this->loadExtension('MODxMailer');
3573
        $sendto = (!isset($p['to'])) ? $this->config['emailsender'] : $p['to'];
3574
        $sendto = explode(',', $sendto);
3575
        foreach ($sendto as $address) {
3576
            list($name, $address) = $this->mail->address_split($address);
3577
            $this->mail->AddAddress($address, $name);
3578
        }
3579 View Code Duplication
        if (isset($p['cc'])) {
3580
            $p['cc'] = explode(',', $p['cc']);
3581
            foreach ($p['cc'] as $address) {
3582
                list($name, $address) = $this->mail->address_split($address);
3583
                $this->mail->AddCC($address, $name);
3584
            }
3585
        }
3586 View Code Duplication
        if (isset($p['bcc'])) {
3587
            $p['bcc'] = explode(',', $p['bcc']);
3588
            foreach ($p['bcc'] as $address) {
3589
                list($name, $address) = $this->mail->address_split($address);
3590
                $this->mail->AddBCC($address, $name);
3591
            }
3592
        }
3593
        if (isset($p['from']) && strpos($p['from'], '<') !== false && substr($p['from'], -1) === '>') {
3594
            list($p['fromname'], $p['from']) = $this->mail->address_split($p['from']);
3595
        }
3596
        $this->mail->From = (!isset($p['from'])) ? $this->config['emailsender'] : $p['from'];
3597
        $this->mail->FromName = (!isset($p['fromname'])) ? $this->config['site_name'] : $p['fromname'];
3598
        $this->mail->Subject = (!isset($p['subject'])) ? $this->config['emailsubject'] : $p['subject'];
3599
        $this->mail->Body = $p['body'];
3600
        if (isset($p['type']) && $p['type'] == 'text') {
3601
            $this->mail->IsHTML(false);
3602
        }
3603
        if (!is_array($files)) {
3604
            $files = array();
3605
        }
3606
        foreach ($files as $f) {
3607
            if (file_exists(MODX_BASE_PATH . $f) && is_file(MODX_BASE_PATH . $f) && is_readable(MODX_BASE_PATH . $f)) {
3608
                $this->mail->AddAttachment(MODX_BASE_PATH . $f);
3609
            }
3610
        }
3611
        $rs = $this->mail->send();
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $rs. Configured minimum length is 3.

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

Loading history...
3612
        return $rs;
3613
    }
3614
3615
    /**
3616
     * @param string $target
3617
     * @param int $limit
3618
     * @param int $trim
3619
     */
3620
    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...
3621
    {
3622
        if ($limit < $trim) {
3623
            $trim = $limit;
3624
        }
3625
3626
        $table_name = $this->getFullTableName($target);
3627
        $count = $this->db->getValue($this->db->select('COUNT(id)', $table_name));
3628
        $over = $count - $limit;
3629
        if (0 < $over) {
3630
            $trim = ($over + $trim);
3631
            $this->db->delete($table_name, '', '', $trim);
3632
        }
3633
        $this->db->optimize($table_name);
3634
    }
3635
3636
    /**
3637
     * Returns true if we are currently in the manager backend
3638
     *
3639
     * @return boolean
3640
     */
3641
    public function isBackend()
3642
    {
3643
        return (defined('IN_MANAGER_MODE') && IN_MANAGER_MODE === true);
3644
    }
3645
3646
    /**
3647
     * Returns true if we are currently in the frontend
3648
     *
3649
     * @return boolean
3650
     */
3651
    public function isFrontend()
3652
    {
3653
        return ! $this->isBackend();
3654
    }
3655
3656
    /**
3657
     * Gets all child documents of the specified document, including those which are unpublished or deleted.
3658
     *
3659
     * @param int $id The Document identifier to start with
3660
     * @param string $sort Sort field
3661
     *                     Default: menuindex
3662
     * @param string $dir Sort direction, ASC and DESC is possible
3663
     *                    Default: ASC
3664
     * @param string $fields Default: id, pagetitle, description, parent, alias, menutitle
3665
     * @return array
3666
     */
3667 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...
3668
    {
3669
3670
        $cacheKey = md5(print_r(func_get_args(), true));
3671
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3672
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3673
        }
3674
3675
        $tblsc = $this->getFullTableName("site_content");
3676
        $tbldg = $this->getFullTableName("document_groups");
3677
        // modify field names to use sc. table reference
3678
        $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3679
        $sort = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
3680
        // get document groups for current user
3681
        if ($docgrp = $this->getUserDocGroups()) {
3682
            $docgrp = implode(",", $docgrp);
3683
        }
3684
        // build query
3685
        $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
3686
        $result = $this->db->select("DISTINCT {$fields}", "{$tblsc} sc
3687
                LEFT JOIN {$tbldg} dg on dg.document = sc.id", "sc.parent = '{$id}' AND ({$access}) GROUP BY sc.id", "{$sort} {$dir}");
3688
        $resourceArray = $this->db->makeArray($result);
3689
        $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
3690
        return $resourceArray;
3691
    }
3692
3693
    /**
3694
     * Gets all active child documents of the specified document, i.e. those which published and not deleted.
3695
     *
3696
     * @param int $id The Document identifier to start with
3697
     * @param string $sort Sort field
3698
     *                     Default: menuindex
3699
     * @param string $dir Sort direction, ASC and DESC is possible
3700
     *                    Default: ASC
3701
     * @param string $fields Default: id, pagetitle, description, parent, alias, menutitle
3702
     * @return array
3703
     */
3704 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...
3705
    {
3706
        $cacheKey = md5(print_r(func_get_args(), true));
3707
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3708
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3709
        }
3710
3711
        $tblsc = $this->getFullTableName("site_content");
3712
        $tbldg = $this->getFullTableName("document_groups");
3713
3714
        // modify field names to use sc. table reference
3715
        $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3716
        $sort = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
3717
        // get document groups for current user
3718
        if ($docgrp = $this->getUserDocGroups()) {
3719
            $docgrp = implode(",", $docgrp);
3720
        }
3721
        // build query
3722
        $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
3723
        $result = $this->db->select("DISTINCT {$fields}", "{$tblsc} sc
3724
                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}");
3725
        $resourceArray = $this->db->makeArray($result);
3726
3727
        $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
3728
3729
        return $resourceArray;
3730
    }
3731
3732
    /**
3733
     * getDocumentChildren
3734
     * @version 1.1.1 (2014-02-19)
3735
     *
3736
     * @desc Returns the children of the selected document/folder as an associative array.
3737
     *
3738
     * @param $parentid {integer} - The parent document identifier. Default: 0 (site root).
3739
     * @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.
3740
     * @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.
3741
     * @param $fields {comma separated string; '*'} - Comma separated list of document fields to get. Default: '*' (all fields).
3742
     * @param $where {string} - Where condition in SQL style. Should include a leading 'AND '. Default: ''.
3743
     * @param $sort {comma separated string} - Should be a comma-separated list of field names on which to sort. Default: 'menuindex'.
3744
     * @param $dir {'ASC'; 'DESC'} - Sort direction, ASC and DESC is possible. Default: 'ASC'.
3745
     * @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).
3746
     *
3747
     * @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...
3748
     */
3749
    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...
3750
    {
3751
3752
        $cacheKey = md5(print_r(func_get_args(), true));
3753
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3754
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3755
        }
3756
3757
        $published = ($published !== 'all') ? 'AND sc.published = ' . $published : '';
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of $published (integer) and 'all' (string) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
3758
        $deleted = ($deleted !== 'all') ? 'AND sc.deleted = ' . $deleted : '';
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of $deleted (integer) and 'all' (string) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
3759
3760
        if ($where != '') {
3761
            $where = 'AND ' . $where;
3762
        }
3763
3764
        // modify field names to use sc. table reference
3765
        $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3766
        $sort = ($sort == '') ? '' : 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
3767
3768
        // get document groups for current user
3769
        if ($docgrp = $this->getUserDocGroups()) {
3770
            $docgrp = implode(',', $docgrp);
3771
        }
3772
3773
        // build query
3774
        $access = ($this->isFrontend() ? 'sc.privateweb=0' : '1="' . $_SESSION['mgrRole'] . '" OR sc.privatemgr=0') . (!$docgrp ? '' : ' OR dg.document_group IN (' . $docgrp . ')');
3775
3776
        $tblsc = $this->getFullTableName('site_content');
3777
        $tbldg = $this->getFullTableName('document_groups');
3778
3779
        $result = $this->db->select("DISTINCT {$fields}", "{$tblsc} sc
3780
                LEFT JOIN {$tbldg} dg on dg.document = sc.id", "sc.parent = '{$parentid}' {$published} {$deleted} {$where} AND ({$access}) GROUP BY sc.id", ($sort ? "{$sort} {$dir}" : ""), $limit);
3781
3782
        $resourceArray = $this->db->makeArray($result);
3783
3784
        $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
3785
3786
        return $resourceArray;
3787
    }
3788
3789
    /**
3790
     * getDocuments
3791
     * @version 1.1.1 (2013-02-19)
3792
     *
3793
     * @desc Returns required documents (their fields).
3794
     *
3795
     * @param $ids {array; comma separated string} - Documents Ids to get. @required
3796
     * @param $published {0; 1; 'all'} - Documents publication status. Once the parameter equals 'all', the result will be returned regardless of whether the documents are published or they are not. Default: 1.
3797
     * @param $deleted {0; 1; 'all'} - Documents removal status. Once the parameter equals 'all', the result will be returned regardless of whether the documents are deleted or they are not. Default: 0.
3798
     * @param $fields {comma separated string; '*'} - Documents fields to get. Default: '*'.
3799
     * @param $where {string} - SQL WHERE clause. Default: ''.
3800
     * @param $sort {comma separated string} - A comma-separated list of field names to sort by. Default: 'menuindex'.
3801
     * @param $dir {'ASC'; 'DESC'} - Sorting direction. Default: 'ASC'.
3802
     * @param $limit {string} - SQL LIMIT (without 'LIMIT '). An empty string means no limit. Default: ''.
3803
     *
3804
     * @return {array; false} - Result array with documents, or false.
0 ignored issues
show
Documentation introduced by
The doc-type {array; could not be parsed: Unknown type name "{array" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
3805
     */
3806
    public function getDocuments($ids = array(), $published = 1, $deleted = 0, $fields = '*', $where = '', $sort = 'menuindex', $dir = 'ASC', $limit = '')
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
Coding Style introduced by
getDocuments uses the super-global variable $_SESSION which is generally not recommended.

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

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

// Better
class Router
{
    private $host;

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

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

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

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
3807
    {
3808
3809
        $cacheKey = md5(print_r(func_get_args(), true));
3810
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3811
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3812
        }
3813
3814
        if (is_string($ids)) {
3815
            if (strpos($ids, ',') !== false) {
3816
                $ids = array_filter(array_map('intval', explode(',', $ids)));
3817
            } else {
3818
                $ids = array($ids);
3819
            }
3820
        }
3821
        if (count($ids) == 0) {
3822
            $this->tmpCache[__FUNCTION__][$cacheKey] = false;
3823
            return false;
3824
        } else {
3825
            // modify field names to use sc. table reference
3826
            $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3827
            $sort = ($sort == '') ? '' : 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $sort))));
3828
            if ($where != '') {
3829
                $where = 'AND ' . $where;
3830
            }
3831
3832
            $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...
3833
            $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...
3834
3835
            // get document groups for current user
3836
            if ($docgrp = $this->getUserDocGroups()) {
3837
                $docgrp = implode(',', $docgrp);
3838
            }
3839
3840
            $access = ($this->isFrontend() ? 'sc.privateweb=0' : '1="' . $_SESSION['mgrRole'] . '" OR sc.privatemgr=0') . (!$docgrp ? '' : ' OR dg.document_group IN (' . $docgrp . ')');
3841
3842
            $tblsc = $this->getFullTableName('site_content');
3843
            $tbldg = $this->getFullTableName('document_groups');
3844
3845
            $result = $this->db->select("DISTINCT {$fields}", "{$tblsc} sc
3846
                    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);
3847
3848
            $resourceArray = $this->db->makeArray($result);
3849
3850
            $this->tmpCache[__FUNCTION__][$cacheKey] = $resourceArray;
3851
3852
            return $resourceArray;
3853
        }
3854
    }
3855
3856
    /**
3857
     * getDocument
3858
     * @version 1.0.1 (2014-02-19)
3859
     *
3860
     * @desc Returns required fields of a document.
3861
     *
3862
     * @param int $id {integer}
3863
     * - Id of a document which data has to be gained. @required
3864
     * @param string $fields {comma separated string; '*'}
3865
     * - Comma separated list of document fields to get. Default: '*'.
3866
     * @param int $published {0; 1; 'all'}
3867
     * - 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.
3868
     * @param int $deleted {0; 1; 'all'}
3869
     * - 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.
3870
     * @return bool {array; false} - Result array with fields or false.
3871
     * - Result array with fields or false.
3872
     */
3873 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...
3874
    {
3875
        if ($id == 0) {
3876
            return false;
3877
        } else {
3878
            $docs = $this->getDocuments(array($id), $published, $deleted, $fields, '', '', '', 1);
3879
3880
            if ($docs != false) {
3881
                return $docs[0];
3882
            } else {
3883
                return false;
3884
            }
3885
        }
3886
    }
3887
3888
    /**
3889
     * @param string $field
3890
     * @param string $docid
3891
     * @return bool|mixed
3892
     */
3893
    public function getField($field = 'content', $docid = '')
3894
    {
3895
        if (empty($docid) && isset($this->documentIdentifier)) {
3896
            $docid = $this->documentIdentifier;
3897
        } elseif (!preg_match('@^[0-9]+$@', $docid)) {
3898
            $docid = $this->getIdFromAlias($docid);
3899
        }
3900
3901
        if (empty($docid)) {
3902
            return false;
3903
        }
3904
3905
        $cacheKey = md5(print_r(func_get_args(), true));
3906
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3907
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3908
        }
3909
3910
        $doc = $this->getDocumentObject('id', $docid);
3911
        if (is_array($doc[$field])) {
3912
            $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...
3913
            $content = $tvs[$field];
3914
        } else {
3915
            $content = $doc[$field];
3916
        }
3917
3918
        $this->tmpCache[__FUNCTION__][$cacheKey] = $content;
3919
3920
        return $content;
3921
    }
3922
3923
    /**
3924
     * Returns the page information as database row, the type of result is
3925
     * defined with the parameter $rowMode
3926
     *
3927
     * @param int $pageid The parent document identifier
3928
     *                    Default: -1 (no result)
3929
     * @param int $active Should we fetch only published and undeleted documents/resources?
3930
     *                     1 = yes, 0 = no
3931
     *                     Default: 1
3932
     * @param string $fields List of fields
3933
     *                       Default: id, pagetitle, description, alias
3934
     * @return boolean|array
3935
     */
3936
    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...
3937
    {
3938
3939
        $cacheKey = md5(print_r(func_get_args(), true));
3940
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
3941
            return $this->tmpCache[__FUNCTION__][$cacheKey];
3942
        }
3943
3944
        if ($pageid == 0) {
3945
            return false;
3946
        } else {
3947
            $tblsc = $this->getFullTableName("site_content");
3948
            $tbldg = $this->getFullTableName("document_groups");
3949
            $activeSql = $active == 1 ? "AND sc.published=1 AND sc.deleted=0" : "";
3950
            // modify field names to use sc. table reference
3951
            $fields = 'sc.' . implode(',sc.', array_filter(array_map('trim', explode(',', $fields))));
3952
            // get document groups for current user
3953
            if ($docgrp = $this->getUserDocGroups()) {
3954
                $docgrp = implode(",", $docgrp);
3955
            }
3956
            $access = ($this->isFrontend() ? "sc.privateweb=0" : "1='" . $_SESSION['mgrRole'] . "' OR sc.privatemgr=0") . (!$docgrp ? "" : " OR dg.document_group IN ($docgrp)");
3957
            $result = $this->db->select($fields, "{$tblsc} sc LEFT JOIN {$tbldg} dg on dg.document = sc.id", "(sc.id='{$pageid}' {$activeSql}) AND ({$access})", "", 1);
3958
            $pageInfo = $this->db->getRow($result);
3959
3960
            $this->tmpCache[__FUNCTION__][$cacheKey] = $pageInfo;
3961
3962
            return $pageInfo;
3963
        }
3964
    }
3965
3966
    /**
3967
     * Returns the parent document/resource of the given docid
3968
     *
3969
     * @param int $pid The parent docid. If -1, then fetch the current document/resource's parent
3970
     *                 Default: -1
3971
     * @param int $active Should we fetch only published and undeleted documents/resources?
3972
     *                     1 = yes, 0 = no
3973
     *                     Default: 1
3974
     * @param string $fields List of fields
3975
     *                       Default: id, pagetitle, description, alias
3976
     * @return boolean|array
3977
     */
3978
    public function getParent($pid = -1, $active = 1, $fields = 'id, pagetitle, description, alias, parent')
3979
    {
3980
        if ($pid == -1) {
3981
            $pid = $this->documentObject['parent'];
3982
            return ($pid == 0) ? false : $this->getPageInfo($pid, $active, $fields);
3983
        } else if ($pid == 0) {
3984
            return false;
3985
        } else {
3986
            // first get the child document
3987
            $child = $this->getPageInfo($pid, $active, "parent");
3988
            // now return the child's parent
3989
            $pid = ($child['parent']) ? $child['parent'] : 0;
3990
            return ($pid == 0) ? false : $this->getPageInfo($pid, $active, $fields);
3991
        }
3992
    }
3993
3994
    /**
3995
     * Returns the id of the current snippet.
3996
     *
3997
     * @return int
3998
     */
3999
    public function getSnippetId()
4000
    {
4001
        if ($this->currentSnippet) {
4002
            $tbl = $this->getFullTableName("site_snippets");
4003
            $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...
4004
            if ($snippetId = $this->db->getValue($rs)) {
4005
                return $snippetId;
4006
            }
4007
        }
4008
        return 0;
4009
    }
4010
4011
    /**
4012
     * Returns the name of the current snippet.
4013
     *
4014
     * @return string
4015
     */
4016
    public function getSnippetName()
4017
    {
4018
        return $this->currentSnippet;
4019
    }
4020
4021
    /**
4022
     * Clear the cache of MODX.
4023
     *
4024
     * @param string $type
4025
     * @param bool $report
4026
     * @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...
4027
     */
4028
    public function clearCache($type = '', $report = false)
4029
    {
4030
        $cache_dir = MODX_BASE_PATH . $this->getCacheFolder();
4031
        if (is_array($type)) {
4032
            foreach ($type as $_) {
4033
                $this->clearCache($_, $report);
4034
            }
4035
        } elseif ($type == 'full') {
4036
            include_once(MODX_MANAGER_PATH . 'processors/cache_sync.class.processor.php');
4037
            $sync = new synccache();
4038
            $sync->setCachepath($cache_dir);
4039
            $sync->setReport($report);
4040
            $sync->emptyCache();
4041
        } elseif (preg_match('@^[1-9][0-9]*$@', $type)) {
4042
            $key = ($this->config['cache_type'] == 2) ? $this->makePageCacheKey($type) : $type;
4043
            $file_name = "docid_" . $key . "_*.pageCache.php";
4044
            $cache_path = $cache_dir . $file_name;
4045
            $files = glob($cache_path);
4046
            $files[] = $cache_dir . "docid_" . $key . ".pageCache.php";
4047
            foreach ($files as $file) {
4048
                if (!is_file($file)) {
4049
                    continue;
4050
                }
4051
                unlink($file);
4052
            }
4053
        } else {
4054
            $files = glob($cache_dir . '*');
4055
            foreach ($files as $file) {
4056
                $name = basename($file);
4057
                if (strpos($name, '.pageCache.php') === false) {
4058
                    continue;
4059
                }
4060
                if (!is_file($file)) {
4061
                    continue;
4062
                }
4063
                unlink($file);
4064
            }
4065
        }
4066
    }
4067
4068
    /**
4069
     * makeUrl
4070
     *
4071
     * @desc Create an URL for the given document identifier. The url prefix and postfix are used, when “friendly_url” is active.
4072
     *
4073
     * @param $id {integer} - The document identifier. @required
4074
     * @param string $alias {string}
4075
     * - The alias name for the document. Default: ''.
4076
     * @param string $args {string}
4077
     * - The paramaters to add to the URL. Default: ''.
4078
     * @param string $scheme {string}
4079
     * - With full as valus, the site url configuration is used. Default: ''.
4080
     * @return mixed|string {string} - Result URL.
4081
     * - Result URL.
4082
     */
4083
    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...
4084
    {
4085
        $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...
4086
        $virtualDir = isset($this->config['virtual_dir']) ? $this->config['virtual_dir'] : '';
4087
        $f_url_prefix = $this->config['friendly_url_prefix'];
4088
        $f_url_suffix = $this->config['friendly_url_suffix'];
4089
4090
        if (!is_numeric($id)) {
4091
            $this->messageQuit("`{$id}` is not numeric and may not be passed to makeUrl()");
4092
        }
4093
4094
        if ($args !== '') {
4095
            // add ? or & to $args if missing
4096
            $args = ltrim($args, '?&');
4097
            $_ = 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...
4098
4099
            if ($_ === false && $this->config['friendly_urls'] == 1) {
4100
                $args = "?{$args}";
4101
            } else {
4102
                $args = "&{$args}";
4103
            }
4104
        }
4105
4106
        if ($id != $this->config['site_start']) {
4107
            if ($this->config['friendly_urls'] == 1 && $alias == '') {
4108
                $alias = $id;
4109
                $alPath = '';
4110
4111
                if ($this->config['friendly_alias_urls'] == 1) {
4112
4113
                    if ($this->config['aliaslistingfolder'] == 1) {
4114
                        $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...
4115
                    } else {
4116
                        $al = $this->aliasListing[$id];
4117
                    }
4118
4119
                    if ($al['isfolder'] === 1 && $this->config['make_folders'] === '1') {
4120
                        $f_url_suffix = '/';
4121
                    }
4122
4123
                    $alPath = !empty ($al['path']) ? $al['path'] . '/' : '';
4124
4125
                    if ($al && $al['alias']) {
4126
                        $alias = $al['alias'];
4127
                    }
4128
4129
                }
4130
4131
                $alias = $alPath . $f_url_prefix . $alias . $f_url_suffix;
4132
                $url = "{$alias}{$args}";
4133
            } else {
4134
                $url = "index.php?id={$id}{$args}";
4135
            }
4136
        } else {
4137
            $url = $args;
4138
        }
4139
4140
        $host = $this->config['base_url'];
4141
4142
        // check if scheme argument has been set
4143
        if ($scheme != '') {
4144
            // for backward compatibility - check if the desired scheme is different than the current scheme
4145
            if (is_numeric($scheme) && $scheme != $_SERVER['HTTPS']) {
4146
                $scheme = ($_SERVER['HTTPS'] ? 'http' : 'https');
4147
            }
4148
4149
            //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...
4150
            $host = $scheme == 'full' ? $this->config['site_url'] : $scheme . '://' . $_SERVER['HTTP_HOST'] . $host;
4151
        }
4152
4153
        //fix strictUrl by Bumkaka
4154
        if ($this->config['seostrict'] == '1') {
4155
            $url = $this->toAlias($url);
4156
        }
4157
4158
        if ($this->config['xhtml_urls']) {
4159
            $url = preg_replace("/&(?!amp;)/", "&amp;", $host . $virtualDir . $url);
4160
        } else {
4161
            $url = $host . $virtualDir . $url;
4162
        }
4163
4164
        $evtOut = $this->invokeEvent('OnMakeDocUrl', array(
4165
            'id' => $id,
4166
            'url' => $url
4167
        ));
4168
4169
        if (is_array($evtOut) && count($evtOut) > 0) {
4170
            $url = array_pop($evtOut);
4171
        }
4172
4173
        return $url;
4174
    }
4175
4176
    /**
4177
     * @param $id
4178
     * @return mixed
4179
     */
4180
    public function getAliasListing($id)
4181
    {
4182
        if (isset($this->aliasListing[$id])) {
4183
            $out = $this->aliasListing[$id];
4184
        } else {
4185
            $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...
4186
            if ($this->db->getRecordCount($q) == '1') {
4187
                $q = $this->db->getRow($q);
4188
                $this->aliasListing[$id] = array(
4189
                    'id' => (int)$q['id'],
4190
                    'alias' => $q['alias'] == '' ? $q['id'] : $q['alias'],
4191
                    'parent' => (int)$q['parent'],
4192
                    'isfolder' => (int)$q['isfolder'],
4193
                );
4194
                if ($this->aliasListing[$id]['parent'] > 0) {
4195
                    //fix alias_path_usage
4196
                    if ($this->config['use_alias_path'] == '1') {
4197
                        //&& $tmp['path'] != '' - fix error slash with epty path
4198
                        $tmp = $this->getAliasListing($this->aliasListing[$id]['parent']);
4199
                        $this->aliasListing[$id]['path'] = $tmp['path'] . ($tmp['alias_visible'] ? (($tmp['parent'] > 0 && $tmp['path'] != '') ? '/' : '') . $tmp['alias'] : '');
4200
                    } else {
4201
                        $this->aliasListing[$id]['path'] = '';
4202
                    }
4203
                }
4204
4205
                $out = $this->aliasListing[$id];
4206
            }
4207
        }
4208
        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...
4209
    }
4210
4211
    /**
4212
     * Returns an entry from the config
4213
     *
4214
     * Note: most code accesses the config array directly and we will continue to support this.
4215
     *
4216
     * @param string $name
4217
     * @return bool|string
4218
     */
4219
    public function getConfig($name = '')
4220
    {
4221
        if (!empty ($this->config[$name])) {
4222
            return $this->config[$name];
4223
        } else {
4224
            return false;
4225
        }
4226
    }
4227
4228
    /**
4229
     * Returns the MODX version information as version, branch, release date and full application name.
4230
     *
4231
     * @param null $data
4232
     * @return array
4233
     */
4234
4235
    public function getVersionData($data = null)
4236
    {
4237
        $out = array();
4238
        if (empty($this->version) || !is_array($this->version)) {
4239
            //include for compatibility modx version < 1.0.10
4240
            include MODX_MANAGER_PATH . "includes/version.inc.php";
4241
            $this->version = array();
4242
            $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...
4243
            $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...
4244
            $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...
4245
            $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...
4246
            $this->version['new_version'] = isset($this->config['newversiontext']) ? $this->config['newversiontext'] : '';
4247
        }
4248
        return (!is_null($data) && is_array($this->version) && isset($this->version[$data])) ? $this->version[$data] : $this->version;
4249
    }
4250
4251
    /**
4252
     * Executes a snippet.
4253
     *
4254
     * @param string $snippetName
4255
     * @param array $params Default: Empty array
4256
     * @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...
4257
     */
4258
    public function runSnippet($snippetName, $params = array())
4259
    {
4260
        if (isset ($this->snippetCache[$snippetName])) {
4261
            $snippet = $this->snippetCache[$snippetName];
4262
            $properties = !empty($this->snippetCache[$snippetName . "Props"]) ? $this->snippetCache[$snippetName . "Props"] : '';
4263
        } else { // not in cache so let's check the db
4264
            $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;";
4265
            $result = $this->db->query($sql);
4266
            if ($this->db->getRecordCount($result) == 1) {
4267
                $row = $this->db->getRow($result);
4268
                $snippet = $this->snippetCache[$snippetName] = $row['snippet'];
4269
                $mergedProperties = array_merge($this->parseProperties($row['properties']), $this->parseProperties($row['sharedproperties']));
4270
                $properties = $this->snippetCache[$snippetName . "Props"] = json_encode($mergedProperties);
4271
            } else {
4272
                $snippet = $this->snippetCache[$snippetName] = "return false;";
4273
                $properties = $this->snippetCache[$snippetName . "Props"] = '';
4274
            }
4275
        }
4276
        // load default params/properties
4277
        $parameters = $this->parseProperties($properties, $snippetName, 'snippet');
4278
        $parameters = array_merge($parameters, $params);
4279
4280
        // run snippet
4281
        return $this->evalSnippet($snippet, $parameters);
4282
    }
4283
4284
    /**
4285
     * Returns the chunk content for the given chunk name
4286
     *
4287
     * @param string $chunkName
4288
     * @return boolean|string
4289
     */
4290
    public function getChunk($chunkName)
4291
    {
4292
        $out = null;
4293
        if (empty($chunkName)) {
4294
            return $out;
4295
        }
4296
        if (isset ($this->chunkCache[$chunkName])) {
4297
            $out = $this->chunkCache[$chunkName];
4298
        } else if (stripos($chunkName, '@FILE') === 0) {
4299
            $out = $this->chunkCache[$chunkName] = $this->atBindFileContent($chunkName);
4300
        } else {
4301
            $where = sprintf("`name`='%s' AND disabled=0", $this->db->escape($chunkName));
4302
            $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...
4303
            if ($this->db->getRecordCount($rs) == 1) {
4304
                $row = $this->db->getRow($rs);
4305
                $out = $this->chunkCache[$chunkName] = $row['snippet'];
4306
            } else {
4307
                $out = $this->chunkCache[$chunkName] = null;
4308
            }
4309
        }
4310
        return $out;
4311
    }
4312
4313
    /**
4314
     * parseText
4315
     * @version 1.0 (2013-10-17)
4316
     *
4317
     * @desc Replaces placeholders in text with required values.
4318
     *
4319
     * @param string $tpl
4320
     * @param array $ph
4321
     * @param string $left
4322
     * @param string $right
4323
     * @param bool $execModifier
4324
     * @return string {string} - Parsed text.
4325
     * - Parsed text.
4326
     * @internal param $chunk {string} - String to parse. - String to parse. @required
4327
     * @internal param $chunkArr {array} - Array of values. Key — placeholder name, value — value. - Array of values. Key — placeholder name, value — value. @required
4328
     * @internal param $prefix {string} - Placeholders prefix. Default: '[+'. - Placeholders prefix. Default: '[+'.
4329
     * @internal param $suffix {string} - Placeholders suffix. Default: '+]'. - Placeholders suffix. Default: '+]'.
4330
     *
4331
     */
4332
    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...
4333
    {
4334
        if (empty($ph) || empty($tpl)) {
4335
            return $tpl;
4336
        }
4337
4338 View Code Duplication
        if ($this->config['enable_at_syntax']) {
4339
            if (stripos($tpl, '<@LITERAL>') !== false) {
4340
                $tpl = $this->escapeLiteralTagsContent($tpl);
4341
            }
4342
        }
4343
4344
        $matches = $this->getTagsFromContent($tpl, $left, $right);
4345
        if (empty($matches)) {
4346
            return $tpl;
4347
        }
4348
4349
        foreach ($matches[1] as $i => $key) {
4350
4351
            if (strpos($key, ':') !== false && $execModifier) {
4352
                list($key, $modifiers) = $this->splitKeyAndFilter($key);
4353
            } else {
4354
                $modifiers = false;
4355
            }
4356
4357
            //          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...
4358
            if (!array_key_exists($key, $ph)) {
4359
                continue;
4360
            } //NULL values must be saved in placeholders, if we got them from database string
4361
4362
            $value = $ph[$key];
4363
4364
            $s = &$matches[0][$i];
4365
            if ($modifiers !== false) {
4366
                if (strpos($modifiers, $left) !== false) {
4367
                    $modifiers = $this->parseText($modifiers, $ph, $left, $right);
4368
                }
4369
                $value = $this->applyFilter($value, $modifiers, $key);
4370
            }
4371 View Code Duplication
            if (strpos($tpl, $s) !== false) {
4372
                $tpl = str_replace($s, $value, $tpl);
4373
            } elseif($this->debug) {
4374
                $this->addLog('parseText parse error', $_SERVER['REQUEST_URI'] . $s, 2);
4375
            }
4376
        }
4377
4378
        return $tpl;
4379
    }
4380
4381
    /**
4382
     * parseChunk
4383
     * @version 1.1 (2013-10-17)
4384
     *
4385
     * @desc Replaces placeholders in a chunk with required values.
4386
     *
4387
     * @param $chunkName {string} - Name of chunk to parse. @required
4388
     * @param $chunkArr {array} - Array of values. Key — placeholder name, value — value. @required
4389
     * @param string $prefix {string}
4390
     * - Placeholders prefix. Default: '{'.
4391
     * @param string $suffix {string}
4392
     * - Placeholders suffix. Default: '}'.
4393
     * @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...
4394
     * - Parsed chunk or false if $chunkArr is not array.
4395
     */
4396
    public function parseChunk($chunkName, $chunkArr, $prefix = '{', $suffix = '}')
4397
    {
4398
        //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...
4399
        if (!is_array($chunkArr)) {
4400
            return false;
4401
        }
4402
4403
        return $this->parseText($this->getChunk($chunkName), $chunkArr, $prefix, $suffix);
0 ignored issues
show
Bug introduced by
It seems like $this->getChunk($chunkName) targeting DocumentParser::getChunk() can also be of type boolean; however, DocumentParser::parseText() does only seem to accept string, maybe add an additional type check?

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

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

An additional type check may prevent trouble.

Loading history...
4404
    }
4405
4406
    /**
4407
     * getTpl
4408
     * get template for snippets
4409
     * @param $tpl {string}
4410
     * @return bool|string {string}
4411
     */
4412
    public function getTpl($tpl)
4413
    {
4414
        $template = $tpl;
4415
        if (preg_match("/^@([^:\s]+)[:\s]+(.+)$/s", trim($tpl), $match)) {
4416
            $command = strtoupper($match[1]);
4417
            $template = $match[2];
4418
        }
4419
        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...
4420
            case 'CODE':
4421
                break;
4422
            case 'FILE':
4423
                $template = file_get_contents(MODX_BASE_PATH . $template);
4424
                break;
4425
            case 'CHUNK':
4426
                $template = $this->getChunk($template);
4427
                break;
4428
            case 'DOCUMENT':
4429
                $doc = $this->getDocument($template, 'content', 'all');
4430
                $template = $doc['content'];
4431
                break;
4432
            case 'SELECT':
4433
                $this->db->getValue($this->db->query("SELECT {$template}"));
4434
                break;
4435
            default:
4436
                if (!($template = $this->getChunk($tpl))) {
4437
                    $template = $tpl;
4438
                }
4439
        }
4440
        return $template;
4441
    }
4442
4443
    /**
4444
     * Returns the timestamp in the date format defined in $this->config['datetime_format']
4445
     *
4446
     * @param int $timestamp Default: 0
4447
     * @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.
4448
     * @return string
4449
     */
4450
    public function toDateFormat($timestamp = 0, $mode = '')
4451
    {
4452
        $timestamp = trim($timestamp);
4453
        if ($mode !== 'formatOnly' && empty($timestamp)) {
4454
            return '-';
4455
        }
4456
        $timestamp = (int)$timestamp;
4457
4458
        switch ($this->config['datetime_format']) {
4459
            case 'YYYY/mm/dd':
4460
                $dateFormat = '%Y/%m/%d';
4461
                break;
4462
            case 'dd-mm-YYYY':
4463
                $dateFormat = '%d-%m-%Y';
4464
                break;
4465
            case 'mm/dd/YYYY':
4466
                $dateFormat = '%m/%d/%Y';
4467
                break;
4468
            /*
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...
4469
            case 'dd-mmm-YYYY':
4470
                $dateFormat = '%e-%b-%Y';
4471
                break;
4472
            */
4473
        }
4474
4475
        if (empty($mode)) {
4476
            $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...
4477
        } elseif ($mode == 'dateOnly') {
4478
            $strTime = strftime($dateFormat, $timestamp);
4479
        } elseif ($mode == 'formatOnly') {
4480
            $strTime = $dateFormat;
4481
        }
4482
        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...
4483
    }
4484
4485
    /**
4486
     * Make a timestamp from a string corresponding to the format in $this->config['datetime_format']
4487
     *
4488
     * @param string $str
4489
     * @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...
4490
     */
4491
    public function toTimeStamp($str)
4492
    {
4493
        $str = trim($str);
4494
        if (empty($str)) {
4495
            return '';
4496
        }
4497
4498
        switch ($this->config['datetime_format']) {
4499 View Code Duplication
            case 'YYYY/mm/dd':
4500
                if (!preg_match('/^[0-9]{4}\/[0-9]{2}\/[0-9]{2}[0-9 :]*$/', $str)) {
4501
                    return '';
4502
                }
4503
                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...
4504
                break;
4505 View Code Duplication
            case 'dd-mm-YYYY':
4506
                if (!preg_match('/^[0-9]{2}-[0-9]{2}-[0-9]{4}[0-9 :]*$/', $str)) {
4507
                    return '';
4508
                }
4509
                list ($d, $m, $Y, $H, $M, $S) = sscanf($str, '%2d-%2d-%4d %2d:%2d:%2d');
4510
                break;
4511 View Code Duplication
            case 'mm/dd/YYYY':
4512
                if (!preg_match('/^[0-9]{2}\/[0-9]{2}\/[0-9]{4}[0-9 :]*$/', $str)) {
4513
                    return '';
4514
                }
4515
                list ($m, $d, $Y, $H, $M, $S) = sscanf($str, '%2d/%2d/%4d %2d:%2d:%2d');
4516
                break;
4517
            /*
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...
4518
            case 'dd-mmm-YYYY':
4519
                if (!preg_match('/^[0-9]{2}-[0-9a-z]+-[0-9]{4}[0-9 :]*$/i', $str)) {return '';}
4520
                list ($m, $d, $Y, $H, $M, $S) = sscanf($str, '%2d-%3s-%4d %2d:%2d:%2d');
4521
                break;
4522
            */
4523
        }
4524
        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...
4525
            $H = 0;
4526
            $M = 0;
4527
            $S = 0;
4528
        }
4529
        $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...
4530
        $timeStamp = (int)$timeStamp;
4531
        return $timeStamp;
4532
    }
4533
4534
    /**
4535
     * Get the TVs of a document's children. Returns an array where each element represents one child doc.
4536
     *
4537
     * Ignores deleted children. Gets all children - there is no where clause available.
4538
     *
4539
     * @param int $parentid The parent docid
4540
     *                 Default: 0 (site root)
4541
     * @param array $tvidnames . Which TVs to fetch - Can relate to the TV ids in the db (array elements should be numeric only)
4542
     *                                               or the TV names (array elements should be names only)
4543
     *                      Default: Empty array
4544
     * @param int $published Whether published or unpublished documents are in the result
4545
     *                      Default: 1
4546
     * @param string $docsort How to sort the result array (field)
4547
     *                      Default: menuindex
4548
     * @param ASC|string $docsortdir How to sort the result array (direction)
4549
     *                      Default: ASC
4550
     * @param string $tvfields Fields to fetch from site_tmplvars, default '*'
4551
     *                      Default: *
4552
     * @param string $tvsort How to sort each element of the result array i.e. how to sort the TVs (field)
4553
     *                      Default: rank
4554
     * @param string $tvsortdir How to sort each element of the result array i.e. how to sort the TVs (direction)
4555
     *                      Default: ASC
4556
     * @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...
4557
     */
4558
    public function getDocumentChildrenTVars($parentid = 0, $tvidnames = array(), $published = 1, $docsort = "menuindex", $docsortdir = "ASC", $tvfields = "*", $tvsort = "rank", $tvsortdir = "ASC")
4559
    {
4560
        $docs = $this->getDocumentChildren($parentid, $published, 0, '*', '', $docsort, $docsortdir);
4561
        if (!$docs) {
4562
            return false;
4563
        } else {
4564
            $result = array();
4565
            // get user defined template variables
4566
            if ($tvfields) {
4567
                $_ = 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...
4568
                foreach ($_ as $i => $v) {
4569
                    if ($v === 'value') {
4570
                        unset($_[$i]);
4571
                    } else {
4572
                        $_[$i] = 'tv.' . $v;
4573
                    }
4574
                }
4575
                $fields = implode(',', $_);
4576
            } else {
4577
                $fields = "tv.*";
4578
            }
4579
4580
            if ($tvsort != '') {
4581
                $tvsort = 'tv.' . implode(',tv.', array_filter(array_map('trim', explode(',', $tvsort))));
4582
            }
4583 View Code Duplication
            if ($tvidnames == "*") {
4584
                $query = "tv.id<>0";
4585
            } else {
4586
                $query = (is_numeric($tvidnames[0]) ? "tv.id" : "tv.name") . " IN ('" . implode("','", $tvidnames) . "')";
4587
            }
4588
4589
            $this->getUserDocGroups();
4590
4591
            foreach ($docs as $doc) {
4592
4593
                $docid = $doc['id'];
4594
4595
                $rs = $this->db->select("{$fields}, IF(tvc.value!='',tvc.value,tv.default_text) as value ", "[+prefix+]site_tmplvars tv
4596
                        INNER JOIN [+prefix+]site_tmplvar_templates tvtpl ON tvtpl.tmplvarid = tv.id
4597
                        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}" : ""));
4598
                $tvs = $this->db->makeArray($rs);
4599
4600
                // get default/built-in template variables
4601
                ksort($doc);
4602
                foreach ($doc as $key => $value) {
4603
                    if ($tvidnames == '*' || in_array($key, $tvidnames)) {
4604
                        $tvs[] = array('name' => $key, 'value' => $value);
4605
                    }
4606
                }
4607
                if (is_array($tvs) && count($tvs)) {
4608
                    $result[] = $tvs;
4609
                }
4610
            }
4611
            return $result;
4612
        }
4613
    }
4614
4615
    /**
4616
     * getDocumentChildrenTVarOutput
4617
     * @version 1.1 (2014-02-19)
4618
     *
4619
     * @desc Returns an array where each element represents one child doc and contains the result from getTemplateVarOutput().
4620
     *
4621
     * @param int $parentid {integer}
4622
     * - Id of parent document. Default: 0 (site root).
4623
     * @param array $tvidnames {array; '*'}
4624
     * - Which TVs to fetch. In the form expected by getTemplateVarOutput(). Default: array().
4625
     * @param int $published {0; 1; 'all'}
4626
     * - 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.
4627
     * @param string $sortBy {string}
4628
     * - How to sort the result array (field). Default: 'menuindex'.
4629
     * @param string $sortDir {'ASC'; 'DESC'}
4630
     * - How to sort the result array (direction). Default: 'ASC'.
4631
     * @param string $where {string}
4632
     * - SQL WHERE condition (use only document fields, not TV). Default: ''.
4633
     * @param string $resultKey {string; false}
4634
     * - Field, which values are keys into result array. Use the “false”, that result array keys just will be numbered. Default: 'id'.
4635
     * @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...
4636
     * - Result array, or false.
4637
     */
4638
    public function getDocumentChildrenTVarOutput($parentid = 0, $tvidnames = array(), $published = 1, $sortBy = 'menuindex', $sortDir = 'ASC', $where = '', $resultKey = 'id')
4639
    {
4640
        $docs = $this->getDocumentChildren($parentid, $published, 0, 'id', $where, $sortBy, $sortDir);
4641
4642
        if (!$docs) {
4643
            return false;
4644
        } else {
4645
            $result = array();
4646
4647
            $unsetResultKey = false;
4648
4649
            if ($resultKey !== false) {
4650
                if (is_array($tvidnames)) {
4651
                    if (count($tvidnames) != 0 && !in_array($resultKey, $tvidnames)) {
4652
                        $tvidnames[] = $resultKey;
4653
                        $unsetResultKey = true;
4654
                    }
4655
                } else if ($tvidnames != '*' && $tvidnames != $resultKey) {
4656
                    $tvidnames = array($tvidnames, $resultKey);
4657
                    $unsetResultKey = true;
4658
                }
4659
            }
4660
4661
            for ($i = 0; $i < count($docs); $i++) {
4662
                $tvs = $this->getTemplateVarOutput($tvidnames, $docs[$i]['id'], $published);
4663
4664
                if ($tvs) {
4665
                    if ($resultKey !== false && array_key_exists($resultKey, $tvs)) {
4666
                        $result[$tvs[$resultKey]] = $tvs;
4667
4668
                        if ($unsetResultKey) {
4669
                            unset($result[$tvs[$resultKey]][$resultKey]);
4670
                        }
4671
                    } else {
4672
                        $result[] = $tvs;
4673
                    }
4674
                }
4675
            }
4676
4677
            return $result;
4678
        }
4679
    }
4680
4681
    /**
4682
     * Modified by Raymond for TV - Orig Modified by Apodigm - DocVars
4683
     * Returns a single site_content field or TV record from the db.
4684
     *
4685
     * If a site content field the result is an associative array of 'name' and 'value'.
4686
     *
4687
     * If a TV the result is an array representing a db row including the fields specified in $fields.
4688
     *
4689
     * @param string $idname Can be a TV id or name
4690
     * @param string $fields Fields to fetch from site_tmplvars. Default: *
4691
     * @param string|type $docid Docid. Defaults to empty string which indicates the current document.
4692
     * @param int $published Whether published or unpublished documents are in the result
4693
     *                        Default: 1
4694
     * @return bool
4695
     */
4696 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...
4697
    {
4698
        if ($idname == "") {
4699
            return false;
4700
        } else {
4701
            $result = $this->getTemplateVars(array($idname), $fields, $docid, $published, "", ""); //remove sorting for speed
4702
            return ($result != false) ? $result[0] : false;
4703
        }
4704
    }
4705
4706
    /**
4707
     * getTemplateVars
4708
     * @version 1.0.1 (2014-02-19)
4709
     *
4710
     * @desc Returns an array of site_content field fields and/or TV records from the db.
4711
     * Elements representing a site content field consist of an associative array of 'name' and 'value'.
4712
     * Elements representing a TV consist of an array representing a db row including the fields specified in $fields.
4713
     *
4714
     * @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
4715
     * @param $fields {comma separated string; '*'} - Fields names in the TV table of MODx database. Default: '*'
4716
     * @param $docid {integer; ''} - Id of a document to get. Default: an empty string which indicates the current document.
4717
     * @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.
4718
     * @param $sort {comma separated string} - Fields of the TV table to sort by. Default: 'rank'.
4719
     * @param $dir {'ASC'; 'DESC'} - How to sort the result array (direction). Default: 'ASC'.
4720
     *
4721
     * @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...
4722
     */
4723
    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...
4724
    {
4725
        $cacheKey = md5(print_r(func_get_args(), true));
4726
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
4727
            return $this->tmpCache[__FUNCTION__][$cacheKey];
4728
        }
4729
4730
        if (($idnames != '*' && !is_array($idnames)) || empty($idnames) ) {
4731
            return false;
4732
        } else {
4733
4734
            // get document record
4735
            if ($docid == '') {
4736
                $docid = $this->documentIdentifier;
4737
                $docRow = $this->documentObject;
4738
            } else {
4739
                $docRow = $this->getDocument($docid, '*', $published);
4740
4741
                if (!$docRow) {
4742
                    $this->tmpCache[__FUNCTION__][$cacheKey] = false;
4743
                    return false;
4744
                }
4745
            }
4746
4747
            // get user defined template variables
4748
            $fields = ($fields == '') ? 'tv.*' : 'tv.' . implode(',tv.', array_filter(array_map('trim', explode(',', $fields))));
4749
            $sort = ($sort == '') ? '' : 'tv.' . implode(',tv.', array_filter(array_map('trim', explode(',', $sort))));
4750
4751 View Code Duplication
            if ($idnames == '*') {
4752
                $query = 'tv.id<>0';
4753
            } else {
4754
                $query = (is_numeric($idnames[0]) ? 'tv.id' : 'tv.name') . " IN ('" . implode("','", $idnames) . "')";
4755
            }
4756
4757
            $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...
4758
                    INNER JOIN " . $this->getFullTableName('site_tmplvar_templates') . " tvtpl ON tvtpl.tmplvarid = tv.id
4759
                    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}" : ""));
4760
4761
            $result = $this->db->makeArray($rs);
4762
4763
            // get default/built-in template variables
4764
            if(is_array($docRow)){
4765
                ksort($docRow);
4766
4767
                foreach ($docRow as $key => $value) {
4768
                    if ($idnames == '*' || in_array($key, $idnames)) {
4769
                        array_push($result, array(
4770
                            'name' => $key,
4771
                            'value' => $value
4772
                        ));
4773
                    }
4774
                }
4775
            }
4776
4777
            $this->tmpCache[__FUNCTION__][$cacheKey] = $result;
4778
4779
            return $result;
4780
        }
4781
    }
4782
4783
    /**
4784
     * getTemplateVarOutput
4785
     * @version 1.0.1 (2014-02-19)
4786
     *
4787
     * @desc Returns an associative array containing TV rendered output values.
4788
     *
4789
     * @param array $idnames {array; '*'}
4790
     * - 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
4791
     * @param string $docid {integer; ''}
4792
     * - Id of a document to get. Default: an empty string which indicates the current document.
4793
     * @param int $published {0; 1; 'all'}
4794
     * - 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.
4795
     * @param string $sep {string}
4796
     * - Separator that is used while concatenating in getTVDisplayFormat(). Default: ''.
4797
     * @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...
4798
     * - Result array, or false.
4799
     */
4800
    public function getTemplateVarOutput($idnames = array(), $docid = '', $published = 1, $sep = '')
4801
    {
4802
        if (is_array($idnames) && empty($idnames) ) {
4803
            return false;
4804
        } else {
4805
            $output = array();
4806
            $vars = ($idnames == '*' || is_array($idnames)) ? $idnames : array($idnames);
4807
4808
            $docid = (int)$docid > 0 ? (int)$docid : $this->documentIdentifier;
4809
            // remove sort for speed
4810
            $result = $this->getTemplateVars($vars, '*', $docid, $published, '', '');
4811
4812
            if ($result == false) {
4813
                return false;
4814
            } else {
4815
                $baspath = MODX_MANAGER_PATH . 'includes';
4816
                include_once $baspath . '/tmplvars.format.inc.php';
4817
                include_once $baspath . '/tmplvars.commands.inc.php';
4818
4819
                for ($i = 0; $i < count($result); $i++) {
4820
                    $row = $result[$i];
4821
4822
                    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...
4823
                        $output[$row['name']] = $row['value'];
4824
                    } else {
4825
                        $output[$row['name']] = getTVDisplayFormat($row['name'], $row['value'], $row['display'], $row['display_params'], $row['type'], $docid, $sep);
4826
                    }
4827
                }
4828
4829
                return $output;
4830
            }
4831
        }
4832
    }
4833
4834
    /**
4835
     * Returns the full table name based on db settings
4836
     *
4837
     * @param string $tbl Table name
4838
     * @return string Table name with prefix
4839
     */
4840
    public function getFullTableName($tbl)
4841
    {
4842
        return $this->db->config['dbase'] . ".`" . $this->db->config['table_prefix'] . $tbl . "`";
4843
    }
4844
4845
    /**
4846
     * Returns the placeholder value
4847
     *
4848
     * @param string $name Placeholder name
4849
     * @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...
4850
     */
4851
    public function getPlaceholder($name)
4852
    {
4853
        return isset($this->placeholders[$name]) ? $this->placeholders[$name] : null;
4854
    }
4855
4856
    /**
4857
     * Sets a value for a placeholder
4858
     *
4859
     * @param string $name The name of the placeholder
4860
     * @param string $value The value of the placeholder
4861
     */
4862
    public function setPlaceholder($name, $value)
4863
    {
4864
        $this->placeholders[$name] = $value;
4865
    }
4866
4867
    /**
4868
     * Set placeholders en masse via an array or object.
4869
     *
4870
     * @param object|array $subject
4871
     * @param string $prefix
4872
     */
4873
    public function toPlaceholders($subject, $prefix = '')
4874
    {
4875
        if (is_object($subject)) {
4876
            $subject = get_object_vars($subject);
4877
        }
4878
        if (is_array($subject)) {
4879
            foreach ($subject as $key => $value) {
4880
                $this->toPlaceholder($key, $value, $prefix);
4881
            }
4882
        }
4883
    }
4884
4885
    /**
4886
     * For use by toPlaceholders(); For setting an array or object element as placeholder.
4887
     *
4888
     * @param string $key
4889
     * @param object|array $value
4890
     * @param string $prefix
4891
     */
4892
    public function toPlaceholder($key, $value, $prefix = '')
4893
    {
4894
        if (is_array($value) || is_object($value)) {
4895
            $this->toPlaceholders($value, "{$prefix}{$key}.");
4896
        } else {
4897
            $this->setPlaceholder("{$prefix}{$key}", $value);
4898
        }
4899
    }
4900
4901
    /**
4902
     * Returns the manager relative URL/path with respect to the site root.
4903
     *
4904
     * @global string $base_url
4905
     * @return string The complete URL to the manager folder
4906
     */
4907
    public function getManagerPath()
4908
    {
4909
        return MODX_MANAGER_URL;
4910
    }
4911
4912
    /**
4913
     * Returns the cache relative URL/path with respect to the site root.
4914
     *
4915
     * @global string $base_url
4916
     * @return string The complete URL to the cache folder
4917
     */
4918
    public function getCachePath()
4919
    {
4920
        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...
4921
        $pth = $base_url . $this->getCacheFolder();
4922
        return $pth;
4923
    }
4924
4925
    /**
4926
     * Sends a message to a user's message box.
4927
     *
4928
     * @param string $type Type of the message
4929
     * @param string $to The recipient of the message
4930
     * @param string $from The sender of the message
4931
     * @param string $subject The subject of the message
4932
     * @param string $msg The message body
4933
     * @param int $private Whether it is a private message, or not
4934
     *                     Default : 0
4935
     */
4936
    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...
4937
    {
4938
        $private = ($private) ? 1 : 0;
4939 View Code Duplication
        if (!is_numeric($to)) {
4940
            // Query for the To ID
4941
            $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...
4942
            $to = $this->db->getValue($rs);
4943
        }
4944 View Code Duplication
        if (!is_numeric($from)) {
4945
            // Query for the From ID
4946
            $rs = $this->db->select('id', $this->getFullTableName("manager_users"), "username='{$from}'");
4947
            $from = $this->db->getValue($rs);
4948
        }
4949
        // insert a new message into user_messages
4950
        $this->db->insert(array(
4951
            'type' => $type,
4952
            'subject' => $subject,
4953
            'message' => $msg,
4954
            'sender' => $from,
4955
            'recipient' => $to,
4956
            'private' => $private,
4957
            'postdate' => $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'],
4958
            'messageread' => 0,
4959
        ), $this->getFullTableName('user_messages'));
4960
    }
4961
4962
    /**
4963
     * Returns current user id.
4964
     *
4965
     * @param string $context . Default is an empty string which indicates the method should automatically pick 'web (frontend) or 'mgr' (backend)
4966
     * @return string
4967
     */
4968 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...
4969
    {
4970
        $out = false;
4971
4972
        if (!empty($context)) {
4973
            if (is_scalar($context) && isset($_SESSION[$context . 'Validated'])) {
4974
                $out = $_SESSION[$context . 'InternalKey'];
4975
            }
4976
        } else {
4977
            switch (true) {
4978
                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...
4979
                    $out = $_SESSION['webInternalKey'];
4980
                    break;
4981
                }
4982
                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...
4983
                    $out = $_SESSION['mgrInternalKey'];
4984
                    break;
4985
                }
4986
            }
4987
        }
4988
        return $out;
4989
    }
4990
4991
    /**
4992
     * Returns current user name
4993
     *
4994
     * @param string $context . Default is an empty string which indicates the method should automatically pick 'web (frontend) or 'mgr' (backend)
4995
     * @return string
4996
     */
4997 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...
4998
    {
4999
        $out = false;
5000
5001
        if (!empty($context)) {
5002
            if (is_scalar($context) && isset($_SESSION[$context . 'Validated'])) {
5003
                $out = $_SESSION[$context . 'Shortname'];
5004
            }
5005
        } else {
5006
            switch (true) {
5007
                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...
5008
                    $out = $_SESSION['webShortname'];
5009
                    break;
5010
                }
5011
                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...
5012
                    $out = $_SESSION['mgrShortname'];
5013
                    break;
5014
                }
5015
            }
5016
        }
5017
        return $out;
5018
    }
5019
5020
    /**
5021
     * Returns current login user type - web or manager
5022
     *
5023
     * @return string
5024
     */
5025
    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...
5026
    {
5027
        if ($this->isFrontend() && isset ($_SESSION['webValidated'])) {
5028
            return 'web';
5029
        } elseif ($this->isBackend() && isset ($_SESSION['mgrValidated'])) {
5030
            return 'manager';
5031
        } else {
5032
            return '';
5033
        }
5034
    }
5035
5036
    /**
5037
     * Returns a user info record for the given manager user
5038
     *
5039
     * @param int $uid
5040
     * @return boolean|string
5041
     */
5042
    public function getUserInfo($uid)
5043
    {
5044
        if (isset($this->tmpCache[__FUNCTION__][$uid])) {
5045
            return $this->tmpCache[__FUNCTION__][$uid];
5046
        }
5047
5048
        $from = '[+prefix+]manager_users mu INNER JOIN [+prefix+]user_attributes mua ON mua.internalkey=mu.id';
5049
        $where = sprintf("mu.id='%s'", $this->db->escape($uid));
5050
        $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...
5051
5052
        if (!$this->db->getRecordCount($rs)) {
5053
            return $this->tmpCache[__FUNCTION__][$uid] = false;
5054
        }
5055
5056
        $row = $this->db->getRow($rs);
5057 View Code Duplication
        if (!isset($row['usertype']) || !$row['usertype']) {
5058
            $row['usertype'] = 'manager';
5059
        }
5060
5061
        $this->tmpCache[__FUNCTION__][$uid] = $row;
5062
5063
        return $row;
5064
    }
5065
5066
    /**
5067
     * Returns a record for the web user
5068
     *
5069
     * @param int $uid
5070
     * @return boolean|string
5071
     */
5072
    public function getWebUserInfo($uid)
5073
    {
5074
        $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...
5075
                INNER JOIN " . $this->getFullTableName("web_user_attributes") . " wua ON wua.internalkey=wu.id", "wu.id='{$uid}'");
5076
        if ($row = $this->db->getRow($rs)) {
5077 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...
5078
                $row["usertype"] = "web";
5079
            }
5080
            return $row;
5081
        }
5082
    }
5083
5084
    /**
5085
     * Returns an array of document groups that current user is assigned to.
5086
     * This function will first return the web user doc groups when running from
5087
     * frontend otherwise it will return manager user's docgroup.
5088
     *
5089
     * @param boolean $resolveIds Set to true to return the document group names
5090
     *                            Default: false
5091
     * @return string|array
5092
     */
5093
    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...
5094
    {
5095
        if ($this->isFrontend() && isset($_SESSION['webDocgroups']) && isset($_SESSION['webValidated'])) {
5096
            $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...
5097
            $dgn = isset($_SESSION['webDocgrpNames']) ? $_SESSION['webDocgrpNames'] : false;
5098
        } else if ($this->isBackend() && isset($_SESSION['mgrDocgroups']) && isset($_SESSION['mgrValidated'])) {
5099
            $dg = $_SESSION['mgrDocgroups'];
5100
            $dgn = isset($_SESSION['mgrDocgrpNames']) ? $_SESSION['mgrDocgrpNames'] : false;
5101
        } else {
5102
            $dg = '';
5103
        }
5104
        if (!$resolveIds) {
5105
            return $dg;
5106
        } else if (is_array($dgn)) {
5107
            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...
5108
        } else if (is_array($dg)) {
5109
            // resolve ids to names
5110
            $dgn = array();
5111
            $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...
5112
            while ($row = $this->db->getRow($ds)) {
5113
                $dgn[] = $row['name'];
5114
            }
5115
            // cache docgroup names to session
5116
            if ($this->isFrontend()) {
5117
                $_SESSION['webDocgrpNames'] = $dgn;
5118
            } else {
5119
                $_SESSION['mgrDocgrpNames'] = $dgn;
5120
            }
5121
            return $dgn;
5122
        }
5123
    }
5124
5125
    /**
5126
     * Change current web user's password
5127
     *
5128
     * @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...
5129
     * @param string $oldPwd
5130
     * @param string $newPwd
5131
     * @return string|boolean Returns true if successful, oterhwise return error
5132
     *                        message
5133
     */
5134
    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...
5135
    {
5136
        $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...
5137
        if ($_SESSION["webValidated"] == 1) {
5138
            $tbl = $this->getFullTableName("web_users");
5139
            $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...
5140
            if ($row = $this->db->getRow($ds)) {
5141
                if ($row["password"] == md5($oldPwd)) {
5142
                    if (strlen($newPwd) < 6) {
5143
                        return "Password is too short!";
5144
                    } elseif ($newPwd == "") {
5145
                        return "You didn't specify a password for this user!";
5146
                    } else {
5147
                        $this->db->update(array(
5148
                            'password' => $this->db->escape($newPwd),
5149
                        ), $tbl, "id='" . $this->getLoginUserID() . "'");
5150
                        // invoke OnWebChangePassword event
5151
                        $this->invokeEvent("OnWebChangePassword", array(
5152
                            "userid" => $row["id"],
5153
                            "username" => $row["username"],
5154
                            "userpassword" => $newPwd
5155
                        ));
5156
                        return true;
5157
                    }
5158
                } else {
5159
                    return "Incorrect password.";
5160
                }
5161
            }
5162
        }
5163
        return $rt;
5164
    }
5165
5166
    /**
5167
     * Returns true if the current web user is a member the specified groups
5168
     *
5169
     * @param array $groupNames
5170
     * @return boolean
5171
     */
5172
    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...
5173
    {
5174
        if (!is_array($groupNames)) {
5175
            return false;
5176
        }
5177
        // check cache
5178
        $grpNames = isset ($_SESSION['webUserGroupNames']) ? $_SESSION['webUserGroupNames'] : false;
5179
        if (!is_array($grpNames)) {
5180
            $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...
5181
                    INNER JOIN " . $this->getFullTableName("web_groups") . " wg ON wg.webgroup=wgn.id AND wg.webuser='" . $this->getLoginUserID() . "'");
5182
            $grpNames = $this->db->getColumn("name", $rs);
5183
            // save to cache
5184
            $_SESSION['webUserGroupNames'] = $grpNames;
5185
        }
5186
        foreach ($groupNames as $k => $v) {
5187
            if (in_array(trim($v), $grpNames)) {
5188
                return true;
5189
            }
5190
        }
5191
        return false;
5192
    }
5193
5194
    /**
5195
     * Registers Client-side CSS scripts - these scripts are loaded at inside
5196
     * the <head> tag
5197
     *
5198
     * @param string $src
5199
     * @param string $media Default: Empty string
5200
     * @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...
5201
     */
5202
    public function regClientCSS($src, $media = '')
5203
    {
5204
        if (empty($src) || isset ($this->loadedjscripts[$src])) {
5205
            return '';
5206
        }
5207
        $nextpos = max(array_merge(array(0), array_keys($this->sjscripts))) + 1;
5208
        $this->loadedjscripts[$src]['startup'] = true;
5209
        $this->loadedjscripts[$src]['version'] = '0';
5210
        $this->loadedjscripts[$src]['pos'] = $nextpos;
5211
        if (strpos(strtolower($src), "<style") !== false || strpos(strtolower($src), "<link") !== false) {
5212
            $this->sjscripts[$nextpos] = $src;
5213
        } else {
5214
            $this->sjscripts[$nextpos] = "\t" . '<link rel="stylesheet" type="text/css" href="' . $src . '" ' . ($media ? 'media="' . $media . '" ' : '') . '/>';
5215
        }
5216
    }
5217
5218
    /**
5219
     * Registers Startup Client-side JavaScript - these scripts are loaded at inside the <head> tag
5220
     *
5221
     * @param string $src
5222
     * @param array $options Default: 'name'=>'', 'version'=>'0', 'plaintext'=>false
5223
     */
5224
    public function regClientStartupScript($src, $options = array('name' => '', 'version' => '0', 'plaintext' => false))
5225
    {
5226
        $this->regClientScript($src, $options, true);
5227
    }
5228
5229
    /**
5230
     * Registers Client-side JavaScript these scripts are loaded at the end of the page unless $startup is true
5231
     *
5232
     * @param string $src
5233
     * @param array $options Default: 'name'=>'', 'version'=>'0', 'plaintext'=>false
5234
     * @param boolean $startup Default: false
5235
     * @return string
5236
     */
5237
    public function regClientScript($src, $options = array('name' => '', 'version' => '0', 'plaintext' => false), $startup = false)
5238
    {
5239
        if (empty($src)) {
5240
            return '';
5241
        } // nothing to register
5242
        if (!is_array($options)) {
5243
            if (is_bool($options))  // backward compatibility with old plaintext parameter
5244
            {
5245
                $options = array('plaintext' => $options);
5246
            } elseif (is_string($options)) // Also allow script name as 2nd param
5247
            {
5248
                $options = array('name' => $options);
5249
            } else {
5250
                $options = array();
5251
            }
5252
        }
5253
        $name = isset($options['name']) ? strtolower($options['name']) : '';
5254
        $version = isset($options['version']) ? $options['version'] : '0';
5255
        $plaintext = isset($options['plaintext']) ? $options['plaintext'] : false;
5256
        $key = !empty($name) ? $name : $src;
5257
        unset($overwritepos); // probably unnecessary--just making sure
5258
5259
        $useThisVer = true;
5260
        if (isset($this->loadedjscripts[$key])) { // a matching script was found
5261
            // if existing script is a startup script, make sure the candidate is also a startup script
5262
            if ($this->loadedjscripts[$key]['startup']) {
5263
                $startup = true;
5264
            }
5265
5266
            if (empty($name)) {
5267
                $useThisVer = false; // if the match was based on identical source code, no need to replace the old one
5268
            } else {
5269
                $useThisVer = version_compare($this->loadedjscripts[$key]['version'], $version, '<');
5270
            }
5271
5272
            if ($useThisVer) {
5273
                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...
5274
                    // remove old script from the bottom of the page (new one will be at the top)
5275
                    unset($this->jscripts[$this->loadedjscripts[$key]['pos']]);
5276
                } else {
5277
                    // overwrite the old script (the position may be important for dependent scripts)
5278
                    $overwritepos = $this->loadedjscripts[$key]['pos'];
5279
                }
5280
            } else { // Use the original version
5281
                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...
5282
                    // need to move the exisiting script to the head
5283
                    $version = $this->loadedjscripts[$key][$version];
5284
                    $src = $this->jscripts[$this->loadedjscripts[$key]['pos']];
5285
                    unset($this->jscripts[$this->loadedjscripts[$key]['pos']]);
5286
                } else {
5287
                    return ''; // the script is already in the right place
5288
                }
5289
            }
5290
        }
5291
5292
        if ($useThisVer && $plaintext != true && (strpos(strtolower($src), "<script") === false)) {
5293
            $src = "\t" . '<script type="text/javascript" src="' . $src . '"></script>';
5294
        }
5295
        if ($startup) {
5296
            $pos = isset($overwritepos) ? $overwritepos : max(array_merge(array(0), array_keys($this->sjscripts))) + 1;
5297
            $this->sjscripts[$pos] = $src;
5298
        } else {
5299
            $pos = isset($overwritepos) ? $overwritepos : max(array_merge(array(0), array_keys($this->jscripts))) + 1;
5300
            $this->jscripts[$pos] = $src;
5301
        }
5302
        $this->loadedjscripts[$key]['version'] = $version;
5303
        $this->loadedjscripts[$key]['startup'] = $startup;
5304
        $this->loadedjscripts[$key]['pos'] = $pos;
5305
        return '';
5306
    }
5307
5308
    /**
5309
     * Returns all registered JavaScripts
5310
     *
5311
     * @return string
5312
     */
5313
    public function regClientStartupHTMLBlock($html)
5314
    {
5315
        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...
5316
    }
5317
5318
    /**
5319
     * Returns all registered startup scripts
5320
     *
5321
     * @return string
5322
     */
5323
    public function regClientHTMLBlock($html)
5324
    {
5325
        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...
5326
    }
5327
5328
    /**
5329
     * Remove unwanted html tags and snippet, settings and tags
5330
     *
5331
     * @param string $html
5332
     * @param string $allowed Default: Empty string
5333
     * @return string
5334
     */
5335
    public function stripTags($html, $allowed = "")
5336
    {
5337
        $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...
5338
        $t = preg_replace('~\[\*(.*?)\*\]~', "", $t); //tv
5339
        $t = preg_replace('~\[\[(.*?)\]\]~', "", $t); //snippet
5340
        $t = preg_replace('~\[\!(.*?)\!\]~', "", $t); //snippet
5341
        $t = preg_replace('~\[\((.*?)\)\]~', "", $t); //settings
5342
        $t = preg_replace('~\[\+(.*?)\+\]~', "", $t); //placeholders
5343
        $t = preg_replace('~{{(.*?)}}~', "", $t); //chunks
5344
        return $t;
5345
    }
5346
5347
    /**
5348
     * Add an event listener to a plugin - only for use within the current execution cycle
5349
     *
5350
     * @param string $evtName
5351
     * @param string $pluginName
5352
     * @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...
5353
     */
5354
    public function addEventListener($evtName, $pluginName)
5355
    {
5356
        if (!$evtName || !$pluginName) {
5357
            return false;
5358
        }
5359
        if (!array_key_exists($evtName, $this->pluginEvent)) {
5360
            $this->pluginEvent[$evtName] = array();
5361
        }
5362
        return array_push($this->pluginEvent[$evtName], $pluginName); // return array count
5363
    }
5364
5365
    /**
5366
     * Remove event listener - only for use within the current execution cycle
5367
     *
5368
     * @param string $evtName
5369
     * @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...
5370
     */
5371
    public function removeEventListener($evtName)
5372
    {
5373
        if (!$evtName) {
5374
            return false;
5375
        }
5376
        unset ($this->pluginEvent[$evtName]);
5377
    }
5378
5379
    /**
5380
     * Remove all event listeners - only for use within the current execution cycle
5381
     */
5382
    public function removeAllEventListener()
5383
    {
5384
        unset ($this->pluginEvent);
5385
        $this->pluginEvent = array();
5386
    }
5387
5388
    /**
5389
     * Invoke an event.
5390
     *
5391
     * @param string $evtName
5392
     * @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.
5393
     * @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...
5394
     */
5395
    public function invokeEvent($evtName, $extParams = array())
5396
    {
5397
        if (!$evtName) {
5398
            return false;
5399
        }
5400
        if (!isset ($this->pluginEvent[$evtName])) {
5401
            return false;
5402
        }
5403
5404
        $results = null;
5405
        foreach ($this->pluginEvent[$evtName] as $pluginName) { // start for loop
5406
            if ($this->dumpPlugins) {
5407
                $eventtime = $this->getMicroTime();
5408
            }
5409
            // reset event object
5410
            $e = &$this->event;
5411
            $e->_resetEventObject();
5412
            $e->name = $evtName;
5413
            $e->activePlugin = $pluginName;
5414
5415
            // get plugin code
5416
            $_ = $this->getPluginCode($pluginName);
5417
            $pluginCode = $_['code'];
5418
            $pluginProperties = $_['props'];
5419
5420
            // load default params/properties
5421
            $parameter = $this->parseProperties($pluginProperties);
5422
            if (!is_array($parameter)) {
5423
                $parameter = array();
5424
            }
5425
            if (!empty($extParams)) {
5426
                $parameter = array_merge($parameter, $extParams);
5427
            }
5428
5429
            // eval plugin
5430
            $this->evalPlugin($pluginCode, $parameter);
5431
5432
            if (class_exists('PHxParser')) {
5433
                $this->config['enable_filter'] = 0;
5434
            }
5435
5436
            if ($this->dumpPlugins) {
5437
                $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...
5438
                $this->pluginsCode .= sprintf('<fieldset><legend><b>%s / %s</b> (%2.2f ms)</legend>', $evtName, $pluginName, $eventtime * 1000);
5439
                foreach ($parameter as $k => $v) {
5440
                    $this->pluginsCode .= "{$k} => " . print_r($v, true) . '<br>';
5441
                }
5442
                $this->pluginsCode .= '</fieldset><br />';
5443
                $this->pluginsTime["{$evtName} / {$pluginName}"] += $eventtime;
5444
            }
5445
            if ($e->getOutput() != '') {
5446
                $results[] = $e->getOutput();
5447
            }
5448
            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...
5449
                break;
5450
            }
5451
        }
5452
5453
        $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...
5454
        return $results;
5455
    }
5456
5457
    /**
5458
     * Returns plugin-code and properties
5459
     *
5460
     * @param string $pluginName
5461
     * @return array Associative array consisting of 'code' and 'props'
5462
     */
5463
    public function getPluginCode($pluginName)
5464
    {
5465
        $plugin = array();
5466
        if (isset ($this->pluginCache[$pluginName])) {
5467
            $pluginCode = $this->pluginCache[$pluginName];
5468
            $pluginProperties = isset($this->pluginCache[$pluginName . "Props"]) ? $this->pluginCache[$pluginName . "Props"] : '';
5469
        } else {
5470
            $pluginName = $this->db->escape($pluginName);
5471
            $result = $this->db->select('name, plugincode, properties', $this->getFullTableName("site_plugins"), "name='{$pluginName}' AND disabled=0");
5472
            if ($row = $this->db->getRow($result)) {
5473
                $pluginCode = $this->pluginCache[$row['name']] = $row['plugincode'];
5474
                $pluginProperties = $this->pluginCache[$row['name'] . "Props"] = $row['properties'];
5475
            } else {
5476
                $pluginCode = $this->pluginCache[$pluginName] = "return false;";
5477
                $pluginProperties = '';
5478
            }
5479
        }
5480
        $plugin['code'] = $pluginCode;
5481
        $plugin['props'] = $pluginProperties;
5482
5483
        return $plugin;
5484
    }
5485
5486
    /**
5487
     * Parses a resource property string and returns the result as an array
5488
     *
5489
     * @param string $propertyString
5490
     * @param string|null $elementName
5491
     * @param string|null $elementType
5492
     * @return array Associative array in the form property name => property value
5493
     */
5494
    public function parseProperties($propertyString, $elementName = null, $elementType = null)
5495
    {
5496
        $propertyString = trim($propertyString);
5497
        $propertyString = str_replace('{}', '', $propertyString);
5498
        $propertyString = str_replace('} {', ',', $propertyString);
5499
        $property = array();
5500
        if (!empty($propertyString) && $propertyString != '{}') {
5501
            $jsonFormat = $this->isJson($propertyString, true);
5502
            // old format
5503
            if ($jsonFormat === false) {
5504
                $props = explode('&', $propertyString);
5505
                foreach ($props as $prop) {
5506
5507
                    if (empty($prop)) {
5508
                        continue;
5509
                    } elseif (strpos($prop, '=') === false) {
5510
                        $property[trim($prop)] = '';
5511
                        continue;
5512
                    }
5513
5514
                    $_ = explode('=', $prop, 2);
5515
                    $key = trim($_[0]);
5516
                    $p = explode(';', trim($_[1]));
5517
                    switch ($p[1]) {
5518
                        case 'list':
5519
                        case 'list-multi':
5520
                        case 'checkbox':
5521
                        case 'radio':
5522
                            $value = !isset($p[3]) ? '' : $p[3];
5523
                            break;
5524
                        default:
5525
                            $value = !isset($p[2]) ? '' : $p[2];
5526
                    }
5527
                    if (!empty($key)) {
5528
                        $property[$key] = $value;
5529
                    }
5530
                }
5531
                // new json-format
5532
            } else if (!empty($jsonFormat)) {
5533
                foreach ($jsonFormat as $key => $row) {
5534
                    if (!empty($key)) {
5535
                        if (is_array($row)) {
5536
                            if (isset($row[0]['value'])) {
5537
                                $value = $row[0]['value'];
5538
                            }
5539
                        } else {
5540
                            $value = $row;
5541
                        }
5542
                        if (isset($value) && $value !== '') {
5543
                            $property[$key] = $value;
5544
                        }
5545
                    }
5546
                }
5547
            }
5548
        }
5549
        if (!empty($elementName) && !empty($elementType)) {
5550
            $out = $this->invokeEvent('OnParseProperties', array(
5551
                'element' => $elementName,
5552
                'type' => $elementType,
5553
                'args' => $property
5554
            ));
5555
            if (is_array($out)) {
5556
                $out = array_pop($out);
5557
            }
5558
            if (is_array($out)) {
5559
                $property = $out;
5560
            }
5561
        }
5562
5563
        return $property;
5564
    }
5565
5566
    /**
5567
     * Parses docBlock from a file and returns the result as an array
5568
     *
5569
     * @param string $element_dir
5570
     * @param string $filename
5571
     * @param boolean $escapeValues
5572
     * @return array Associative array in the form property name => property value
5573
     */
5574
    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...
5575
    {
5576
        $params = array();
5577
        $fullpath = $element_dir . '/' . $filename;
5578
        if (is_readable($fullpath)) {
5579
            $tpl = @fopen($fullpath, "r");
5580
            if ($tpl) {
5581
                $params['filename'] = $filename;
5582
                $docblock_start_found = false;
5583
                $name_found = false;
5584
                $description_found = false;
5585
                $docblock_end_found = false;
5586
                $arrayParams = array('author', 'documentation', 'reportissues', 'link');
5587
5588
                while (!feof($tpl)) {
5589
                    $line = fgets($tpl);
5590
                    $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...
5591
                    $docblock_start_found = $r['docblock_start_found'];
5592
                    $name_found = $r['name_found'];
5593
                    $description_found = $r['description_found'];
5594
                    $docblock_end_found = $r['docblock_end_found'];
5595
                    $param = $r['param'];
5596
                    $val = $r['val'];
5597
                    if (!$docblock_end_found) {
5598
                        break;
5599
                    }
5600
                    if (!$docblock_start_found || !$name_found || !$description_found || empty($param)) {
5601
                        continue;
5602
                    }
5603 View Code Duplication
                    if (!empty($param)) {
5604
                        if (in_array($param, $arrayParams)) {
5605
                            if (!isset($params[$param])) {
5606
                                $params[$param] = array();
5607
                            }
5608
                            $params[$param][] = $escapeValues ? $this->db->escape($val) : $val;
5609
                        } else {
5610
                            $params[$param] = $escapeValues ? $this->db->escape($val) : $val;
5611
                        }
5612
                    }
5613
                }
5614
                @fclose($tpl);
5615
            }
5616
        }
5617
        return $params;
5618
    }
5619
5620
    /**
5621
     * Parses docBlock from string and returns the result as an array
5622
     *
5623
     * @param string $string
5624
     * @param boolean $escapeValues
5625
     * @return array Associative array in the form property name => property value
5626
     */
5627
    public function parseDocBlockFromString($string, $escapeValues = false)
5628
    {
5629
        $params = array();
5630
        if (!empty($string)) {
5631
            $string = str_replace('\r\n', '\n', $string);
5632
            $exp = explode('\n', $string);
5633
            $docblock_start_found = false;
5634
            $name_found = false;
5635
            $description_found = false;
5636
            $docblock_end_found = false;
5637
            $arrayParams = array('author', 'documentation', 'reportissues', 'link');
5638
5639
            foreach ($exp as $line) {
5640
                $r = $this->parseDocBlockLine($line, $docblock_start_found, $name_found, $description_found, $docblock_end_found);
5641
                $docblock_start_found = $r['docblock_start_found'];
5642
                $name_found = $r['name_found'];
5643
                $description_found = $r['description_found'];
5644
                $docblock_end_found = $r['docblock_end_found'];
5645
                $param = $r['param'];
5646
                $val = $r['val'];
5647
                if (!$docblock_start_found) {
5648
                    continue;
5649
                }
5650
                if ($docblock_end_found) {
5651
                    break;
5652
                }
5653 View Code Duplication
                if (!empty($param)) {
5654
                    if (in_array($param, $arrayParams)) {
5655
                        if (!isset($params[$param])) {
5656
                            $params[$param] = array();
5657
                        }
5658
                        $params[$param][] = $escapeValues ? $this->db->escape($val) : $val;
5659
                    } else {
5660
                        $params[$param] = $escapeValues ? $this->db->escape($val) : $val;
5661
                    }
5662
                }
5663
            }
5664
        }
5665
        return $params;
5666
    }
5667
5668
    /**
5669
     * Parses docBlock of a component´s source-code and returns the result as an array
5670
     * (modified parseDocBlock() from modules/stores/setup.info.php by Bumkaka & Dmi3yy)
5671
     *
5672
     * @param string $line
5673
     * @param boolean $docblock_start_found
5674
     * @param boolean $name_found
5675
     * @param boolean $description_found
5676
     * @param boolean $docblock_end_found
5677
     * @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...
5678
     */
5679
    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...
5680
    {
5681
        $param = '';
5682
        $val = '';
5683
        $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...
5684
        if (!$docblock_start_found) {
5685
            // find docblock start
5686
            if (strpos($line, '/**') !== false) {
5687
                $docblock_start_found = true;
5688
            }
5689
        } elseif (!$name_found) {
5690
            // find name
5691
            if (preg_match("/^\s+\*\s+(.+)/", $line, $ma)) {
5692
                $param = 'name';
5693
                $val = trim($ma[1]);
5694
                $name_found = !empty($val);
5695
            }
5696
        } elseif (!$description_found) {
5697
            // find description
5698
            if (preg_match("/^\s+\*\s+(.+)/", $line, $ma)) {
5699
                $param = 'description';
5700
                $val = trim($ma[1]);
5701
                $description_found = !empty($val);
5702
            }
5703
        } else {
5704
            if (preg_match("/^\s+\*\s+\@([^\s]+)\s+(.+)/", $line, $ma)) {
5705
                $param = trim($ma[1]);
5706
                $val = trim($ma[2]);
5707
                if (!empty($param) && !empty($val)) {
5708
                    if ($param == 'internal') {
5709
                        $ma = null;
5710
                        if (preg_match("/\@([^\s]+)\s+(.+)/", $val, $ma)) {
5711
                            $param = trim($ma[1]);
5712
                            $val = trim($ma[2]);
5713
                        }
5714
                    }
5715
                }
5716
            } elseif (preg_match("/^\s*\*\/\s*$/", $line)) {
5717
                $docblock_end_found = true;
5718
            }
5719
        }
5720
        return array(
5721
            'docblock_start_found' => $docblock_start_found,
5722
            'name_found' => $name_found,
5723
            'description_found' => $description_found,
5724
            'docblock_end_found' => $docblock_end_found,
5725
            'param' => $param,
5726
            'val' => $val
5727
        );
5728
    }
5729
5730
    /**
5731
     * Renders docBlock-parameters into human readable list
5732
     *
5733
     * @param array $parsed
5734
     * @return string List in HTML-format
5735
     */
5736
    public function convertDocBlockIntoList($parsed)
5737
    {
5738
        global $_lang;
5739
5740
        // Replace special placeholders & make URLs + Emails clickable
5741
        $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...
5742
        $regexUrl = "/((http|https|ftp|ftps)\:\/\/[^\/]+(\/[^\s]+[^,.?!:;\s])?)/";
5743
        $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';
5744
        $emailSubject = isset($parsed['name']) ? '?subject=' . $parsed['name'] : '';
5745
        $emailSubject .= isset($parsed['version']) ? ' v' . $parsed['version'] : '';
5746
        foreach ($parsed as $key => $val) {
5747
            if (is_array($val)) {
5748
                foreach ($val as $key2 => $val2) {
5749
                    $val2 = $this->parseText($val2, $ph);
5750 View Code Duplication
                    if (preg_match($regexUrl, $val2, $url)) {
5751
                        $val2 = preg_replace($regexUrl, "<a href=\"{$url[0]}\" target=\"_blank\">{$url[0]}</a> ", $val2);
5752
                    }
5753 View Code Duplication
                    if (preg_match($regexEmail, $val2, $url)) {
5754
                        $val2 = preg_replace($regexEmail, '<a href="mailto:\\1' . $emailSubject . '">\\1</a>', $val2);
5755
                    }
5756
                    $parsed[$key][$key2] = $val2;
5757
                }
5758
            } else {
5759
                $val = $this->parseText($val, $ph);
5760 View Code Duplication
                if (preg_match($regexUrl, $val, $url)) {
5761
                    $val = preg_replace($regexUrl, "<a href=\"{$url[0]}\" target=\"_blank\">{$url[0]}</a> ", $val);
5762
                }
5763 View Code Duplication
                if (preg_match($regexEmail, $val, $url)) {
5764
                    $val = preg_replace($regexEmail, '<a href="mailto:\\1' . $emailSubject . '">\\1</a>', $val);
5765
                }
5766
                $parsed[$key] = $val;
5767
            }
5768
        }
5769
5770
        $arrayParams = array(
5771
            'documentation' => $_lang['documentation'],
5772
            'reportissues' => $_lang['report_issues'],
5773
            'link' => $_lang['further_info'],
5774
            'author' => $_lang['author_infos']
5775
        );
5776
5777
        $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...
5778
        $list = isset($parsed['logo']) ? '<img src="' . $this->config['base_url'] . ltrim($parsed['logo'], "/") . '" style="float:right;max-width:100px;height:auto;" />' . $nl : '';
5779
        $list .= '<p>' . $nl;
5780
        $list .= isset($parsed['name']) ? '<strong>' . $parsed['name'] . '</strong><br/>' . $nl : '';
5781
        $list .= isset($parsed['description']) ? $parsed['description'] . $nl : '';
5782
        $list .= '</p><br/>' . $nl;
5783
        $list .= isset($parsed['version']) ? '<p><strong>' . $_lang['version'] . ':</strong> ' . $parsed['version'] . '</p>' . $nl : '';
5784
        $list .= isset($parsed['license']) ? '<p><strong>' . $_lang['license'] . ':</strong> ' . $parsed['license'] . '</p>' . $nl : '';
5785
        $list .= isset($parsed['lastupdate']) ? '<p><strong>' . $_lang['last_update'] . ':</strong> ' . $parsed['lastupdate'] . '</p>' . $nl : '';
5786
        $list .= '<br/>' . $nl;
5787
        $first = true;
5788
        foreach ($arrayParams as $param => $label) {
5789
            if (isset($parsed[$param])) {
5790
                if ($first) {
5791
                    $list .= '<p><strong>' . $_lang['references'] . '</strong></p>' . $nl;
5792
                    $list .= '<ul class="docBlockList">' . $nl;
5793
                    $first = false;
5794
                }
5795
                $list .= '    <li><strong>' . $label . '</strong>' . $nl;
5796
                $list .= '        <ul>' . $nl;
5797
                foreach ($parsed[$param] as $val) {
5798
                    $list .= '            <li>' . $val . '</li>' . $nl;
5799
                }
5800
                $list .= '        </ul></li>' . $nl;
5801
            }
5802
        }
5803
        $list .= !$first ? '</ul>' . $nl : '';
5804
5805
        return $list;
5806
    }
5807
5808
    /**
5809
     * @param string $string
5810
     * @return string
5811
     */
5812
    public function removeSanitizeSeed($string = '')
5813
    {
5814
        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...
5815
5816
        if (!$string || strpos($string, $sanitize_seed) === false) {
5817
            return $string;
5818
        }
5819
5820
        return str_replace($sanitize_seed, '', $string);
5821
    }
5822
5823
    /**
5824
     * @param string $content
5825
     * @return string
5826
     */
5827
    public function cleanUpMODXTags($content = '')
5828
    {
5829
        if ($this->minParserPasses < 1) {
5830
            return $content;
5831
        }
5832
5833
        $enable_filter = $this->config['enable_filter'];
5834
        $this->config['enable_filter'] = 1;
5835
        $_ = 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...
5836
        foreach ($_ as $brackets) {
5837
            list($left, $right) = explode(' ', $brackets);
5838
            if (strpos($content, $left) !== false) {
5839
                if ($left === '[*') {
5840
                    $content = $this->mergeDocumentContent($content);
5841
                } elseif ($left === '[(') {
5842
                    $content = $this->mergeSettingsContent($content);
5843
                } elseif ($left === '{{') {
5844
                    $content = $this->mergeChunkContent($content);
5845
                } elseif ($left === '[[') {
5846
                    $content = $this->evalSnippets($content);
5847
                }
5848
            }
5849
        }
5850
        foreach ($_ as $brackets) {
5851
            list($left, $right) = explode(' ', $brackets);
5852
            if (strpos($content, $left) !== false) {
5853
                $matches = $this->getTagsFromContent($content, $left, $right);
5854
                $content = str_replace($matches[0], '', $content);
5855
            }
5856
        }
5857
        $this->config['enable_filter'] = $enable_filter;
5858
        return $content;
5859
    }
5860
5861
    /**
5862
     * @param string $str
5863
     * @param string $allowable_tags
5864
     * @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...
5865
     */
5866
    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...
5867
    {
5868
        $str = strip_tags($str, $allowable_tags);
5869
        modx_sanitize_gpc($str);
5870
        return $str;
5871
    }
5872
5873
    /**
5874
     * @param string $name
5875
     * @param string $phpCode
5876
     */
5877
    public function addSnippet($name, $phpCode)
5878
    {
5879
        $this->snippetCache['#' . $name] = $phpCode;
5880
    }
5881
5882
    /**
5883
     * @param string $name
5884
     * @param string $text
5885
     */
5886
    public function addChunk($name, $text)
5887
    {
5888
        $this->chunkCache['#' . $name] = $text;
5889
    }
5890
5891
    /**
5892
     * @param string $phpcode
5893
     * @param string $evalmode
5894
     * @param string $safe_functions
5895
     * @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...
5896
     */
5897
    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...
5898
    {
5899
        if ($evalmode == '') {
5900
            $evalmode = $this->config['allow_eval'];
5901
        }
5902
        if ($safe_functions == '') {
5903
            $safe_functions = $this->config['safe_functions_at_eval'];
5904
        }
5905
5906
        modx_sanitize_gpc($phpcode);
5907
5908
        switch ($evalmode) {
5909
            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...
5910
                $isSafe = $this->isSafeCode($phpcode, $safe_functions);
0 ignored issues
show
Bug introduced by
It seems like $phpcode can also be of type array; however, DocumentParser::isSafeCode() does only seem to accept string, maybe add an additional type check?

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

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

    return array();
}

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

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

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

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

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

    return array();
}

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

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

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

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

Loading history...
6594
     */
6595
    public function htmlspecialchars($str, $flags = ENT_COMPAT, $encode = '')
6596
    {
6597
        $this->loadExtension('PHPCOMPAT');
6598
        return $this->phpcompat->htmlspecialchars($str, $flags, $encode);
6599
    }
6600
6601
    /**
6602
     * @param $string
6603
     * @param bool $returnData
6604
     * @return bool|mixed
6605
     */
6606
    public function isJson($string, $returnData = false)
6607
    {
6608
        $data = json_decode($string, true);
6609
        return (json_last_error() == JSON_ERROR_NONE) ? ($returnData ? $data : true) : false;
6610
    }
6611
6612
    /**
6613
     * @param $key
6614
     * @return array
6615
     */
6616
    public function splitKeyAndFilter($key)
6617
    {
6618
        if ($this->config['enable_filter'] == 1 && strpos($key, ':') !== false && stripos($key, '@FILE') !== 0) {
6619
            list($key, $modifiers) = explode(':', $key, 2);
6620
        } else {
6621
            $modifiers = false;
6622
        }
6623
6624
        $key = trim($key);
6625
        if ($modifiers !== false) {
6626
            $modifiers = trim($modifiers);
6627
        }
6628
6629
        return array($key, $modifiers);
6630
    }
6631
6632
    /**
6633
     * @param string $value
6634
     * @param bool $modifiers
6635
     * @param string $key
6636
     * @return string
6637
     */
6638
    public function applyFilter($value = '', $modifiers = false, $key = '')
6639
    {
6640
        if ($modifiers === false || $modifiers == 'raw') {
6641
            return $value;
6642
        }
6643
        if ($modifiers !== false) {
6644
            $modifiers = trim($modifiers);
6645
        }
6646
6647
        $this->loadExtension('MODIFIERS');
6648
        return $this->filter->phxFilter($key, $value, $modifiers);
0 ignored issues
show
Bug introduced by
It seems like $modifiers defined by parameter $modifiers on line 6638 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...
6649
    }
6650
6651
    // End of class.
6652
6653
6654
    /**
6655
     * Get Clean Query String
6656
     *
6657
     * Fixes the issue where passing an array into the q get variable causes errors
6658
     *
6659
     */
6660
    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...
6661
    {
6662
        $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...
6663
6664
        //Return null if the query doesn't exist
6665
        if (empty($q)) {
6666
            return null;
6667
        }
6668
6669
        //If we have a string, return it
6670
        if (is_string($q)) {
6671
            return $q;
6672
        }
6673
6674
        //If we have an array, return the first element
6675
        if (is_array($q)) {
6676
            return $q[0];
6677
        }
6678
    }
6679
6680
    /**
6681
     * @param string $title
6682
     * @param string $msg
6683
     * @param int $type
6684
     */
6685
    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...
6686
    {
6687
        if ($title === '') {
6688
            $title = 'no title';
6689
        }
6690
        if (is_array($msg)) {
6691
            $msg = '<pre>' . print_r($msg, true) . '</pre>';
6692
        } elseif ($msg === '') {
6693
            $msg = $_SERVER['REQUEST_URI'];
6694
        }
6695
        $this->logEvent(0, $type, $msg, $title);
6696
    }
6697
6698
}
6699
6700
/**
6701
 * System Event Class
6702
 */
6703
class SystemEvent
0 ignored issues
show
Coding Style introduced by
The property $_propagate is not named in camelCase.

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

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

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

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

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

Loading history...
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

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

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

namespace YourVendor;

class YourClass { }

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

Loading history...
6704
{
6705
    public $name = '';
6706
    public $_propagate = true;
6707
    /**
6708
     * @deprecated use setOutput(), getOutput()
6709
     * @var string
6710
     */
6711
    public $_output;
6712
    public $activated = false;
6713
    public $activePlugin = '';
6714
    public $params = array();
6715
6716
    /**
6717
     * @param string $name Name of the event
6718
     */
6719
    public function __construct($name = "")
6720
    {
6721
        $this->_resetEventObject();
6722
        $this->name = $name;
6723
    }
6724
6725
    /**
6726
     * Display a message to the user
6727
     *
6728
     * @global array $SystemAlertMsgQueque
6729
     * @param string $msg The message
6730
     */
6731
    public function alert($msg)
6732
    {
6733
        global $SystemAlertMsgQueque;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

    public function myFunction() {
        // Do something
    }
}
Loading history...
6734
        if ($msg == "") {
6735
            return;
6736
        }
6737
        if (is_array($SystemAlertMsgQueque)) {
6738
            $title = '';
6739
            if ($this->name && $this->activePlugin) {
6740
                $title = "<div><b>" . $this->activePlugin . "</b> - <span style='color:maroon;'>" . $this->name . "</span></div>";
6741
            }
6742
            $SystemAlertMsgQueque[] = "$title<div style='margin-left:10px;margin-top:3px;'>$msg</div>";
6743
        }
6744
    }
6745
6746
    /**
6747
     * Output
6748
     *
6749
     * @param string $msg
6750
     * @deprecated see addOutput
6751
     */
6752
    public function output($msg)
6753
    {
6754
        $this->_output .= $msg;
0 ignored issues
show
Deprecated Code introduced by
The property SystemEvent::$_output has been deprecated with message: use setOutput(), getOutput()

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...
6755
    }
6756
6757
    /**
6758
     * @param mixed $data
6759
     */
6760
    public function setOutput($data)
6761
    {
6762
        $this->_output = $data;
0 ignored issues
show
Deprecated Code introduced by
The property SystemEvent::$_output has been deprecated with message: use setOutput(), getOutput()

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...
6763
    }
6764
6765
    /**
6766
     * @return mixed
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string.

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

Loading history...
6767
     */
6768
    public function getOutput()
6769
    {
6770
        return $this->_output;
0 ignored issues
show
Deprecated Code introduced by
The property SystemEvent::$_output has been deprecated with message: use setOutput(), getOutput()

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...
6771
    }
6772
6773
    /**
6774
     * Stop event propogation
6775
     */
6776
    public function stopPropagation()
6777
    {
6778
        $this->_propagate = false;
6779
    }
6780
6781
    public function _resetEventObject()
6782
    {
6783
        unset ($this->returnedValues);
6784
        $this->name = "";
6785
        $this->setOutput(null);
6786
        $this->_propagate = true;
6787
        $this->activated = false;
6788
    }
6789
}
6790