Completed
Push — develop ( 5cb106...80f130 )
by Dmytro
13:36
created

Core   F

Complexity

Total Complexity 1426

Size/Duplication

Total Lines 6976
Duplicated Lines 6.59 %

Coupling/Cohesion

Components 1
Dependencies 9

Importance

Changes 0
Metric Value
dl 460
loc 6976
rs 0.8
c 0
b 0
f 0
wmc 1426
lcom 1
cbo 9

189 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 2
A getService() 0 12 3
A hasService() 0 4 1
B createService() 0 24 8
A checkServiceAlias() 0 9 3
A resolveServiceArguments() 0 13 3
A initializeService() 0 13 6
A getDatabase() 0 4 1
A getMail() 0 4 1
A getPhpCompat() 0 4 1
A getPasswordHash() 0 4 1
A getMakeTable() 0 4 1
A getExportSite() 0 4 1
A getDeprecatedCore() 0 4 1
A getManagerApi() 0 4 1
A getModifiers() 0 4 1
A initialize() 0 18 4
A __clone() 0 3 1
A getInstance() 0 7 2
A __get() 0 15 3
B __call() 0 34 8
A checkSQLconnect() 0 8 5
C loadExtension() 0 24 12
A getMicroTime() 0 5 1
C sendRedirect() 0 48 15
A sendForward() 0 17 3
A sendErrorPage() 0 12 3
A sendUnauthorizedPage() 0 18 4
A getSettings() 0 29 4
B recoverySiteCache() 0 43 8
F getUserSettings() 3 72 21
A getDocumentIdentifier() 0 20 5
A isLoggedIn() 0 14 5
A checkSession() 0 4 1
A checkPreview() 0 12 4
A checkSiteStatus() 0 12 3
F cleanDocumentIdentifier() 6 55 22
A getCacheFolder() 0 4 1
A getHashFile() 0 4 1
B makePageCacheKey() 0 20 6
A checkCache() 0 4 1
F getDocumentObjectFromCache() 0 85 16
F outputContent() 0 126 25
A RecoveryEscapedTags() 0 5 1
A getTagsForEscape() 0 9 2
A getTimerStats() 0 17 3
B setConditional() 0 20 11
B updatePubStatus() 14 51 7
A checkPublishStatus() 0 4 1
B postProcess() 0 35 10
A getTagsFromContent() 0 12 3
F _getTagsFromContent() 0 84 30
F mergeDocumentContent() 13 64 19
F _contextValue() 62 111 30
B mergeSettingsContent() 10 41 11
D mergeChunkContent() 5 61 14
C mergePlaceholderContent() 10 51 13
B mergeConditionalTagsContent() 20 44 7
B _prepareCTag() 0 27 8
F _parseCTagCMD() 0 61 20
A ignoreCommentedTagsContent() 0 18 5
B escapeLiteralTagsContent() 5 23 6
B detectError() 0 12 7
B evalPlugin() 12 40 8
B evalSnippet() 10 43 11
C evalSnippets() 10 50 13
B _getSGVar() 0 31 8
B _get_snip_result() 0 46 7
F getParamsFromString() 3 121 31
D _getSplitPosition() 0 53 20
A _split_snip_call() 6 25 4
B _getSnippetObject() 0 42 8
A toAlias() 0 5 1
B makeFriendlyURL() 0 28 8
C rewriteUrls() 0 61 15
F sendStrictURI() 6 64 19
F getDocumentObject() 0 91 23
C parseDocumentSource() 0 43 11
F executeParser() 0 127 20
A mb_basename() 0 5 1
B _IIS_furl_fix() 0 31 7
D prepareResponse() 0 72 13
A _sendErrorForUnpubPage() 0 16 3
A _sendRedirectForRefPage() 0 11 3
A _getTemplateCodeFromDB() 0 13 2
B getParentIds() 0 20 8
A getUltimateParentId() 0 12 4
D getChildIds() 0 66 19
A webAlertAndQuit() 0 34 4
A hasPermission() 0 9 2
B elementIsLocked() 0 21 6
B getLockedElements() 0 31 6
A buildLockedElementsCache() 0 22 3
A cleanupExpiredLocks() 0 23 4
B cleanupMultipleActiveUsers() 0 38 11
A determineLockState() 0 16 4
A lockElement() 0 13 6
A unlockElement() 0 16 6
B updateValidatedUserSession() 9 25 6
B logEvent() 0 46 10
F sendmail() 14 82 33
A rotate_log() 0 15 3
A isBackend() 0 4 2
A isFrontend() 0 4 1
A getAllChildren() 25 25 5
A getActiveChildren() 27 27 5
B getDocumentChildren() 0 39 10
F getDocuments() 0 49 13
A getDocument() 14 14 3
B getField() 0 29 7
B getPageInfo() 0 29 7
A getParent() 0 15 6
A getSnippetId() 0 11 3
A getSnippetName() 0 4 1
B clearCache() 0 38 11
F makeUrl() 0 92 25
B getAliasListing() 0 30 9
A getConfig() 0 4 1
C getVersionData() 0 15 11
A runSnippet() 0 25 4
A getChunk() 0 26 5
C parseText() 10 48 14
A parseChunk() 0 9 2
B getTpl() 0 31 8
B toDateFormat() 0 34 9
B toTimeStamp() 18 42 11
C getDocumentChildrenTVars() 5 65 15
C getDocumentChildrenTVarOutput() 0 42 13
A getTemplateVar() 9 9 3
C getTemplateVars() 5 59 16
B getTemplateVarOutput() 0 29 10
A getFullTableName() 0 4 1
A getPlaceholder() 0 4 2
A setPlaceholder() 0 4 1
A toPlaceholders() 0 11 4
A toPlaceholder() 0 8 3
A getManagerPath() 0 4 1
A getCachePath() 0 4 1
A sendAlert() 10 25 4
B getLoginUserID() 22 22 8
B getLoginUserName() 22 22 8
A getLoginUserType() 0 10 5
A getUserInfo() 3 24 5
A getWebUserInfo() 3 11 4
C getUserDocGroups() 0 31 14
B changeWebUserPassword() 0 31 6
B isMemberOfWebGroup() 0 21 6
A regClientCSS() 0 15 6
A regClientStartupScript() 0 4 1
F regClientScript() 0 70 23
A regClientStartupHTMLBlock() 0 4 1
A regClientHTMLBlock() 0 4 1
A stripTags() 0 11 1
A addEventListener() 0 10 4
A removeEventListener() 0 7 2
A removeAllEventListener() 0 5 1
C invokeEvent() 0 61 12
A getPluginCode() 0 22 4
D parseProperties() 7 77 27
C parseDocBlockFromFile() 10 45 14
B parseDocBlockFromString() 10 40 10
C parseDocBlockLine() 0 50 13
F convertDocBlockIntoList() 12 71 21
A removeSanitizeSeed() 0 10 3
B cleanUpMODXTags() 0 33 10
A strip_tags() 0 6 1
A addSnippet() 0 5 1
A addChunk() 0 4 1
B findElements() 0 41 10
B safeEval() 0 45 10
B isSafeCode() 0 37 11
B atBindFileContent() 3 47 10
A getExtFromFilename() 0 10 2
B phpError() 13 39 11
F messageQuit() 11 200 42
C get_backtrace() 0 74 16
A getRegisteredClientScripts() 0 4 1
A getRegisteredClientStartupScripts() 0 4 1
A stripAlias() 0 17 2
A nicesize() 0 12 3
B getHiddenIdFromAlias() 0 24 6
B getIdFromAlias() 0 40 10
B atBindInclude() 3 42 11
A htmlspecialchars() 0 4 1
A isJson() 0 5 3
A splitKeyAndFilter() 0 15 5
A applyFilter() 0 11 4
A _getCleanQueryString() 0 19 5
A addLog() 0 12 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Core often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Core, and based on these observations, apply Extract Interface, too.

1
<?php namespace EvolutionCMS;
2
3
/**
4
 * @property Mail $mail
5
 *      $this->loadExtension('MODxMailer');
6
 * @property Database $db
7
 *      $this->loadExtension('DBAPI')
8
 * @property Legacy\PhpCompat $phpcompat
9
 *      $this->loadExtension('PHPCOMPAT');
10
 * @property Legacy\Modifiers $filter
11
 *      $this->loadExtension('MODIFIERS');
12
 * @property Legacy\ExportSite $export
13
 *      $this->loadExtension('EXPORT_SITE');
14
 * @property Support\MakeTable $table
15
 *      $this->loadExtension('makeTable');
16
 * @property Legacy\ManagerApi $manager
17
 *      $this->loadExtension('ManagerAPI');
18
 * @property Legacy\PasswordHash $phpass
19
 *      $this->loadExtension('phpass');
20
 */
21
class Core implements Interfaces\CoreInterface
0 ignored issues
show
Coding Style introduced by
The property $table_prefix is not named in camelCase.

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

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

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

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

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

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

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

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

Loading history...
22
{
23
    /**
24
     * This is New evolution
25
     * @var string
26
     */
27
    public $apiVersion = '1.0.0';
28
29
    /**
30
     * event object
31
     * @var Event
32
     */
33
34
    public $event;
35
    /**
36
     * event object
37
     * @var Event
38
     * @deprecated
39
     */
40
    public $Event;
41
42
    /**
43
     * @var array
44
     */
45
    public $pluginEvent = array();
46
47
    /**
48
     * @var array
49
     */
50
    public $config = array();
51
    /**
52
     * @var array
53
     */
54
    public $configGlobal = null; // contains backup of settings overwritten by user-settings
55
    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...
56
    public $result;
57
    public $sql;
58
    public $table_prefix;
59
    public $debug = false;
60
    public $documentIdentifier;
61
    public $documentMethod;
62
    public $documentGenerated;
63
    public $documentContent;
64
    public $documentOutput;
65
    public $tstart;
66
    public $mstart;
67
    public $minParserPasses;
68
    public $maxParserPasses;
69
    public $documentObject;
70
    public $templateObject;
71
    public $snippetObjects;
72
    public $stopOnNotice = false;
73
    public $executedQueries;
74
    public $queryTime;
75
    public $currentSnippet;
76
    public $documentName;
77
    public $aliases;
78
    public $visitor;
79
    public $entrypage;
80
    public $documentListing;
81
    /**
82
     * feed the parser the execution start time
83
     * @var bool
84
     */
85
    public $dumpSnippets = false;
86
    public $snippetsCode;
87
    public $snippetsTime = array();
88
    public $chunkCache;
89
    public $snippetCache;
90
    public $contentTypes;
91
    public $dumpSQL = false;
92
    public $queryCode;
93
    public $virtualDir;
94
    public $placeholders;
95
    public $sjscripts = array();
96
    public $jscripts = array();
97
    public $loadedjscripts = array();
98
    public $documentMap;
99
    public $forwards = 3;
100
    public $error_reporting = 1;
101
    public $dumpPlugins = false;
102
    public $pluginsCode;
103
    public $pluginsTime = array();
104
    public $pluginCache = array();
105
    public $aliasListing;
106
    public $lockedElements = null;
107
    public $tmpCache = array();
108
    private $version = array();
109
    public $extensions = array();
110
    public $cacheKey = null;
111
    public $recentUpdate = 0;
112
    public $useConditional = false;
113
    protected $systemCacheKey = null;
114
    public $snipLapCount = 0;
115
    public $messageQuitCount;
116
    public $time;
117
    public $sid;
118
    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...
119
    public $decoded_request_uri;
120
    /**
121
     * @var Legacy\DeprecatedCore
122
     * @deprecated use ->getDeprecatedCore()
123
     */
124
    public $old;
125
126
    /**
127
     * Hold the class instance.
128
     * @var self
129
     */
130
    private static $instance = null;
131
132
    private $services;
133
    private $serviceStore = [];
134
135
    /**
136
     * @var array
137
     * $this->{$key}
138
     */
139
    public $providerAliases = [
140
        'db' => Interfaces\DatabaseInterface::class,
141
        'mail' => Interfaces\MailInterface::class,
142
        'phpcompat' => Interfaces\PhpCompatInterface::class,
143
        'phpass' => Interfaces\PasswordHashInterface::class,
144
        'table' => Interfaces\MakeTableInterface::class,
145
        'export' => Interfaces\ExportSiteInerface::class,
146
        'manager' => Interfaces\ManagerApiInterface::class,
147
        'filter' => Interfaces\ModifiersInterface::class
148
    ];
149
150
    /**
151
     * @var array
152
     * $this->loadExtension($key)
153
     */
154
    public $extensionAlias = [
155
        'DBAPI' => Interfaces\DatabaseInterface::class,
156
        'MODxMailer' => Interfaces\MailInterface::class,
157
        'PHPCOMPAT' => Interfaces\PhpCompatInterface::class,
158
        'phpass' => Interfaces\PasswordHashInterface::class,
159
        'makeTable' => Interfaces\MakeTableInterface::class,
160
        'EXPORT_SITE' => Interfaces\ExportSiteInerface::class,
161
        'ManagerAPI' => Interfaces\ManagerApiInterface::class,
162
        'MODIFIERS' => Interfaces\ModifiersInterface::class
163
    ];
164
165
    /**
166
     * @param array $services
167
     * @param array $parameters
0 ignored issues
show
Bug introduced by
There is no parameter named $parameters. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
168
     */
169
    public function __construct(array $services = array())
170
    {
171
        self::$instance = $this;
172
        if (empty($services)) {
173
            $services   = include EVO_SERVICES_FILE;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned correctly; expected 1 space but found 3 spaces

This check looks for improperly formatted assignments.

Every assignment must have exactly one space before and one space after the equals operator.

To illustrate:

$a = "a";
$ab = "ab";
$abc = "abc";

will have no issues, while

$a   = "a";
$ab  = "ab";
$abc = "abc";

will report issues in lines 1 and 2.

Loading history...
174
        }
175
        $this->services     =  $services;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned correctly; expected 1 space but found 5 spaces

This check looks for improperly formatted assignments.

Every assignment must have exactly one space before and one space after the equals operator.

To illustrate:

$a = "a";
$ab = "ab";
$abc = "abc";

will have no issues, while

$a   = "a";
$ab  = "ab";
$abc = "abc";

will report issues in lines 1 and 2.

Loading history...
176
177
        $this->initialize();
178
    }
179
180
    /**
181
     * {@inheritDoc}
182
     */
183
    public function getService($name)
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...
184
    {
185
        if (!$this->hasService($name)) {
186
            throw new Exceptions\ServiceNotFoundException('Service not found: '.$name);
187
        }
188
        // If we haven't created it, create it and save to store
189
        if (!isset($this->serviceStore[$name])) {
190
            $this->serviceStore[$name] = $this->createService($name);
191
        }
192
        // Return service from store
193
        return $this->serviceStore[$name];
194
    }
195
    /**
196
     * {@inheritDoc}
197
     */
198
    public function hasService($name)
199
    {
200
        return isset($this->services[$name]);
201
    }
202
203
    /**
204
     * Attempt to create a service.
205
     *
206
     * @param string $name The service name.
207
     *
208
     * @return mixed The created service.
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use object.

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...
209
     *
210
     * @throws Exceptions\ContainerException On failure.
211
     */
212
    private function createService($name)
213
    {
214
        $entry = &$this->services[$name];
215
        if (!is_array($entry) || !isset($entry['class'])) {
216
            throw new Exceptions\ContainerException($name.' service entry must be an array containing a \'class\' key');
217
        } elseif (!class_exists($entry['class'])) {
218
            throw new Exceptions\ContainerException($name.' service class does not exist: '.$entry['class']);
219
        } elseif (isset($entry['lock'])) {
220
            throw new Exceptions\ContainerException($name.' contains circular reference');
221
        }
222
        $entry['lock'] = true;
223
        $arguments = isset($entry['arguments']) ? $this->resolveServiceArguments($entry['arguments']) : [];
0 ignored issues
show
Documentation introduced by
$entry['arguments'] 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...
224
        $reflector = new \ReflectionClass($entry['class']);
225
        $service = $reflector->newInstanceArgs($arguments);
226
        if (isset($entry['calls'])) {
227
            $this->initializeService($service, $name, $entry['calls']);
0 ignored issues
show
Documentation introduced by
$entry['calls'] 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...
228
        }
229
230
        if ($alias = $this->checkServiceAlias($name)) {
231
            $this->{$alias} = $service;
232
        }
233
234
        return $service;
235
    }
236
237
    private function checkServiceAlias($name){
238
        foreach ($this->providerAliases as $alias => $interface) {
239
            if($name === $interface) {
240
                return $alias;
241
            }
242
        }
243
244
        return false;
245
    }
246
247
    /**
248
     * Resolve argument definitions into an array of arguments.
249
     *
250
     * @param array  $argumentDefinitions The service arguments definition.
251
     *
252
     * @return array The service constructor arguments.
253
     *
254
     * @throws Exceptions\ContainerException On failure.
255
     */
256
    private function resolveServiceArguments(array $argumentDefinitions)
257
    {
258
        $arguments = [];
259
        foreach ($argumentDefinitions as $argumentDefinition) {
260
            if ($argumentDefinition instanceof Interfaces\ServiceProviderInterface) {
261
                $argumentServiceName = $argumentDefinition->getName();
262
                $arguments[] = $this->getService($argumentServiceName);
263
            } else {
264
                $arguments[] = $argumentDefinition;
265
            }
266
        }
267
        return $arguments;
268
    }
269
    /**
270
     * Initialize a service using the call definitions.
271
     *
272
     * @param object $service         The service.
273
     * @param string $name            The service name.
274
     * @param array  $callDefinitions The service calls definition.
275
     *
276
     * @throws Exceptions\ContainerException On failure.
277
     */
278
    private function initializeService($service, $name, array $callDefinitions)
279
    {
280
        foreach ($callDefinitions as $callDefinition) {
281
            if (!is_array($callDefinition) || !isset($callDefinition['method'])) {
282
                throw new Exceptions\ContainerException($name.' service calls must be arrays containing a \'method\' key');
283
            } elseif (!is_callable([$service, $callDefinition['method']])) {
284
                throw new Exceptions\ContainerException($name.' service asks for call to uncallable method: '.$callDefinition['method']);
285
            }
286
            $arguments = isset($callDefinition['arguments']) ? $this->resolveServiceArguments($callDefinition['arguments']) : [];
287
288
            call_user_func_array([$service, $callDefinition['method']], $arguments);
289
        }
290
    }
291
292
    /**
293
     * @return Database
294
     * @throws Exceptions\ServiceNotFoundException
295
     */
296
    public function getDatabase()
297
    {
298
        return $this->getService(Interfaces\DatabaseInterface::class);
299
    }
300
301
    /**
302
     * @return Mail
303
     * @throws Exceptions\ServiceNotFoundException
304
     */
305
    public function getMail()
306
    {
307
        return $this->getService(Interfaces\MailInterface::class);
308
    }
309
310
    /**
311
     * @return Legacy\PhpCompat
312
     * @throws Exceptions\ServiceNotFoundException
313
     */
314
    public function getPhpCompat()
315
    {
316
        return $this->getService(Interfaces\PhpCompatInterface::class);
317
    }
318
319
    /**
320
     * @return Legacy\PasswordHash
321
     * @throws Exceptions\ServiceNotFoundException
322
     */
323
    public function getPasswordHash()
324
    {
325
        return $this->getService(Interfaces\PasswordHashInterface::class);
326
    }
327
328
    /**
329
     * @return Support\MakeTable
330
     * @throws Exceptions\ServiceNotFoundException
331
     */
332
    public function getMakeTable()
333
    {
334
        return $this->getService(Interfaces\MakeTableInterface::class);
335
    }
336
337
    /**
338
     * @return Legacy\ExportSite
339
     * @throws Exceptions\ServiceNotFoundException
340
     */
341
    public function getExportSite()
342
    {
343
        return $this->getService(Interfaces\ExportSiteInerface::class);
344
    }
345
346
    /**
347
     * @return mixed
348
     * @throws Exceptions\ServiceNotFoundException
349
     */
350
    public function getDeprecatedCore()
351
    {
352
        return $this->getService(Interfaces\DeprecatedCoreInterface::class);
353
    }
354
355
    /**
356
     * @return mixed
357
     * @throws Exceptions\ServiceNotFoundException
358
     */
359
    public function getManagerApi()
360
    {
361
        return $this->getService(Interfaces\ManagerApiInterface::class);
362
    }
363
364
    /**
365
     * @return mixed
366
     * @throws Exceptions\ServiceNotFoundException
367
     */
368
    public function getModifiers()
369
    {
370
        return $this->getService(Interfaces\ModifiersInterface::class);
371
    }
372
373
    public function initialize()
0 ignored issues
show
Coding Style introduced by
initialize 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...
374
    {
375
        if ($this->isLoggedIn()) {
376
            ini_set('display_errors', 1);
377
        }
378
        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...
379
        if (substr(PHP_OS, 0, 3) === 'WIN' && $database_server === 'localhost') {
380
            $database_server = '127.0.0.1';
381
        }
382
        // events
383
        $this->event = new Event();
384
        $this->Event = &$this->event; //alias for backward compatibility
0 ignored issues
show
Deprecated Code introduced by
The property EvolutionCMS\Core::$Event has been deprecated.

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

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

Loading history...
385
        // set track_errors ini variable
386
        @ ini_set("track_errors", "1"); // enable error tracking in $php_errormsg
387
        $this->time = $_SERVER['REQUEST_TIME']; // for having global timestamp
388
389
        $this->q = self::_getCleanQueryString();
390
    }
391
392
    final public function __clone()
393
    {
394
    }
395
396
    /**
397
     * @param array $services
398
     * @return self
399
     */
400
    public static function getInstance(array $services = array())
401
    {
402
        if (self::$instance === null) {
403
            self::$instance = new static($services);
404
        }
405
        return self::$instance;
406
    }
407
408
    /**
409
     * @param string $name
410
     * @return mixed|null
411
     * @throws Exceptions\ServiceNotFoundException
412
     */
413
    public function __get($name)
414
    {
415
        if (isset($this->providerAliases[$name])) {
416
            if ($this->getConfig('error_reporting', 0) > 1) {
417
                trigger_error(
418
                    'Property $' . $name . ' is deprecated and should no longer be used. ' .
419
                    'Alternative ->getService(' . $this->providerAliases[$name] . '::class)',
420
                    E_USER_DEPRECATED
421
                );
422
            }
423
            return $this->getService($this->providerAliases[$name]);
424
        }
425
426
        return null;
427
    }
428
429
    /**
430
     * @param $method_name
431
     * @param $arguments
432
     * @return mixed
433
     */
434
    function __call($method_name, $arguments)
0 ignored issues
show
Coding Style Naming introduced by
The parameter $method_name is not named in camelCase.

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

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

Loading history...
435
    {
436
        $old = $this->getDeprecatedCore();
437
        //////////@TODO LOAD DeprecatedCore
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...
438
        if (method_exists($old, $method_name)) {
439
            $error_type = 1;
440
        } else {
441
            $error_type = 3;
442
        }
443
444
        if (!isset($this->config['error_reporting']) || 1 < $this->config['error_reporting']) {
445
            if ($error_type == 1) {
446
                $title = 'Call deprecated method';
447
                $msg = $this->getPhpCompat()->htmlspecialchars("\$modx->{$method_name}() is deprecated function");
448
            } else {
449
                $title = 'Call undefined method';
450
                $msg = $this->getPhpCompat()->htmlspecialchars("\$modx->{$method_name}() is undefined function");
451
            }
452
            $info = debug_backtrace();
453
            $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...
454
            if (!empty($this->currentSnippet)) {
455
                $m[] = 'Snippet - ' . $this->currentSnippet;
456
            } elseif (!empty($this->event->activePlugin)) {
457
                $m[] = 'Plugin - ' . $this->event->activePlugin;
458
            }
459
            $m[] = $this->decoded_request_uri;
460
            $m[] = str_replace('\\', '/', $info[0]['file']) . '(line:' . $info[0]['line'] . ')';
461
            $msg = implode('<br />', $m);
462
            $this->logEvent(0, $error_type, $msg, $title);
463
        }
464
        if (method_exists($old, $method_name)) {
465
            return call_user_func_array(array($old, $method_name), $arguments);
466
        }
467
    }
468
469
    /**
470
     * @param string $connector
471
     * @return bool
472
     */
473
    public function checkSQLconnect($connector = 'db')
474
    {
475
        $flag = false;
476
        if (is_scalar($connector) && !empty($connector) && isset($this->{$connector}) && $this->{$connector} instanceof Interfaces\DatabaseInterface) {
477
            $flag = (bool)$this->{$connector}->conn;
478
        }
479
        return $flag;
480
    }
481
482
    /**
483
     * Loads an extension from the extenders folder.
484
     * You can load any extension creating a boot file:
485
     * MODX_MANAGER_PATH."includes/extenders/ex_{$extname}.inc.php"
486
     * $extname - extension name in lowercase
487
     *
488
     * @deprecated use getService
489
     * @param $extname
490
     * @param bool $reload
491
     * @return bool
492
     */
493
    public function loadExtension($extname, $reload = true)
494
    {
495
        $out = false;
496
        if (isset($this->extensionAlias[$extname])) {
497
            $out = $this->getService($this->extensionAlias[$extname]);
498
        } else {
499
            $flag = ($reload || !in_array($extname, $this->extensions));
500
            if ($this->checkSQLconnect('db') && $flag) {
501
                $evtOut = $this->invokeEvent('OnBeforeLoadExtension', array('name' => $extname, 'reload' => $reload));
502
                if (is_array($evtOut) && count($evtOut) > 0) {
503
                    $out = array_pop($evtOut);
504
                }
505
            }
506
            if (!$out && $flag) {
507
                $extname = trim(str_replace(array('..', '/', '\\'), '', strtolower($extname)));
508
                $filename = MODX_MANAGER_PATH . "includes/extenders/ex_{$extname}.inc.php";
509
                $out = is_file($filename) ? include $filename : false;
510
            }
511
            if ($out && !in_array($extname, $this->extensions)) {
512
                $this->extensions[] = $extname;
513
            }
514
        }
515
        return $out;
516
    }
517
518
    /**
519
     * Returns the current micro time
520
     *
521
     * @return float
522
     */
523
    public function getMicroTime()
524
    {
525
        list ($usec, $sec) = explode(' ', microtime());
526
        return ((float)$usec + (float)$sec);
527
    }
528
529
    /**
530
     * Redirect
531
     *
532
     * @param string $url
533
     * @param int $count_attempts
534
     * @param string $type $type
535
     * @param string $responseCode
536
     * @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...
537
     * @global string $base_url
538
     * @global string $site_url
539
     */
540
    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...
541
    {
542
        $header = '';
543
        if (empty ($url)) {
544
            return false;
545
        }
546
        if ($count_attempts == 1) {
547
            // append the redirect count string to the url
548
            $currentNumberOfRedirects = isset ($_REQUEST['err']) ? $_REQUEST['err'] : 0;
549
            if ($currentNumberOfRedirects > 3) {
550
                $this->messageQuit('Redirection attempt failed - please ensure the document you\'re trying to redirect to exists. <p>Redirection URL: <i>' . $url . '</i></p>');
551
            } else {
552
                $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...
553
                if (strpos($url, "?") > 0) {
554
                    $url .= "&err=$currentNumberOfRedirects";
555
                } else {
556
                    $url .= "?err=$currentNumberOfRedirects";
557
                }
558
            }
559
        }
560
        if ($type == 'REDIRECT_REFRESH') {
561
            $header = 'Refresh: 0;URL=' . $url;
562
        } elseif ($type == 'REDIRECT_META') {
563
            $header = '<META HTTP-EQUIV="Refresh" CONTENT="0; URL=' . $url . '" />';
564
            echo $header;
565
            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...
566
        } elseif ($type == 'REDIRECT_HEADER' || empty ($type)) {
567
            // check if url has /$base_url
568
            if (substr($url, 0, strlen(MODX_BASE_URL)) == MODX_BASE_URL) {
569
                // append $site_url to make it work with Location:
570
                $url = MODX_SITE_URL . substr($url, strlen(MODX_BASE_URL));
571
            }
572
            if (strpos($url, "\n") === false) {
573
                $header = 'Location: ' . $url;
574
            } else {
575
                $this->messageQuit('No newline allowed in redirect url.');
576
            }
577
        }
578
        if ($responseCode && (strpos($responseCode, '30') !== false)) {
579
            header($responseCode);
580
        }
581
582
        if(!empty($header)) {
583
            header($header);
584
        }
585
586
        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...
587
    }
588
589
    /**
590
     * Forward to another page
591
     *
592
     * @param int|string $id
593
     * @param string $responseCode
594
     */
595
    public function sendForward($id, $responseCode = '')
596
    {
597
        if ($this->forwards > 0) {
598
            $this->forwards = $this->forwards - 1;
599
            $this->documentIdentifier = $id;
600
            $this->documentMethod = 'id';
601
            if ($responseCode) {
602
                header($responseCode);
603
            }
604
            $this->prepareResponse();
605
            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...
606
        } else {
607
            $this->messageQuit("Internal Server Error id={$id}");
608
            header('HTTP/1.0 500 Internal Server Error');
609
            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...
610
        }
611
    }
612
613
    /**
614
     * Redirect to the error page, by calling sendForward(). This is called for example when the page was not found.
615
     * @param bool $noEvent
616
     */
617
    public function sendErrorPage($noEvent = false)
618
    {
619
        $this->systemCacheKey = 'notfound';
620
        if (!$noEvent) {
621
            // invoke OnPageNotFound event
622
            $this->invokeEvent('OnPageNotFound');
623
        }
624
        $url = $this->config['error_page'] ? $this->config['error_page'] : $this->config['site_start'];
625
626
        $this->sendForward($url, 'HTTP/1.0 404 Not Found');
627
        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...
628
    }
629
630
    /**
631
     * @param bool $noEvent
632
     */
633
    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...
634
    {
635
        // invoke OnPageUnauthorized event
636
        $_REQUEST['refurl'] = $this->documentIdentifier;
637
        $this->systemCacheKey = 'unauth';
638
        if (!$noEvent) {
639
            $this->invokeEvent('OnPageUnauthorized');
640
        }
641
        if ($this->config['unauthorized_page']) {
642
            $unauthorizedPage = $this->config['unauthorized_page'];
643
        } elseif ($this->config['error_page']) {
644
            $unauthorizedPage = $this->config['error_page'];
645
        } else {
646
            $unauthorizedPage = $this->config['site_start'];
647
        }
648
        $this->sendForward($unauthorizedPage, 'HTTP/1.1 401 Unauthorized');
649
        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...
650
    }
651
652
    /**
653
     * Get MODX settings including, but not limited to, the system_settings table
654
     */
655
    public function getSettings()
656
    {
657
        if (!isset($this->config['site_name'])) {
658
            $this->recoverySiteCache();
659
        }
660
661
        // setup default site id - new installation should generate a unique id for the site.
662
        if (!isset($this->config['site_id'])) {
663
            $this->config['site_id'] = "MzGeQ2faT4Dw06+U49x3";
664
        }
665
666
        // store base_url and base_path inside config array
667
        $this->config['base_url'] = MODX_BASE_URL;
668
        $this->config['base_path'] = MODX_BASE_PATH;
669
        $this->config['site_url'] = MODX_SITE_URL;
670
        $this->config['valid_hostnames'] = MODX_SITE_HOSTNAMES;
671
        $this->config['site_manager_url'] = MODX_MANAGER_URL;
672
        $this->config['site_manager_path'] = MODX_MANAGER_PATH;
673
        $this->error_reporting = $this->config['error_reporting'];
674
        $this->config['filemanager_path'] = str_replace('[(base_path)]', MODX_BASE_PATH, $this->config['filemanager_path']);
675
        $this->config['rb_base_dir'] = str_replace('[(base_path)]', MODX_BASE_PATH, $this->config['rb_base_dir']);
676
677
        if (!isset($this->config['enable_at_syntax'])) {
678
            $this->config['enable_at_syntax'] = 1;
679
        } // @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...
680
681
        // now merge user settings into evo-configuration
682
        $this->getUserSettings();
683
    }
684
685
    private function recoverySiteCache()
686
    {
687
        $site_cache_dir = MODX_BASE_PATH . $this->getCacheFolder();
688
        $site_cache_path = $site_cache_dir . 'siteCache.idx.php';
689
690
        if (is_file($site_cache_path)) {
691
            include($site_cache_path);
692
        }
693
        if (isset($this->config['site_name'])) {
694
            return;
695
        }
696
697
        $cache = new Cache();
698
        $cache->setCachepath($site_cache_dir);
699
        $cache->setReport(false);
700
        $cache->buildCache($this);
0 ignored issues
show
Documentation introduced by
$this is of type this<EvolutionCMS\Core>, but the function expects a object<EvolutionCMS\DocumentParser>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
701
702
        clearstatcache();
703
        if (is_file($site_cache_path)) {
704
            include($site_cache_path);
705
        }
706
        if (isset($this->config['site_name'])) {
707
            return;
708
        }
709
710
        $rs = $this->getDatabase()->select(
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...
711
            'setting_name, setting_value',
712
            $this->getDatabase()->getFullTableName('system_settings')
713
        );
714
        while ($row = $this->getDatabase()->getRow($rs)) {
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se...ame('system_settings')) on line 710 can also be of type boolean; however, EvolutionCMS\Database::getRow() does only seem to accept object<mysqli_result>, 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...
715
            $this->config[$row['setting_name']] = $row['setting_value'];
716
        }
717
718
        if (!$this->config['enable_filter']) {
719
            return;
720
        }
721
722
        $where = "plugincode LIKE '%phx.parser.class.inc.php%OnParseDocument();%' AND disabled != 1";
723
        $rs = $this->getDatabase()->select('id', $this->getDatabase()->getFullTableName('site_plugins'), $where);
724
        if ($this->getDatabase()->getRecordCount($rs)) {
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se...site_plugins'), $where) on line 723 can also be of type boolean; however, EvolutionCMS\Database::getRecordCount() does only seem to accept object<mysqli_result>, 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...
725
            $this->config['enable_filter'] = '0';
726
        }
727
    }
