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 ( 45cc9f...98bc42 )
by Brendan
05:52
created

Symphony::isUpgradeAvailable()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 0
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
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`, `$_POST` and `$_REQUEST` 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
     * The `$_REQUEST` array has been added in 2.7.0
99
     */
100
    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...
101
    {
102
        self::$Profiler = Profiler::instance();
103
104
        if (get_magic_quotes_gpc()) {
105
            General::cleanArray($_SERVER);
106
            General::cleanArray($_COOKIE);
107
            General::cleanArray($_GET);
108
            General::cleanArray($_POST);
109
            General::cleanArray($_REQUEST);
110
        }
111
112
        // Initialize language management
113
        Lang::initialize();
114
        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...
115
116
        $this->initialiseLog();
117
118
        GenericExceptionHandler::initialise(self::Log());
119
        GenericErrorHandler::initialise(self::Log());
120
121
        $this->initialiseDatabase();
122
        $this->initialiseExtensionManager();
123
        $this->initialiseSessionAndCookies();
124
125
        // If the user is not a logged in Author, turn off the verbose error messages.
126
        if (!self::isLoggedIn() && is_null(self::$Author)) {
127
            GenericExceptionHandler::$enabled = false;
128
        }
129
130
        // Engine is ready.
131
        self::$Profiler->sample('Engine Initialisation');
132
    }
133
134
    /**
135
     * Setter for the Symphony Log and Error Handling system
136
     *
137
     * @since Symphony 2.6.0
138
     */
139
    public static function initialiseErrorHandler()
140
    {
141
        // Initialise logging
142
        self::initialiseLog();
143
        GenericExceptionHandler::initialise(self::Log());
144
        GenericErrorHandler::initialise(self::Log());
145
    }
146
147
    /**
148
     * Accessor for the Symphony instance, whether it be Frontend
149
     * or Administration
150
     *
151
     * @since Symphony 2.2
152
     * @throws Exception
153
     * @return Symphony
154
     */
155
    public static function Engine()
156
    {
157
        if (class_exists('Administration', false)) {
158
            return Administration::instance();
159
        } elseif (class_exists('Frontend', false)) {
160
            return Frontend::instance();
161
        } else {
162
            throw new Exception(__('No suitable engine object found'));
163
        }
164
    }
165
166
    /**
167
     * Setter for `$Configuration`. This function initialise the configuration
168
     * object and populate its properties based on the given `$array`. Since
169
     * Symphony 2.6.5, it will also set Symphony's date constants.
170
     *
171
     * @since Symphony 2.3
172
     * @param array $data
173
     *  An array of settings to be stored into the Configuration object
174
     */
175
    public static function initialiseConfiguration(array $data = array())
176
    {
177
        if (empty($data)) {
178
            // Includes the existing CONFIG file and initialises the Configuration
179
            // by setting the values with the setArray function.
180
            include CONFIG;
181
182
            $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...
183
        }
184
185
        self::$Configuration = new Configuration(true);
186
        self::$Configuration->setArray($data);
187
188
        // Set date format throughout the system
189
        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...
190
        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...
191
        define_safe('__SYM_DATETIME_FORMAT__', __SYM_DATE_FORMAT__ . self::Configuration()->get('datetime_separator', 'region') . __SYM_TIME_FORMAT__);
192
        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...
193
    }
194
195
    /**
196
     * Accessor for the current `Configuration` instance. This contains
197
     * representation of the the Symphony config file.
198
     *
199
     * @return Configuration
200
     */
201
    public static function Configuration()
202
    {
203
        return self::$Configuration;
204
    }
205
206
    /**
207
     * Is XSRF enabled for this Symphony install?
208
     *
209
     * @since Symphony 2.4
210
     * @return boolean
211
     */
212
    public static function isXSRFEnabled()
213
    {
214
        return self::Configuration()->get('enable_xsrf', 'symphony') === 'yes';
215
    }
216
217
    /**
218
     * Accessor for the current `Profiler` instance.
219
     *
220
     * @since Symphony 2.3
221
     * @return Profiler
222
     */
223
    public static function Profiler()
224
    {
225
        return self::$Profiler;
226
    }
227
228
    /**
229
     * Setter for `$Log`. This function uses the configuration
230
     * settings in the 'log' group in the Configuration to create an instance. Date
231
     * formatting options are also retrieved from the configuration.
232
     *
233
     * @param string $filename (optional)
234
     *  The file to write the log to, if omitted this will default to `ACTIVITY_LOG`
235
     * @throws Exception
236
     * @return bool|void
237
     */
238
    public static function initialiseLog($filename = null)
239
    {
240
        if (self::$Log instanceof Log) {
241
            return true;
242
        }
243
244
        if (is_null($filename)) {
245
            $filename = ACTIVITY_LOG;
246
        }
247
248
        // Get the Handler from the Configuration
249
        $handler = self::Configuration()->get('handler', 'log');
250
        $context = array_merge(array(
251
                'vars' => array(
252
                    'filename' => $filename
253
                )
254
            ),
255
            self::Configuration()->get()
256
        );
257
258
        // Create the base handler
259
        if (is_array($handler['args'])) {
260
            array_walk($handler['args'], 'General::replacePlaceholdersWithContext', $context);
261
            $reflection = new ReflectionClass($handler['class']);
262
            $handler = $reflection->newInstanceArgs($handler['args']);
263
        } else {
264
            $handler = new \Monolog\Handler\StreamHandler($filename);
265
        }
266
267
        // Create the base formatter
268
        if ($format = self::Configuration()->get('formatter', 'log')) {
269
            array_walk($format['args'], 'General::replacePlaceholdersWithContext', $context);
270
            $reflection = new ReflectionClass($format['class']);
271
            $formatter = $reflection->newInstanceArgs($format['args']);
272
            $handler->setFormatter($formatter);
273
        }
274
275
        // Create the log object
276
        $logger = new Logger(basename($filename));
277
        $logger->pushHandler($handler);
278
279
        self::$Log = new Log($logger);
280
    }
281
282
    /**
283
     * Accessor for the current `Log` instance
284
     *
285
     * @since Symphony 2.3
286
     * @return Log
287
     */
288
    public static function Log()
289
    {
290
        return self::$Log;
291
    }
292
293
    /**
294
     * Setter for `$Session`. This will use PHP's parse_url
295
     * function on the current URL to set a session using the `session_name`
296
     * defined in the Symphony configuration. The is either admin or public.
297
     * The session will last for the time defined in configuration.
298
     *
299
     * @since Symphony 3.0.0
300
     */
301
    public function initialiseSessionAndCookies()
302
    {
303
        $name = '';
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...
304
        $timeout = $this->getSessionTimeout();
305
        $cookie_path = DIRROOT === '' ? '/' : DIRROOT;
306
307
        $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...
308
        if (class_exists('Administration', false)) {
309
            $name = self::Configuration()->get('admin_session_name', 'session');
310
        } else {
311
            $name = self::Configuration()->get('public_session_name', 'session');
312
        }
313
314
        if (is_null($name)) {
315
            $name = 'symphony';
316
        }
317
318
        // The handler accepts a database in a move towards dependency injection
319
        $handler = new DatabaseSessionHandler(self::Database(), array(
320
            'session_lifetime' => $timeout
321
        ), $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...
322
323
        // The session accepts a handler in a move towards dependency injection
324
        self::$Session = new Session($handler, array(
325
            'session_gc_probability' => self::Configuration()->get('session_gc_probability', 'session'),
326
            'session_gc_divisor' => self::Configuration()->get('session_gc_divisor', 'session'),
327
            'session_gc_maxlifetime' => $timeout,
328
            'session_cookie_lifetime' => $timeout,
329
            'session_cookie_path' => $cookie_path,
330
            'session_cookie_domain' => null,
331
            'session_cookie_secure' => (defined(__SECURE__) ? true : false),
332
            'session_cookie_httponly' => true
333
        ), $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...
334
335
        // Initialise the cookie handler
336
        self::$Cookies = new Cookies(array(
337
            'domain' => self::Session()->getDomain(),
338
            'path' => $cookie_path,
339
            'expires' => time() + $timeout,
340
            'secure' => (defined(__SECURE__) ? true : false),
341
            'httponly' => true
342
        ));
343
344
        // Start the session
345
        self::Session()->start();
346
347
        // The flash accepts a session in a move towards dependency injection
348
        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...
349
350
        // Fetch the current cookies from the header
351
        self::Cookies()->fetch();
352
    }
353
354
    /**
355
     * Accessor for the current `$Session` instance.
356
     *
357
     * @since 3.0.0
358
     * @return Session
359
     */
360
    public static function Session()
361
    {
362
        return self::$Session;
363
    }
364
365
    /**
366
     * Accessor for the current `$Cookies` instance.
367
     *
368
     * @since 2.0.0
369
     * @return Cookies
370
     */
371
    public static function Cookies()
372
    {
373
        return self::$Cookies;
374
    }
375
376
    /**
377
     * Accessor for the current `$Flash` instance.
378
     *
379
     * @since 3.0.0
380
     * @return SessionFlash
381
     */
382
    public static function Flash()
383
    {
384
        return self::$Flash;
385
    }
386
387
    /**
388
     * Gets the configuerd session timeout as seconds, based on the environment instance
389
     * @return int
390
     *  The seconds
391
     */
392
    private function getSessionTimeout()
393
    {
394
        if (class_exists('Administration', false)) {
395
            $time = (self::Configuration()->get('admin_session_expires', 'symphony') ? self::Configuration()->get('admin_session_expires', 'symphony') : '2 weeks');
396
        } else {
397
            $time = (self::Configuration()->get('public_session_expires', 'symphony') ? self::Configuration()->get('public_session_expires', 'symphony') : '2 weeks');
398
        }
399
400
        if (is_string($time) && !is_numeric($time)) {
401
            $time = DateTimeObj::stringToSeconds($time);
402
        }
403
404
        return $time;
405
    }
406
407
    /**
408
     * Setter for `$ExtensionManager` using the current
409
     * Symphony instance as the parent. If for some reason this fails,
410
     * a Symphony Error page will be thrown
411
     *
412
     * @param boolean $force (optional)
413
     *  When set to true, this function will always create a new
414
     *  instance of ExtensionManager, replacing self::$ExtensionManager.
415
     * @return void
416
     */
417
    public static function initialiseExtensionManager($force = false)
418
    {
419
        if (!$force && self::$ExtensionManager instanceof ExtensionManager) {
420
            return;
421
        }
422
423
        self::$ExtensionManager = new ExtensionManager;
424
425
        if (!(self::$ExtensionManager instanceof ExtensionManager)) {
426
            self::throwCustomError(__('Error creating Symphony extension manager.'));
427
        }
428
    }
429
430
    /**
431
     * Accessor for the current `$ExtensionManager` instance.
432
     *
433
     * @since Symphony 2.2
434
     * @return ExtensionManager
435
     */
436
    public static function ExtensionManager()
437
    {
438
        return self::$ExtensionManager;
439
    }
440
441
    /**
442
     * Setter for `$Database`, accepts a Database object. If `$database`
443
     * is omitted, this function will set `$Database` to be of the `MySQL`
444
     * class.
445
     *
446
     * @since Symphony 2.3
447
     * @param stdClass $database (optional)
448
     *  The class to handle all Database operations, if omitted this function
449
     *  will set `self::$Database` to be an instance of the `MySQL` class.
450
     * @return boolean
451
     *  This function will always return true
452
     */
453
    public static function setDatabase(stdClass $database = null)
454
    {
455
        if (self::Database()) {
456
            return true;
457
        }
458
459
        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...
460
461
        return true;
462
    }
463
464
    /**
465
     * Accessor for the current `$Database` instance.
466
     *
467
     * @return MySQL
468
     */
469
    public static function Database()
470
    {
471
        return self::$Database;
472
    }
473
474
    /**
475
     * This will initialise the Database class and attempt to create a connection
476
     * using the connection details provided in the Symphony configuration. If any
477
     * errors occur whilst doing so, a Symphony Error Page is displayed.
478
     *
479
     * @throws SymphonyErrorPage
480
     * @return boolean
481
     *  This function will return true if the `$Database` was
482
     *  initialised successfully.
483
     */
484
    public static function initialiseDatabase()
485
    {
486
        self::setDatabase();
487
        $details = self::Configuration()->get('database');
488
489
        try {
490
            if (!self::Database()->connect($details['host'], $details['user'], $details['password'], $details['port'], $details['db'])) {
491
                return false;
492
            }
493
494
            if (!self::Database()->isConnected()) {
495
                return false;
496
            }
497
498
            self::Database()->setPrefix($details['tbl_prefix']);
499
            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...
500
501
            if (isset($details['query_caching'])) {
502
                if ($details['query_caching'] === 'off') {
503
                    self::Database()->disableCaching();
504
                } elseif ($details['query_caching'] === 'on') {
505
                    self::Database()->enableCaching();
506
                }
507
            }
508
509
            if (isset($details['query_logging'])) {
510
                if ($details['query_logging'] === 'off') {
511
                    self::Database()->disableLogging();
512
                } elseif ($details['query_logging'] === 'on') {
513
                    self::Database()->enableLogging();
514
                }
515
            }
516
        } catch (DatabaseException $e) {
517
            self::throwCustomError(
518
                $e->getDatabaseErrorCode() . ': ' . $e->getDatabaseErrorMessage(),
519
                __('Symphony Database Error'),
520
                Page::HTTP_STATUS_ERROR,
521
                'database',
522
                array(
523
                    'error' => $e,
524
                    'message' => __('There was a problem whilst attempting to establish a database connection. Please check all connection information is correct.') . ' ' . __('The following error was returned:')
525
                )
526
            );
527
        }
528
529
        return true;
530
    }
531
532
    /**
533
     * Accessor for the current `$Author` instance.
534
     *
535
     * @since Symphony 2.5.0
536
     * @return Author
537
     */
538
    public static function Author()
539
    {
540
        return self::$Author;
541
    }
542
543
    /**
544
     * Attempts to log an Author in given a username and password.
545
     * If the password is not hashed, it will be hashed using the sha1
546
     * algorithm. The username and password will be sanitized before
547
     * being used to query the Database. If an Author is found, they
548
     * will be logged in and the sanitized username and password (also hashed)
549
     * will be saved as values in the `$Session`.
550
     *
551
     * @see toolkit.Cryptography#hash()
552
     * @throws DatabaseException
553
     * @param string $username
554
     *  The Author's username. This will be sanitized before use.
555
     * @param string $password
556
     *  The Author's password. This will be sanitized and then hashed before use
557
     * @param boolean $isHash
558
     *  If the password provided is already hashed, setting this parameter to
559
     *  true will stop it becoming rehashed. By default it is false.
560
     * @return boolean
561
     *  True if the Author was logged in, false otherwise
562
     */
563
    public static function login($username, $password, $isHash = false)
564
    {
565
        $username = trim(self::Database()->cleanValue($username));
566
        $password = trim(self::Database()->cleanValue($password));
567
568
        if (strlen($username) > 0 && strlen($password) > 0) {
569
            $author = AuthorManager::fetch('id', 'ASC', 1, null, sprintf(
570
                "`username` = '%s'",
571
                $username
572
            ));
573
574
            if (!empty($author) && Cryptography::compare($password, current($author)->get('password'), $isHash)) {
575
                self::$Author = current($author);
576
577
                // Only migrate hashes if there is no update available as the update might change the tbl_authors table.
578
                if (self::isUpgradeAvailable() === false && Cryptography::requiresMigration(self::$Author->get('password'))) {
579
                    self::$Author->set('password', Cryptography::hash($password));
580
581
                    self::Database()->update(array('password' => self::$Author->get('password')), 'tbl_authors',
582
                        " `id` = ?", array(self::$Author->get('id'))
583
                    );
584
                }
585
586
                self::Session()->set('username', $username);
587
                self::Session()->set('pass', self::$Author->get('password'));
588
589
                self::Database()->update(array(
590
                    'last_seen' => DateTimeObj::get('Y-m-d H:i:s')
591
                    ),
592
                    'tbl_authors',
593
                    " `id` = ?",
594
                    array(self::$Author->get('id'))
595
                );
596
597
                // Only set custom author language in the backend
598
                if (class_exists('Administration', false)) {
599
                    Lang::set(self::$Author->get('language'));
600
                }
601
602
                return true;
603
            }
604
        }
605
606
        return false;
607
    }
608
609
    /**
610
     * Symphony allows Authors to login via the use of tokens instead of
611
     * a username and password. A token is derived from concatenating the
612
     * Author's username and password and applying the sha1 hash to
613
     * it, from this, a portion of the hash is used as the token. This is a useful
614
     * feature often used when setting up other Authors accounts or if an
615
     * Author forgets their password.
616
     *
617
     * @param string $token
618
     *  The Author token, which is a portion of the hashed string concatenation
619
     *  of the Author's username and password
620
     * @throws DatabaseException
621
     * @return boolean
622
     *  True if the Author is logged in, false otherwise
623
     */
624
    public static function loginFromToken($token)
625
    {
626
        $token = self::Database()->cleanValue($token);
627
        $tokenLength = strlen(trim($token));
628
629
        if ($tokenLength === 0) {
630
            return false;
631
        }
632
633
        if ($tokenLength === 6 || $tokenLength === 16) {
634
            $row = self::Database()->fetchRow(0, "
635
                SELECT `a`.`id`, `a`.`username`, `a`.`password`
636
                FROM `tbl_authors` AS `a`, `tbl_forgotpass` AS `f`
637
                WHERE `a`.`id` = `f`.`author_id`
638
                AND `f`.`expiry` > ?
639
                AND `f`.`token` = ?
640
                LIMIT 1",
641
                array(
642
                    DateTimeObj::getGMT('c'),
643
                    $token
644
                )
645
            );
646
647
            self::Database()->delete('tbl_forgotpass', " `token` = ? ", array($token));
648
        } else {
649
            $row = self::Database()->fetchRow(0, sprintf(
650
                "SELECT `id`, `username`, `password`
651
                FROM `tbl_authors`
652
                WHERE SUBSTR(%s(CONCAT(`username`, `password`)), 1, 8) = ?
653
                AND `auth_token_active` = 'yes'
654
                LIMIT 1",
655
                'SHA1'
656
                ),
657
                array($token)
658
            );
659
        }
660
661
        if ($row) {
662
            self::$Author = AuthorManager::fetchByID($row['id']);
663
            self::Session()->set('username', $row['username']);
664
            self::Session()->set('pass', $row['password']);
665
            self::Database()->update(array('last_seen' => DateTimeObj::getGMT('Y-m-d H:i:s')), 'tbl_authors', "`id` = ?", array(
666
                $row['id']
667
            ));
668
669
            return true;
670
        }
671
672
        return false;
673
    }
674
675
    /**
676
     * This function will destroy the currently logged in `$Author`
677
     * session, essentially logging them out.
678
     *
679
     * @see core.Session#expire()
680
     */
681
    public static function logout()
682
    {
683
        self::Session()->expire();
684
    }
685
686
    /**
687
     * This function determines whether an there is a currently logged in
688
     * Author for Symphony by using the `$Session`'s username
689
     * and password. If an Author is found, they will be logged in, otherwise
690
     * the `$Session` will be destroyed.
691
     *
692
     * @see login()
693
     * @return boolean
694
     */
695
    public static function isLoggedIn()
696
    {
697
        // Check to see if Symphony exists, or if we already have an Author instance.
698
        if (is_null(self::$_instance) || self::$Author) {
699
            return true;
700
        }
701
702
        // No author instance found, attempt to log in with the cookied credentials
703
        return self::login(self::Session()->get('username'), self::Session()->get('pass'), true);
704
    }
705
706
    /**
707
     * Returns the most recent version found in the `/install/migrations` folder.
708
     * Returns a version string to be used in `version_compare()` if an updater
709
     * has been found. Returns `FALSE` otherwise.
710
     *
711
     * @since Symphony 2.3.1
712
     * @return string|boolean
713
     */
714
    public static function getMigrationVersion()
715
    {
716
        if (self::isInstallerAvailable()) {
717
            $migrations = scandir(DOCROOT . '/install/migrations');
718
            $migration_file = end($migrations);
719
            $migration_class = 'migration_' . str_replace('.', '', substr($migration_file, 0, -4));
720
            return call_user_func(array($migration_class, 'getVersion'));
721
        }
722
723
        return false;
724
    }
725
726
    /**
727
     * Checks if an update is available and applicable for the current installation.
728
     *
729
     * @since Symphony 2.3.1
730
     * @return boolean
731
     */
732
    public static function isUpgradeAvailable()
733
    {
734
        if (self::isInstallerAvailable()) {
735
            $migration_version = self::getMigrationVersion();
736
            $current_version = Symphony::Configuration()->get('version', 'symphony');
737
738
            return version_compare($current_version, $migration_version, '<');
739
        }
740
741
        return false;
742
    }
743
744
    /**
745
     * Checks if the installer/upgrader is available.
746
     *
747
     * @since Symphony 2.3.1
748
     * @return boolean
749
     */
750
    public static function isInstallerAvailable()
751
    {
752
        return file_exists(DOCROOT . '/install/index.php');
753
    }
754
755
    /**
756
     * A wrapper for throwing a new Symphony Error page.
757
     *
758
     * This methods sets the `GenericExceptionHandler::$enabled` value to `true`.
759
     *
760
     * @see core.SymphonyErrorPage
761
     * @param string|XMLElement $message
762
     *  A description for this error, which can be provided as a string
763
     *  or as an XMLElement.
764
     * @param string $heading
765
     *  A heading for the error page
766
     * @param integer $status
767
     *  Properly sets the HTTP status code for the response. Defaults to
768
     *  `Page::HTTP_STATUS_ERROR`. Use `Page::HTTP_STATUS_XXX` to set this value.
769
     * @param string $template
770
     *  A string for the error page template to use, defaults to 'generic'. This
771
     *  can be the name of any template file in the `TEMPLATES` directory.
772
     *  A template using the naming convention of `tpl.*.php`.
773
     * @param array $additional
774
     *  Allows custom information to be passed to the Symphony Error Page
775
     *  that the template may want to expose, such as custom Headers etc.
776
     * @throws SymphonyErrorPage
777
     */
778
    public static function throwCustomError($message, $heading = 'Symphony Fatal Error', $status = Page::HTTP_STATUS_ERROR, $template = 'generic', array $additional = array())
779
    {
780
        GenericExceptionHandler::$enabled = true;
781
        throw new SymphonyErrorPage($message, $heading, $template, $additional, $status);
782
    }
783
784
    /**
785
     * Setter accepts a previous Exception. Useful for determining the context
786
     * of a current exception (ie. detecting recursion).
787
     *
788
     * @since Symphony 2.3.2
789
     * @param Exception $ex
790
     */
791
    public static function setException(Exception $ex)
792
    {
793
        self::$exception = $ex;
794
    }
795
796
    /**
797
     * Accessor for `self::$exception`.
798
     *
799
     * @since Symphony 2.3.2
800
     * @return Exception|null
801
     */
802
    public static function getException()
803
    {
804
        return self::$exception;
805
    }
806
807
    /**
808
     * Returns the page namespace based on the current URL.
809
     * A few examples:
810
     *
811
     * /login
812
     * /publish
813
     * /blueprints/datasources
814
     * [...]
815
     * /extension/$extension_name/$page_name
816
     *
817
     * This method is especially useful in couple with the translation function.
818
     *
819
     * @see toolkit#__()
820
     * @return string
821
     *  The page namespace, without any action string (e.g. "new", "saved") or
822
     *  any value that depends upon the single setup (e.g. the section handle in
823
     *  /publish/$handle)
824
     */
825
    public static function getPageNamespace()
826
    {
827
        if (self::$namespace !== false) {
828
            return self::$namespace;
829
        }
830
831
        $page = getCurrentPage();
832
833
        if (!is_null($page)) {
834
            $page = trim($page, '/');
835
        }
836
837
        if (substr($page, 0, 7) === 'publish') {
838
            self::$namespace = '/publish';
839
        } elseif (empty($page) && isset($_REQUEST['mode'])) {
840
            self::$namespace = '/login';
841
        } elseif (empty($page)) {
842
            self::$namespace = null;
843
        } else {
844
            $bits = explode('/', $page);
845
846
            if ($bits[0] === 'extension') {
847
                self::$namespace = sprintf('/%s/%s/%s', $bits[0], $bits[1], $bits[2]);
848
            } else {
849
                self::$namespace =  sprintf('/%s/%s', $bits[0], isset($bits[1]) ? $bits[1] : '');
850
            }
851
        }
852
853
        return self::$namespace;
854
    }
855
}
856