GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — integration ( 5cdb31...81c93f )
by Brendan
04:03
created

Symphony   F

Complexity

Total Complexity 87

Size/Duplication

Total Lines 844
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 22

Importance

Changes 10
Bugs 1 Features 1
Metric Value
c 10
b 1
f 1
dl 0
loc 844
rs 1.263
wmc 87
lcom 3
cbo 22

31 Methods

Rating   Name   Duplication   Size   Complexity  
A initialiseErrorHandler() 0 7 1
A Engine() 0 10 3
A Configuration() 0 4 1
A isXSRFEnabled() 0 4 1
A Profiler() 0 4 1
A setDatabase() 0 10 3
A Database() 0 4 1
A initialiseExtensionManager() 0 12 4
A getMigrationVersion() 0 15 2
A isUpgradeAvailable() 0 11 2
A isInstallerAvailable() 0 4 1
A throwCustomError() 0 5 1
A setException() 0 4 1
A getException() 0 4 1
D getPageNamespace() 0 30 9
B __construct() 0 32 4
A initialiseConfiguration() 0 19 2
B initialiseLog() 0 43 5
A Log() 0 4 1
B initialiseSessionAndCookies() 0 53 5
A Session() 0 4 1
A Cookies() 0 4 1
A Flash() 0 4 1
B getSessionTimeout() 0 14 6
A ExtensionManager() 0 4 1
C initialiseDatabase() 0 47 10
A Author() 0 4 1
C login() 0 45 8
B loginFromToken() 0 50 5
A logout() 0 4 1
A isLoggedIn() 0 10 3

How to fix   Complexity   

Complex Class

Complex classes like Symphony 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 Symphony, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * @package core
5
 */
6
/**
7
 * The Symphony class is an abstract class that implements the
8
 * Singleton interface. It provides the glue that forms the Symphony
9
 * CMS and initialises the toolkit classes. Symphony is extended by
10
 * the Frontend and Administration classes
11
 */