728
729
    /**
730
     * Get user settings and merge into MODX configuration
731
     * @return array
732
     */
733
    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...
734
    {
735
        $tbl_web_user_settings = $this->getDatabase()->getFullTableName('web_user_settings');
736
        $tbl_user_settings = $this->getDatabase()->getFullTableName('user_settings');
737
738
        // load user setting if user is logged in
739
        $usrSettings = array();
740
        if ($id = $this->getLoginUserID()) {
741
            $usrType = $this->getLoginUserType();
742
            if (isset ($usrType) && $usrType == 'manager') {
743
                $usrType = 'mgr';
744
            }
745
746
            if ($usrType == 'mgr' && $this->isBackend()) {
747
                // invoke the OnBeforeManagerPageInit event, only if in backend
748
                $this->invokeEvent("OnBeforeManagerPageInit");
749
            }
750
751
            if (isset ($_SESSION[$usrType . 'UsrConfigSet'])) {
752
                $usrSettings = &$_SESSION[$usrType . 'UsrConfigSet'];
753
            } else {
754
                if ($usrType == 'web') {
755
                    $from = $tbl_web_user_settings;
756
                    $where = "webuser='{$id}'";
757
                } else {
758
                    $from = $tbl_user_settings;
759
                    $where = "user='{$id}'";
760
                }
761
762
                $which_browser_default = $this->configGlobal['which_browser'] ? $this->configGlobal['which_browser'] : $this->config['which_browser'];
763
764
                $result = $this->getDatabase()->select('setting_name, setting_value', $from, $where);
765
                while ($row = $this->getDatabase()->getRow($result)) {
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->getDatabase()->se..._value', $from, $where) on line 764 can also be of type boolean; however, EvolutionCMS\Database::getRow() does only seem to accept object<mysqli_result>, 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...
766 View Code Duplication
                    if ($row['setting_name'] == 'which_browser' && $row['setting_value'] == 'default') {
767
                        $row['setting_value'] = $which_browser_default;
768
                    }
769
                    $usrSettings[$row['setting_name']] = $row['setting_value'];
770
                }
771
                if (isset ($usrType)) {
772
                    $_SESSION[$usrType . 'UsrConfigSet'] = $usrSettings;
773
                } // store user settings in session
774
            }
775
        }
776
        if ($this->isFrontend() && $mgrid = $this->getLoginUserID('mgr')) {
777
            $musrSettings = array();
778
            if (isset ($_SESSION['mgrUsrConfigSet'])) {
779
                $musrSettings = &$_SESSION['mgrUsrConfigSet'];
780
            } else {
781
                if ($result = $this->getDatabase()->select('setting_name, setting_value', $tbl_user_settings, "user='{$mgrid}'")) {
782
                    while ($row = $this->getDatabase()->getRow($result)) {
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->getDatabase()->se...ngs, "user='{$mgrid}'") on line 781 can also be of type boolean; however, EvolutionCMS\Database::getRow() does only seem to accept object<mysqli_result>, 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...
783
                        $musrSettings[$row['setting_name']] = $row['setting_value'];
784
                    }
785
                    $_SESSION['mgrUsrConfigSet'] = $musrSettings; // store user settings in session
786
                }
787
            }
788
            if (!empty ($musrSettings)) {
789
                $usrSettings = array_merge($musrSettings, $usrSettings);
790
            }
791
        }
792
        // save global values before overwriting/merging array
793
        foreach ($usrSettings as $param => $value) {
794
            if (isset($this->config[$param])) {
795
                $this->configGlobal[$param] = $this->config[$param];
796
            }
797
        }
798
799
        $this->config = array_merge($this->config, $usrSettings);
800
        $this->config['filemanager_path'] = str_replace('[(base_path)]', MODX_BASE_PATH, $this->config['filemanager_path']);
801
        $this->config['rb_base_dir'] = str_replace('[(base_path)]', MODX_BASE_PATH, $this->config['rb_base_dir']);
802
803
        return $usrSettings;
804
    }
805
806
    /**
807
     * Returns the document identifier of the current request
808
     *
809
     * @param string $method id and alias are allowed
810
     * @return int
811
     */
812
    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...
813
    {
814
        // function to test the query and find the retrieval method
815
        if ($method === 'alias') {
816
            return $this->getDatabase()->escape($_REQUEST['q']);
817
        }
818
819
        $id_ = filter_input(INPUT_GET, 'id');
820
        if ($id_) {
821
            if (preg_match('@^[1-9][0-9]*$@', $id_)) {
822
                return $id_;
823
            } else {
824
                $this->sendErrorPage();
825
            }
826
        } elseif (strpos($_SERVER['REQUEST_URI'], 'index.php/') !== false) {
827
            $this->sendErrorPage();
828
        } else {
829
            return $this->config['site_start'];
830
        }
831
    }
832
833
    /**
834
     * Check for manager or webuser login session since v1.2
835
     *
836
     * @param string $context
837
     * @return bool
838
     */
839
    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...
840
    {
841
        if (substr($context, 0, 1) == 'm') {
842
            $_ = '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...
843
        } else {
844
            $_ = 'webValidated';
845
        }
846
847
        if (MODX_CLI || (isset($_SESSION[$_]) && !empty($_SESSION[$_]))) {
0 ignored issues
show
Coding Style introduced by
The if-else statement can be simplified to return MODX_CLI || isset... !empty($_SESSION[$_]);.
Loading history...
848
            return true;
849
        } else {
850
            return false;
851
        }
852
    }
853
854
    /**
855
     * Check for manager login session
856
     *
857
     * @return boolean
858
     */
859
    public function checkSession()
860
    {
861
        return $this->isLoggedin();
862
    }
863
864
    /**
865
     * Checks, if a the result is a preview
866
     *
867
     * @return boolean
868
     */
869
    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...
870
    {
871
        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...
872
            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...
873
                return true;
874
            } else {
875
                return false;
876
            }
877
        } else {
878
            return false;
879
        }
880
    }
881
882
    /**
883
     * check if site is offline
884
     *
885
     * @return boolean
886
     */
887
    public function checkSiteStatus()
888
    {
889
        if ($this->config['site_status']) {
890
            return true;
891
        }  // site online
892
        elseif ($this->isLoggedin()) {
893
            return true;
894
        }  // site offline but launched via the manager
895
        else {
896
            return false;
897
        } // site is offline
898
    }
899
900
    /**
901
     * Create a 'clean' document identifier with path information, friendly URL suffix and prefix.
902
     *
903
     * @param string $qOrig
904
     * @return string
905
     */
906
    public function cleanDocumentIdentifier($qOrig)
907
    {
908
        if (!$qOrig) {
909
            $qOrig = $this->config['site_start'];
910
        }
911
        $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...
912
913
        $pre = $this->config['friendly_url_prefix'];
914
        $suf = $this->config['friendly_url_suffix'];
915
        $pre = preg_quote($pre, '/');
916
        $suf = preg_quote($suf, '/');
917 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...
918
            $q = $_[1];
919
        }
920 View Code Duplication
        if ($suf && preg_match('@(.*)' . $suf . '$@', $q, $_)) {
921
            $q = $_[1];
922
        }
923
924
        /* First remove any / before or after */
925
        $q = trim($q, '/');
926
927
        /* Save path if any */
928
        /* FS#476 and FS#308: only return virtualDir if friendly paths are enabled */
929
        if ($this->config['use_alias_path'] == 1) {
930
            $_ = strrpos($q, '/');
931
            $this->virtualDir = $_ !== false ? substr($q, 0, $_) : '';
932
            if ($_ !== false) {
933
                $q = preg_replace('@.*/@', '', $q);
934
            }
935
        } else {
936
            $this->virtualDir = '';
937
        }
938
939
        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 */
940
            /* FS#476 and FS#308: check that id is valid in terms of virtualDir structure */
941
            if ($this->config['use_alias_path'] == 1) {
942
                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))))) {
943
                    $this->documentMethod = 'id';
944
                    return $q;
945
                } else { /* not a valid id in terms of virtualDir, treat as alias */
946
                    $this->documentMethod = 'alias';
947
                    return $q;
948
                }
949
            } else {
950
                $this->documentMethod = 'id';
951
                return $q;
952
            }
953
        } else { /* we didn't get an ID back, so instead we assume it's an alias */
954
            if ($this->config['friendly_alias_urls'] != 1) {
955
                $q = $qOrig;
956
            }
957
            $this->documentMethod = 'alias';
958
            return $q;
959
        }
960
    }
961
962
    /**
963
     * @return string
964
     */
965
    public function getCacheFolder()
966
    {
967
        return "assets/cache/";
968
    }
969
970
    /**
971
     * @param $key
972
     * @return string
973
     */
974
    public function getHashFile($key)
975
    {
976
        return $this->getCacheFolder() . "docid_" . $key . ".pageCache.php";
977
    }
978
979
    /**
980
     * @param $id
981
     * @return array|mixed|null|string
982
     */
983
    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...
984
        $hash = $id;
985
        $tmp = null;
986
        $params = array();
987
        if(!empty($this->systemCacheKey)){
988
            $hash = $this->systemCacheKey;
989
        }else {
990
            if (!empty($_GET)) {
991
                // Sort GET parameters so that the order of parameters on the HTTP request don't affect the generated cache ID.
992
                $params = $_GET;
993
                ksort($params);
994
                $hash .= '_'.md5(http_build_query($params));
995
            }
996
        }
997
        $evtOut = $this->invokeEvent("OnMakePageCacheKey", array ("hash" => $hash, "id" => $id, 'params' => $params));
998
        if (is_array($evtOut) && count($evtOut) > 0){
999
            $tmp = array_pop($evtOut);
1000
        }
1001
        return empty($tmp) ? $hash : $tmp;
1002
    }
1003
1004
    /**
1005
     * @param $id
1006
     * @param bool $loading
1007
     * @return string
1008
     */
1009
    public function checkCache($id, $loading = false)
1010
    {
1011
        return $this->getDocumentObjectFromCache($id, $loading);
1012
    }
1013
1014
    /**
1015
     * Check the cache for a specific document/resource
1016
     *
1017
     * @param int $id
1018
     * @param bool $loading
1019
     * @return string
1020
     */
1021
    public function getDocumentObjectFromCache($id, $loading = false)
1022
    {
1023
        $key = ($this->config['cache_type'] == 2) ? $this->makePageCacheKey($id) : $id;
1024
        if ($loading) {
1025
            $this->cacheKey = $key;
1026
        }
1027
1028
        $cache_path = $this->getHashFile($key);
1029
1030
        if (!is_file($cache_path)) {
1031
            $this->documentGenerated = 1;
1032
            return '';
1033
        }
1034
        $content = file_get_contents($cache_path, false);
1035
        if (substr($content, 0, 5) === '<?php') {
1036
            $content = substr($content, strpos($content, '?>') + 2);
1037
        } // remove php header
1038
        $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...
1039
        if (count($a) == 1) {
1040
            $result = $a[0];
1041
        } // return only document content
1042
        else {
1043
            $docObj = unserialize($a[0]); // rebuild document object
1044
            // check page security
1045
            if ($docObj['privateweb'] && isset ($docObj['__MODxDocGroups__'])) {
1046
                $pass = false;
1047
                $usrGrps = $this->getUserDocGroups();
1048
                $docGrps = explode(',', $docObj['__MODxDocGroups__']);
1049
                // check is user has access to doc groups
1050
                if (is_array($usrGrps)) {
1051
                    foreach ($usrGrps as $k => $v) {
1052
                        if (!in_array($v, $docGrps)) {
1053
                            continue;
1054
                        }
1055
                        $pass = true;
1056
                        break;
1057
                    }
1058
                }
1059
                // diplay error pages if user has no access to cached doc
1060
                if (!$pass) {
1061
                    if ($this->config['unauthorized_page']) {
1062
                        // check if file is not public
1063
                        $rs = $this->getDatabase()->select(
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...
1064
                            'count(id)',
1065
                            $this->getDatabase()->getFullTableName('document_groups'),
1066
                            "document='{$id}'",
1067
                            '',
1068
                            '1'
1069
                        );
1070
                        $total = $this->getDatabase()->getValue($rs);
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se...ment='{$id}'", '', '1') on line 1063 can also be of type boolean; however, EvolutionCMS\Database::getValue() does only seem to accept object<mysqli_result>|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...
1071
                    } else {
1072
                        $total = 0;
1073
                    }
1074
1075
                    if ($total > 0) {
1076
                        $this->sendUnauthorizedPage();
1077
                    } else {
1078
                        $this->sendErrorPage();
1079
                    }
1080
1081
                    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...
1082
                }
1083
            }
1084
            // Grab the Scripts
1085
            if (isset($docObj['__MODxSJScripts__'])) {
1086
                $this->sjscripts = $docObj['__MODxSJScripts__'];
1087
            }
1088
            if (isset($docObj['__MODxJScripts__'])) {
1089
                $this->jscripts = $docObj['__MODxJScripts__'];
1090
            }
1091
1092
            // Remove intermediate variables
1093
            unset($docObj['__MODxDocGroups__'], $docObj['__MODxSJScripts__'], $docObj['__MODxJScripts__']);
1094
1095
            $this->documentObject = $docObj;
1096
1097
            $result = $a[1]; // return document content
1098
        }
1099
1100
        $this->documentGenerated = 0;
1101
        // invoke OnLoadWebPageCache  event
1102
        $this->documentContent = $result;
1103
        $this->invokeEvent('OnLoadWebPageCache');
1104
        return $result;
1105
    }
