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
Pull Request — integration (#2604)
by Brendan
05:28
created

Installer::checkConfiguration()   F

Complexity

Conditions 17
Paths 3456

Size

Total Lines 131
Code Lines 74

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 17
eloc 74
c 2
b 1
f 0
nc 3456
nop 1
dl 0
loc 131
rs 2

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * @package install
5
 */
6
namespace SymphonyCms\Installer\Lib;
7
8
use Administration;
9
use DatabaseException;
10
use DateTimeObj;
11
use Exception;
12
use General;
13
use GenericErrorHandler;
14
use GenericExceptionHandler;
15
use Lang;
16
use Profiler;
17
use Symphony;
18
use SymphonyCms\Installer\Steps;
19
20
class Installer extends Administration
21
{
22
    protected static $requirements;
23
24
    /**
25
     * Override the default Symphony constructor to initialise the Log, Config
26
     * and Database objects for installation/update. This allows us to use the
27
     * normal accessors.
28
     */
29
    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...
30
    {
31
        self::$Profiler = Profiler::instance();
32
        self::$Profiler->sample('Engine Initialisation');
33
34
        if (get_magic_quotes_gpc()) {
35
            General::cleanArray($_SERVER);
36
            General::cleanArray($_COOKIE);
37
            General::cleanArray($_GET);
38
            General::cleanArray($_POST);
39
        }
40
41
        // Include the default Config for installation.
42
        static::initialiseConfiguration(require_once(INSTALL . '/includes/config_default.php'));
43
44
        // Initialize date/time
45
        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...
46
        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...
47
        define_safe(
48
            '__SYM_DATETIME_FORMAT__',
49
            __SYM_DATE_FORMAT__ . self::Configuration()->get('datetime_separator', 'region') . __SYM_TIME_FORMAT__
50
        );
51
        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...
52
53
        // Initialize Language, Logs and Database
54
        static::initialiseLang();
55
        static::initialiseLog(INSTALL_LOGS . '/install');
56
        static::initialiseDatabase();
57
58
        // Initialize error handlers
59
        GenericExceptionHandler::initialise(Symphony::Log());
60
        GenericErrorHandler::initialise(Symphony::Log());
61
62
        self::$requirements = new Requirements();
63
    }
64
65
    /**
66
     * Initialises the language by looking at the `lang` key, passed via GET or POST
67
     */
68
    public static function initialiseLang()
69
    {
70
        $lang = !empty($_REQUEST['lang']) ? preg_replace('/[^a-zA-Z\-]/', null, $_REQUEST['lang']) : 'en';
71
        Lang::initialize();
72
        Lang::set($lang, false);
73
    }
74
75
    /**
76
     * Overrides the default `initialiseLog()` method and writes logs to  `manifest/logs/install`
77
     *
78
     * @param null $filename
79
     * @return boolean|void
80
     * @throws Exception
81
     */
82 View Code Duplication
    public static function initialiseLog($filename = null)
83
    {
84
        if (is_dir(INSTALL_LOGS) || General::realiseDirectory(
85
            INSTALL_LOGS,
86
            self::Configuration()->get('write_mode', 'directory')
0 ignored issues
show
Documentation introduced by
self::Configuration()->g...ite_mode', 'directory') is of type array|string, but the function expects a integer.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
87
        )
88
        ) {
89
            return parent::initialiseLog($filename);
90
        }
91
92
        return;
93
    }
94
95
    /**
96
     * Overrides the default `initialiseDatabase()` method
97
     * This allows us to still use the normal accessor
98
     */
99
    public static function initialiseDatabase()
100
    {
101
        self::setDatabase();
102
    }
103
104
    /**
105
     * This function returns an instance of the Installer class. It is the only way
106
     * to create a new Installer, as it implements the Singleton interface
107
     *
108
     * @return Installer
109
     */
110
    public static function instance()
111
    {
112
        if (!(self::$_instance instanceof Installer)) {
113
            self::$_instance = new Installer;
114
        }
115
116
        return self::$_instance;
117
    }
118
119
    public function run()
120
    {
121
        // Make sure a log file is available
122
        if (is_null(Symphony::Log())) {
123
            self::__render(new InstallerPage('missing-log'));
124
        }
125
126
        // Check essential server requirements
127
        $errors = self::__checkRequirements();
128 View Code Duplication
        if (!empty($errors)) {
129
            Symphony::Log()->error('Installer - Missing requirements.');
130
131
            foreach ($errors as $err) {
132
                Symphony::Log()->error(
133
                    sprintf('Requirement - %s', $err['msg'])
134
                );
135
            }
136
137
            self::__render(new InstallerPage('requirements', array(
138
                'errors' => $errors
139
            )));
140
        }
141
142
        // If language is not set and there is language packs available, show language selection pages
143
        if (!isset($_POST['lang']) && count(Lang::getAvailableLanguages(false)) > 1) {
144
            self::__render(new InstallerPage('languages'));
145
        }
146
147
        // Check for configuration errors and, if there are no errors, install Symphony!
148
        if (isset($_POST['fields'])) {
149
            $fields = $_POST['fields'];
150
            $errors = self::checkConfiguration($fields);
151 View Code Duplication
            if (!empty($errors)) {
152
                Symphony::Log()->error('Installer - Wrong configuration.');
153
154
                foreach ($errors as $err) {
155
                    Symphony::Log()->error(sprintf('Configuration - %s', $err['msg']));
156
                }
157
            } else {
158
                $disabled_extensions = self::install($fields);
159
160
                self::__render(new InstallerPage('success', array(
161
                    'disabled-extensions' => $disabled_extensions
162
                )));
163
            }
164
        }
165
166
        // Display the Installation page
167
        self::__render(new InstallerPage('configuration', array(
168
            'errors' => $errors,
169
            'default-config' => Symphony::Configuration()->get()
170
        )));
171
    }
172
173
    /**
174
     * @param InstallerPage $page
175
     */
176
    protected static function __render(InstallerPage $page)
177
    {
178
        header('Content-Type: text/html; charset=utf-8');
179
        echo $page->generate();
180
        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...
181
    }
182
183
    /**
184
     * This function checks the server can support a Symphony installation.
185
     * If any of these requirements fail the installation will not proceed.
186
     *
187
     * @return array
188
     *  An associative array of errors, with `msg` and `details` keys
189
     */
190
    private static function __checkRequirements()
191
    {
192
        $errors = array();
193
194
        // Make sure the install.sql file exists
195
        if (!file_exists(INSTALL . '/includes/install.sql') || !is_readable(INSTALL . '/includes/install.sql')) {
196
            $errors[] = array(
197
                'msg' => __('Missing install.sql file'),
198
                'details' => __(
199
                    'It appears that %s is either missing or not readable. This is required to populate the database and must be uploaded before installation can commence. Ensure that PHP has read permissions.',
200
                    array('<code>install.sql</code>')
201
                )
202
            );
203
        }
204
205
        $errors = array_merge($errors, self::$requirements->check());
206
207
        return $errors;
208
    }
209
210
    /**
211
     * This function checks the current Configuration (which is the values entered
212
     * by the user on the installation form) to ensure that `/symphony` and `/workspace`
213
     * folders exist and are writable and that the Database credentials are correct.
214
     * Once those initial checks pass, the rest of the form values are validated.
215
     *
216
     * @param array $fields
217
     * @return array An associative array of errors if something went wrong, otherwise an empty array.
218
     */
219
    public static function checkConfiguration(array $fields)
220
    {
221
        $errors = array();
222
223
        // Testing the database connection
224
        try {
225
            Symphony::Database()->connect(
226
                $fields['database']['host'],
227
                $fields['database']['user'],
228
                $fields['database']['password'],
229
                $fields['database']['port'],
230
                $fields['database']['db']
231
            );
232
        } catch (DatabaseException $e) {
233
            // Invalid credentials
234
            // @link http://dev.mysql.com/doc/refman/5.5/en/error-messages-server.html
235
            if ($e->getDatabaseErrorCode() === 1044 || $e->getDatabaseErrorCode() === 1045) {
236
                $errors['database-invalid-credentials'] = array(
237
                    'msg' => 'Database credentials were denied',
238
                    'details' => __('Symphony was unable to access the database with these credentials.')
239
                );
240
            } // Connection related
241
            else {
242
                $errors['no-database-connection'] = array(
243
                    'msg' => 'Could not establish database connection.',
244
                    'details' => __('Symphony was unable to establish a valid database connection. You may need to modify host or port settings.')
245
                );
246
            }
247
        }
248
249
        try {
250
            // Check the database table prefix is legal. #1815
251
            if (!preg_match('/^[0-9a-zA-Z\$_]*$/', $fields['database']['tbl_prefix'])) {
252
                $errors['database-table-prefix'] = array(
253
                    'msg' => 'Invalid database table prefix: ‘' . $fields['database']['tbl_prefix'] . '’',
254
                    'details' => __(
255
                        'The table prefix %s is invalid. The table prefix must only contain numbers, letters or underscore characters.',
256
                        array('<code>' . $fields['database']['tbl_prefix'] . '</code>')
257
                    )
258
                );
259
            } // Check the database credentials
260
            elseif (Symphony::Database()->isConnected()) {
261
                // Incorrect MySQL version
262
                $version = Symphony::Database()->fetchVar('version', 0, "SELECT VERSION() AS `version`;");
263
                if (version_compare($version, '5.5', '<')) {
264
                    $errors['database-incorrect-version'] = array(
265
                        'msg' => 'MySQL Version is not correct. ' . $version . ' detected.',
266
                        'details' => __(
267
                            'Symphony requires %1$s or greater to work, however version %2$s was detected. This requirement must be met before installation can proceed.',
268
                            array('<code>MySQL 5.5</code>', '<code>' . $version . '</code>')
269
                        )
270
                    );
271
                } else {
272
                    // Existing table prefix
273
                    if (Symphony::Database()->tableExists($fields['database']['tbl_prefix'] . '%')) {
274
                        $errors['database-table-prefix'] = array(
275
                            'msg' => 'Database table prefix clash with ‘' . $fields['database']['db'] . '’',
276
                            'details' => __(
277
                                'The table prefix %s is already in use. Please choose a different prefix to use with Symphony.',
278
                                array(
279
280
                                    '<code>' . $fields['database']['tbl_prefix'] . '</code>'
281
                                )
282
                            )
283
                        );
284
                    }
285
                }
286
            }
287
        } catch (DatabaseException $e) {
288
            $errors['unknown-database'] = array(
289
                'msg' => 'Database ‘' . $fields['database']['db'] . '’ not found.',
290
                'details' => __('Symphony was unable to connect to the specified database.')
291
            );
292
        }
293
294
        // Website name not entered
295
        if (trim($fields['general']['sitename']) === '') {
296
            $errors['general-no-sitename'] = array(
297
                'msg' => 'No sitename entered.',
298
                'details' => __('You must enter a Site name. This will be shown at the top of your backend.')
299
            );
300
        }
301
302
        // Username Not Entered
303
        if (trim($fields['user']['username']) === '') {
304
            $errors['user-no-username'] = array(
305
                'msg' => 'No username entered.',
306
                'details' => __('You must enter a Username. This will be your Symphony login information.')
307
            );
308
        }
309
310
        // Password Not Entered
311
        if (trim($fields['user']['password']) === '') {
312
            $errors['user-no-password'] = array(
313
                'msg' => 'No password entered.',
314
                'details' => __('You must enter a Password. This will be your Symphony login information.')
315
            );
316
        } // Password mismatch
317
        elseif ($fields['user']['password'] !== $fields['user']['confirm-password']) {
318
            $errors['user-password-mismatch'] = array(
319
                'msg' => 'Passwords did not match.',
320
                'details' => __('The password and confirmation did not match. Please retype your password.')
321
            );
322
        }
323
324
        // No Name entered
325
        if (trim($fields['user']['firstname']) === '' || trim($fields['user']['lastname']) === '') {
326
            $errors['user-no-name'] = array(
327
                'msg' => 'Did not enter First and Last names.',
328
                'details' => __('You must enter your name.')
329
            );
330
        }
331
332
        // Invalid Email
333
        if (!preg_match('/^\w(?:\.?[\w%+-]+)*@\w(?:[\w-]*\.)+?[a-z]{2,}$/i', $fields['user']['email'])) {
334
            $errors['user-invalid-email'] = array(
335
                'msg' => 'Invalid email address supplied.',
336
                'details' => __('This is not a valid email address. You must provide an email address since you will need it if you forget your password.')
337
            );
338
        }
339
340
        // Admin path not entered
341
        if (trim($fields['symphony']['admin-path']) === '') {
342
            $errors['no-symphony-path'] = array(
343
                'msg' => 'No Symphony path entered.',
344
                'details' => __('You must enter a path for accessing Symphony, or leave the default. This will be used to access Symphony\'s backend.')
345
            );
346
        }
347
348
        return $errors;
349
    }
350
351
    /**
352
     * @param array $data
353
     * @return array
354
     */
355
    public static function install(array $data)
356
    {
357
        $start = microtime(true);
358
        Symphony::Log()->info('INSTALLATION PROCESS STARTED (' . DateTimeObj::get('c') . ')');
359
360
        // Configuration: Populating array
361
        $conf = Symphony::Configuration()->get();
362
363
        foreach ($conf as $group => $settings) {
0 ignored issues
show
Bug introduced by
The expression $conf of type array|string is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

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

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

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

Loading history...
364
            foreach ($settings as $key => $value) {
365
                // This ensures on data the configuration cares about is populated,
366
                // anything else will be ignored and accessible in `$data`.
367
                if (isset($data[$group]) && isset($data[$group][$key])) {
368
                    $conf[$group][$key] = $data[$group][$key];
369
                }
370
            }
371
        }
372
373
        Symphony::Configuration()->setArray($conf);
0 ignored issues
show
Bug introduced by
It seems like $conf defined by \Symphony::Configuration()->get() on line 361 can also be of type string; however, Configuration::setArray() does only seem to accept array, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
374
375
        $steps = [
376
            // Create database
377
            Steps\CreateDatabase::class,
378
            // Create manifest folder structure
379
            Steps\CreateManifest::class,
380
            // Write .htaccess
381
            Steps\CreateHtaccess::class,
382
            // Create or import the workspace
383
            Steps\Workspace::class,
384
            // Enable extensions
385
            Steps\EnableExtensions::class,
386
            // Enable language
387
            Steps\EnableLanguage::class
388
        ];
389
390
        try {
391
            foreach ($steps as $step) {
392
                $installStep = new $step(Symphony::Log()->getLog());
393
                $installStep->setOverride(false);
394
395 View Code Duplication
                if (false === $installStep->handle(Symphony::Configuration(), $data)) {
396
                    throw new Exception(sprintf('Aborting installation, %s failed', $step));
397
                }
398
            }
399
        } catch (Exception $ex) {
400
            self::__abort($ex->getMessage(), $start);
401
        }
402
403
        // Writing configuration file
404
        Symphony::Log()->info('WRITING: Configuration File');
405
        if (!Symphony::Configuration()->write(CONFIG, Symphony::Configuration()->get('write_mode', 'file'))) {
0 ignored issues
show
Documentation introduced by
\Symphony::Configuration...t('write_mode', 'file') is of type array|string, but the function expects a integer|null.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
406
            self::__abort(
407
                'Could not create config file ‘' . CONFIG . '’. Check permission on /manifest.',
408
                $start
409
            );
410
        }
411
412
        // Installation completed. Woo-hoo!
413
        Symphony::Log()->info(sprintf(
414
            'INSTALLATION COMPLETED: Execution Time - %d sec (%s)',
415
            max(1, time() - $start),
416
            date('d.m.y H:i:s')
417
        ));
418
419
        return [];
420
    }
421
422
    /**
423
     * If something went wrong, the `__abort` function will write an entry to the Log
424
     * file and display the failure page to the user.
425
     *
426
     * @todo: Resume installation after an error has been fixed.
427
     * @param string $message
428
     * @param integer $start
429
     */
430
    protected static function __abort($message, $start)
431
    {
432
        Symphony::Log()->error($message);
433
        Symphony::Log()->error(sprintf(
434
            'INSTALLATION ABORTED: Execution Time - %f sec (%s)',
435
            microtime(true) - $start,
436
            date('d.m.y H:i:s')
437
        ));
438
439
        self::__render(new InstallerPage('failure'));
440
    }
441
}
442