12
use Monolog\Logger;
13
14
abstract class Symphony implements Singleton
15
{
16
    /**
17
     * An instance of the Symphony class, either `Administration` or `Frontend`.
18
     * @var Symphony
19
     */
20
    protected static $_instance = null;
21
22
    /**
23
     * An instance of the Profiler class
24
     * @var Profiler
25
     */
26
    protected static $Profiler = null;
27
28
    /**
29
     * An instance of the `Configuration` class
30
     * @var Configuration
31
     */
32
    private static $Configuration = null;
33
34
    /**
35
     * An instance of the `Database` class
36
     * @var MySQL
37
     */
38
    private static $Database = null;
39
40
    /**
41
     * An instance of the `ExtensionManager` class
42
     * @var ExtensionManager
43
     */
44
    private static $ExtensionManager = null;
45
46
    /**
47
     * An instance of the `Log` class
48
     * @var Log
49
     */
50
    private static $Log = null;
51
52
    /**
53
     * The current page namespace, used for translations
54
     * @since Symphony 2.3
55
     * @var string
56
     */
57
    private static $namespace = false;
58
59
    /**
60
     * An instance of the Cookies class
61
     * @var Cookies
62
     */
63
    public static $Cookies = null;
64
65
    /**
66
     * An instance of the Session class
67
     * @var Session
68
     */
69
    public static $Session = null;
70
71
    /**
72
     * An instance of the SessionFlash class
73
     * @var Session
74
     */
75
    public static $Flash = null;
76
77
    /**
78
     * An instance of the currently logged in Author
79
     * @var Author
80
     */
81
    public static $Author = null;
82
83
    /**
84
     * A previous exception that has been fired. Defaults to null.
85
     * @since Symphony 2.3.2
86
     * @var Exception
87
     */
88
    private static $exception = null;
89
90
    /**
91
     * The Symphony constructor initialises the class variables of Symphony. At present
92
     * constructor has a couple of responsibilities:
93
     * - Start a profiler instance
94
     * - If magic quotes are enabled, clean `$_SERVER`, `$_COOKIE`, `$_GET` and `$_POST` arrays
95
     * - Initialise the correct Language for the currently logged in Author.
96
     * - Start the session and adjust the error handling if the user is logged in
97
     */
98
    protected function __construct()
0 ignored issues
show
Coding Style introduced by
__construct uses the super-global variable $_SERVER which is generally not recommended.

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

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

// Better
class Router
{
    private $host;

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

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

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

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

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

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

// Better
class Router
{
    private $host;

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

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

class Controller
{
    public function myAction(Request $request)
    {
        // Instead 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
__construct 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...
99
    {
100
        self::$Profiler = Profiler::instance();
101
102
        if (get_magic_quotes_gpc()) {
103
            General::cleanArray($_SERVER);
104
            General::cleanArray($_COOKIE);
105
            General::cleanArray($_GET);
106
            General::cleanArray($_POST);
107
        }
108
109
        // Initialize language management
110
        Lang::initialize();
111
        Lang::set(self::$Configuration->get('lang', 'symphony'));
0 ignored issues
show
Bug introduced by
It seems like self::$Configuration->get('lang', 'symphony') targeting Configuration::get() can also be of type array; however, Lang::set() 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...
112
113
        $this->initialiseLog();
114
115
        GenericExceptionHandler::initialise(self::Log());
116
        GenericErrorHandler::initialise(self::Log());
117
118
        $this->initialiseDatabase();
119
        $this->initialiseExtensionManager();
120
        $this->initialiseSessionAndCookies();
121
122
        // If the user is not a logged in Author, turn off the verbose error messages.
123
        if (!self::isLoggedIn() && is_null(self::$Author)) {
124
            GenericExceptionHandler::$enabled = false;
125
        }
126
127
        // Engine is ready.
128
        self::$Profiler->sample('Engine Initialisation');
129
    }
130
131
    /**
132
     * Setter for the Symphony Log and Error Handling system
133
     *
134
     * @since Symphony 2.6.0
135
     */
136
    public static function initialiseErrorHandler()
137
    {
138
        // Initialise logging
139
        self::initialiseLog();
140
        GenericExceptionHandler::initialise(self::Log());
141
        GenericErrorHandler::initialise(self::Log());
142
    }
143
144
    /**
145
     * Accessor for the Symphony instance, whether it be Frontend
146
     * or Administration
147
     *
148
     * @since Symphony 2.2
149
     * @throws Exception
150
     * @return Symphony
151
     */
152
    public static function Engine()
153
    {
154
        if (class_exists('Administration', false)) {
155
            return Administration::instance();
156
        } elseif (class_exists('Frontend', false)) {
157
            return Frontend::instance();
158
        } else {
159
            throw new Exception(__('No suitable engine object found'));
160
        }
161
    }
162
163
    /**
164
     * Setter for `$Configuration`. This function initialise the configuration
165
     * object and populate its properties based on the given `$array`. Since
166
     * Symphony 2.6.5, it will also set Symphony's date constants.
167
     *
168
     * @since Symphony 2.3
169
     * @param array $data
170
     *  An array of settings to be stored into the Configuration object
171
     */
172
    public static function initialiseConfiguration(array $data = array())
173
    {
174
        if (empty($data)) {
175
            // Includes the existing CONFIG file and initialises the Configuration
176
            // by setting the values with the setArray function.
177
            include CONFIG;
178
179
            $data = $settings;
0 ignored issues
show
Bug introduced by
The variable $settings does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
180
        }
181
182
        self::$Configuration = new Configuration(true);
183
        self::$Configuration->setArray($data);
184
185
        // Set date format throughout the system
186
        define_safe('__SYM_DATE_FORMAT__', self::Configuration()->get('date_format', 'region'));
0 ignored issues
show
Bug introduced by
It seems like self::Configuration()->g...date_format', 'region') targeting Configuration::get() can also be of type array; however, define_safe() 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...
187
        define_safe('__SYM_TIME_FORMAT__', self::Configuration()->get('time_format', 'region'));
0 ignored issues
show
Bug introduced by
It seems like self::Configuration()->g...time_format', 'region') targeting Configuration::get() can also be of type array; however, define_safe() 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...
188
        define_safe('__SYM_DATETIME_FORMAT__', __SYM_DATE_FORMAT__ . self::Configuration()->get('datetime_separator', 'region') . __SYM_TIME_FORMAT__);
189
        DateTimeObj::setSettings(self::Configuration()->get('region'));
0 ignored issues
show
Bug introduced by
It seems like self::Configuration()->get('region') targeting Configuration::get() can also be of type string; however, DateTimeObj::setSettings() does only seem to accept array, 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...
190
    }
191
192
    /**
193
     * Accessor for the current `Configuration` instance. This contains
194
     * representation of the the Symphony config file.
195
     *
196
     * @return Configuration
197
     */
198
    public static function Configuration()
199
    {
200
        return self::$Configuration;
201
    }
202
203
    /**
204
     * Is XSRF enabled for this Symphony install?
205
     *
206
     * @since Symphony 2.4
207
     * @return boolean
208
     */
209
    public static function isXSRFEnabled()
210
    {
211
        return self::Configuration()->get('enable_xsrf', 'symphony') === 'yes';
212
    }
213
214
    /**
215
     * Accessor for the current `Profiler` instance.
216
     *
217
     * @since Symphony 2.3
218
     * @return Profiler
219
     */
220
    public static function Profiler()
221
    {
222
        return self::$Profiler;
223
    }
224
225
    /**
226
     * Setter for `$Log`. This function uses the configuration
227
     * settings in the 'log' group in the Configuration to create an instance. Date
228
     * formatting options are also retrieved from the configuration.
229
     *
230
     * @param string $filename (optional)
231
     *  The file to write the log to, if omitted this will default to `ACTIVITY_LOG`
232
     * @throws Exception
233
     * @return bool|void
234
     */
235
    public static function initialiseLog($filename = null)
236
    {
237
        if (self::$Log instanceof Log) {
238
            return true;
239
        }
240
241
        if (is_null($filename)) {
242
            $filename = ACTIVITY_LOG;
243
        }
244
245
        // Get the Handler from the Configuration
246
        $handler = self::Configuration()->get('handler', 'log');
247
        $context = array_merge(array(
248
                'vars' => array(
249
                    'filename' => $filename
250
                )
251
            ),
252
            self::Configuration()->get()
253
        );
254
255
        // Create the base handler
256
        if (is_array($handler['args'])) {
257
            array_walk($handler['args'], 'General::replacePlaceholdersWithContext', $context);
258
            $reflection = new ReflectionClass($handler['class']);
259
            $handler = $reflection->newInstanceArgs($handler['args']);
260
        } else {
261
            $handler = new \Monolog\Handler\StreamHandler($filename);
262
        }
263
264
        // Create the base formatter
265
        if ($format = self::Configuration()->get('formatter', 'log')) {
266
            array_walk($format['args'], 'General::replacePlaceholdersWithContext', $context);
267
            $reflection = new ReflectionClass($format['class']);
268
            $formatter = $reflection->newInstanceArgs($format['args']);
269
            $handler->setFormatter($formatter);
270
        }
271
272
        // Create the log object
273
        $logger = new Logger(basename($filename));
274
        $logger->pushHandler($handler);
275
276
        self::$Log = new Log($logger);
277
    }
278
279
    /**
280
     * Accessor for the current `Log` instance
281
     *
282
     * @since Symphony 2.3
283
     * @return Log
284
     */
285
    public static function Log()
286
    {
287
        return self::$Log;
288
    }
289
290
    /**
291
     * Setter for `$Session`. This will use PHP's parse_url
292
     * function on the current URL to set a session using the `session_name`
293
     * defined in the Symphony configuration. The is either admin or public.
294
     * The session will last for the time defined in configuration.
295
     *
296
     * @since Symphony 3.0.0
297
     */
298
    public function initialiseSessionAndCookies()
0 ignored issues
show
Coding Style introduced by
initialiseSessionAndCookies 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...
299
    {
300
        $cookie_path = @parse_url(URL, PHP_URL_PATH);
301
        $cookie_path = '/' . trim($cookie_path, '/');
302
303
        $timeout = $this->getSessionTimeout();
304
305
        $name = null;
0 ignored issues
show
Unused Code introduced by
$name 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...
306
        if (class_exists('Administration', false)) {
307
            $name = self::Configuration()->get('admin_session_name', 'session');
308
        } else {
309
            $name = self::Configuration()->get('public_session_name', 'session');
310
        }
311
312
        if (is_null($name)) {
313
            $name = 'symphony';
314
        }
315
316
        // The handler accepts a database in a move towards dependency injection
317
        $handler = new DatabaseSessionHandler(self::Database(), array(
318
            'session_lifetime' => $timeout
319
        ), $name);
0 ignored issues
show
Bug introduced by
It seems like $name can also be of type array; however, DatabaseSessionHandler::__construct() 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...
320
321
        // The session accepts a handler in a move towards dependency injection
322
        self::$Session = new Session($handler, array(
323
            'session_gc_probability' => self::Configuration()->get('session_gc_probability', 'session'),
324
            'session_gc_divisor' => self::Configuration()->get('session_gc_divisor', 'session'),
325
            'session_gc_maxlifetime' => $timeout,
326
            'session_cookie_lifetime' => $timeout,
327
            'session_cookie_path' => $cookie_path,
328
            'session_cookie_domain' => null,
329
            'session_cookie_secure' => (defined(__SECURE__) ? true : false),
330
            'session_cookie_httponly' => true
331
        ), $name);
0 ignored issues
show
Bug introduced by
It seems like $name can also be of type array; however, Session::__construct() 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...
332
333
        // Initialise the cookie handler
334
        self::$Cookies = new Cookies(array(
335
            'domain' => self::Session()->getDomain(),
336
            'path' => $cookie_path,
337
            'expires' => time() + $timeout,
338
            'secure' => (defined(__SECURE__) ? true : false),
339
            'httponly' => true
340
        ));
341
342
        // Start the session
343
        self::Session()->start($_SESSION);
344
345
        // The flash accepts a session in a move towards dependency injection
346
        self::$Flash = new SessionFlash(self::Session());
0 ignored issues
show
Documentation Bug introduced by
It seems like new \SessionFlash(self::Session()) of type object<SessionFlash> is incompatible with the declared type object<Session> of property $Flash.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
347
348
        // Fetch the current cookies from the header
349
        self::Cookies()->fetch();
350
    }
351
352
    /**
353
     * Accessor for the current `$Session` instance.
354
     *
355
     * @since 3.0.0
356
     * @return Session
357
     */
358
    public static function Session()
359
    {
360
        return self::$Session;
361
    }
362
363
    /**
364
     * Accessor for the current `$Cookies` instance.
365
     *
366
     * @since 2.0.0
367
     * @return Cookies
368
     */
369
    public static function Cookies()
370
    {
371
        return self::$Cookies;
372
    }
373
374
    /**
375
     * Accessor for the current `$Flash` instance.
376
     *
377
     * @since 3.0.0
378
     * @return SessionFlash
379
     */
380
    public static function Flash()
381
    {
382
        return self::$Flash;
383
    }
384
385
    /**
386
     * Gets the configuerd session timeout as seconds, based on the environment instance
387
     * @return int
388
     *  The seconds
389
     */
390
    private function getSessionTimeout()
391
    {
392
        if (class_exists('Administration', false)) {
393
            $time = (self::Configuration()->get('admin_session_expires', 'symphony') ? self::Configuration()->get('admin_session_expires', 'symphony') : '2 weeks');
394
        } else {
395
            $time = (self::Configuration()->get('public_session_expires', 'symphony') ? self::Configuration()->get('public_session_expires', 'symphony') : '2 weeks');
396
        }
397
398
        if (is_string($time) && !is_numeric($time)) {
399
            $time = DateTimeObj::stringToSeconds($time);
400
        }
401
402
        return $time;
403
    }
404
405
    /**
406
     * Setter for `$ExtensionManager` using the current
407
     * Symphony instance as the parent. If for some reason this fails,
408
     * a Symphony Error page will be thrown
409
     *
410
     * @param boolean $force (optional)
411
     *  When set to true, this function will always create a new
412
     *  instance of ExtensionManager, replacing self::$ExtensionManager.
413
     * @return void
414
     */
415
    public static function initialiseExtensionManager($force = false)
416
    {
417
        if (!$force && self::$ExtensionManager instanceof ExtensionManager) {
418
            return;
419
        }
420
421
        self::$ExtensionManager = new ExtensionManager;
422
423
        if (!(self::$ExtensionManager instanceof ExtensionManager)) {
424
            self::throwCustomError(__('Error creating Symphony extension manager.'));
425
        }
426
    }
427
428
    /**
429
     * Accessor for the current `$ExtensionManager` instance.
430
     *
431
     * @since Symphony 2.2
432
     * @return ExtensionManager
433
     */
434
    public static function ExtensionManager()
435
    {
436
        return self::$ExtensionManager;
437
    }
438
439
    /**
440
     * Setter for `$Database`, accepts a Database object. If `$database`
441
     * is omitted, this function will set `$Database` to be of the `MySQL`
442
     * class.
443
     *
444
     * @since Symphony 2.3
445
     * @param stdClass $database (optional)
446
     *  The class to handle all Database operations, if omitted this function
447
     *  will set `self::$Database` to be an instance of the `MySQL` class.
448
     * @return boolean
449
     *  This function will always return true
450
     */
451
    public static function setDatabase(stdClass $database = null)
452
    {
453
        if (self::Database()) {
454
            return true;
455
        }
456
457
        self::$Database = !is_null($database) ? $database : new MySQL;
0 ignored issues
show
Documentation Bug introduced by
It seems like !is_null($database) ? $database : new \MySQL() can also be of type object<stdClass>. However, the property $Database is declared as type object<MySQL>. 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...
458
459
        return true;
460
    }
461
462
    /**
463
     * Accessor for the current `$Database` instance.
464
     *
465
     * @return MySQL
466
     */
467
    public static function Database()
468
    {
469
        return self::$Database;
470
    }
471
472
    /**
473
     * This will initialise the Database class and attempt to create a connection
474
     * using the connection details provided in the Symphony configuration. If any
475
     * errors occur whilst doing so, a Symphony Error Page is displayed.
476
     *
477
     * @throws SymphonyErrorPage
478
     * @return boolean
479
     *  This function will return true if the `$Database` was
480
     *  initialised successfully.
481
     */
482
    public static function initialiseDatabase()
483
    {
484
        self::setDatabase();
485
        $details = self::Configuration()->get('database');
486
487
        try {
488
            if (!self::Database()->connect($details['host'], $details['user'], $details['password'], $details['port'], $details['db'])) {
489
                return false;
490
            }
491
492
            if (!self::Database()->isConnected()) {
493
                return false;
494
            }
495
496
            self::Database()->setPrefix($details['tbl_prefix']);
497
            self::Database()->setTimeZone(self::Configuration()->get('timezone', 'region'));
0 ignored issues
show
Bug introduced by
It seems like self::Configuration()->get('timezone', 'region') targeting Configuration::get() can also be of type array; however, MySQL::setTimeZone() does only seem to accept string|null, 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...
498
499
            if (isset($details['query_caching'])) {
500
                if ($details['query_caching'] === 'off') {
501
                    self::Database()->disableCaching();
502
                } elseif ($details['query_caching'] === 'on') {
503
                    self::Database()->enableCaching();
504
                }
505
            }
506
507
            if (isset($details['query_logging'])) {
508
                if ($details['query_logging'] === 'off') {
509
                    self::Database()->disableLogging();
510
                } elseif ($details['query_logging'] === 'on') {
511
                    self::Database()->enableLogging();
512
                }
513
            }
514
        } catch (DatabaseException $e) {
515
            self::throwCustomError(
516
                $e->getDatabaseErrorCode() . ': ' . $e->getDatabaseErrorMessage(),
517
                __('Symphony Database Error'),
518
                Page::HTTP_STATUS_ERROR,
519
                'database',
520
                array(
521
                    'error' => $e,
522
                    'message' => __('There was a problem whilst attempting to establish a database connection. Please check all connection information is correct.') . ' ' . __('The following error was returned:')
523
                )
524
            );
525
        }
526
527
        return true;
528
    }
529
530
    /**
531
     * Accessor for the current `$Author` instance.
532
     *
533
     * @since Symphony 2.5.0
534
     * @return Author
535
     */
536
    public static function Author()
537
    {
538
        return self::$Author;
539
    }
540
541
    /**
542
     * Attempts to log an Author in given a username and password.
543
     * If the password is not hashed, it will be hashed using the sha1
544
     * algorithm. The username and password will be sanitized before
545
     * being used to query the Database. If an Author is found, they
546
     * will be logged in and the sanitized username and password (also hashed)
547
     * will be saved as values in the `$Session`.
548
     *
549
     * @see toolkit.Cryptography#hash()
550
     * @throws DatabaseException
551
     * @param string $username
552
     *  The Author's username. This will be sanitized before use.
553
     * @param string $password
554
     *  The Author's password. This will be sanitized and then hashed before use
555
     * @param boolean $isHash
556
     *  If the password provided is already hashed, setting this parameter to
557
     *  true will stop it becoming rehashed. By default it is false.
558
     * @return boolean
559
     *  True if the Author was logged in, false otherwise
560
     */
561
    public static function login($username, $password, $isHash = false)
562
    {
563
        $username = trim(self::Database()->cleanValue($username));
564
        $password = trim(self::Database()->cleanValue($password));
565
566
        if (strlen($username) > 0 && strlen($password) > 0) {
567
            $author = AuthorManager::fetch('id', 'ASC', 1, null, sprintf(
568
                "`username` = '%s'",
569
                $username
570
            ));
571
572
            if (!empty($author) && Cryptography::compare($password, current($author)->get('password'), $isHash)) {
573
                self::$Author = current($author);
574
575
                // Only migrate hashes if there is no update available as the update might change the tbl_authors table.
576
                if (self::isUpgradeAvailable() === false && Cryptography::requiresMigration(self::$Author->get('password'))) {
577
                    self::$Author->set('password', Cryptography::hash($password));
578
579
                    self::Database()->update(array('password' => self::$Author->get('password')), 'tbl_authors',
580
                        " `id` = ?", array(self::$Author->get('id'))
581
                    );
582
                }
583
584
                self::Session()->set('username', $username);
585
                self::Session()->set('pass', self::$Author->get('password'));
586
587
                self::Database()->update(array(
588
                    'last_seen' => DateTimeObj::get('Y-m-d H:i:s')
589
                    ),
590
                    'tbl_authors',
591
                    " `id` = ?",
592
                    array(self::$Author->get('id'))
593
                );
594
595
                // Only set custom author language in the backend
596
                if (class_exists('Administration', false)) {
597
                    Lang::set(self::$Author->get('language'));
598
                }
599
600
                return true;
601
            }
602
        }
603
604
        return false;
605
    }
606
607
    /**
608
     * Symphony allows Authors to login via the use of tokens instead of
609
     * a username and password. A token is derived from concatenating the
610
     * Author's username and password and applying the sha1 hash to
611
     * it, from this, a portion of the hash is used as the token. This is a useful
612
     * feature often used when setting up other Authors accounts or if an
613
     * Author forgets their password.
614
     *
615
     * @param string $token
616
     *  The Author token, which is a portion of the hashed string concatenation
617
     *  of the Author's username and password
618
     * @throws DatabaseException
619
     * @return boolean
620
     *  True if the Author is logged in, false otherwise
621
     */
622
    public static function loginFromToken($token)
623
    {
624
        $token = self::Database()->cleanValue($token);
625
        $tokenLength = strlen(trim($token));
626
627
        if ($tokenLength === 0) {
628
            return false;
629
        }
630
631
        if ($tokenLength === 6 || $tokenLength === 16) {
632
            $row = self::Database()->fetchRow(0, "
633
                SELECT `a`.`id`, `a`.`username`, `a`.`password`
634
                FROM `tbl_authors` AS `a`, `tbl_forgotpass` AS `f`
635
                WHERE `a`.`id` = `f`.`author_id`
636
                AND `f`.`expiry` > ?
637
                AND `f`.`token` = ?
638
                LIMIT 1",
639
                array(
640
                    DateTimeObj::getGMT('c'),
641
                    $token
642
                )
643
            );
644
645
            self::Database()->delete('tbl_forgotpass', " `token` = ? ", array($token));
646
        } else {
647
            $row = self::Database()->fetchRow(0, sprintf(
648
                "SELECT `id`, `username`, `password`
649
                FROM `tbl_authors`
650
                WHERE SUBSTR(%s(CONCAT(`username`, `password`)), 1, 8) = ?
651
                AND `auth_token_active` = 'yes'
652
                LIMIT 1",
653
                'SHA1'
654
                ),
655
                array($token)
656
            );
657
        }
658
659
        if ($row) {
660
            self::$Author = AuthorManager::fetchByID($row['id']);
661
            self::Session()->set('username', $row['username']);
662
            self::Session()->set('pass', $row['password']);
663
            self::Database()->update(array('last_seen' => DateTimeObj::getGMT('Y-m-d H:i:s')), 'tbl_authors', "`id` = ?", array(
664
                $row['id']
665
            ));
666
667
            return true;
668
        }
669
670
        return false;
671
    }
672
673
    /**
674
     * This function will destroy the currently logged in `$Author`
675
     * session, essentially logging them out.
676
     *
677
     * @see core.Session#expire()
678
     */
679
    public static function logout()
680
    {
681
        self::Session()->expire();
682
    }
683
684
    /**
685
     * This function determines whether an there is a currently logged in
686
     * Author for Symphony by using the `$Session`'s username
687
     * and password. If an Author is found, they will be logged in, otherwise
688
     * the `$Session` will be destroyed.
689
     *
690
     * @see login()
691
     * @return boolean
692
     */
693
    public static function isLoggedIn()
694
    {
695
        // Check to see if Symphony exists, or if we already have an Author instance.
696
        if (is_null(self::$_instance) || self::$Author) {
697
            return true;
698
        }
699
700
        // No author instance found, attempt to log in with the cookied credentials
701
        return self::login(self::Session()->get('username'), self::Session()->get('pass'), true);
702
    }
703
704
    /**
705
     * Returns the most recent version found in the `/install/migrations` folder.
706
     * Returns a version string to be used in `version_compare()` if an updater
707
     * has been found. Returns `FALSE` otherwise.
708
     *
709
     * @since Symphony 2.3.1
710
     * @return string|boolean
711
     */
712
    public static function getMigrationVersion()
713
    {
714
        if (self::isInstallerAvailable()) {
715
            $migrations = scandir(DOCROOT . '/install/migrations');
716
            $migration_file = end($migrations);
717
718
            include_once DOCROOT . '/install/lib/class.migration.php';
719
            include_once DOCROOT . '/install/migrations/' . $migration_file;
720
721
            $migration_class = 'migration_' . str_replace('.', '', substr($migration_file, 0, -4));
722
            return call_user_func(array($migration_class, 'getVersion'));
723
        }
724
725
        return false;
726
    }
727
728
    /**
729
     * Checks if an update is available and applicable for the current installation.
730
     *
731
     * @since Symphony 2.3.1
732
     * @return boolean
733
     */
734
    public static function isUpgradeAvailable()
735
    {
736
        if (self::isInstallerAvailable()) {
737
            $migration_version = self::getMigrationVersion();
738
            $current_version = Symphony::Configuration()->get('version', 'symphony');
739
740
            return version_compare($current_version, $migration_version, '<');
741
        }
742
743
        return false;
744
    }
745
746
    /**
747
     * Checks if the installer/upgrader is available.
748
     *
749
     * @since Symphony 2.3.1
750
     * @return boolean
751
     */
752
    public static function isInstallerAvailable()
753
    {
754
        return file_exists(DOCROOT . '/install/index.php');
755
    }
756
757
    /**
758
     * A wrapper for throwing a new Symphony Error page.
759
     *
760
     * This methods sets the `GenericExceptionHandler::$enabled` value to `true`.
761
     *
762
     * @see core.SymphonyErrorPage
763
     * @param string|XMLElement $message
764
     *  A description for this error, which can be provided as a string
765
     *  or as an XMLElement.
766
     * @param string $heading
767
     *  A heading for the error page
768
     * @param integer $status
769
     *  Properly sets the HTTP status code for the response. Defaults to
770
     *  `Page::HTTP_STATUS_ERROR`. Use `Page::HTTP_STATUS_XXX` to set this value.
771
     * @param string $template
772
     *  A string for the error page template to use, defaults to 'generic'. This
773
     *  can be the name of any template file in the `TEMPLATES` directory.
774
     *  A template using the naming convention of `tpl.*.php`.
775
     * @param array $additional
776
     *  Allows custom information to be passed to the Symphony Error Page
777
     *  that the template may want to expose, such as custom Headers etc.
778
     * @throws SymphonyErrorPage
779
     */
780
    public static function throwCustomError($message, $heading = 'Symphony Fatal Error', $status = Page::HTTP_STATUS_ERROR, $template = 'generic', array $additional = array())
781
    {
782
        GenericExceptionHandler::$enabled = true;
783
        throw new SymphonyErrorPage($message, $heading, $template, $additional, $status);
784
    }
785
786
    /**
787
     * Setter accepts a previous Exception. Useful for determining the context
788
     * of a current exception (ie. detecting recursion).
789
     *
790
     * @since Symphony 2.3.2
791
     * @param Exception $ex
792
     */
793
    public static function setException(Exception $ex)
794
    {
795
        self::$exception = $ex;
796
    }
797
798
    /**
799
     * Accessor for `self::$exception`.
800
     *
801
     * @since Symphony 2.3.2
802
     * @return Exception|null
803
     */
804
    public static function getException()
805
    {
806
        return self::$exception;
807
    }
808
809
    /**
810
     * Returns the page namespace based on the current URL.
811
     * A few examples:
812
     *
813
     * /login
814
     * /publish
815
     * /blueprints/datasources
816
     * [...]
817
     * /extension/$extension_name/$page_name
818
     *
819
     * This method is especially useful in couple with the translation function.
820
     *
821
     * @see toolkit#__()
822
     * @return string
823
     *  The page namespace, without any action string (e.g. "new", "saved") or
824
     *  any value that depends upon the single setup (e.g. the section handle in
825
     *  /publish/$handle)
826
     */
827
    public static function getPageNamespace()
828
    {
829
        if (self::$namespace !== false) {
830
            return self::$namespace;
831
        }
832
833
        $page = getCurrentPage();
834
835
        if (!is_null($page)) {
836
            $page = trim($page, '/');
837
        }
838
839
        if (substr($page, 0, 7) === 'publish') {
840
            self::$namespace = '/publish';
841
        } elseif (empty($page) && isset($_REQUEST['mode'])) {
842
            self::$namespace = '/login';
843
        } elseif (empty($page)) {
844
            self::$namespace = null;
845
        } else {
846
            $bits = explode('/', $page);
847
848
            if ($bits[0] === 'extension') {
849
                self::$namespace = sprintf('/%s/%s/%s', $bits[0], $bits[1], $bits[2]);
850
            } else {
851
                self::$namespace =  sprintf('/%s/%s', $bits[0], isset($bits[1]) ? $bits[1] : '');
852
            }
853
        }
854
855
        return self::$namespace;
856
    }
857
}
858
859
/**
860
 * The `SymphonyErrorPageHandler` extends the `GenericExceptionHandler`
861
 * to allow the template for the exception to be provided from the `TEMPLATES`
862
 * directory
863
 */
864
class SymphonyErrorPageHandler extends GenericExceptionHandler
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

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

Loading history...
865
{
866
    /**
867
     * The render function will take a `SymphonyErrorPage` exception and
868
     * output a HTML page. This function first checks to see if their is a custom
869
     * template for this exception otherwise it reverts to using the default
870
     * `usererror.generic.php`
871
     *
872
     * @param Exception $e
873
     *  The Exception object
874
     * @return string
875
     *  An HTML string
876
     */
877
    public static function render(Exception $e)
878
    {
879
        if ($e->getTemplate() === false) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Exception as the method getTemplate() does only exist in the following sub-classes of Exception: SymphonyErrorPage. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
880
            Page::renderStatusCode($e->getHttpStatusCode());
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Exception as the method getHttpStatusCode() does only exist in the following sub-classes of Exception: SymphonyErrorPage. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
881
882
            if (isset($e->getAdditional()->header)) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Exception as the method getAdditional() does only exist in the following sub-classes of Exception: SymphonyErrorPage. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
883
                header($e->getAdditional()->header);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Exception as the method getAdditional() does only exist in the following sub-classes of Exception: SymphonyErrorPage. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
884
            }
885
886
            echo '<h1>Symphony Fatal Error</h1><p>'.$e->getMessage().'</p>';
887
            exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method render() 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...
888
        }
889
890
        include $e->getTemplate();
891
    }
892
}
893
894
/**
895
 * `SymphonyErrorPage` extends the default `Exception` class. All
896
 * of these exceptions will halt execution immediately and return the
897
 * exception as a HTML page. By default the HTML template is `usererror.generic.php`
898
 * from the `TEMPLATES` directory.
899
 */
900
901
class SymphonyErrorPage extends Exception
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

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

Loading history...
902
{
903
    /**
904
     * A heading for the error page, this will be prepended to
905
     * "Symphony Fatal Error".
906
     * @return string
907
     */
908
    private $_heading;
909
910
    /**
911
     * A string for the error page template to use, defaults to 'generic'. This
912
     * can be the name of any template file in the `TEMPLATES` directory.
913
     * A template using the naming convention of `usererror.*.php`.
914
     * @var string
915
     */
916
    private $_template = 'generic';
917
918
    /**
919
     * If the message as provided as an `XMLElement`, it will be saved to
920
     * this parameter
921
     * @var XMLElement
922
     */
923
    private $_messageObject = null;
924
925
    /**
926
     * An object of an additional information for this error page. Note that
927
     * this is provided as an array and then typecast to an object
928
     * @var StdClass
929
     */
930
    private $_additional = null;
931
932
    /**
933
     * A simple container for the response status code.
934
     * Full value is setted usign `$Page->setHttpStatus()`
935
     * in the template.
936
     */
937
    private $_status = Page::HTTP_STATUS_ERROR;
938
939
    /**
940
     * Constructor for SymphonyErrorPage sets it's class variables
941
     *
942
     * @param string|XMLElement $message
943
     *  A description for this error, which can be provided as a string
944
     *  or as an XMLElement.
945
     * @param string $heading
946
     *  A heading for the error page, by default this is "Symphony Fatal Error"
947
     * @param string $template
948
     *  A string for the error page template to use, defaults to 'generic'. This
949
     *  can be the name of any template file in the `TEMPLATES` directory.
950
     *  A template using the naming convention of `tpl.*.php`.
951
     * @param array $additional
952
     *  Allows custom information to be passed to the Symphony Error Page
953
     *  that the template may want to expose, such as custom Headers etc.
954
     * @param integer $status
955
     *  Properly sets the HTTP status code for the response. Defaults to
956
     *  `Page::HTTP_STATUS_ERROR`
957
     */
958
    public function __construct($message, $heading = 'Symphony Fatal Error', $template = 'generic', array $additional = array(), $status = Page::HTTP_STATUS_ERROR)
959
    {
960
        if ($message instanceof XMLElement) {
961
            $this->_messageObject = $message;
962
            $message = $this->_messageObject->generate();
963
        }
964
965
        parent::__construct($message);
966
967
        $this->_heading = $heading;
968
        $this->_template = $template;
969
        $this->_additional = (object)$additional;
970
        $this->_status = $status;
971
    }
972
973
    /**
974
     * Accessor for the `$_heading` of the error page
975
     *
976
     * @return string
977
     */
978
    public function getHeading()
979
    {
980
        return $this->_heading;
981
    }
982
983
    /**
984
     * Accessor for `$_messageObject`
985
     *
986
     * @return XMLElement
987
     */
988
    public function getMessageObject()
989
    {
990
        return $this->_messageObject;
991
    }
992
993
    /**
994
     * Accessor for `$_additional`
995
     *
996
     * @return StdClass
997
     */
998
    public function getAdditional()
999
    {
1000
        return $this->_additional;
1001
    }
1002
1003
    /**
1004
     * Accessor for `$_status`
1005
     *
1006
     * @since Symphony 2.3.2
1007
     * @return integer
1008
     */
1009
    public function getHttpStatusCode()
1010
    {
1011
        return $this->_status;
1012
    }
1013
1014
    /**
1015
     * Returns the path to the current template by looking at the
1016
     * `WORKSPACE/template/` directory, then at the `TEMPLATES`
1017
     * directory for the convention `usererror.*.php`. If the template
1018
     * is not found, `false` is returned
1019
     *
1020
     * @since Symphony 2.3
1021
     * @return mixed
1022
     *  String, which is the path to the template if the template is found,
1023
     *  false otherwise
1024
     */
1025
    public function getTemplate()
1026
    {
1027
        $format = '%s/usererror.%s.php';
1028
1029
        if (file_exists($template = sprintf($format, WORKSPACE . '/template', $this->_template))) {
1030
            return $template;
1031
        } elseif (file_exists($template = sprintf($format, TEMPLATE, $this->_template))) {
1032
            return $template;
1033
        } else {
1034
            return false;
1035
        }
1036
    }
1037
1038
    /**
1039
     * A simple getter to the template name in order to be able
1040
     * to identify which type of exception this is.
1041
     *
1042
     * @since Symphony 2.3.2
1043
     * @return string
1044
     */
1045
    public function getTemplateName()
1046
    {
1047
        return $this->_template;
1048
    }
1049
}
1050
1051
/**
1052
 * The `DatabaseExceptionHandler` provides a render function to provide
1053
 * customised output for database exceptions. It displays the exception
1054
 * message as provided by the Database.
1055
 */
1056
class DatabaseExceptionHandler extends GenericExceptionHandler
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

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

Loading history...
1057
{
1058
    /**
1059
     * The render function will take a `DatabaseException` and output a
1060
     * HTML page.
1061
     *
1062
     * @param Exception $e
1063
     *  The Exception object
1064
     * @return string
1065
     *  An HTML string
1066
     */
1067
    public static function render(Exception $e)
1068
    {
1069
        $trace = $queries = null;
1070
1071
        foreach ($e->getTrace() as $t) {
1072
            $trace .= sprintf(
1073
                '<li><code><em>[%s:%d]</em></code></li><li><code>&#160;&#160;&#160;&#160;%s%s%s();</code></li>',
1074
                $t['file'],
1075
                $t['line'],
1076
                (isset($t['class']) ? $t['class'] : null),
1077
                (isset($t['type']) ? $t['type'] : null),
1078
                $t['function']
1079
            );
1080
        }
1081
1082 View Code Duplication
        if (is_object(Symphony::Database())) {
1083
            $debug = Symphony::Database()->debug();
1084
1085
            if (!empty($debug)) {
1086
                foreach ($debug as $query) {
1087
                    $queries .= sprintf(
1088
                        '<li><em>[%01.4f]</em><code> %s;</code> </li>',
1089
                        (isset($query['execution_time']) ? $query['execution_time'] : null),
1090
                        htmlspecialchars($query['query'])
1091
                    );
1092
                }
1093
            }
1094
        }
1095
1096
        $html = sprintf(
1097
            file_get_contents(self::getTemplate('fatalerror.database')),
1098
            $e->getDatabaseErrorMessage(),
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Exception as the method getDatabaseErrorMessage() does only exist in the following sub-classes of Exception: DatabaseException. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1099
            $e->getQuery(),
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Exception as the method getQuery() does only exist in the following sub-classes of Exception: DatabaseException. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1100
            $trace,
1101
            $queries
1102
        );
1103
1104
        $html = str_replace('{ASSETS_URL}', ASSETS_URL, $html);
1105
        $html = str_replace('{SYMPHONY_URL}', SYMPHONY_URL, $html);
1106
        $html = str_replace('{URL}', URL, $html);
1107
1108
        return $html;
1109
    }
1110
}
1111