1106
1107
    /**
1108
     * Final processing and output of the document/resource.
1109
     *
1110
     * - runs uncached snippets
1111
     * - add javascript to <head>
1112
     * - removes unused placeholders
1113
     * - converts URL tags [~...~] to URLs
1114
     *
1115
     * @param boolean $noEvent Default: false
1116
     */
1117
    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...
1118
    {
1119
        $this->documentOutput = $this->documentContent;
1120
1121
        if ($this->documentGenerated == 1 && $this->documentObject['cacheable'] == 1 && $this->documentObject['type'] == 'document' && $this->documentObject['published'] == 1) {
1122
            if (!empty($this->sjscripts)) {
1123
                $this->documentObject['__MODxSJScripts__'] = $this->sjscripts;
1124
            }
1125
            if (!empty($this->jscripts)) {
1126
                $this->documentObject['__MODxJScripts__'] = $this->jscripts;
1127
            }
1128
        }
1129
1130
        // check for non-cached snippet output
1131
        if (strpos($this->documentOutput, '[!') > -1) {
1132
            $this->recentUpdate = $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'];
1133
1134
            $this->documentOutput = str_replace('[!', '[[', $this->documentOutput);
1135
            $this->documentOutput = str_replace('!]', ']]', $this->documentOutput);
1136
1137
            // Parse document source
1138
            $this->documentOutput = $this->parseDocumentSource($this->documentOutput);
1139
        }
1140
1141
        // Moved from prepareResponse() by sirlancelot
1142
        // Insert Startup jscripts & CSS scripts into template - template must have a <head> tag
1143
        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...
1144
            // change to just before closing </head>
1145
            // $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...
1146
            $this->documentOutput = preg_replace("/(<\/head>)/i", $js . "\n\\1", $this->documentOutput);
1147
        }
1148
1149
        // Insert jscripts & html block into template - template must have a </body> tag
1150
        if ($js = $this->getRegisteredClientScripts()) {
1151
            $this->documentOutput = preg_replace("/(<\/body>)/i", $js . "\n\\1", $this->documentOutput);
1152
        }
1153
        // End fix by sirlancelot
1154
1155
        $this->documentOutput = $this->cleanUpMODXTags($this->documentOutput);
1156
1157
        $this->documentOutput = $this->rewriteUrls($this->documentOutput);
1158
1159
        // send out content-type and content-disposition headers
1160
        if (IN_PARSER_MODE == "true") {
1161
            $type = !empty ($this->contentTypes[$this->documentIdentifier]) ? $this->contentTypes[$this->documentIdentifier] : "text/html";
1162
            header('Content-Type: ' . $type . '; charset=' . $this->config['modx_charset']);
1163
            //            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...
1164
            //                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...
1165
            if (!$this->checkPreview() && $this->documentObject['content_dispo'] == 1) {
1166
                if ($this->documentObject['alias']) {
1167
                    $name = $this->documentObject['alias'];
1168
                } else {
1169
                    // strip title of special characters
1170
                    $name = $this->documentObject['pagetitle'];
1171
                    $name = strip_tags($name);
1172
                    $name = $this->cleanUpMODXTags($name);
1173
                    $name = strtolower($name);
1174
                    $name = preg_replace('/&.+?;/', '', $name); // kill entities
1175
                    $name = preg_replace('/[^\.%a-z0-9 _-]/', '', $name);
1176
                    $name = preg_replace('/\s+/', '-', $name);
1177
                    $name = preg_replace('|-+|', '-', $name);
1178
                    $name = trim($name, '-');
1179
                }
1180
                $header = 'Content-Disposition: attachment; filename=' . $name;
1181
                header($header);
1182
            }
1183
        }
1184
        $this->setConditional();
1185
1186
        $stats = $this->getTimerStats($this->tstart);
1187
1188
        $out =& $this->documentOutput;
1189
        $out = str_replace("[^q^]", $stats['queries'], $out);
1190
        $out = str_replace("[^qt^]", $stats['queryTime'], $out);
1191
        $out = str_replace("[^p^]", $stats['phpTime'], $out);
1192
        $out = str_replace("[^t^]", $stats['totalTime'], $out);
1193
        $out = str_replace("[^s^]", $stats['source'], $out);
1194
        $out = str_replace("[^m^]", $stats['phpMemory'], $out);
1195
        //$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...
1196
1197
        // invoke OnWebPagePrerender event
1198
        if (!$noEvent) {
1199
            $evtOut = $this->invokeEvent('OnWebPagePrerender', array('documentOutput' => $this->documentOutput));
1200
            if (is_array($evtOut) && count($evtOut) > 0) {
1201
                $this->documentOutput = $evtOut['0'];
1202
            }
1203
        }
1204
1205
        $this->documentOutput = $this->removeSanitizeSeed($this->documentOutput);
1206
1207
        if (strpos($this->documentOutput, '\{') !== false) {
1208
            $this->documentOutput = $this->RecoveryEscapedTags($this->documentOutput);
1209
        } elseif (strpos($this->documentOutput, '\[') !== false) {
1210
            $this->documentOutput = $this->RecoveryEscapedTags($this->documentOutput);
1211
        }
1212
1213
        echo $this->documentOutput;
1214
1215
        if ($this->dumpSQL) {
1216
            echo $this->queryCode;
1217
        }
1218
        if ($this->dumpSnippets) {
1219
            $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...
1220
            $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...
1221
            foreach ($this->snippetsTime as $s => $v) {
1222
                $t = $v['time'];
1223
                $sname = $v['sname'];
1224
                $sc .= sprintf("%s. %s (%s)<br>", $s, $sname, sprintf("%2.2f ms", $t)); // currentSnippet
1225
                $tt += $t;
1226
            }
1227
            echo "<fieldset><legend><b>Snippets</b> (" . count($this->snippetsTime) . " / " . sprintf("%2.2f ms", $tt) . ")</legend>{$sc}</fieldset><br />";
1228
            echo $this->snippetsCode;
1229
        }
1230
        if ($this->dumpPlugins) {
1231
            $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...
1232
            $tt = 0;
1233
            foreach ($this->pluginsTime as $s => $t) {
1234
                $ps .= "$s (" . sprintf("%2.2f ms", $t * 1000) . ")<br>";
1235
                $tt += $t;
1236
            }
1237
            echo "<fieldset><legend><b>Plugins</b> (" . count($this->pluginsTime) . " / " . sprintf("%2.2f ms", $tt * 1000) . ")</legend>{$ps}</fieldset><br />";
1238
            echo $this->pluginsCode;
1239
        }
1240
1241
        ob_end_flush();
1242
    }
1243
1244
    /**
1245
     * @param $contents
1246
     * @return mixed
1247
     */
1248
    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...
1249
    {
1250
        list($sTags, $rTags) = $this->getTagsForEscape();
1251
        return str_replace($rTags, $sTags, $contents);
1252
    }
1253
1254
    /**
1255
     * @param string $tags
1256
     * @return array[]
1257
     */
1258
    public function getTagsForEscape($tags = '{{,}},[[,]],[!,!],[*,*],[(,)],[+,+],[~,~],[^,^]')
1259
    {
1260
        $srcTags = explode(',', $tags);
1261
        $repTags = array();
1262
        foreach ($srcTags as $tag) {
1263
            $repTags[] = '\\' . $tag[0] . '\\' . $tag[1];
1264
        }
1265
        return array($srcTags, $repTags);
1266
    }
1267
1268
    /**
1269
     * @param $tstart
1270
     * @return array
1271
     */
1272
    public function getTimerStats($tstart)
1273
    {
1274
        $stats = array();
1275
1276
        $stats['totalTime'] = ($this->getMicroTime() - $tstart);
1277
        $stats['queryTime'] = $this->queryTime;
1278
        $stats['phpTime'] = $stats['totalTime'] - $stats['queryTime'];
1279
1280
        $stats['queryTime'] = sprintf("%2.4f s", $stats['queryTime']);
1281
        $stats['totalTime'] = sprintf("%2.4f s", $stats['totalTime']);
1282
        $stats['phpTime'] = sprintf("%2.4f s", $stats['phpTime']);
1283
        $stats['source'] = $this->documentGenerated == 1 ? "database" : "cache";
1284
        $stats['queries'] = isset ($this->executedQueries) ? $this->executedQueries : 0;
1285
        $stats['phpMemory'] = (memory_get_peak_usage(true) / 1024 / 1024) . " mb";
1286
1287
        return $stats;
1288
    }
1289
1290
    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...
1291
    {
1292
        if (!empty($_POST) || (defined('MODX_API_MODE') && MODX_API_MODE) || $this->getLoginUserID('mgr') || !$this->useConditional || empty($this->recentUpdate)) {
1293
            return;
1294
        }
1295
        $last_modified = gmdate('D, d M Y H:i:s T', $this->recentUpdate);
1296
        $etag = md5($last_modified);
1297
        $HTTP_IF_MODIFIED_SINCE = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? $_SERVER['HTTP_IF_MODIFIED_SINCE'] : false;
1298
        $HTTP_IF_NONE_MATCH = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? $_SERVER['HTTP_IF_NONE_MATCH'] : false;
1299
        header('Pragma: no-cache');
1300
1301
        if ($HTTP_IF_MODIFIED_SINCE == $last_modified || strpos($HTTP_IF_NONE_MATCH, $etag) !== false) {
1302
            header('HTTP/1.1 304 Not Modified');
1303
            header('Content-Length: 0');
1304
            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...
1305
        } else {
1306
            header("Last-Modified: {$last_modified}");
1307
            header("ETag: '{$etag}'");
1308
        }
1309
    }
1310
1311
    /**
1312
     * Checks the publish state of page
1313
     */
1314
    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...
1315
    {
1316
        $cacheRefreshTime = 0;
1317
        $recent_update = 0;
1318
        @include(MODX_BASE_PATH . $this->getCacheFolder() . 'sitePublishing.idx.php');
1319
        $this->recentUpdate = $recent_update;
1320
1321
        $timeNow = $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'];
1322
        if ($timeNow < $cacheRefreshTime || $cacheRefreshTime == 0) {
1323
            return;
1324
        }
1325
1326
        // now, check for documents that need publishing
1327
        $field = array('published' => 1, 'publishedon' => $timeNow);
1328
        $where = "pub_date <= {$timeNow} AND pub_date!=0 AND published=0";
1329
        $result_pub = $this->getDatabase()->select(
1330
            'id',
1331
            $this->getDatabase()->getFullTableName('site_content'),
1332
            $where
1333
        );
1334
        $this->getDatabase()->update($field, $this->getDatabase()->getFullTableName('site_content'), $where);
1335 View Code Duplication
        if ($this->getDatabase()->getRecordCount($result_pub) >= 1) { //Event unPublished doc
0 ignored issues
show
Bug introduced by
It seems like $result_pub defined by $this->getDatabase()->se...site_content'), $where) on line 1329 can also be of type boolean; however, EvolutionCMS\Database::getRecordCount() does only seem to accept object<mysqli_result>, 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...
1336
            while ($row_pub = $this->getDatabase()->getRow($result_pub)) {
0 ignored issues
show
Bug introduced by
It seems like $result_pub defined by $this->getDatabase()->se...site_content'), $where) on line 1329 can also be of type boolean; however, EvolutionCMS\Database::getRow() does only seem to accept object<mysqli_result>, 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...
1337
                $this->invokeEvent("OnDocUnPublished", array(
1338
                    "docid" => $row_pub['id']
1339
                ));
1340
            }
1341
        }
1342
1343
        // now, check for documents that need un-publishing
1344
        $field = array('published' => 0, 'publishedon' => 0);
1345
        $where = "unpub_date <= {$timeNow} AND unpub_date!=0 AND published=1";
1346
        $result_unpub = $this->getDatabase()->select(
1347
            'id',
1348
            $this->getDatabase()->getFullTableName('site_content'),
1349
            $where
1350
        );
1351
        $this->getDatabase()->update($field, $this->getDatabase()->getFullTableName('site_content'), $where);
1352 View Code Duplication
        if ($this->getDatabase()->getRecordCount($result_unpub) >= 1) { //Event unPublished doc
0 ignored issues
show
Bug introduced by
It seems like $result_unpub defined by $this->getDatabase()->se...site_content'), $where) on line 1346 can also be of type boolean; however, EvolutionCMS\Database::getRecordCount() does only seem to accept object<mysqli_result>, 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...
1353
            while ($row_unpub = $this->getDatabase()->getRow($result_unpub)) {
0 ignored issues
show
Bug introduced by
It seems like $result_unpub defined by $this->getDatabase()->se...site_content'), $where) on line 1346 can also be of type boolean; however, EvolutionCMS\Database::getRow() does only seem to accept object<mysqli_result>, 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...
1354
                $this->invokeEvent("OnDocUnPublished", array(
1355
                    "docid" => $row_unpub['id']
1356
                ));
1357
            }
1358
        }
1359
1360
        $this->recentUpdate = $timeNow;
1361
1362
        // clear the cache
1363
        $this->clearCache('full');
1364
    }
1365
1366
    public function checkPublishStatus()
1367
    {
1368
        $this->updatePubStatus();
1369
    }
1370
1371
    /**
1372
     * Final jobs.
1373
     *
1374
     * - cache page
1375
     */
1376
    public function postProcess()
1377
    {
1378
        // if the current document was generated, cache it!
1379
        $cacheable = ($this->config['enable_cache'] && $this->documentObject['cacheable']) ? 1 : 0;
1380
        if ($cacheable && $this->documentGenerated && $this->documentObject['type'] == 'document' && $this->documentObject['published']) {
1381
            // invoke OnBeforeSaveWebPageCache event
1382
            $this->invokeEvent("OnBeforeSaveWebPageCache");
1383
1384
            if (!empty($this->cacheKey) && is_scalar($this->cacheKey)) {
1385
                // get and store document groups inside document object. Document groups will be used to check security on cache pages
1386
                $where = "document='{$this->documentIdentifier}'";
1387
                $rs = $this->getDatabase()->select(
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...
1388
                    'document_group',
1389
                    $this->getDatabase()->getFullTableName('document_groups'),
1390
                    $where
1391
                );
1392
                $docGroups = $this->getDatabase()->getColumn('document_group', $rs);
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se...ument_groups'), $where) on line 1387 can also be of type boolean; however, EvolutionCMS\Database::getColumn() does only seem to accept object<mysqli_result>|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...
1393
1394
                // Attach Document Groups and Scripts
1395
                if (is_array($docGroups)) {
1396
                    $this->documentObject['__MODxDocGroups__'] = implode(",", $docGroups);
1397
                }
1398
1399
                $docObjSerial = serialize($this->documentObject);
1400
                $cacheContent = $docObjSerial . "<!--__MODxCacheSpliter__-->" . $this->documentContent;
1401
                $page_cache_path = MODX_BASE_PATH . $this->getHashFile($this->cacheKey);
1402
                file_put_contents($page_cache_path, "<?php die('Unauthorized access.'); ?>$cacheContent");
1403
            }
1404
        }
1405
1406
        // Useful for example to external page counters/stats packages
1407
        $this->invokeEvent('OnWebPageComplete');
1408
1409
        // end post processing
1410
    }
1411
1412
    /**
1413
     * @param $content
1414
     * @param string $left
1415
     * @param string $right
1416
     * @return array
1417
     */
1418
    public function getTagsFromContent($content, $left = '[+', $right = '+]')
1419
    {
1420
        $_ = $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...
1421
        if (empty($_)) {
1422
            return array();
1423
        }
1424
        foreach ($_ as $v) {
1425
            $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...
1426
            $tags[1][] = $v;
1427
        }
1428
        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...
1429
    }
1430
1431
    /**
1432
     * @param $content
1433
     * @param string $left
1434
     * @param string $right
1435
     * @return array
1436
     */
1437
    public function _getTagsFromContent($content, $left = '[+', $right = '+]')
1438
    {
1439
        if (strpos($content, $left) === false) {
1440
            return array();
1441
        }
1442
        $spacer = md5('<<<EVO>>>');
1443
        if($left==='{{' && strpos($content,';}}')!==false)  $content = str_replace(';}}', sprintf(';}%s}',   $spacer),$content);
1444
        if($left==='{{' && strpos($content,'{{}}')!==false) $content = str_replace('{{}}',sprintf('{%$1s{}%$1s}',$spacer),$content);
1445
        if($left==='[[' && strpos($content,']]]]')!==false) $content = str_replace(']]]]',sprintf(']]%s]]',  $spacer),$content);
1446
        if($left==='[[' && strpos($content,']]]')!==false)  $content = str_replace(']]]', sprintf(']%s]]',   $spacer),$content);
1447
1448
        $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...
1449
        $pos[']]>'] = strpos($content, ']]>');
1450
1451
        if ($pos['<![CDATA['] !== false && $pos[']]>'] !== false) {
1452
            $content = substr($content, 0, $pos['<![CDATA[']) . substr($content, $pos[']]>'] + 3);
1453
        }
1454
1455
        $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...
1456
        $piece = array();
1457
        foreach ($lp as $lc => $lv) {
1458
            if ($lc !== 0) {
1459
                $piece[] = $left;
1460
            }
1461
            if (strpos($lv, $right) === false) {
1462
                $piece[] = $lv;
1463
            } else {
1464
                $rp = explode($right, $lv);
1465
                foreach ($rp as $rc => $rv) {
1466
                    if ($rc !== 0) {
1467
                        $piece[] = $right;
1468
                    }
1469
                    $piece[] = $rv;
1470
                }
1471
            }
1472
        }
1473
        $lc = 0;
1474
        $rc = 0;
1475
        $fetch = '';
1476
        $tags = array();
1477
        foreach ($piece as $v) {
1478
            if ($v === $left) {
1479
                if (0 < $lc) {
1480
                    $fetch .= $left;
1481
                }
1482
                $lc++;
1483
            } elseif ($v === $right) {
1484
                if ($lc === 0) {
1485
                    continue;
1486
                }
1487
                $rc++;
1488
                if ($lc === $rc) {
1489
                    // #1200 Enable modifiers in Wayfinder - add nested placeholders to $tags like for $fetch = "phx:input=`[+wf.linktext+]`:test"
1490
                    if (strpos($fetch, $left) !== false) {
1491
                        $nested = $this->_getTagsFromContent($fetch, $left, $right);
1492
                        foreach ($nested as $tag) {
1493
                            if (!in_array($tag, $tags)) {
1494
                                $tags[] = $tag;
1495
                            }
1496
                        }
1497
                    }
1498
1499
                    if (!in_array($fetch, $tags)) {  // Avoid double Matches
1500
                        $tags[] = $fetch; // Fetch
1501
                    };
1502
                    $fetch = ''; // and reset
1503
                    $lc = 0;
1504
                    $rc = 0;
1505
                } else {
1506
                    $fetch .= $right;
1507
                }
1508
            } else {
1509
                if (0 < $lc) {
1510
                    $fetch .= $v;
1511
                } else {
1512
                    continue;
1513
                }
1514
            }
1515
        }
1516
        foreach($tags as $i=>$tag) {
1517
            if(strpos($tag,$spacer)!==false) $tags[$i] = str_replace($spacer, '', $tag);
1518
        }
1519
        return $tags;
1520
    }
1521
1522
    /**
1523
     * Merge content fields and TVs
1524
     *
1525
     * @param $content
1526
     * @param bool $ph
1527
     * @return string
1528
     * @internal param string $template
1529
     */
1530
    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...
1531
    {
1532 View Code Duplication
        if ($this->config['enable_at_syntax']) {
1533
            if (stripos($content, '<@LITERAL>') !== false) {
1534
                $content = $this->escapeLiteralTagsContent($content);
1535
            }
1536
        }
1537
        if (strpos($content, '[*') === false) {
1538
            return $content;
1539
        }
1540
        if (!isset($this->documentIdentifier)) {
1541
            return $content;
1542
        }
1543
        if (!isset($this->documentObject) || empty($this->documentObject)) {
1544
            return $content;
1545
        }
1546
1547
        if (!$ph) {
1548
            $ph = $this->documentObject;
1549
        }
1550
1551
        $matches = $this->getTagsFromContent($content, '[*', '*]');
1552
        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...
1553
            return $content;
1554
        }
1555
1556
        foreach ($matches[1] as $i => $key) {
1557
            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...
1558
            if (substr($key, 0, 1) == '#') {
1559
                $key = substr($key, 1);
1560
            } // remove # for QuickEdit format
1561
1562
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1563
            if (strpos($key, '@') !== false) {
1564
                list($key, $context) = explode('@', $key, 2);
1565
            } else {
1566
                $context = false;
1567
            }
1568
1569
            // 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...
1570
            if ($context) {
1571
                $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...
1572
            } else {
1573
                $value = isset($ph[$key]) ? $ph[$key] : '';
1574
            }
1575
1576 View Code Duplication
            if (is_array($value)) {
1577
                $value = getTVDisplayFormat($value[0], $value[1], $value[2], $value[3], $value[4]);
1578
            }
1579
1580
            $s = &$matches[0][$i];
1581
            if ($modifiers !== false) {
1582
                $value = $this->applyFilter($value, $modifiers, $key);
1583
            }
1584
1585 View Code Duplication
            if (strpos($content, $s) !== false) {
1586
                $content = str_replace($s, $value, $content);
1587
            } elseif($this->debug) {
1588
                $this->addLog('mergeDocumentContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1589
            }
1590
        }
1591
1592
        return $content;
1593
    }
1594
1595
    /**
1596
     * @param $key
1597
     * @param bool|int $parent
1598
     * @return bool|mixed|string
1599
     */
1600
    public function _contextValue($key, $parent = false)
1601
    {
1602
        if (preg_match('/@\d+\/u/', $key)) {
1603
            $key = str_replace(array('@', '/u'), array('@u(', ')'), $key);
1604
        }
1605
        list($key, $str) = explode('@', $key, 2);
1606
1607
        if (strpos($str, '(')) {
1608
            list($context, $option) = explode('(', $str, 2);
1609
        } else {
1610
            list($context, $option) = array($str, false);
1611
        }
1612
1613
        if ($option) {
1614
            $option = trim($option, ')(\'"`');
1615
        }
1616
1617
        switch (strtolower($context)) {
1618
            case 'site_start':
1619
                $docid = $this->config['site_start'];
1620
                break;
1621
            case 'parent':
1622
            case 'p':
1623
                $docid = $parent;
1624
                if ($docid == 0) {
1625
                    $docid = $this->config['site_start'];
1626
                }
1627
                break;
1628
            case 'ultimateparent':
1629
            case 'uparent':
1630
            case 'up':
1631
            case 'u':
1632 View Code Duplication
                if (strpos($str, '(') !== false) {
1633
                    $top = substr($str, strpos($str, '('));
1634
                    $top = trim($top, '()"\'');
1635
                } else {
1636
                    $top = 0;
1637
                }
1638
                $docid = $this->getUltimateParentId($this->documentIdentifier, $top);
1639
                break;
1640
            case 'alias':
1641
                $str = substr($str, strpos($str, '('));
1642
                $str = trim($str, '()"\'');
1643
                $docid = $this->getIdFromAlias($str);
1644
                break;
1645 View Code Duplication
            case 'prev':
1646
                if (!$option) {
1647
                    $option = 'menuindex,ASC';
1648
                } elseif (strpos($option, ',') === false) {
1649
                    $option .= ',ASC';
1650
                }
1651
                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...
1652
                $children = $this->getActiveChildren($parent, $by, $dir);
1653
                $find = false;
1654
                $prev = false;
1655
                foreach ($children as $row) {
1656
                    if ($row['id'] == $this->documentIdentifier) {
1657
                        $find = true;
1658
                        break;
1659
                    }
1660
                    $prev = $row;
1661
                }
1662
                if ($find) {
1663
                    if (isset($prev[$key])) {
1664
                        return $prev[$key];
1665
                    } else {
1666
                        $docid = $prev['id'];
1667
                    }
1668
                } else {
1669
                    $docid = '';
1670
                }
1671
                break;
1672 View Code Duplication
            case 'next':
1673
                if (!$option) {
1674
                    $option = 'menuindex,ASC';
1675
                } elseif (strpos($option, ',') === false) {
1676
                    $option .= ',ASC';
1677
                }
1678
                list($by, $dir) = explode(',', $option, 2);
1679
                $children = $this->getActiveChildren($parent, $by, $dir);
1680
                $find = false;
1681
                $next = false;
1682
                foreach ($children as $row) {
1683
                    if ($find) {
1684
                        $next = $row;
1685
                        break;
1686
                    }
1687
                    if ($row['id'] == $this->documentIdentifier) {
1688
                        $find = true;
1689
                    }
1690
                }
1691
                if ($find) {
1692
                    if (isset($next[$key])) {
1693
                        return $next[$key];
1694
                    } else {
1695
                        $docid = $next['id'];
1696
                    }
1697
                } else {
1698
                    $docid = '';
1699
                }
1700
                break;
1701
            default:
1702
                $docid = $str;
1703
        }
1704
        if (preg_match('@^[1-9][0-9]*$@', $docid)) {
1705
            $value = $this->getField($key, $docid);
1706
        } else {
1707
            $value = '';
1708
        }
1709
        return $value;
1710
    }
1711
1712
    /**
1713
     * Merge system settings
1714
     *
1715
     * @param $content
1716
     * @param bool|array $ph
1717
     * @return string
1718
     * @internal param string $template
1719
     */
1720
    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...
1721
    {
1722 View Code Duplication
        if ($this->config['enable_at_syntax']) {
1723
            if (stripos($content, '<@LITERAL>') !== false) {
1724
                $content = $this->escapeLiteralTagsContent($content);
1725
            }
1726
        }
1727
        if (strpos($content, '[(') === false) {
1728
            return $content;
1729
        }
1730
1731
        if (empty($ph)) {
1732
            $ph = $this->config;
1733
        }
1734
1735
        $matches = $this->getTagsFromContent($content, '[(', ')]');
1736
        if (empty($matches)) {
1737
            return $content;
1738
        }
1739
1740
        foreach ($matches[1] as $i => $key) {
1741
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1742
1743
            if (isset($ph[$key])) {
1744
                $value = $ph[$key];
1745
            } else {
1746
                continue;
1747
            }
1748
1749
            if ($modifiers !== false) {
1750
                $value = $this->applyFilter($value, $modifiers, $key);
1751
            }
1752
            $s = &$matches[0][$i];
1753 View Code Duplication
            if (strpos($content, $s) !== false) {
1754
                $content = str_replace($s, $value, $content);
1755
            } elseif($this->debug) {
1756
                $this->addLog('mergeSettingsContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1757
            }
1758
        }
1759
        return $content;
1760
    }
1761
1762
    /**
1763
     * Merge chunks
1764
     *
1765
     * @param string $content
1766
     * @param bool|array $ph
1767
     * @return string
1768
     */
1769
    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...
1770
    {
1771
        if ($this->config['enable_at_syntax']) {
1772
            if (strpos($content, '{{ ') !== false) {
1773
                $content = str_replace(array('{{ ', ' }}'), array('\{\{ ', ' \}\}'), $content);
1774
            }
1775
            if (stripos($content, '<@LITERAL>') !== false) {
1776
                $content = $this->escapeLiteralTagsContent($content);
1777
            }
1778
        }
1779
        if (strpos($content, '{{') === false) {
1780
            return $content;
1781
        }
1782
1783
        if (empty($ph)) {
1784
            $ph = $this->chunkCache;
1785
        }
1786
1787
        $matches = $this->getTagsFromContent($content, '{{', '}}');
1788
        if (empty($matches)) {
1789
            return $content;
1790
        }
1791
1792
        foreach ($matches[1] as $i => $key) {
1793
            $snip_call = $this->_split_snip_call($key);
1794
            $key = $snip_call['name'];
1795
            $params = $this->getParamsFromString($snip_call['params']);
1796
1797
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1798
1799
            if (!isset($ph[$key])) {
1800
                $ph[$key] = $this->getChunk($key);
1801
            }
1802
            $value = $ph[$key];
1803
1804
            if (is_null($value)) {
1805
                continue;
1806
            }
1807
1808
            $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 1795 can also be of type null; however, EvolutionCMS\Core::parseText() does only seem to accept array, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
1809
            $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 1795 can also be of type null; however, EvolutionCMS\Core::mergePlaceholderContent() does only seem to accept boolean|array, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
1810
            if ($this->config['enable_at_syntax']) {
1811
                $value = $this->mergeConditionalTagsContent($value);
1812
            }
1813
            $value = $this->mergeDocumentContent($value);
1814
            $value = $this->mergeSettingsContent($value);
1815
            $value = $this->mergeChunkContent($value);
1816
1817
            if ($modifiers !== false) {
1818
                $value = $this->applyFilter($value, $modifiers, $key);
1819
            }
1820
1821
            $s = &$matches[0][$i];
1822 View Code Duplication
            if (strpos($content, $s) !== false) {
1823
                $content = str_replace($s, $value, $content);
1824
            } elseif($this->debug) {
1825
                $this->addLog('mergeChunkContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1826
            }
1827
        }
1828
        return $content;
1829
    }
1830
1831
    /**
1832
     * Merge placeholder values
1833
     *
1834
     * @param string $content
1835
     * @param bool|array $ph
1836
     * @return string
1837
     */
1838
    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...
1839
    {
1840
1841 View Code Duplication
        if ($this->config['enable_at_syntax']) {
1842
            if (stripos($content, '<@LITERAL>') !== false) {
1843
                $content = $this->escapeLiteralTagsContent($content);
1844
            }
1845
        }
1846
        if (strpos($content, '[+') === false) {
1847
            return $content;
1848
        }
1849
1850
        if (empty($ph)) {
1851
            $ph = $this->placeholders;
1852
        }
1853
1854
        if ($this->config['enable_at_syntax']) {
1855
            $content = $this->mergeConditionalTagsContent($content);
1856
        }
1857
1858
        $content = $this->mergeDocumentContent($content);
1859
        $content = $this->mergeSettingsContent($content);
1860
        $matches = $this->getTagsFromContent($content, '[+', '+]');
1861
        if (empty($matches)) {
1862
            return $content;
1863
        }
1864
        foreach ($matches[1] as $i => $key) {
1865
1866
            list($key, $modifiers) = $this->splitKeyAndFilter($key);
1867
1868
            if (isset($ph[$key])) {
1869
                $value = $ph[$key];
1870
            } elseif ($key === 'phx') {
1871
                $value = '';
1872
            } else {
1873
                continue;
1874
            }
1875
1876
            if ($modifiers !== false) {
1877
                $modifiers = $this->mergePlaceholderContent($modifiers);
1878
                $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...
1879
            }
1880
            $s = &$matches[0][$i];
1881 View Code Duplication
            if (strpos($content, $s) !== false) {
1882
                $content = str_replace($s, $value, $content);
1883
            } elseif($this->debug) {
1884
                $this->addLog('mergePlaceholderContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
1885
            }
1886
        }
1887
        return $content;
1888
    }
1889
1890
    /**
1891
     * @param $content
1892
     * @param string $iftag
1893
     * @param string $elseiftag
1894
     * @param string $elsetag
1895
     * @param string $endiftag
1896
     * @return mixed|string
1897
     */
1898
    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...
1899
    {
1900
        if (strpos($content, '@IF') !== false) {
1901
            $content = $this->_prepareCTag($content, $iftag, $elseiftag, $elsetag, $endiftag);
1902
        }
1903
1904
        if (strpos($content, $iftag) === false) {
1905
            return $content;
1906
        }
1907
1908
        $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...
1909
        $content = str_replace(array('<?php', '<?=', '<?', '?>'), array("{$sp}b", "{$sp}p", "{$sp}s", "{$sp}e"), $content);
1910
1911
        $pieces = explode('<@IF:', $content);
1912 View Code Duplication
        foreach ($pieces as $i => $split) {
1913
            if ($i === 0) {
1914
                $content = $split;
1915
                continue;
1916
            }
1917
            list($cmd, $text) = explode('>', $split, 2);
1918
            $cmd = str_replace("'", "\'", $cmd);
1919
            $content .= "<?php if(\$this->_parseCTagCMD('" . $cmd . "')): ?>";
1920
            $content .= $text;
1921
        }
1922
        $pieces = explode('<@ELSEIF:', $content);
1923 View Code Duplication
        foreach ($pieces as $i => $split) {
1924
            if ($i === 0) {
1925
                $content = $split;
1926
                continue;
1927
            }
1928
            list($cmd, $text) = explode('>', $split, 2);
1929
            $cmd = str_replace("'", "\'", $cmd);
1930
            $content .= "<?php elseif(\$this->_parseCTagCMD('" . $cmd . "')): ?>";
1931
            $content .= $text;
1932
        }
1933
1934
        $content = str_replace(array('<@ELSE>', '<@ENDIF>'), array('<?php else:?>', '<?php endif;?>'), $content);
1935
        ob_start();
1936
        $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...
1937
        $content = ob_get_clean();
1938
        $content = str_replace(array("{$sp}b", "{$sp}p", "{$sp}s", "{$sp}e"), array('<?php', '<?=', '<?', '?>'), $content);
1939
1940
        return $content;
1941
    }
1942
1943
    /**
1944
     * @param $content
1945
     * @param string $iftag
1946
     * @param string $elseiftag
1947
     * @param string $elsetag
1948
     * @param string $endiftag
1949
     * @return mixed
1950
     */
1951
    private function _prepareCTag($content, $iftag = '<@IF:', $elseiftag = '<@ELSEIF:', $elsetag = '<@ELSE>', $endiftag = '<@ENDIF>')
1952
    {
1953
        if (strpos($content, '<!--@IF ') !== false) {
1954
            $content = str_replace('<!--@IF ', $iftag, $content);
1955
        } // for jp
1956
        if (strpos($content, '<!--@IF:') !== false) {
1957
            $content = str_replace('<!--@IF:', $iftag, $content);
1958
        }
1959
        if (strpos($content, $iftag) === false) {
1960
            return $content;
1961
        }
1962
        if (strpos($content, '<!--@ELSEIF:') !== false) {
1963
            $content = str_replace('<!--@ELSEIF:', $elseiftag, $content);
1964
        } // for jp
1965
        if (strpos($content, '<!--@ELSE-->') !== false) {
1966
            $content = str_replace('<!--@ELSE-->', $elsetag, $content);
1967
        }  // for jp
1968
        if (strpos($content, '<!--@ENDIF-->') !== false) {
1969
            $content = str_replace('<!--@ENDIF-->', $endiftag, $content);
1970
        }    // for jp
1971
        if (strpos($content, '<@ENDIF-->') !== false) {
1972
            $content = str_replace('<@ENDIF-->', $endiftag, $content);
1973
        }
1974
        $tags = array($iftag, $elseiftag, $elsetag, $endiftag);
1975
        $content = str_ireplace($tags, $tags, $content); // Change to capital letters
1976
        return $content;
1977
    }
1978
1979
    /**
1980
     * @param $cmd
1981
     * @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...
1982
     */
1983
    private function _parseCTagCMD($cmd)
1984
    {
1985
        $cmd = trim($cmd);
1986
        $reverse = substr($cmd, 0, 1) === '!' ? true : false;
1987
        if ($reverse) {
1988
            $cmd = ltrim($cmd, '!');
1989
        }
1990
        if (strpos($cmd, '[!') !== false) {
1991
            $cmd = str_replace(array('[!', '!]'), array('[[', ']]'), $cmd);
1992
        }
1993
        $safe = 0;
1994
        while ($safe < 20) {
1995
            $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...
1996
            if (strpos($cmd, '[*') !== false) {
1997
                $cmd = $this->mergeDocumentContent($cmd);
1998
            }
1999
            if (strpos($cmd, '[(') !== false) {
2000
                $cmd = $this->mergeSettingsContent($cmd);
2001
            }
2002
            if (strpos($cmd, '{{') !== false) {
2003
                $cmd = $this->mergeChunkContent($cmd);
2004
            }
2005
            if (strpos($cmd, '[[') !== false) {
2006
                $cmd = $this->evalSnippets($cmd);
2007
            }
2008
            if (strpos($cmd, '[+') !== false && strpos($cmd, '[[') === false) {
2009
                $cmd = $this->mergePlaceholderContent($cmd);
2010
            }
2011
            if ($bt === md5($cmd)) {
2012
                break;
2013
            }
2014
            $safe++;
2015
        }
2016
        $cmd = ltrim($cmd);
2017
        $cmd = rtrim($cmd, '-');
2018
        $cmd = str_ireplace(array(' and ', ' or '), array('&&', '||'), $cmd);
2019
2020
        if (!preg_match('@^[0-9]*$@', $cmd) && preg_match('@^[0-9<= \-\+\*/\(\)%!&|]*$@', $cmd)) {
2021
            $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...
2022
        } else {
2023
            $_ = 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...
2024
            foreach ($_ as $left) {
2025
                if (strpos($cmd, $left) !== false) {
2026
                    $cmd = 0;
2027
                    break;
2028
                }
2029
            }
2030
        }
2031
        $cmd = trim($cmd);
2032
        if (!preg_match('@^[0-9]+$@', $cmd)) {
2033
            $cmd = empty($cmd) ? 0 : 1;
2034
        } elseif ($cmd <= 0) {
2035
            $cmd = 0;
2036
        }
2037
2038
        if ($reverse) {
2039
            $cmd = !$cmd;
2040
        }
2041
2042
        return $cmd;
2043
    }
2044
2045
    /**
2046
     * Remove Comment-Tags from output like <!--@- Comment -@-->
2047
     * @param $content
2048
     * @param string $left
2049
     * @param string $right
2050
     * @return mixed
2051
     */
2052
    function ignoreCommentedTagsContent($content, $left = '<!--@-', $right = '-@-->')
2053
    {
2054
        if (strpos($content, $left) === false) {
2055
            return $content;
2056
        }
2057
2058
        $matches = $this->getTagsFromContent($content, $left, $right);
2059
        if (!empty($matches)) {
2060
            foreach ($matches[0] as $i => $v) {
2061
                $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...
2062
            }
2063
            $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...
2064
            if (strpos($content, $left) !== false) {
2065
                $content = str_replace($matches[0], '', $content);
2066
            }
2067
        }
2068
        return $content;
2069
    }
2070
2071
    /**
2072
     * @param $content
2073
     * @param string $left
2074
     * @param string $right
2075
     * @return mixed
2076
     */
2077
    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...
2078
    {
2079
        if (stripos($content, $left) === false) {
2080
            return $content;
2081
        }
2082
2083
        $matches = $this->getTagsFromContent($content, $left, $right);
2084
        if (empty($matches)) {
2085
            return $content;
2086
        }
2087
2088
        list($sTags, $rTags) = $this->getTagsForEscape();
2089
        foreach ($matches[1] as $i => $v) {
2090
            $v = str_ireplace($sTags, $rTags, $v);
2091
            $s = &$matches[0][$i];
2092 View Code Duplication
            if (strpos($content, $s) !== false) {
2093
                $content = str_replace($s, $v, $content);
2094
            } elseif($this->debug) {
2095
                $this->addLog('ignoreCommentedTagsContent parse error', $_SERVER['REQUEST_URI'] . $s, 2);
2096
            }
2097
        }
2098
        return $content;
2099
    }
2100
2101
    /**
2102
     * Detect PHP error according to MODX error level
2103
     *
2104
     * @param integer $error PHP error level
2105
     * @return boolean Error detected
2106
     */
2107
2108
    public function detectError($error)
2109
    {
2110
        $detected = false;
2111
        if ($this->config['error_reporting'] == 99 && $error) {
2112
            $detected = true;
2113
        } elseif ($this->config['error_reporting'] == 2 && ($error & ~E_NOTICE)) {
2114
            $detected = true;
2115
        } elseif ($this->config['error_reporting'] == 1 && ($error & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT)) {
2116
            $detected = true;
2117
        }
2118
        return $detected;
2119
    }
2120
2121
    /**
2122
     * Run a plugin
2123
     *
2124
     * @param string $pluginCode Code to run
2125
     * @param array $params
2126
     */
2127
    public function evalPlugin($pluginCode, $params)
2128
    {
2129
        $modx = &$this;
2130
        $modx->event->params = &$params; // store params inside event object
2131
        if (is_array($params)) {
2132
            extract($params, EXTR_SKIP);
2133
        }
2134
        /* 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...
2135
        // 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.
2136
        // Related to https://github.com/modxcms/evolution/issues/1130
2137
        $lock_file_path = MODX_BASE_PATH . 'assets/cache/lock_' . str_replace(' ','-',strtolower($this->event->activePlugin)) . '.pageCache.php';
2138
        if($this->isBackend()) {
2139
            if(is_file($lock_file_path)) {
2140
                $msg = sprintf("Plugin parse error, Temporarily disabled '%s'.", $this->event->activePlugin);
2141
                $this->logEvent(0, 3, $msg, $msg);
2142
                return;
2143
            }
2144
            elseif(stripos($this->event->activePlugin,'ElementsInTree')===false) touch($lock_file_path);
2145
        }*/
2146
        ob_start();
2147
        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...
2148
        $msg = ob_get_contents();
2149
        ob_end_clean();
2150
        // When reached here, no fatal error occured so the lock should be removed.
2151
        /*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...
2152
2153 View Code Duplication
        if ((0 < $this->config['error_reporting']) && $msg && isset($php_errormsg)) {
2154
            $error_info = error_get_last();
2155
            if ($this->detectError($error_info['type'])) {
2156
                $msg = ($msg === false) ? 'ob_get_contents() error' : $msg;
2157
                $this->messageQuit('PHP Parse Error', '', true, $error_info['type'], $error_info['file'], 'Plugin', $error_info['message'], $error_info['line'], $msg);
2158
                if ($this->isBackend()) {
2159
                    $this->event->alert('An error occurred while loading. Please see the event log for more information.<p>' . $msg . '</p>');
2160
                }
2161
            }
2162
        } else {
2163
            echo $msg;
2164
        }
2165
        unset($modx->event->params);
2166
    }
2167
2168
    /**
2169
     * Run a snippet
2170
     *
2171
     * @param $phpcode
2172
     * @param array $params
2173
     * @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...
2174
     * @internal param string $snippet Code to run
2175
     */
2176
    public function evalSnippet($phpcode, $params)
2177
    {
2178
        $modx = &$this;
2179
        /*
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...
2180
        if(isset($params) && is_array($params)) {
2181
            foreach($params as $k=>$v) {
2182
                $v = strtolower($v);
2183
                if($v==='false')    $params[$k] = false;
2184
                elseif($v==='true') $params[$k] = true;
2185
            }
2186
        }*/
2187
        $modx->event->params = &$params; // store params inside event object
2188
        if (is_array($params)) {
2189
            extract($params, EXTR_SKIP);
2190
        }
2191
        ob_start();
2192
        if (strpos($phpcode, ';') !== false) {
2193
            if (substr($phpcode, 0, 5) === '<?php') {
2194
                $phpcode = substr($phpcode, 5);
2195
            }
2196
            $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...
2197
        } else {
2198
            $return = call_user_func_array($phpcode, array($params));
2199
        }
2200
        $echo = ob_get_contents();
2201
        ob_end_clean();
2202 View Code Duplication
        if ((0 < $this->config['error_reporting']) && isset($php_errormsg)) {
2203
            $error_info = error_get_last();
2204
            if ($this->detectError($error_info['type'])) {
2205
                $echo = ($echo === false) ? 'ob_get_contents() error' : $echo;
2206
                $this->messageQuit('PHP Parse Error', '', true, $error_info['type'], $error_info['file'], 'Snippet', $error_info['message'], $error_info['line'], $echo);
2207
                if ($this->isBackend()) {
2208
                    $this->event->alert('An error occurred while loading. Please see the event log for more information<p>' . $echo . $return . '</p>');
2209
                }
2210
            }
2211
        }
2212
        unset($modx->event->params);
2213
        if (is_array($return) || is_object($return)) {
2214
            return $return;
2215
        } else {
2216
            return $echo . $return;
2217
        }
2218
    }
2219
2220
    /**
2221
     * Run snippets as per the tags in $documentSource and replace the tags with the returned values.
2222
     *
2223
     * @param $content
2224
     * @return string
2225
     * @internal param string $documentSource
2226
     */
2227
    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...
2228
    {
2229
        if (strpos($content, '[[') === false) {
2230
            return $content;
2231
        }
2232
2233
        $matches = $this->getTagsFromContent($content, '[[', ']]');
2234
2235
        if (empty($matches)) {
2236
            return $content;
2237
        }
2238
2239
        $this->snipLapCount++;
2240
        if ($this->dumpSnippets) {
2241
            $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);
2242
        }
2243
2244
        foreach ($matches[1] as $i => $call) {
2245
            $s = &$matches[0][$i];
2246
            if (substr($call, 0, 2) === '$_') {
2247
                if (strpos($content, '_PHX_INTERNAL_') === false) {
2248
                    $value = $this->_getSGVar($call);
2249
                } else {
2250
                    $value = $s;
2251
                }
2252 View Code Duplication
                if (strpos($content, $s) !== false) {
2253
                    $content = str_replace($s, $value, $content);
2254
                } elseif($this->debug) {
2255
                    $this->addLog('evalSnippetsSGVar parse error', $_SERVER['REQUEST_URI'] . $s, 2);
2256
                }
2257
                continue;
2258
            }
2259
            $value = $this->_get_snip_result($call);
2260
            if (is_null($value)) {
2261
                continue;
2262
            }
2263
2264 View Code Duplication
            if (strpos($content, $s) !== false) {
2265
                $content = str_replace($s, $value, $content);
2266
            } elseif($this->debug) {
2267
                $this->addLog('evalSnippets parse error', $_SERVER['REQUEST_URI'] . $s, 2);
2268
            }
2269
        }
2270
2271
        if ($this->dumpSnippets) {
2272
            $this->snippetsCode .= '</fieldset><br />';
2273
        }
2274
2275
        return $content;
2276
    }
2277
2278
    /**
2279
     * @param $value
2280
     * @return mixed|string
2281
     */
2282
    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...
2283
    { // Get super globals
2284
        $key = $value;
2285
        $_ = $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...
2286
        $this->config['enable_filter'] = 1;
2287
        list($key, $modifiers) = $this->splitKeyAndFilter($key);
2288
        $this->config['enable_filter'] = $_;
2289
        $key = str_replace(array('(', ')'), array("['", "']"), $key);
2290
        $key = rtrim($key, ';');
2291
        if (strpos($key, '$_SESSION') !== false) {
2292
            $_ = $_SESSION;
2293
            $key = str_replace('$_SESSION', '$_', $key);
2294
            if (isset($_['mgrFormValues'])) {
2295
                unset($_['mgrFormValues']);
2296
            }
2297
            if (isset($_['token'])) {
2298
                unset($_['token']);
2299
            }
2300
        }
2301
        if (strpos($key, '[') !== false) {
2302
            $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...
2303
        } 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...
2304
            $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...
2305
        } else {
2306
            $value = '';
2307
        }
2308
        if ($modifiers !== false) {
2309
            $value = $this->applyFilter($value, $modifiers, $key);
2310
        }
2311
        return $value;
2312
    }
2313
2314
    /**
2315
     * @param $piece
2316
     * @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...
2317
     */
2318
    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...
2319
    {
2320
        if (ltrim($piece) !== $piece) {
2321
            return '';
2322
        }
2323
2324
        $eventtime = $this->dumpSnippets ? $this->getMicroTime() : 0;
2325
2326
        $snip_call = $this->_split_snip_call($piece);
2327
        $key = $snip_call['name'];
2328
2329
        list($key, $modifiers) = $this->splitKeyAndFilter($key);
2330
        $snip_call['name'] = $key;
2331
        $snippetObject = $this->_getSnippetObject($key);
2332
        if (is_null($snippetObject['content'])) {
2333
            return null;
2334
        }
2335
2336
        $this->currentSnippet = $snippetObject['name'];
2337
2338
        // current params
2339
        $params = $this->getParamsFromString($snip_call['params']);
2340
2341
        if (!isset($snippetObject['properties'])) {
2342
            $snippetObject['properties'] = array();
2343
        }
2344
        $default_params = $this->parseProperties($snippetObject['properties'], $this->currentSnippet, 'snippet');
2345
        $params = array_merge($default_params, $params);
2346
2347
        $value = $this->evalSnippet($snippetObject['content'], $params);
2348
        $this->currentSnippet = '';
2349
        if ($modifiers !== false) {
2350
            $value = $this->applyFilter($value, $modifiers, $key);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type array or object; however, EvolutionCMS\Core::applyFilter() does only seem to accept string, maybe add an additional type check?

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

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

    return array();
}

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

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

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

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

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

An additional type check may prevent trouble.

Loading history...
4654
    }
4655
4656
    /**
4657
     * getTpl
4658
     * get template for snippets
4659
     * @param $tpl {string}
4660
     * @return bool|string {string}
4661
     */
4662
    public function getTpl($tpl)
4663
    {
4664
        $template = $tpl;
4665
        $command = '';
4666
        if (preg_match("/^@([^:\s]+)[:\s]+(.+)$/s", trim($tpl), $match)) {
4667
            $command = strtoupper($match[1]);
4668
            $template = $match[2];
4669
        }
4670
        switch ($command) {
4671
            case 'CODE':
4672
                break;
4673
            case 'FILE':
4674
                $template = file_get_contents(MODX_BASE_PATH . $template);
4675
                break;
4676
            case 'CHUNK':
4677
                $template = $this->getChunk($template);
4678
                break;
4679
            case 'DOCUMENT':
4680
                $doc = $this->getDocument($template, 'content', 'all');
4681
                $template = $doc['content'];
4682
                break;
4683
            case 'SELECT':
4684
                $this->getDatabase()->getValue($this->getDatabase()->query("SELECT {$template}"));
0 ignored issues
show
Bug introduced by
It seems like $this->getDatabase()->query("SELECT {$template}") targeting EvolutionCMS\Database::query() can also be of type boolean; however, EvolutionCMS\Database::getValue() does only seem to accept object<mysqli_result>|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...
4685
                break;
4686
            default:
4687
                if (!($template = $this->getChunk($tpl))) {
4688
                    $template = $tpl;
4689
                }
4690
        }
4691
        return $template;
4692
    }
4693
4694
    /**
4695
     * Returns the timestamp in the date format defined in $this->config['datetime_format']
4696
     *
4697
     * @param int $timestamp Default: 0
4698
     * @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.
4699
     * @return string
4700
     */
4701
    public function toDateFormat($timestamp = 0, $mode = '')
4702
    {
4703
        $timestamp = trim($timestamp);
4704
        if ($mode !== 'formatOnly' && empty($timestamp)) {
4705
            return '-';
4706
        }
4707
        $timestamp = (int)$timestamp;
4708
4709
        switch ($this->config['datetime_format']) {
4710
            case 'YYYY/mm/dd':
4711
                $dateFormat = '%Y/%m/%d';
4712
                break;
4713
            case 'dd-mm-YYYY':
4714
                $dateFormat = '%d-%m-%Y';
4715
                break;
4716
            case 'mm/dd/YYYY':
4717
                $dateFormat = '%m/%d/%Y';
4718
                break;
4719
            /*
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...
4720
            case 'dd-mmm-YYYY':
4721
                $dateFormat = '%e-%b-%Y';
4722
                break;
4723
            */
4724
        }
4725
4726
        if (empty($mode)) {
4727
            $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...
4728
        } elseif ($mode == 'dateOnly') {
4729
            $strTime = strftime($dateFormat, $timestamp);
4730
        } elseif ($mode == 'formatOnly') {
4731
            $strTime = $dateFormat;
4732
        }
4733
        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...
4734
    }
4735
4736
    /**
4737
     * Make a timestamp from a string corresponding to the format in $this->config['datetime_format']
4738
     *
4739
     * @param string $str
4740
     * @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...
4741
     */
4742
    public function toTimeStamp($str)
4743
    {
4744
        $str = trim($str);
4745
        if (empty($str)) {
4746
            return '';
4747
        }
4748
4749
        switch ($this->config['datetime_format']) {
4750 View Code Duplication
            case 'YYYY/mm/dd':
4751
                if (!preg_match('/^[0-9]{4}\/[0-9]{2}\/[0-9]{2}[0-9 :]*$/', $str)) {
4752
                    return '';
4753
                }
4754
                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...
4755
                break;
4756 View Code Duplication
            case 'dd-mm-YYYY':
4757
                if (!preg_match('/^[0-9]{2}-[0-9]{2}-[0-9]{4}[0-9 :]*$/', $str)) {
4758
                    return '';
4759
                }
4760
                list ($d, $m, $Y, $H, $M, $S) = sscanf($str, '%2d-%2d-%4d %2d:%2d:%2d');
4761
                break;
4762 View Code Duplication
            case 'mm/dd/YYYY':
4763
                if (!preg_match('/^[0-9]{2}\/[0-9]{2}\/[0-9]{4}[0-9 :]*$/', $str)) {
4764
                    return '';
4765
                }
4766
                list ($m, $d, $Y, $H, $M, $S) = sscanf($str, '%2d/%2d/%4d %2d:%2d:%2d');
4767
                break;
4768
            /*
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...
4769
            case 'dd-mmm-YYYY':
4770
                if (!preg_match('/^[0-9]{2}-[0-9a-z]+-[0-9]{4}[0-9 :]*$/i', $str)) {return '';}
4771
                list ($m, $d, $Y, $H, $M, $S) = sscanf($str, '%2d-%3s-%4d %2d:%2d:%2d');
4772
                break;
4773
            */
4774
        }
4775
        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...
4776
            $H = 0;
4777
            $M = 0;
4778
            $S = 0;
4779
        }
4780
        $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...
4781
        $timeStamp = (int)$timeStamp;
4782
        return $timeStamp;
4783
    }
4784
4785
    /**
4786
     * Get the TVs of a document's children. Returns an array where each element represents one child doc.
4787
     *
4788
     * Ignores deleted children. Gets all children - there is no where clause available.
4789
     *
4790
     * @param int $parentid The parent docid
4791
     *                 Default: 0 (site root)
4792
     * @param array $tvidnames . Which TVs to fetch - Can relate to the TV ids in the db (array elements should be numeric only)
4793
     *                                               or the TV names (array elements should be names only)
4794
     *                      Default: Empty array
4795
     * @param int $published Whether published or unpublished documents are in the result
4796
     *                      Default: 1
4797
     * @param string $docsort How to sort the result array (field)
4798
     *                      Default: menuindex
4799
     * @param ASC|string $docsortdir How to sort the result array (direction)
4800
     *                      Default: ASC
4801
     * @param string $tvfields Fields to fetch from site_tmplvars, default '*'
4802
     *                      Default: *
4803
     * @param string $tvsort How to sort each element of the result array i.e. how to sort the TVs (field)
4804
     *                      Default: rank
4805
     * @param string $tvsortdir How to sort each element of the result array i.e. how to sort the TVs (direction)
4806
     *                      Default: ASC
4807
     * @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...
4808
     */
4809
    public function getDocumentChildrenTVars($parentid = 0, $tvidnames = array(), $published = 1, $docsort = "menuindex", $docsortdir = "ASC", $tvfields = "*", $tvsort = "rank", $tvsortdir = "ASC")
4810
    {
4811
        $docs = $this->getDocumentChildren($parentid, $published, 0, '*', '', $docsort, $docsortdir);
4812
        if (!$docs) {
4813
            return false;
4814
        } else {
4815
            $result = array();
4816
            // get user defined template variables
4817
            if ($tvfields) {
4818
                $_ = 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...
4819
                foreach ($_ as $i => $v) {
4820
                    if ($v === 'value') {
4821
                        unset($_[$i]);
4822
                    } else {
4823
                        $_[$i] = 'tv.' . $v;
4824
                    }
4825
                }
4826
                $fields = implode(',', $_);
4827
            } else {
4828
                $fields = "tv.*";
4829
            }
4830
4831
            if ($tvsort != '') {
4832
                $tvsort = 'tv.' . implode(',tv.', array_filter(array_map('trim', explode(',', $tvsort))));
4833
            }
4834 View Code Duplication
            if ($tvidnames == "*") {
4835
                $query = "tv.id<>0";
4836
            } else {
4837
                $query = (is_numeric($tvidnames[0]) ? "tv.id" : "tv.name") . " IN ('" . implode("','", $tvidnames) . "')";
4838
            }
4839
4840
            $this->getUserDocGroups();
4841
4842
            foreach ($docs as $doc) {
4843
4844
                $docid = $doc['id'];
4845
4846
                $rs = $this->getDatabase()->select(
4847
                    "{$fields}, IF(tvc.value!='',tvc.value,tv.default_text) as value ",
4848
4849
                    $this->getDatabase()->getFullTableName("site_tmplvars") .
4850
                    " tv INNER JOIN " . $this->getDatabase()->getFullTableName("site_tmplvar_templates") .
4851
                    " tvtpl ON tvtpl.tmplvarid = tv.id LEFT JOIN " .
4852
                    $this->getDatabase()->getFullTableName("site_tmplvar_contentvalues") .
4853
                    " tvc ON tvc.tmplvarid=tv.id AND tvc.contentid='{$docid}'",
4854
4855
                    "{$query} AND tvtpl.templateid = '{$doc['template']}'",
4856
                    ($tvsort ? "{$tvsort} {$tvsortdir}" : "")
4857
                );
4858
                $tvs = $this->getDatabase()->makeArray($rs);
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se...rt} {$tvsortdir}" : '') on line 4846 can also be of type boolean; however, EvolutionCMS\Database::makeArray() does only seem to accept string|object<mysqli_result>, 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...
4859
4860
                // get default/built-in template variables
4861
                ksort($doc);
4862
                foreach ($doc as $key => $value) {
4863
                    if ($tvidnames == '*' || in_array($key, $tvidnames)) {
4864
                        $tvs[] = array('name' => $key, 'value' => $value);
4865
                    }
4866
                }
4867
                if (is_array($tvs) && count($tvs)) {
4868
                    $result[] = $tvs;
4869
                }
4870
            }
4871
            return $result;
4872
        }
4873
    }
4874
4875
    /**
4876
     * getDocumentChildrenTVarOutput
4877
     * @version 1.1 (2014-02-19)
4878
     *
4879
     * @desc Returns an array where each element represents one child doc and contains the result from getTemplateVarOutput().
4880
     *
4881
     * @param int $parentid {integer}
4882
     * - Id of parent document. Default: 0 (site root).
4883
     * @param array $tvidnames {array; '*'}
4884
     * - Which TVs to fetch. In the form expected by getTemplateVarOutput(). Default: array().
4885
     * @param int $published {0; 1; 'all'}
4886
     * - 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.
4887
     * @param string $sortBy {string}
4888
     * - How to sort the result array (field). Default: 'menuindex'.
4889
     * @param string $sortDir {'ASC'; 'DESC'}
4890
     * - How to sort the result array (direction). Default: 'ASC'.
4891
     * @param string $where {string}
4892
     * - SQL WHERE condition (use only document fields, not TV). Default: ''.
4893
     * @param string $resultKey {string; false}
4894
     * - Field, which values are keys into result array. Use the “false”, that result array keys just will be numbered. Default: 'id'.
4895
     * @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...
4896
     * - Result array, or false.
4897
     */
4898
    public function getDocumentChildrenTVarOutput($parentid = 0, $tvidnames = array(), $published = 1, $sortBy = 'menuindex', $sortDir = 'ASC', $where = '', $resultKey = 'id')
4899
    {
4900
        $docs = $this->getDocumentChildren($parentid, $published, 0, 'id', $where, $sortBy, $sortDir);
4901
4902
        if (!$docs) {
4903
            return false;
4904
        } else {
4905
            $result = array();
4906
4907
            $unsetResultKey = false;
4908
4909
            if ($resultKey !== false) {
4910
                if (is_array($tvidnames)) {
4911
                    if (count($tvidnames) != 0 && !in_array($resultKey, $tvidnames)) {
4912
                        $tvidnames[] = $resultKey;
4913
                        $unsetResultKey = true;
4914
                    }
4915
                } else if ($tvidnames != '*' && $tvidnames != $resultKey) {
4916
                    $tvidnames = array($tvidnames, $resultKey);
4917
                    $unsetResultKey = true;
4918
                }
4919
            }
4920
4921
            for ($i = 0; $i < count($docs); $i++) {
4922
                $tvs = $this->getTemplateVarOutput($tvidnames, $docs[$i]['id'], $published);
4923
4924
                if ($tvs) {
4925
                    if ($resultKey !== false && array_key_exists($resultKey, $tvs)) {
4926
                        $result[$tvs[$resultKey]] = $tvs;
4927
4928
                        if ($unsetResultKey) {
4929
                            unset($result[$tvs[$resultKey]][$resultKey]);
4930
                        }
4931
                    } else {
4932
                        $result[] = $tvs;
4933
                    }
4934
                }
4935
            }
4936
4937
            return $result;
4938
        }
4939
    }
4940
4941
    /**
4942
     * Modified by Raymond for TV - Orig Modified by Apodigm - DocVars
4943
     * Returns a single site_content field or TV record from the db.
4944
     *
4945
     * If a site content field the result is an associative array of 'name' and 'value'.
4946
     *
4947
     * If a TV the result is an array representing a db row including the fields specified in $fields.
4948
     *
4949
     * @param string $idname Can be a TV id or name
4950
     * @param string $fields Fields to fetch from site_tmplvars. Default: *
4951
     * @param string|type $docid Docid. Defaults to empty string which indicates the current document.
4952
     * @param int $published Whether published or unpublished documents are in the result
4953
     *                        Default: 1
4954
     * @return bool
4955
     */
4956 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...
4957
    {
4958
        if ($idname == "") {
4959
            return false;
4960
        } else {
4961
            $result = $this->getTemplateVars(array($idname), $fields, $docid, $published, "", ""); //remove sorting for speed
4962
            return ($result != false) ? $result[0] : false;
4963
        }
4964
    }
4965
4966
    /**
4967
     * getTemplateVars
4968
     * @version 1.0.1 (2014-02-19)
4969
     *
4970
     * @desc Returns an array of site_content field fields and/or TV records from the db.
4971
     * Elements representing a site content field consist of an associative array of 'name' and 'value'.
4972
     * Elements representing a TV consist of an array representing a db row including the fields specified in $fields.
4973
     *
4974
     * @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
4975
     * @param $fields {comma separated string; '*'} - Fields names in the TV table of MODx database. Default: '*'
4976
     * @param $docid {integer; ''} - Id of a document to get. Default: an empty string which indicates the current document.
4977
     * @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.
4978
     * @param $sort {comma separated string} - Fields of the TV table to sort by. Default: 'rank'.
4979
     * @param $dir {'ASC'; 'DESC'} - How to sort the result array (direction). Default: 'ASC'.
4980
     *
4981
     * @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...
4982
     */
4983
    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...
4984
    {
4985
        $cacheKey = md5(print_r(func_get_args(), true));
4986
        if (isset($this->tmpCache[__FUNCTION__][$cacheKey])) {
4987
            return $this->tmpCache[__FUNCTION__][$cacheKey];
4988
        }
4989
4990
        if (($idnames != '*' && !is_array($idnames)) || empty($idnames) ) {
4991
            return false;
4992
        } else {
4993
4994
            // get document record
4995
            if ($docid == '') {
4996
                $docid = $this->documentIdentifier;
4997
                $docRow = $this->documentObject;
4998
            } else {
4999
                $docRow = $this->getDocument($docid, '*', $published);
5000
5001
                if (!$docRow) {
5002
                    $this->tmpCache[__FUNCTION__][$cacheKey] = false;
5003
                    return false;
5004
                }
5005
            }
5006
5007
            // get user defined template variables
5008
            $fields = ($fields == '') ? 'tv.*' : 'tv.' . implode(',tv.', array_filter(array_map('trim', explode(',', $fields))));
5009
            $sort = ($sort == '') ? '' : 'tv.' . implode(',tv.', array_filter(array_map('trim', explode(',', $sort))));
5010
5011 View Code Duplication
            if ($idnames == '*') {
5012
                $query = 'tv.id<>0';
5013
            } else {
5014
                $query = (is_numeric($idnames[0]) ? 'tv.id' : 'tv.name') . " IN ('" . implode("','", $idnames) . "')";
5015
            }
5016
5017
            $rs = $this->getDatabase()->select("{$fields}, IF(tvc.value != '', tvc.value, tv.default_text) as value", $this->getDatabase()->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...
5018
                    INNER JOIN " . $this->getDatabase()->getFullTableName('site_tmplvar_templates') . " tvtpl ON tvtpl.tmplvarid = tv.id
5019
                    LEFT JOIN " . $this->getDatabase()->getFullTableName('site_tmplvar_contentvalues') . " tvc ON tvc.tmplvarid=tv.id AND tvc.contentid = '{$docid}'", "{$query} AND tvtpl.templateid = '{$docRow['template']}'", ($sort ? "{$sort} {$dir}" : ""));
5020
5021
            $result = $this->getDatabase()->makeArray($rs);
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se... "{$sort} {$dir}" : '') on line 5017 can also be of type boolean; however, EvolutionCMS\Database::makeArray() does only seem to accept string|object<mysqli_result>, 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...
5022
5023
            // get default/built-in template variables
5024
            if(is_array($docRow)){
5025
                ksort($docRow);
5026
5027
                foreach ($docRow as $key => $value) {
5028
                    if ($idnames == '*' || in_array($key, $idnames)) {
5029
                        array_push($result, array(
5030
                            'name' => $key,
5031
                            'value' => $value
5032
                        ));
5033
                    }
5034
                }
5035
            }
5036
5037
            $this->tmpCache[__FUNCTION__][$cacheKey] = $result;
5038
5039
            return $result;
5040
        }
5041
    }
5042
5043
    /**
5044
     * getTemplateVarOutput
5045
     * @version 1.0.1 (2014-02-19)
5046
     *
5047
     * @desc Returns an associative array containing TV rendered output values.
5048
     *
5049
     * @param array $idnames {array; '*'}
5050
     * - 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
5051
     * @param string $docid {integer; ''}
5052
     * - Id of a document to get. Default: an empty string which indicates the current document.
5053
     * @param int $published {0; 1; 'all'}
5054
     * - 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.
5055
     * @param string $sep {string}
5056
     * - Separator that is used while concatenating in getTVDisplayFormat(). Default: ''.
5057
     * @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...
5058
     * - Result array, or false.
5059
     */
5060
    public function getTemplateVarOutput($idnames = array(), $docid = '', $published = 1, $sep = '')
5061
    {
5062
        if (is_array($idnames) && empty($idnames) ) {
5063
            return false;
5064
        } else {
5065
            $output = array();
5066
            $vars = ($idnames == '*' || is_array($idnames)) ? $idnames : array($idnames);
5067
5068
            $docid = (int)$docid > 0 ? (int)$docid : $this->documentIdentifier;
5069
            // remove sort for speed
5070
            $result = $this->getTemplateVars($vars, '*', $docid, $published, '', '');
5071
5072
            if ($result == false) {
5073
                return false;
5074
            } else {
5075
                for ($i = 0; $i < count($result); $i++) {
5076
                    $row = $result[$i];
5077
5078
                    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...
5079
                        $output[$row['name']] = $row['value'];
5080
                    } else {
5081
                        $output[$row['name']] = getTVDisplayFormat($row['name'], $row['value'], $row['display'], $row['display_params'], $row['type'], $docid, $sep);
5082
                    }
5083
                }
5084
5085
                return $output;
5086
            }
5087
        }
5088
    }
5089
5090
    /**
5091
     * Returns the full table name based on db settings
5092
     *
5093
     * @param string $tbl Table name
5094
     * @return string Table name with prefix
5095
     * @deprecated use ->getDatabase()->getFullTableName()
5096
     */
5097
    public function getFullTableName($tbl)
5098
    {
5099
        return $this->getDatabase()->getFullTableName($tbl);
5100
    }
5101
5102
    /**
5103
     * Returns the placeholder value
5104
     *
5105
     * @param string $name Placeholder name
5106
     * @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...
5107
     */
5108
    public function getPlaceholder($name)
5109
    {
5110
        return isset($this->placeholders[$name]) ? $this->placeholders[$name] : null;
5111
    }
5112
5113
    /**
5114
     * Sets a value for a placeholder
5115
     *
5116
     * @param string $name The name of the placeholder
5117
     * @param string $value The value of the placeholder
5118
     */
5119
    public function setPlaceholder($name, $value)
5120
    {
5121
        $this->placeholders[$name] = $value;
5122
    }
5123
5124
    /**
5125
     * Set placeholders en masse via an array or object.
5126
     *
5127
     * @param object|array $subject
5128
     * @param string $prefix
5129
     */
5130
    public function toPlaceholders($subject, $prefix = '')
5131
    {
5132
        if (is_object($subject)) {
5133
            $subject = get_object_vars($subject);
5134
        }
5135
        if (is_array($subject)) {
5136
            foreach ($subject as $key => $value) {
5137
                $this->toPlaceholder($key, $value, $prefix);
5138
            }
5139
        }
5140
    }
5141
5142
    /**
5143
     * For use by toPlaceholders(); For setting an array or object element as placeholder.
5144
     *
5145
     * @param string $key
5146
     * @param object|array $value
5147
     * @param string $prefix
5148
     */
5149
    public function toPlaceholder($key, $value, $prefix = '')
5150
    {
5151
        if (is_array($value) || is_object($value)) {
5152
            $this->toPlaceholders($value, "{$prefix}{$key}.");
5153
        } else {
5154
            $this->setPlaceholder("{$prefix}{$key}", $value);
5155
        }
5156
    }
5157
5158
    /**
5159
     * Returns the manager relative URL/path with respect to the site root.
5160
     *
5161
     * @global string $base_url
5162
     * @return string The complete URL to the manager folder
5163
     */
5164
    public function getManagerPath()
5165
    {
5166
        return MODX_MANAGER_URL;
5167
    }
5168
5169
    /**
5170
     * Returns the cache relative URL/path with respect to the site root.
5171
     *
5172
     * @global string $base_url
5173
     * @return string The complete URL to the cache folder
5174
     */
5175
    public function getCachePath()
5176
    {
5177
        return MODX_BASE_URL . $this->getCacheFolder();
5178
    }
5179
5180
    /**
5181
     * Sends a message to a user's message box.
5182
     *
5183
     * @param string $type Type of the message
5184
     * @param string $to The recipient of the message
5185
     * @param string $from The sender of the message
5186
     * @param string $subject The subject of the message
5187
     * @param string $msg The message body
5188
     * @param int $private Whether it is a private message, or not
5189
     *                     Default : 0
5190
     */
5191
    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...
5192
    {
5193
        $private = ($private) ? 1 : 0;
5194 View Code Duplication
        if (!is_numeric($to)) {
5195
            // Query for the To ID
5196
            $rs = $this->getDatabase()->select('id', $this->getDatabase()->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...
5197
            $to = $this->getDatabase()->getValue($rs);
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se...'), "username='{$to}'") on line 5196 can also be of type boolean; however, EvolutionCMS\Database::getValue() does only seem to accept object<mysqli_result>|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...
5198
        }
5199 View Code Duplication
        if (!is_numeric($from)) {
5200
            // Query for the From ID
5201
            $rs = $this->getDatabase()->select('id', $this->getDatabase()->getFullTableName("manager_users"), "username='{$from}'");
5202
            $from = $this->getDatabase()->getValue($rs);
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se..., "username='{$from}'") on line 5201 can also be of type boolean; however, EvolutionCMS\Database::getValue() does only seem to accept object<mysqli_result>|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...
5203
        }
5204
        // insert a new message into user_messages
5205
        $this->getDatabase()->insert(array(
5206
            'type' => $type,
5207
            'subject' => $subject,
5208
            'message' => $msg,
5209
            'sender' => $from,
5210
            'recipient' => $to,
5211
            'private' => $private,
5212
            'postdate' => $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time'],
5213
            'messageread' => 0,
5214
        ), $this->getDatabase()->getFullTableName('user_messages'));
5215
    }
5216
5217
    /**
5218
     * Returns current user id.
5219
     *
5220
     * @param string $context . Default is an empty string which indicates the method should automatically pick 'web (frontend) or 'mgr' (backend)
5221
     * @return string
5222
     */
5223 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...
5224
    {
5225
        $out = false;
5226
5227
        if (!empty($context)) {
5228
            if (is_scalar($context) && isset($_SESSION[$context . 'Validated'])) {
5229
                $out = $_SESSION[$context . 'InternalKey'];
5230
            }
5231
        } else {
5232
            switch (true) {
5233
                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...
5234
                    $out = $_SESSION['webInternalKey'];
5235
                    break;
5236
                }
5237
                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...
5238
                    $out = $_SESSION['mgrInternalKey'];
5239
                    break;
5240
                }
5241
            }
5242
        }
5243
        return $out;
5244
    }
5245
5246
    /**
5247
     * Returns current user name
5248
     *
5249
     * @param string $context . Default is an empty string which indicates the method should automatically pick 'web (frontend) or 'mgr' (backend)
5250
     * @return string
5251
     */
5252 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...
5253
    {
5254
        $out = false;
5255
5256
        if (!empty($context)) {
5257
            if (is_scalar($context) && isset($_SESSION[$context . 'Validated'])) {
5258
                $out = $_SESSION[$context . 'Shortname'];
5259
            }
5260
        } else {
5261
            switch (true) {
5262
                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...
5263
                    $out = $_SESSION['webShortname'];
5264
                    break;
5265
                }
5266
                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...
5267
                    $out = $_SESSION['mgrShortname'];
5268
                    break;
5269
                }
5270
            }
5271
        }
5272
        return $out;
5273
    }
5274
5275
    /**
5276
     * Returns current login user type - web or manager
5277
     *
5278
     * @return string
5279
     */
5280
    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...
5281
    {
5282
        if ($this->isFrontend() && isset ($_SESSION['webValidated'])) {
5283
            return 'web';
5284
        } elseif ($this->isBackend() && isset ($_SESSION['mgrValidated'])) {
5285
            return 'manager';
5286
        } else {
5287
            return '';
5288
        }
5289
    }
5290
5291
    /**
5292
     * Returns a user info record for the given manager user
5293
     *
5294
     * @param int $uid
5295
     * @return boolean|string
5296
     */
5297
    public function getUserInfo($uid)
5298
    {
5299
        if (isset($this->tmpCache[__FUNCTION__][$uid])) {
5300
            return $this->tmpCache[__FUNCTION__][$uid];
5301
        }
5302
5303
        $from = $this->getDatabase()->getFullTableName('manager_users') . ' mu INNER JOIN ' .
5304
            $this->getDatabase()->getFullTableName('user_attributes'). ' mua ON mua.internalkey=mu.id';
5305
        $where = sprintf("mu.id='%s'", $this->getDatabase()->escape($uid));
5306
        $rs = $this->getDatabase()->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...
5307
5308
        if (!$this->getDatabase()->getRecordCount($rs)) {
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se..., $from, $where, '', 1) on line 5306 can also be of type boolean; however, EvolutionCMS\Database::getRecordCount() does only seem to accept object<mysqli_result>, 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...
5309
            return $this->tmpCache[__FUNCTION__][$uid] = false;
5310
        }
5311
5312
        $row = $this->getDatabase()->getRow($rs);
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se..., $from, $where, '', 1) on line 5306 can also be of type boolean; however, EvolutionCMS\Database::getRow() does only seem to accept object<mysqli_result>, 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...
5313 View Code Duplication
        if (!isset($row['usertype']) || !$row['usertype']) {
5314
            $row['usertype'] = 'manager';
5315
        }
5316
5317
        $this->tmpCache[__FUNCTION__][$uid] = $row;
5318
5319
        return $row;
5320
    }
5321
5322
    /**
5323
     * Returns a record for the web user
5324
     *
5325
     * @param int $uid
5326
     * @return boolean|string
5327
     */
5328
    public function getWebUserInfo($uid)
5329
    {
5330
        $rs = $this->getDatabase()->select('wu.username, wu.password, wua.*', $this->getDatabase()->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...
5331
                INNER JOIN " . $this->getDatabase()->getFullTableName("web_user_attributes") . " wua ON wua.internalkey=wu.id", "wu.id='{$uid}'");
5332
        if ($row = $this->getDatabase()->getRow($rs)) {
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se....id', "wu.id='{$uid}'") on line 5330 can also be of type boolean; however, EvolutionCMS\Database::getRow() does only seem to accept object<mysqli_result>, 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...
5333 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...
5334
                $row["usertype"] = "web";
5335
            }
5336
            return $row;
5337
        }
5338
    }
5339
5340
    /**
5341
     * Returns an array of document groups that current user is assigned to.
5342
     * This function will first return the web user doc groups when running from
5343
     * frontend otherwise it will return manager user's docgroup.
5344
     *
5345
     * @param boolean $resolveIds Set to true to return the document group names
5346
     *                            Default: false
5347
     * @return string|array
5348
     */
5349
    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...
5350
    {
5351
        if ($this->isFrontend() && isset($_SESSION['webDocgroups']) && isset($_SESSION['webValidated'])) {
5352
            $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...
5353
            $dgn = isset($_SESSION['webDocgrpNames']) ? $_SESSION['webDocgrpNames'] : false;
5354
        } else if ($this->isBackend() && isset($_SESSION['mgrDocgroups']) && isset($_SESSION['mgrValidated'])) {
5355
            $dg = $_SESSION['mgrDocgroups'];
5356
            $dgn = isset($_SESSION['mgrDocgrpNames']) ? $_SESSION['mgrDocgrpNames'] : false;
5357
        } else {
5358
            $dg = '';
5359
        }
5360
        if (!$resolveIds) {
5361
            return $dg;
5362
        } else if (is_array($dgn)) {
5363
            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...
5364
        } else if (is_array($dg)) {
5365
            // resolve ids to names
5366
            $dgn = array();
5367
            $ds = $this->getDatabase()->select('name', $this->getDatabase()->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...
5368
            while ($row = $this->getDatabase()->getRow($ds)) {
0 ignored issues
show
Bug introduced by
It seems like $ds defined by $this->getDatabase()->se...mplode(',', $dg) . ')') on line 5367 can also be of type boolean; however, EvolutionCMS\Database::getRow() does only seem to accept object<mysqli_result>, 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...
5369
                $dgn[] = $row['name'];
5370
            }
5371
            // cache docgroup names to session
5372
            if ($this->isFrontend()) {
5373
                $_SESSION['webDocgrpNames'] = $dgn;
5374
            } else {
5375
                $_SESSION['mgrDocgrpNames'] = $dgn;
5376
            }
5377
            return $dgn;
5378
        }
5379
    }
5380
5381
    /**
5382
     * Change current web user's password
5383
     *
5384
     * @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...
5385
     * @param string $oldPwd
5386
     * @param string $newPwd
5387
     * @return string|boolean Returns true if successful, oterhwise return error
5388
     *                        message
5389
     */
5390
    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...
5391
    {
5392
        $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...
5393
        if ($_SESSION["webValidated"] == 1) {
5394
            $tbl = $this->getDatabase()->getFullTableName("web_users");
5395
            $ds = $this->getDatabase()->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...
5396
            if ($row = $this->getDatabase()->getRow($ds)) {
0 ignored issues
show
Bug introduced by
It seems like $ds defined by $this->getDatabase()->se...etLoginUserID() . '\'') on line 5395 can also be of type boolean; however, EvolutionCMS\Database::getRow() does only seem to accept object<mysqli_result>, 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...
5397
                if ($row["password"] == md5($oldPwd)) {
5398
                    if (strlen($newPwd) < 6) {
5399
                        return "Password is too short!";
5400
                    } elseif ($newPwd == "") {
5401
                        return "You didn't specify a password for this user!";
5402
                    } else {
5403
                        $this->getDatabase()->update(array(
5404
                            'password' => $this->getDatabase()->escape($newPwd),
5405
                        ), $tbl, "id='" . $this->getLoginUserID() . "'");
5406
                        // invoke OnWebChangePassword event
5407
                        $this->invokeEvent("OnWebChangePassword", array(
5408
                            "userid" => $row["id"],
5409
                            "username" => $row["username"],
5410
                            "userpassword" => $newPwd
5411
                        ));
5412
                        return true;
5413
                    }
5414
                } else {
5415
                    return "Incorrect password.";
5416
                }
5417
            }
5418
        }
5419
        return $rt;
5420
    }
5421
5422
    /**
5423
     * Returns true if the current web user is a member the specified groups
5424
     *
5425
     * @param array $groupNames
5426
     * @return boolean
5427
     */
5428
    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...
5429
    {
5430
        if (!is_array($groupNames)) {
5431
            return false;
5432
        }
5433
        // check cache
5434
        $grpNames = isset ($_SESSION['webUserGroupNames']) ? $_SESSION['webUserGroupNames'] : false;
5435
        if (!is_array($grpNames)) {
5436
            $rs = $this->getDatabase()->select('wgn.name', $this->getDatabase()->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...
5437
                    INNER JOIN " . $this->getDatabase()->getFullTableName("web_groups") . " wg ON wg.webgroup=wgn.id AND wg.webuser='" . $this->getLoginUserID() . "'");
5438
            $grpNames = $this->getDatabase()->getColumn("name", $rs);
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se...etLoginUserID() . '\'') on line 5436 can also be of type boolean; however, EvolutionCMS\Database::getColumn() does only seem to accept object<mysqli_result>|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...
5439
            // save to cache
5440
            $_SESSION['webUserGroupNames'] = $grpNames;
5441
        }
5442
        foreach ($groupNames as $k => $v) {
5443
            if (in_array(trim($v), $grpNames)) {
5444
                return true;
5445
            }
5446
        }
5447
        return false;
5448
    }
5449
5450
    /**
5451
     * Registers Client-side CSS scripts - these scripts are loaded at inside
5452
     * the <head> tag
5453
     *
5454
     * @param string $src
5455
     * @param string $media Default: Empty string
5456
     * @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...
5457
     */
5458
    public function regClientCSS($src, $media = '')
5459
    {
5460
        if (empty($src) || isset ($this->loadedjscripts[$src])) {
5461
            return '';
5462
        }
5463
        $nextpos = max(array_merge(array(0), array_keys($this->sjscripts))) + 1;
5464
        $this->loadedjscripts[$src]['startup'] = true;
5465
        $this->loadedjscripts[$src]['version'] = '0';
5466
        $this->loadedjscripts[$src]['pos'] = $nextpos;
5467
        if (strpos(strtolower($src), "<style") !== false || strpos(strtolower($src), "<link") !== false) {
5468
            $this->sjscripts[$nextpos] = $src;
5469
        } else {
5470
            $this->sjscripts[$nextpos] = "\t" . '<link rel="stylesheet" type="text/css" href="' . $src . '" ' . ($media ? 'media="' . $media . '" ' : '') . '/>';
5471
        }
5472
    }
5473
5474
    /**
5475
     * Registers Startup Client-side JavaScript - these scripts are loaded at inside the <head> tag
5476
     *
5477
     * @param string $src
5478
     * @param array $options Default: 'name'=>'', 'version'=>'0', 'plaintext'=>false
5479
     */
5480
    public function regClientStartupScript($src, $options = array('name' => '', 'version' => '0', 'plaintext' => false))
5481
    {
5482
        $this->regClientScript($src, $options, true);
5483
    }
5484
5485
    /**
5486
     * Registers Client-side JavaScript these scripts are loaded at the end of the page unless $startup is true
5487
     *
5488
     * @param string $src
5489
     * @param array $options Default: 'name'=>'', 'version'=>'0', 'plaintext'=>false
5490
     * @param boolean $startup Default: false
5491
     * @return string
5492
     */
5493
    public function regClientScript($src, $options = array('name' => '', 'version' => '0', 'plaintext' => false), $startup = false)
5494
    {
5495
        if (empty($src)) {
5496
            return '';
5497
        } // nothing to register
5498
        if (!is_array($options)) {
5499
            if (is_bool($options))  // backward compatibility with old plaintext parameter
5500
            {
5501
                $options = array('plaintext' => $options);
5502
            } elseif (is_string($options)) // Also allow script name as 2nd param
5503
            {
5504
                $options = array('name' => $options);
5505
            } else {
5506
                $options = array();
5507
            }
5508
        }
5509
        $name = isset($options['name']) ? strtolower($options['name']) : '';
5510
        $version = isset($options['version']) ? $options['version'] : '0';
5511
        $plaintext = isset($options['plaintext']) ? $options['plaintext'] : false;
5512
        $key = !empty($name) ? $name : $src;
5513
        unset($overwritepos); // probably unnecessary--just making sure
5514
5515
        $useThisVer = true;
5516
        if (isset($this->loadedjscripts[$key])) { // a matching script was found
5517
            // if existing script is a startup script, make sure the candidate is also a startup script
5518
            if ($this->loadedjscripts[$key]['startup']) {
5519
                $startup = true;
5520
            }
5521
5522
            if (empty($name)) {
5523
                $useThisVer = false; // if the match was based on identical source code, no need to replace the old one
5524
            } else {
5525
                $useThisVer = version_compare($this->loadedjscripts[$key]['version'], $version, '<');
5526
            }
5527
5528
            if ($useThisVer) {
5529
                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...
5530
                    // remove old script from the bottom of the page (new one will be at the top)
5531
                    unset($this->jscripts[$this->loadedjscripts[$key]['pos']]);
5532
                } else {
5533
                    // overwrite the old script (the position may be important for dependent scripts)
5534
                    $overwritepos = $this->loadedjscripts[$key]['pos'];
5535
                }
5536
            } else { // Use the original version
5537
                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...
5538
                    // need to move the exisiting script to the head
5539
                    $version = $this->loadedjscripts[$key][$version];
5540
                    $src = $this->jscripts[$this->loadedjscripts[$key]['pos']];
5541
                    unset($this->jscripts[$this->loadedjscripts[$key]['pos']]);
5542
                } else {
5543
                    return ''; // the script is already in the right place
5544
                }
5545
            }
5546
        }
5547
5548
        if ($useThisVer && $plaintext != true && (strpos(strtolower($src), "<script") === false)) {
5549
            $src = "\t" . '<script type="text/javascript" src="' . $src . '"></script>';
5550
        }
5551
        if ($startup) {
5552
            $pos = isset($overwritepos) ? $overwritepos : max(array_merge(array(0), array_keys($this->sjscripts))) + 1;
5553
            $this->sjscripts[$pos] = $src;
5554
        } else {
5555
            $pos = isset($overwritepos) ? $overwritepos : max(array_merge(array(0), array_keys($this->jscripts))) + 1;
5556
            $this->jscripts[$pos] = $src;
5557
        }
5558
        $this->loadedjscripts[$key]['version'] = $version;
5559
        $this->loadedjscripts[$key]['startup'] = $startup;
5560
        $this->loadedjscripts[$key]['pos'] = $pos;
5561
        return '';
5562
    }
5563
5564
    /**
5565
     * Returns all registered JavaScripts
5566
     *
5567
     * @return string
5568
     */
5569
    public function regClientStartupHTMLBlock($html)
5570
    {
5571
        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...
5572
    }
5573
5574
    /**
5575
     * Returns all registered startup scripts
5576
     *
5577
     * @return string
5578
     */
5579
    public function regClientHTMLBlock($html)
5580
    {
5581
        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...
5582
    }
5583
5584
    /**
5585
     * Remove unwanted html tags and snippet, settings and tags
5586
     *
5587
     * @param string $html
5588
     * @param string $allowed Default: Empty string
5589
     * @return string
5590
     */
5591
    public function stripTags($html, $allowed = "")
5592
    {
5593
        $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...
5594
        $t = preg_replace('~\[\*(.*?)\*\]~', "", $t); //tv
5595
        $t = preg_replace('~\[\[(.*?)\]\]~', "", $t); //snippet
5596
        $t = preg_replace('~\[\!(.*?)\!\]~', "", $t); //snippet
5597
        $t = preg_replace('~\[\((.*?)\)\]~', "", $t); //settings
5598
        $t = preg_replace('~\[\+(.*?)\+\]~', "", $t); //placeholders
5599
        $t = preg_replace('~{{(.*?)}}~', "", $t); //chunks
5600
        return $t;
5601
    }
5602
5603
    /**
5604
     * Add an event listener to a plugin - only for use within the current execution cycle
5605
     *
5606
     * @param string $evtName
5607
     * @param string $pluginName
5608
     * @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...
5609
     */
5610
    public function addEventListener($evtName, $pluginName)
5611
    {
5612
        if (!$evtName || !$pluginName) {
5613
            return false;
5614
        }
5615
        if (!array_key_exists($evtName, $this->pluginEvent)) {
5616
            $this->pluginEvent[$evtName] = array();
5617
        }
5618
        return array_push($this->pluginEvent[$evtName], $pluginName); // return array count
5619
    }
5620
5621
    /**
5622
     * Remove event listener - only for use within the current execution cycle
5623
     *
5624
     * @param string $evtName
5625
     * @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...
5626
     */
5627
    public function removeEventListener($evtName)
5628
    {
5629
        if (!$evtName) {
5630
            return false;
5631
        }
5632
        unset ($this->pluginEvent[$evtName]);
5633
    }
5634
5635
    /**
5636
     * Remove all event listeners - only for use within the current execution cycle
5637
     */
5638
    public function removeAllEventListener()
5639
    {
5640
        unset ($this->pluginEvent);
5641
        $this->pluginEvent = array();
5642
    }
5643
5644
    /**
5645
     * Invoke an event.
5646
     *
5647
     * @param string $evtName
5648
     * @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.
5649
     * @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...
5650
     */
5651
    public function invokeEvent($evtName, $extParams = array())
5652
    {
5653
        if (!$evtName) {
5654
            return false;
5655
        }
5656
        if (!isset ($this->pluginEvent[$evtName])) {
5657
            return false;
5658
        }
5659
5660
        $results = null;
5661
        foreach ($this->pluginEvent[$evtName] as $pluginName) { // start for loop
5662
            if ($this->dumpPlugins) {
5663
                $eventtime = $this->getMicroTime();
5664
            }
5665
            // reset event object
5666
            $e = &$this->event;
5667
            $e->_resetEventObject();
5668
            $e->name = $evtName;
5669
            $e->activePlugin = $pluginName;
5670
5671
            // get plugin code
5672
            $_ = $this->getPluginCode($pluginName);
5673
            $pluginCode = $_['code'];
5674
            $pluginProperties = $_['props'];
5675
5676
            // load default params/properties
5677
            $parameter = $this->parseProperties($pluginProperties);
5678
            if (!is_array($parameter)) {
5679
                $parameter = array();
5680
            }
5681
            if (!empty($extParams)) {
5682
                $parameter = array_merge($parameter, $extParams);
5683
            }
5684
5685
            // eval plugin
5686
            $this->evalPlugin($pluginCode, $parameter);
5687
5688
            if (class_exists('PHxParser')) {
5689
                $this->config['enable_filter'] = 0;
5690
            }
5691
5692
            if ($this->dumpPlugins) {
5693
                $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...
5694
                $this->pluginsCode .= sprintf('<fieldset><legend><b>%s / %s</b> (%2.2f ms)</legend>', $evtName, $pluginName, $eventtime * 1000);
5695
                foreach ($parameter as $k => $v) {
5696
                    $this->pluginsCode .= "{$k} => " . print_r($v, true) . '<br>';
5697
                }
5698
                $this->pluginsCode .= '</fieldset><br />';
5699
                $this->pluginsTime["{$evtName} / {$pluginName}"] += $eventtime;
5700
            }
5701
            if ($e->getOutput() != '') {
5702
                $results[] = $e->getOutput();
5703
            }
5704
            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...
5705
                break;
5706
            }
5707
        }
5708
5709
        $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...
5710
        return $results;
5711
    }
5712
5713
    /**
5714
     * Returns plugin-code and properties
5715
     *
5716
     * @param string $pluginName
5717
     * @return array Associative array consisting of 'code' and 'props'
5718
     */
5719
    public function getPluginCode($pluginName)
5720
    {
5721
        $plugin = array();
5722
        if (isset ($this->pluginCache[$pluginName])) {
5723
            $pluginCode = $this->pluginCache[$pluginName];
5724
            $pluginProperties = isset($this->pluginCache[$pluginName . "Props"]) ? $this->pluginCache[$pluginName . "Props"] : '';
5725
        } else {
5726
            $pluginName = $this->getDatabase()->escape($pluginName);
5727
            $result = $this->getDatabase()->select('name, plugincode, properties', $this->getDatabase()->getFullTableName("site_plugins"), "name='{$pluginName}' AND disabled=0");
5728
            if ($row = $this->getDatabase()->getRow($result)) {
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->getDatabase()->se...Name}' AND disabled=0") on line 5727 can also be of type boolean; however, EvolutionCMS\Database::getRow() does only seem to accept object<mysqli_result>, 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...
5729
                $pluginCode = $this->pluginCache[$row['name']] = $row['plugincode'];
5730
                $pluginProperties = $this->pluginCache[$row['name'] . "Props"] = $row['properties'];
5731
            } else {
5732
                $pluginCode = $this->pluginCache[$pluginName] = "return false;";
5733
                $pluginProperties = '';
5734
            }
5735
        }
5736
        $plugin['code'] = $pluginCode;
5737
        $plugin['props'] = $pluginProperties;
5738
5739
        return $plugin;
5740
    }
5741
5742
    /**
5743
     * Parses a resource property string and returns the result as an array
5744
     *
5745
     * @param string|array $propertyString
5746
     * @param string|null $elementName
5747
     * @param string|null $elementType
5748
     * @return array Associative array in the form property name => property value
5749
     */
5750
    public function parseProperties($propertyString, $elementName = null, $elementType = null)
5751
    {
5752
        $property = array();
5753
5754
        if(\is_scalar($propertyString)) {
5755
            $propertyString = trim($propertyString);
5756
            $propertyString = str_replace('{}', '', $propertyString);
5757
            $propertyString = str_replace('} {', ',', $propertyString);
5758
            if (!empty($propertyString) && $propertyString != '{}') {
5759
                $jsonFormat = $this->isJson($propertyString, true);
5760
                // old format
5761
                if ($jsonFormat === false) {
5762
                    $props = explode('&', $propertyString);
5763
                    foreach ($props as $prop) {
5764
5765
                        if (empty($prop)) {
5766
                            continue;
5767
                        } elseif (strpos($prop, '=') === false) {
5768
                            $property[trim($prop)] = '';
5769
                            continue;
5770
                        }
5771
5772
                        $_ = explode('=', $prop, 2);
5773
                        $key = trim($_[0]);
5774
                        $p = explode(';', trim($_[1]));
5775
                        switch ($p[1]) {
5776
                            case 'list':
5777
                            case 'list-multi':
5778
                            case 'checkbox':
5779
                            case 'radio':
5780
                                $value = !isset($p[3]) ? '' : $p[3];
5781
                                break;
5782
                            default:
5783
                                $value = !isset($p[2]) ? '' : $p[2];
5784
                        }
5785
                        if (!empty($key)) {
5786
                            $property[$key] = $value;
5787
                        }
5788
                    }
5789
                    // new json-format
5790
                } else if (!empty($jsonFormat)) {
5791
                    foreach ($jsonFormat as $key => $row) {
5792
                        if (!empty($key)) {
5793 View Code Duplication
                            if (is_array($row)) {
5794
                                if (isset($row[0]['value'])) {
5795
                                    $value = $row[0]['value'];
5796
                                }
5797
                            } else {
5798
                                $value = $row;
5799
                            }
5800
                            if (isset($value) && $value !== '') {
5801
                                $property[$key] = $value;
5802
                            }
5803
                        }
5804
                    }
5805
                }
5806
            }
5807
        }
5808
        elseif(\is_array($propertyString)) {
5809
            $property = $propertyString;
5810
        }
5811
        if (!empty($elementName) && !empty($elementType)) {
5812
            $out = $this->invokeEvent('OnParseProperties', array(
5813
                'element' => $elementName,
5814
                'type' => $elementType,
5815
                'args' => $property
5816
            ));
5817
            if (is_array($out)) {
5818
                $out = array_pop($out);
5819
            }
5820
            if (is_array($out)) {
5821
                $property = $out;
5822
            }
5823
        }
5824
5825
        return $property;
5826
    }
5827
5828
    /**
5829
     * Parses docBlock from a file and returns the result as an array
5830
     *
5831
     * @param string $element_dir
5832
     * @param string $filename
5833
     * @param boolean $escapeValues
5834
     * @return array Associative array in the form property name => property value
5835
     */
5836
    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...
5837
    {
5838
        $params = array();
5839
        $fullpath = $element_dir . '/' . $filename;
5840
        if (is_readable($fullpath)) {
5841
            $tpl = @fopen($fullpath, "r");
5842
            if ($tpl) {
5843
                $params['filename'] = $filename;
5844
                $docblock_start_found = false;
5845
                $name_found = false;
5846
                $description_found = false;
5847
                $docblock_end_found = false;
5848
                $arrayParams = array('author', 'documentation', 'reportissues', 'link');
5849
5850
                while (!feof($tpl)) {
5851
                    $line = fgets($tpl);
5852
                    $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...
5853
                    $docblock_start_found = $r['docblock_start_found'];
5854
                    $name_found = $r['name_found'];
5855
                    $description_found = $r['description_found'];
5856
                    $docblock_end_found = $r['docblock_end_found'];
5857
                    $param = $r['param'];
5858
                    $val = $r['val'];
5859
                    if (!$docblock_end_found) {
5860
                        break;
5861
                    }
5862
                    if (!$docblock_start_found || !$name_found || !$description_found || empty($param)) {
5863
                        continue;
5864
                    }
5865 View Code Duplication
                    if (!empty($param)) {
5866
                        if (in_array($param, $arrayParams)) {
5867
                            if (!isset($params[$param])) {
5868
                                $params[$param] = array();
5869
                            }
5870
                            $params[$param][] = $escapeValues ? $this->getDatabase()->escape($val) : $val;
5871
                        } else {
5872
                            $params[$param] = $escapeValues ? $this->getDatabase()->escape($val) : $val;
5873
                        }
5874
                    }
5875
                }
5876
                @fclose($tpl);
5877
            }
5878
        }
5879
        return $params;
5880
    }
5881
5882
    /**
5883
     * Parses docBlock from string and returns the result as an array
5884
     *
5885
     * @param string $string
5886
     * @param boolean $escapeValues
5887
     * @return array Associative array in the form property name => property value
5888
     */
5889
    public function parseDocBlockFromString($string, $escapeValues = false)
5890
    {
5891
        $params = array();
5892
        if (!empty($string)) {
5893
            $string = str_replace('\r\n', '\n', $string);
5894
            $exp = explode('\n', $string);
5895
            $docblock_start_found = false;
5896
            $name_found = false;
5897
            $description_found = false;
5898
            $docblock_end_found = false;
5899
            $arrayParams = array('author', 'documentation', 'reportissues', 'link');
5900
5901
            foreach ($exp as $line) {
5902
                $r = $this->parseDocBlockLine($line, $docblock_start_found, $name_found, $description_found, $docblock_end_found);
5903
                $docblock_start_found = $r['docblock_start_found'];
5904
                $name_found = $r['name_found'];
5905
                $description_found = $r['description_found'];
5906
                $docblock_end_found = $r['docblock_end_found'];
5907
                $param = $r['param'];
5908
                $val = $r['val'];
5909
                if (!$docblock_start_found) {
5910
                    continue;
5911
                }
5912
                if ($docblock_end_found) {
5913
                    break;
5914
                }
5915 View Code Duplication
                if (!empty($param)) {
5916
                    if (in_array($param, $arrayParams)) {
5917
                        if (!isset($params[$param])) {
5918
                            $params[$param] = array();
5919
                        }
5920
                        $params[$param][] = $escapeValues ? $this->getDatabase()->escape($val) : $val;
5921
                    } else {
5922
                        $params[$param] = $escapeValues ? $this->getDatabase()->escape($val) : $val;
5923
                    }
5924
                }
5925
            }
5926
        }
5927
        return $params;
5928
    }
5929
5930
    /**
5931
     * Parses docBlock of a component´s source-code and returns the result as an array
5932
     * (modified parseDocBlock() from modules/stores/setup.info.php by Bumkaka & Dmi3yy)
5933
     *
5934
     * @param string $line
5935
     * @param boolean $docblock_start_found
5936
     * @param boolean $name_found
5937
     * @param boolean $description_found
5938
     * @param boolean $docblock_end_found
5939
     * @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...
5940
     */
5941
    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...
5942
    {
5943
        $param = '';
5944
        $val = '';
5945
        $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...
5946
        if (!$docblock_start_found) {
5947
            // find docblock start
5948
            if (strpos($line, '/**') !== false) {
5949
                $docblock_start_found = true;
5950
            }
5951
        } elseif (!$name_found) {
5952
            // find name
5953
            if (preg_match("/^\s+\*\s+(.+)/", $line, $ma)) {
5954
                $param = 'name';
5955
                $val = trim($ma[1]);
5956
                $name_found = !empty($val);
5957
            }
5958
        } elseif (!$description_found) {
5959
            // find description
5960
            if (preg_match("/^\s+\*\s+(.+)/", $line, $ma)) {
5961
                $param = 'description';
5962
                $val = trim($ma[1]);
5963
                $description_found = !empty($val);
5964
            }
5965
        } else {
5966
            if (preg_match("/^\s+\*\s+\@([^\s]+)\s+(.+)/", $line, $ma)) {
5967
                $param = trim($ma[1]);
5968
                $val = trim($ma[2]);
5969
                if (!empty($param) && !empty($val)) {
5970
                    if ($param == 'internal') {
5971
                        $ma = null;
5972
                        if (preg_match("/\@([^\s]+)\s+(.+)/", $val, $ma)) {
5973
                            $param = trim($ma[1]);
5974
                            $val = trim($ma[2]);
5975
                        }
5976
                    }
5977
                }
5978
            } elseif (preg_match("/^\s*\*\/\s*$/", $line)) {
5979
                $docblock_end_found = true;
5980
            }
5981
        }
5982
        return array(
5983
            'docblock_start_found' => $docblock_start_found,
5984
            'name_found' => $name_found,
5985
            'description_found' => $description_found,
5986
            'docblock_end_found' => $docblock_end_found,
5987
            'param' => $param,
5988
            'val' => $val
5989
        );
5990
    }
5991
5992
    /**
5993
     * Renders docBlock-parameters into human readable list
5994
     *
5995
     * @param array $parsed
5996
     * @return string List in HTML-format
5997
     */
5998
    public function convertDocBlockIntoList($parsed)
5999
    {
6000
        global $_lang;
6001
6002
        // Replace special placeholders & make URLs + Emails clickable
6003
        $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...
6004
        $regexUrl = "/((http|https|ftp|ftps)\:\/\/[^\/]+(\/[^\s]+[^,.?!:;\s])?)/";
6005
        $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';
6006
        $emailSubject = isset($parsed['name']) ? '?subject=' . $parsed['name'] : '';
6007
        $emailSubject .= isset($parsed['version']) ? ' v' . $parsed['version'] : '';
6008
        foreach ($parsed as $key => $val) {
6009
            if (is_array($val)) {
6010
                foreach ($val as $key2 => $val2) {
6011
                    $val2 = $this->parseText($val2, $ph);
6012 View Code Duplication
                    if (preg_match($regexUrl, $val2, $url)) {
6013
                        $val2 = preg_replace($regexUrl, "<a href=\"{$url[0]}\" target=\"_blank\">{$url[0]}</a> ", $val2);
6014
                    }
6015 View Code Duplication
                    if (preg_match($regexEmail, $val2, $url)) {
6016
                        $val2 = preg_replace($regexEmail, '<a href="mailto:\\1' . $emailSubject . '">\\1</a>', $val2);
6017
                    }
6018
                    $parsed[$key][$key2] = $val2;
6019
                }
6020
            } else {
6021
                $val = $this->parseText($val, $ph);
6022 View Code Duplication
                if (preg_match($regexUrl, $val, $url)) {
6023
                    $val = preg_replace($regexUrl, "<a href=\"{$url[0]}\" target=\"_blank\">{$url[0]}</a> ", $val);
6024
                }
6025 View Code Duplication
                if (preg_match($regexEmail, $val, $url)) {
6026
                    $val = preg_replace($regexEmail, '<a href="mailto:\\1' . $emailSubject . '">\\1</a>', $val);
6027
                }
6028
                $parsed[$key] = $val;
6029
            }
6030
        }
6031
6032
        $arrayParams = array(
6033
            'documentation' => $_lang['documentation'],
6034
            'reportissues' => $_lang['report_issues'],
6035
            'link' => $_lang['further_info'],
6036
            'author' => $_lang['author_infos']
6037
        );
6038
6039
        $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...
6040
        $list = isset($parsed['logo']) ? '<img src="' . $this->config['base_url'] . ltrim($parsed['logo'], "/") . '" style="float:right;max-width:100px;height:auto;" />' . $nl : '';
6041
        $list .= '<p>' . $nl;
6042
        $list .= isset($parsed['name']) ? '<strong>' . $parsed['name'] . '</strong><br/>' . $nl : '';
6043
        $list .= isset($parsed['description']) ? $parsed['description'] . $nl : '';
6044
        $list .= '</p><br/>' . $nl;
6045
        $list .= isset($parsed['version']) ? '<p><strong>' . $_lang['version'] . ':</strong> ' . $parsed['version'] . '</p>' . $nl : '';
6046
        $list .= isset($parsed['license']) ? '<p><strong>' . $_lang['license'] . ':</strong> ' . $parsed['license'] . '</p>' . $nl : '';
6047
        $list .= isset($parsed['lastupdate']) ? '<p><strong>' . $_lang['last_update'] . ':</strong> ' . $parsed['lastupdate'] . '</p>' . $nl : '';
6048
        $list .= '<br/>' . $nl;
6049
        $first = true;
6050
        foreach ($arrayParams as $param => $label) {
6051
            if (isset($parsed[$param])) {
6052
                if ($first) {
6053
                    $list .= '<p><strong>' . $_lang['references'] . '</strong></p>' . $nl;
6054
                    $list .= '<ul class="docBlockList">' . $nl;
6055
                    $first = false;
6056
                }
6057
                $list .= '    <li><strong>' . $label . '</strong>' . $nl;
6058
                $list .= '        <ul>' . $nl;
6059
                foreach ($parsed[$param] as $val) {
6060
                    $list .= '            <li>' . $val . '</li>' . $nl;
6061
                }
6062
                $list .= '        </ul></li>' . $nl;
6063
            }
6064
        }
6065
        $list .= !$first ? '</ul>' . $nl : '';
6066
6067
        return $list;
6068
    }
6069
6070
    /**
6071
     * @param string $string
6072
     * @return string
6073
     */
6074
    public function removeSanitizeSeed($string = '')
6075
    {
6076
        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...
6077
6078
        if (!$string || strpos($string, $sanitize_seed) === false) {
6079
            return $string;
6080
        }
6081
6082
        return str_replace($sanitize_seed, '', $string);
6083
    }
6084
6085
    /**
6086
     * @param string $content
6087
     * @return string
6088
     */
6089
    public function cleanUpMODXTags($content = '')
6090
    {
6091
        if ($this->minParserPasses < 1) {
6092
            return $content;
6093
        }
6094
6095
        $enable_filter = $this->config['enable_filter'];
6096
        $this->config['enable_filter'] = 1;
6097
        $_ = 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...
6098
        foreach ($_ as $brackets) {
6099
            list($left, $right) = explode(' ', $brackets);
6100
            if (strpos($content, $left) !== false) {
6101
                if ($left === '[*') {
6102
                    $content = $this->mergeDocumentContent($content);
6103
                } elseif ($left === '[(') {
6104
                    $content = $this->mergeSettingsContent($content);
6105
                } elseif ($left === '{{') {
6106
                    $content = $this->mergeChunkContent($content);
6107
                } elseif ($left === '[[') {
6108
                    $content = $this->evalSnippets($content);
6109
                }
6110
            }
6111
        }
6112
        foreach ($_ as $brackets) {
6113
            list($left, $right) = explode(' ', $brackets);
6114
            if (strpos($content, $left) !== false) {
6115
                $matches = $this->getTagsFromContent($content, $left, $right);
6116
                $content = str_replace($matches[0], '', $content);
6117
            }
6118
        }
6119
        $this->config['enable_filter'] = $enable_filter;
6120
        return $content;
6121
    }
6122
6123
    /**
6124
     * @param string $str
6125
     * @param string $allowable_tags
6126
     * @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...
6127
     */
6128
    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...
6129
    {
6130
        $str = strip_tags($str, $allowable_tags);
6131
        modx_sanitize_gpc($str);
6132
        return $str;
6133
    }
6134
6135
    /**
6136
     * {@inheritdoc}
6137
     */
6138
    public function addSnippet($name, $phpCode, $namespace = '#', array $defaultParams = array())
6139
    {
6140
        $this->snippetCache[$namespace . $name] = $phpCode;
6141
        $this->snippetCache[$namespace . $name . 'Props'] = $defaultParams;
6142
    }
6143
6144
    /**
6145
     * {@inheritdoc}
6146
     */
6147
    public function addChunk($name, $text, $namespace = '#')
6148
    {
6149
        $this->chunkCache[$namespace . $name] = $text;
6150
    }
6151
6152
    /**
6153
     * {@inheritdoc}
6154
     */
6155
    public function findElements($type, $scanPath, array $ext)
6156
    {
6157
        $out = array();
6158
6159
        if (! is_dir($scanPath) || empty($ext)) {
6160
            return $out;
6161
        }
6162
        $iterator = new \RecursiveIteratorIterator(
6163
            new \RecursiveDirectoryIterator($scanPath, \RecursiveDirectoryIterator::SKIP_DOTS),
6164
            \RecursiveIteratorIterator::SELF_FIRST
6165
        );
6166
        foreach ($iterator as $item) {
6167
            /**
6168
             * @var \SplFileInfo $item
6169
             */
6170
            if ($item->isFile() && $item->isReadable() && \in_array($item->getExtension(), $ext)) {
6171
                $name = $item->getBasename('.' . $item->getExtension());
6172
                $path = ltrim(str_replace(
6173
                    array(rtrim($scanPath, '//'), '/'),
6174
                    array('', '\\'),
6175
                    $item->getPath() . '/'
6176
                ), '\\');
6177
6178
                if (!empty($path)) {
6179
                    $name = $path . $name;
6180
                }
6181
                switch ($type) {
6182
                    case 'chunk':
6183
                        $out[$name] = file_get_contents($item->getRealPath());
6184
                        break;
6185
                    case 'snippet':
6186
                        $out[$name] = "return require '" . $item->getRealPath() . "';";
6187
                        break;
6188
                    default:
6189
                        throw new \Exception;
6190
                }
6191
            }
6192
        }
6193
6194
        return $out;
6195
    }
6196
6197
    /**
6198
     * @param string $phpcode
6199
     * @param string $evalmode
6200
     * @param string $safe_functions
6201
     * @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...
6202
     */
6203
    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...
6204
    {
6205
        if ($evalmode == '') {
6206
            $evalmode = $this->config['allow_eval'];
6207
        }
6208
        if ($safe_functions == '') {
6209
            $safe_functions = $this->config['safe_functions_at_eval'];
6210
        }
6211
6212
        modx_sanitize_gpc($phpcode);
6213
6214
        switch ($evalmode) {
6215
            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...
6216
                $isSafe = $this->isSafeCode($phpcode, $safe_functions);
0 ignored issues
show
Bug introduced by
It seems like $phpcode can also be of type array; however, EvolutionCMS\Core::isSafeCode() does only seem to accept string, maybe add an additional type check?

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

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

    return array();
}

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

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

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

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

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

    return array();
}

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

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

Loading history...
6220
                break;
6221
            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...
6222
                $isSafe = true;
6223
                break; // Should debug only
6224
            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...
6225
            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...
6226
                return $phpcode;
6227
        }
6228
6229
        if (!$isSafe) {
6230
            $msg = $phpcode . "\n" . $this->currentSnippet . "\n" . print_r($_SERVER, true);
6231
            $title = sprintf('Unknown eval was executed (%s)', $this->getPhpCompat()->htmlspecialchars(substr(trim($phpcode), 0, 50)));
6232
            $this->messageQuit($title, '', true, '', '', 'Parser', $msg);
6233
            return;
6234
        }
6235
6236
        ob_start();
6237
        $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...
6238
        $echo = ob_get_clean();
6239
6240
        if (is_array($return)) {
6241
            return 'array()';
6242
        }
6243
6244
        $output = $echo . $return;
6245
        modx_sanitize_gpc($output);
6246
        return $this->getPhpCompat()->htmlspecialchars($output); // Maybe, all html tags are dangerous
6247
    }
6248
6249
    /**
6250
     * @param string $phpcode
6251
     * @param string $safe_functions
6252
     * @return bool
6253
     */
6254
    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...
6255
    { // 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...
6256
        if ($safe_functions == '') {
6257
            return false;
6258
        }
6259
6260
        $safe = explode(',', $safe_functions);
6261
6262
        $phpcode = rtrim($phpcode, ';') . ';';
6263
        $tokens = token_get_all('<?php ' . $phpcode);
6264
        foreach ($tokens as $i => $token) {
6265
            if (!is_array($token)) {
6266
                continue;
6267
            }
6268
            $tokens[$i]['token_name'] = token_name($token[0]);
6269
        }
6270
        foreach ($tokens as $token) {
6271
            if (!is_array($token)) {
6272
                continue;
6273
            }
6274
            switch ($token['token_name']) {
6275
                case 'T_STRING':
6276
                    if (!in_array($token[1], $safe)) {
6277
                        return false;
6278
                    }
6279
                    break;
6280
                case 'T_VARIABLE':
6281
                    if ($token[1] == '$GLOBALS') {
6282
                        return false;
6283
                    }
6284
                    break;
6285
                case 'T_EVAL':
6286
                    return false;
6287
            }
6288
        }
6289
        return true;
6290
    }
6291
6292
    /**
6293
     * @param string $str
6294
     * @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...
6295
     */
6296
    public function atBindFileContent($str = '')
6297
    {
6298
6299
        $search_path = array('assets/tvs/', 'assets/chunks/', 'assets/templates/', $this->config['rb_base_url'] . 'files/', '');
6300
6301
        if (stripos($str, '@FILE') !== 0) {
6302
            return $str;
6303
        }
6304 View Code Duplication
        if (strpos($str, "\n") !== false) {
6305
            $str = substr($str, 0, strpos("\n", $str));
6306
        }
6307
6308
        if ($this->getExtFromFilename($str) === '.php') {
6309
            return 'Could not retrieve PHP file.';
6310
        }
6311
6312
        $str = substr($str, 6);
6313
        $str = trim($str);
6314
        if (strpos($str, '\\') !== false) {
6315
            $str = str_replace('\\', '/', $str);
6316
        }
6317
        $str = ltrim($str, '/');
6318
6319
        $errorMsg = sprintf("Could not retrieve string '%s'.", $str);
6320
6321
        foreach ($search_path as $path) {
6322
            $file_path = MODX_BASE_PATH . $path . $str;
6323
            if (strpos($file_path, MODX_MANAGER_PATH) === 0) {
6324
                return $errorMsg;
6325
            } elseif (is_file($file_path)) {
6326
                break;
6327
            } else {
6328
                $file_path = false;
6329
            }
6330
        }
6331
6332
        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...
6333
            return $errorMsg;
6334
        }
6335
6336
        $content = (string)file_get_contents($file_path);
6337
        if ($content === false) {
6338
            return $errorMsg;
6339
        }
6340
6341
        return $content;
6342
    }
6343
6344
    /**
6345
     * @param $str
6346
     * @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...
6347
     */
6348
    public function getExtFromFilename($str)
6349
    {
6350
        $str = strtolower(trim($str));
6351
        $pos = strrpos($str, '.');
6352
        if ($pos === false) {
6353
            return false;
6354
        } else {
6355
            return substr($str, $pos);
6356
        }
6357
    }
6358
    /***************************************************************************************/
6359
    /* End of API functions                                       */
6360
    /***************************************************************************************/
6361
6362
    /**
6363
     * PHP error handler set by http://www.php.net/manual/en/function.set-error-handler.php
6364
     *
6365
     * Checks the PHP error and calls messageQuit() unless:
6366
     *  - error_reporting() returns 0, or
6367
     *  - the PHP error level is 0, or
6368
     *  - the PHP error level is 8 (E_NOTICE) and stopOnNotice is false
6369
     *
6370
     * @param int $nr The PHP error level as per http://www.php.net/manual/en/errorfunc.constants.php
6371
     * @param string $text Error message
6372
     * @param string $file File where the error was detected
6373
     * @param string $line Line number within $file
6374
     * @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...
6375
     */
6376
    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...
6377
    {
6378
        if (error_reporting() == 0 || $nr == 0) {
6379
            return true;
6380
        }
6381
        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...
6382
            switch ($nr) {
6383
                case E_NOTICE:
6384
                    if ($this->error_reporting <= 2) {
6385
                        return true;
6386
                    }
6387
                    $isError = false;
6388
                    $msg = 'PHP Minor Problem (this message show logged in only)';
6389
                    break;
6390
                case E_STRICT:
6391 View Code Duplication
                case E_DEPRECATED:
6392
                    if ($this->error_reporting <= 1) {
6393
                        return true;
6394
                    }
6395
                    $isError = true;
6396
                    $msg = 'PHP Strict Standards Problem';
6397
                    break;
6398 View Code Duplication
                default:
6399
                    if ($this->error_reporting === 0) {
6400
                        return true;
6401
                    }
6402
                    $isError = true;
6403
                    $msg = 'PHP Parse Error';
6404
            }
6405
        }
6406
        if (is_readable($file)) {
6407
            $source = file($file);
6408
            $source = $this->getPhpCompat()->htmlspecialchars($source[$line - 1]);
6409
        } else {
6410
            $source = "";
6411
        } //Error $nr in $file at $line: <div><code>$source</code></div>
6412
6413
        $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...
6414
    }
6415
6416
    /**
6417
     * @param string $msg
6418
     * @param string $query
6419
     * @param bool $is_error
6420
     * @param string $nr
6421
     * @param string $file
6422
     * @param string $source
6423
     * @param string $text
6424
     * @param string $line
6425
     * @param string $output
6426
     * @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...
6427
     */
6428
    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...
6429
    {
6430
6431
        if (0 < $this->messageQuitCount) {
6432
            return;
6433
        }
6434
        $this->messageQuitCount++;
6435
        $MakeTable = new Support\MakeTable();
6436
        $MakeTable->setTableClass('grid');
6437
        $MakeTable->setRowRegularClass('gridItem');
6438
        $MakeTable->setRowAlternateClass('gridAltItem');
6439
        $MakeTable->setColumnWidths(array('100px'));
6440
6441
        $table = array();
6442
6443
        $version = isset ($GLOBALS['modx_version']) ? $GLOBALS['modx_version'] : '';
6444
        $release_date = isset ($GLOBALS['release_date']) ? $GLOBALS['release_date'] : '';
6445
        $request_uri = "http://" . $_SERVER['HTTP_HOST'] . ($_SERVER["SERVER_PORT"] == 80 ? "" : (":" . $_SERVER["SERVER_PORT"])) . $_SERVER['REQUEST_URI'];
6446
        $request_uri = $this->getPhpCompat()->htmlspecialchars($request_uri, ENT_QUOTES, $this->config['modx_charset']);
6447
        $ua = $this->getPhpCompat()->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...
6448
        $referer = $this->getPhpCompat()->htmlspecialchars($_SERVER['HTTP_REFERER'], ENT_QUOTES, $this->config['modx_charset']);
6449
        if ($is_error) {
6450
            $str = '<h2 style="color:red">&laquo; Evo Parse Error &raquo;</h2>';
6451
            if ($msg != 'PHP Parse Error') {
6452
                $str .= '<h3 style="color:red">' . $msg . '</h3>';
6453
            }
6454
        } else {
6455
            $str = '<h2 style="color:#003399">&laquo; Evo Debug/ stop message &raquo;</h2>';
6456
            $str .= '<h3 style="color:#003399">' . $msg . '</h3>';
6457
        }
6458
6459
        if (!empty ($query)) {
6460
            $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>';
6461
        }
6462
6463
        $errortype = array(
6464
            E_ERROR => "ERROR",
6465
            E_WARNING => "WARNING",
6466
            E_PARSE => "PARSING ERROR",
6467
            E_NOTICE => "NOTICE",
6468
            E_CORE_ERROR => "CORE ERROR",
6469
            E_CORE_WARNING => "CORE WARNING",
6470
            E_COMPILE_ERROR => "COMPILE ERROR",
6471
            E_COMPILE_WARNING => "COMPILE WARNING",
6472
            E_USER_ERROR => "USER ERROR",
6473
            E_USER_WARNING => "USER WARNING",
6474
            E_USER_NOTICE => "USER NOTICE",
6475
            E_STRICT => "STRICT NOTICE",
6476
            E_RECOVERABLE_ERROR => "RECOVERABLE ERROR",
6477
            E_DEPRECATED => "DEPRECATED",
6478
            E_USER_DEPRECATED => "USER DEPRECATED"
6479
        );
6480
6481
        if (!empty($nr) || !empty($file)) {
6482
            if ($text != '') {
6483
                $str .= '<div style="font-weight:bold;border:1px solid #ccc;padding:8px;color:#333;background-color:#ffffcd;margin-bottom:15px;">Error : ' . $text . '</div>';
6484
            }
6485
            if ($output != '') {
6486
                $str .= '<div style="font-weight:bold;border:1px solid #ccc;padding:8px;color:#333;background-color:#ffffcd;margin-bottom:15px;">' . $output . '</div>';
6487
            }
6488
            if ($nr !== '') {
6489
                $table[] = array('ErrorType[num]', $errortype [$nr] . "[" . $nr . "]");
6490
            }
6491
            if ($file) {
6492
                $table[] = array('File', $file);
6493
            }
6494
            if ($line) {
6495
                $table[] = array('Line', $line);
6496
            }
6497
6498
        }
6499
6500
        if ($source != '') {
6501
            $table[] = array("Source", $source);
6502
        }
6503
6504
        if (!empty($this->currentSnippet)) {
6505
            $table[] = array('Current Snippet', $this->currentSnippet);
6506
        }
6507
6508
        if (!empty($this->event->activePlugin)) {
6509
            $table[] = array('Current Plugin', $this->event->activePlugin . '(' . $this->event->name . ')');
6510
        }
6511
6512
        $str .= $MakeTable->create($table, array('Error information', ''));
6513
        $str .= "<br />";
6514
6515
        $table = array();
6516
        $table[] = array('REQUEST_URI', $request_uri);
6517
6518
        if ($this->getManagerApi()->action) {
6519
            include_once(MODX_MANAGER_PATH . 'includes/actionlist.inc.php');
6520
            global $action_list;
6521
            $actionName = (isset($action_list[$this->getManagerApi()->action])) ? " - {$action_list[$this->getManagerApi()->action]}" : '';
6522
6523
            $table[] = array('Manager action', $this->getManagerApi()->action . $actionName);
6524
        }
6525
6526
        if (preg_match('@^[0-9]+@', $this->documentIdentifier)) {
6527
            $resource = $this->getDocumentObject('id', $this->documentIdentifier);
6528
            $url = $this->makeUrl($this->documentIdentifier, '', '', 'full');
6529
            $table[] = array('Resource', '[' . $this->documentIdentifier . '] <a href="' . $url . '" target="_blank">' . $resource['pagetitle'] . '</a>');
6530
        }
6531
        $table[] = array('Referer', $referer);
6532
        $table[] = array('User Agent', $ua);
6533
        $table[] = array('IP', $_SERVER['REMOTE_ADDR']);
6534
        $table[] = array('Current time', date("Y-m-d H:i:s", $_SERVER['REQUEST_TIME'] + $this->config['server_offset_time']));
6535
        $str .= $MakeTable->create($table, array('Basic info', ''));
6536
        $str .= "<br />";
6537
6538
        $table = array();
6539
        $table[] = array('MySQL', '[^qt^] ([^q^] Requests)');
6540
        $table[] = array('PHP', '[^p^]');
6541
        $table[] = array('Total', '[^t^]');
6542
        $table[] = array('Memory', '[^m^]');
6543
        $str .= $MakeTable->create($table, array('Benchmarks', ''));
6544
        $str .= "<br />";
6545
6546
        $totalTime = ($this->getMicroTime() - $this->tstart);
6547
6548
        $mem = memory_get_peak_usage(true);
6549
        $total_mem = $mem - $this->mstart;
6550
        $total_mem = ($total_mem / 1024 / 1024) . ' mb';
6551
6552
        $queryTime = $this->queryTime;
6553
        $phpTime = $totalTime - $queryTime;
6554
        $queries = isset ($this->executedQueries) ? $this->executedQueries : 0;
6555
        $queryTime = sprintf("%2.4f s", $queryTime);
6556
        $totalTime = sprintf("%2.4f s", $totalTime);
6557
        $phpTime = sprintf("%2.4f s", $phpTime);
6558
6559
        $str = str_replace('[^q^]', $queries, $str);
6560
        $str = str_replace('[^qt^]', $queryTime, $str);
6561
        $str = str_replace('[^p^]', $phpTime, $str);
6562
        $str = str_replace('[^t^]', $totalTime, $str);
6563
        $str = str_replace('[^m^]', $total_mem, $str);
6564
6565
        if (isset($php_errormsg) && !empty($php_errormsg)) {
6566
            $str = "<b>{$php_errormsg}</b><br />\n{$str}";
6567
        }
6568
        $str .= $this->get_backtrace(debug_backtrace());
6569
        // Log error
6570
        if (!empty($this->currentSnippet)) {
6571
            $source = 'Snippet - ' . $this->currentSnippet;
6572
        } elseif (!empty($this->event->activePlugin)) {
6573
            $source = 'Plugin - ' . $this->event->activePlugin;
6574
        } elseif ($source !== '') {
6575
            $source = 'Parser - ' . $source;
6576
        } elseif ($query !== '') {
6577
            $source = 'SQL Query';
6578
        } else {
6579
            $source = 'Parser';
6580
        }
6581
        if ($msg) {
6582
            $source .= ' / ' . $msg;
6583
        }
6584
        if (isset($actionName) && !empty($actionName)) {
6585
            $source .= $actionName;
6586
        }
6587 View Code Duplication
        switch ($nr) {
6588
            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...
6589
            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...
6590
            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...
6591
            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...
6592
            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...
6593
                $error_level = 2;
6594
                break;
6595
            default:
6596
                $error_level = 3;
6597
        }
6598
        $this->logEvent(0, $error_level, $str, $source);
6599
6600
        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...
6601
            return true;
6602
        }
6603
        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...
6604
            return true;
6605
        }
6606
6607
        // Set 500 response header
6608
        if ($error_level !== 2) {
6609
            header('HTTP/1.1 500 Internal Server Error');
6610
        }
6611
6612
        ob_get_clean();
6613
        // Display error
6614
        if (isset($_SESSION['mgrValidated'])) {
6615
            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>
6616
                 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
6617
                 <link rel="stylesheet" type="text/css" href="' . $this->config['site_manager_url'] . 'media/style/' . $this->config['manager_theme'] . '/style.css" />
6618
                 <style type="text/css">body { padding:10px; } td {font:inherit;}</style>
6619
                 </head><body>
6620
                 ' . $str . '</body></html>';
6621
6622
        } else {
6623
            echo 'Error';
6624
        }
6625
        ob_end_flush();
6626
        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...
6627
    }
6628
6629
    /**
6630
     * @param $backtrace
6631
     * @return string
6632
     */
6633
    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...
6634
    {
6635
        $MakeTable = new Support\MakeTable();
6636
        $MakeTable->setTableClass('grid');
6637
        $MakeTable->setRowRegularClass('gridItem');
6638
        $MakeTable->setRowAlternateClass('gridAltItem');
6639
        $table = array();
6640
        $backtrace = array_reverse($backtrace);
6641
        foreach ($backtrace as $key => $val) {
6642
            $key++;
6643
            if (substr($val['function'], 0, 11) === 'messageQuit') {
6644
                break;
6645
            } elseif (substr($val['function'], 0, 8) === 'phpError') {
6646
                break;
6647
            }
6648
            $path = str_replace('\\', '/', $val['file']);
6649
            if (strpos($path, MODX_BASE_PATH) === 0) {
6650
                $path = substr($path, strlen(MODX_BASE_PATH));
6651
            }
6652
            switch (get_by_key($val, 'type')) {
6653
                case '->':
6654
                case '::':
6655
                    $functionName = $val['function'] = $val['class'] . $val['type'] . $val['function'];
6656
                    break;
6657
                default:
6658
                    $functionName = $val['function'];
6659
            }
6660
            $tmp = 1;
6661
            $_ = (!empty($val['args'])) ? count($val['args']) : 0;
6662
            $args = array_pad(array(), $_, '$var');
6663
            $args = implode(", ", $args);
6664
            $modx = &$this;
6665
            $args = preg_replace_callback('/\$var/', function () use ($modx, &$tmp, $val) {
6666
                $arg = $val['args'][$tmp - 1];
6667
                switch (true) {
6668
                    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...
6669
                        $out = 'NULL';
6670
                        break;
6671
                    }
6672
                    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...
6673
                        $out = $arg;
6674
                        break;
6675
                    }
6676
                    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...
6677
                        $out = strlen($arg) > 20 ? 'string $var' . $tmp : ("'" . $this->getPhpCompat()->htmlspecialchars(str_replace("'", "\\'", $arg)) . "'");
6678
                        break;
6679
                    }
6680
                    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...
6681
                        $out = $arg ? 'TRUE' : 'FALSE';
6682
                        break;
6683
                    }
6684
                    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...
6685
                        $out = 'array $var' . $tmp;
6686
                        break;
6687
                    }
6688
                    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...
6689
                        $out = get_class($arg) . ' $var' . $tmp;
6690
                        break;
6691
                    }
6692
                    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...
6693
                        $out = '$var' . $tmp;
6694
                    }
6695
                }
6696
                $tmp++;
6697
                return $out;
6698
            }, $args);
6699
            $line = array(
6700
                "<strong>" . $functionName . "</strong>(" . $args . ")",
6701
                $path . " on line " . $val['line']
6702
            );
6703
            $table[] = array(implode("<br />", $line));
6704
        }
6705
        return $MakeTable->create($table, array('Backtrace'));
6706
    }
6707
6708
    /**
6709
     * @return string
6710
     */
6711
    public function getRegisteredClientScripts()
6712
    {
6713
        return implode("\n", $this->jscripts);
6714
    }
6715
6716
    /**
6717
     * @return string
6718
     */
6719
    public function getRegisteredClientStartupScripts()
6720
    {
6721
        return implode("\n", $this->sjscripts);
6722
    }
6723
6724
    /**
6725
     * Format alias to be URL-safe. Strip invalid characters.
6726
     *
6727
     * @param string $alias Alias to be formatted
6728
     * @return string Safe alias
6729
     */
6730
    public function stripAlias($alias)
6731
    {
6732
        // let add-ons overwrite the default behavior
6733
        $results = $this->invokeEvent('OnStripAlias', array('alias' => $alias));
6734
        if (!empty($results)) {
6735
            // if multiple plugins are registered, only the last one is used
6736
            return end($results);
6737
        } else {
6738
            // default behavior: strip invalid characters and replace spaces with dashes.
6739
            $alias = strip_tags($alias); // strip HTML
6740
            $alias = preg_replace('/[^\.A-Za-z0-9 _-]/', '', $alias); // strip non-alphanumeric characters
6741
            $alias = preg_replace('/\s+/', '-', $alias); // convert white-space to dash
6742
            $alias = preg_replace('/-+/', '-', $alias);  // convert multiple dashes to one
6743
            $alias = trim($alias, '-'); // trim excess
6744
            return $alias;
6745
        }
6746
    }
6747
6748
    /**
6749
     * @param $size
6750
     * @return string
6751
     */
6752
    public function nicesize($size)
6753
    {
6754
        $sizes = array('Tb' => 1099511627776, 'Gb' => 1073741824, 'Mb' => 1048576, 'Kb' => 1024, 'b' => 1);
6755
        $precisions = count($sizes) - 1;
6756
        foreach ($sizes as $unit => $bytes) {
6757
            if ($size >= $bytes) {
6758
                return number_format($size / $bytes, $precisions) . ' ' . $unit;
6759
            }
6760
            $precisions--;
6761
        }
6762
        return '0 b';
6763
    }
6764
6765
    /**
6766
     * @param $parentid
6767
     * @param $alias
6768
     * @return bool
6769
     */
6770
    public function getHiddenIdFromAlias($parentid, $alias)
6771
    {
6772
        $table = $this->getDatabase()->getFullTableName('site_content');
6773
        $query = $this->getDatabase()->query("SELECT sc.id, children.id AS child_id, children.alias, COUNT(children2.id) AS children_count
6774
            FROM {$table} sc
6775
            JOIN {$table} children ON children.parent = sc.id
6776
            LEFT JOIN {$table} children2 ON children2.parent = children.id
6777
            WHERE sc.parent = {$parentid} AND sc.alias_visible = '0' GROUP BY children.id;");
6778
6779
        while ($child = $this->getDatabase()->getRow($query)) {
0 ignored issues
show
Bug introduced by
It seems like $query defined by $this->getDatabase()->qu...GROUP BY children.id;") on line 6773 can also be of type boolean; however, EvolutionCMS\Database::getRow() does only seem to accept object<mysqli_result>, 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...
6780
            if ($child['alias'] == $alias || $child['child_id'] == $alias) {
6781
                return $child['child_id'];
6782
            }
6783
6784
            if ($child['children_count'] > 0) {
6785
                $id = $this->getHiddenIdFromAlias($child['id'], $alias);
6786
                if ($id) {
6787
                    return $id;
6788
                }
6789
            }
6790
        }
6791
6792
        return false;
6793
    }
6794
6795
    /**
6796
     * @param $alias
6797
     * @return bool|int
6798
     */
6799
    public function getIdFromAlias($alias)
6800
    {
6801
        if (isset($this->documentListing[$alias])) {
6802
            return $this->documentListing[$alias];
6803
        }
6804
6805
        $tbl_site_content = $this->getDatabase()->getFullTableName('site_content');
6806
        if ($this->config['use_alias_path'] == 1) {
6807
            if ($alias == '.') {
6808
                return 0;
6809
            }
6810
6811
            if (strpos($alias, '/') !== false) {
6812
                $_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...
6813
            } else {
6814
                $_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...
6815
            }
6816
            $id = 0;
6817
6818
            foreach ($_a as $alias) {
6819
                if ($id === false) {
6820
                    break;
6821
                }
6822
                $alias = $this->getDatabase()->escape($alias);
6823
                $rs = $this->getDatabase()->select('id', $tbl_site_content, "deleted=0 and parent='{$id}' and alias='{$alias}'");
6824
                if ($this->getDatabase()->getRecordCount($rs) == 0) {
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se... and alias='{$alias}'") on line 6823 can also be of type boolean; however, EvolutionCMS\Database::getRecordCount() does only seem to accept object<mysqli_result>, 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...
6825
                    $rs = $this->getDatabase()->select('id', $tbl_site_content, "deleted=0 and parent='{$id}' and id='{$alias}'");
6826
                }
6827
                $next = $this->getDatabase()->getValue($rs);
0 ignored issues
show
Bug introduced by
It seems like $rs can also be of type boolean; however, EvolutionCMS\Database::getValue() does only seem to accept object<mysqli_result>|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...
6828
                $id = !$next ? $this->getHiddenIdFromAlias($id, $alias) : $next;
6829
            }
6830
        } else {
6831
            $rs = $this->getDatabase()->select('id', $tbl_site_content, "deleted=0 and alias='{$alias}'", 'parent, menuindex');
6832
            $id = $this->getDatabase()->getValue($rs);
0 ignored issues
show
Bug introduced by
It seems like $rs defined by $this->getDatabase()->se...", 'parent, menuindex') on line 6831 can also be of type boolean; however, EvolutionCMS\Database::getValue() does only seem to accept object<mysqli_result>|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...
6833
            if (!$id) {
6834
                $id = false;
6835
            }
6836
        }
6837
        return $id;
6838
    }
6839
6840
    /**
6841
     * @param string $str
6842
     * @return bool|mixed|string
6843
     */
6844
    public function atBindInclude($str = '')
6845
    {
6846
        if (strpos($str, '@INCLUDE') !== 0) {
6847
            return $str;
6848
        }
6849 View Code Duplication
        if (strpos($str, "\n") !== false) {
6850
            $str = substr($str, 0, strpos("\n", $str));
6851
        }
6852
6853
        $str = substr($str, 9);
6854
        $str = trim($str);
6855
        $str = str_replace('\\', '/', $str);
6856
        $str = ltrim($str, '/');
6857
6858
        $tpl_dir = 'assets/templates/';
6859
6860
        if (strpos($str, MODX_MANAGER_PATH) === 0) {
6861
            return false;
6862
        } elseif (is_file(MODX_BASE_PATH . $str)) {
6863
            $file_path = MODX_BASE_PATH . $str;
6864
        } elseif (is_file(MODX_BASE_PATH . "{$tpl_dir}{$str}")) {
6865
            $file_path = MODX_BASE_PATH . $tpl_dir . $str;
6866
        } else {
6867
            return false;
6868
        }
6869
6870
        if (!$file_path || !is_file($file_path)) {
6871
            return false;
6872
        }
6873
6874
        ob_start();
6875
        $modx = &$this;
6876
        $result = include($file_path);
6877
        if ($result === 1) {
6878
            $result = '';
6879
        }
6880
        $content = ob_get_clean();
6881
        if (!$content && $result) {
6882
            $content = $result;
6883
        }
6884
        return $content;
6885
    }
6886
6887
    // php compat
6888
6889
    /**
6890
     * @param $str
6891
     * @param int $flags
6892
     * @param string $encode
6893
     * @return mixed
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use null|string.

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

Loading history...
6894
     */
6895
    public function htmlspecialchars($str, $flags = ENT_COMPAT, $encode = '')
6896
    {
6897
        return $this->getPhpCompat()->htmlspecialchars($str, $flags, $encode);
6898
    }
6899
6900
    /**
6901
     * @param $string
6902
     * @param bool $returnData
6903
     * @return bool|mixed
6904
     */
6905
    public function isJson($string, $returnData = false)
6906
    {
6907
        $data = json_decode($string, true);
6908
        return (json_last_error() == JSON_ERROR_NONE) ? ($returnData ? $data : true) : false;
6909
    }
6910
6911
    /**
6912
     * @param $key
6913
     * @return array
6914
     */
6915
    public function splitKeyAndFilter($key)
6916
    {
6917
        if ($this->config['enable_filter'] == 1 && strpos($key, ':') !== false && stripos($key, '@FILE') !== 0) {
6918
            list($key, $modifiers) = explode(':', $key, 2);
6919
        } else {
6920
            $modifiers = false;
6921
        }
6922
6923
        $key = trim($key);
6924
        if ($modifiers !== false) {
6925
            $modifiers = trim($modifiers);
6926
        }
6927
6928
        return array($key, $modifiers);
6929
    }
6930
6931
    /**
6932
     * @param string $value
6933
     * @param bool $modifiers
6934
     * @param string $key
6935
     * @return string
6936
     */
6937
    public function applyFilter($value = '', $modifiers = false, $key = '')
6938
    {
6939
        if ($modifiers === false || $modifiers == 'raw') {
6940
            return $value;
6941
        }
6942
        if ($modifiers !== false) {
6943
            $modifiers = trim($modifiers);
6944
        }
6945
6946
        return $this->getModifiers()->phxFilter($key, $value, $modifiers);
6947
    }
6948
6949
    // End of class.
6950
6951
6952
    /**
6953
     * Get Clean Query String
6954
     *
6955
     * Fixes the issue where passing an array into the q get variable causes errors
6956
     *
6957
     */
6958
    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...
6959
    {
6960
        $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...
6961
6962
        //Return null if the query doesn't exist
6963
        if (empty($q)) {
6964
            return null;
6965
        }
6966
6967
        //If we have a string, return it
6968
        if (is_string($q)) {
6969
            return $q;
6970
        }
6971
6972
        //If we have an array, return the first element
6973
        if (is_array($q)) {
6974
            return $q[0];
6975
        }
6976
    }
6977
6978
    /**
6979
     * @param string $title
6980
     * @param string $msg
6981
     * @param int $type
6982
     */
6983
    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...
6984
    {
6985
        if ($title === '') {
6986
            $title = 'no title';
6987
        }
6988
        if (is_array($msg)) {
6989
            $msg = '<pre>' . print_r($msg, true) . '</pre>';
6990
        } elseif ($msg === '') {
6991
            $msg = $_SERVER['REQUEST_URI'];
6992
        }
6993
        $this->logEvent(0, $type, $msg, $title);
6994
    }
6995
6996
}
6997