Completed
Push — master ( daed8c...cf758d )
by Damian
08:03
created

src/Dev/Install/Installer.php (2 issues)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace SilverStripe\Dev\Install;
4
5
use Exception;
6
use SilverStripe\Control\HTTPRequest;
7
use SilverStripe\Control\Session;
8
use SilverStripe\Core\CoreKernel;
9
use SilverStripe\Control\HTTPApplication;
10
use SilverStripe\Core\Kernel;
11
use SilverStripe\Core\Startup\ParameterConfirmationToken;
12
use SilverStripe\ORM\DatabaseAdmin;
13
use SilverStripe\Security\DefaultAdminService;
14
use SilverStripe\Security\Security;
15
16
/**
17
 * SilverStripe CMS SilverStripe\Dev\Install\Installer
18
 * This installer doesn't use any of the fancy SilverStripe stuff in case it's unsupported.
19
 */
20
class Installer extends InstallRequirements
21
{
22
    public function __construct()
23
    {
24
        // Cache the baseDir value
25
        $this->getBaseDir();
26
    }
27
28
    protected function installHeader()
29
    {
30
        ?>
31
        <html>
32
        <head>
33
            <meta charset="utf-8"/>
34
            <title>Installing SilverStripe...</title>
35
            <link rel="stylesheet" type="text/css"
36
                  href="framework/src/Dev/Install/client/styles/install.css"/>
37
            <script src="//code.jquery.com/jquery-1.7.2.min.js"></script>
38
        </head>
39
        <body>
40
        <div class="install-header">
41
            <div class="inner">
42
                <div class="brand">
43
                    <span class="logo"></span>
44
45
                    <h1>SilverStripe</h1>
46
                </div>
47
            </div>
48
        </div>
49
50
        <div id="Navigation">&nbsp;</div>
51
        <div class="clear"><!-- --></div>
52
53
        <div class="main">
54
            <div class="inner">
55
                <h2>Installing SilverStripe...</h2>
56
57
                <p>I am now running through the installation steps (this should take about 30 seconds)</p>
58
59
                <p>If you receive a fatal error, refresh this page to continue the installation</p>
60
                <ul>
61
        <?php
62
    }
63
64
    public function install($config)
65
    {
66
        // Render header
67
        $this->installHeader();
68
69
        $webserver = $this->findWebserver();
70
        $isIIS = $this->isIIS();
71
        $isApache = $this->isApache();
72
73
        flush();
74
75
        if (isset($config['stats'])) {
76
            if (file_exists(FRAMEWORK_PATH . '/silverstripe_version')) {
77
                $silverstripe_version = file_get_contents(FRAMEWORK_PATH . '/silverstripe_version');
78
            } else {
79
                $silverstripe_version = "unknown";
80
            }
81
82
            $phpVersion = urlencode(phpversion());
83
            $encWebserver = urlencode($webserver);
84
            $dbType = $config['db']['type'];
85
86
            // Try to determine the database version from the helper
87
            $databaseVersion = $config['db']['type'];
88
            $helper = $this->getDatabaseConfigurationHelper($dbType);
89
            if ($helper && method_exists($helper, 'getDatabaseVersion')) {
90
                $versionConfig = $config['db'][$dbType];
91
                $versionConfig['type'] = $dbType;
92
                $databaseVersion = urlencode($dbType . ': ' . $helper->getDatabaseVersion($versionConfig));
93
            }
94
95
            $url = "http://ss2stat.silverstripe.com/Installation/add?SilverStripe=$silverstripe_version&PHP=$phpVersion&Database=$databaseVersion&WebServer=$encWebserver";
96
97
            if (isset($_SESSION['StatsID']) && $_SESSION['StatsID']) {
98
                $url .= '&ID=' . $_SESSION['StatsID'];
99
            }
100
101
            @$_SESSION['StatsID'] = file_get_contents($url);
102
        }
103
104
        if (file_exists('mysite/_config.php')) {
105
            // Truncate the contents of _config instead of deleting it - we can't re-create it because Windows handles permissions slightly
106
            // differently to UNIX based filesystems - it takes the permissions from the parent directory instead of retaining them
107
            $fh = fopen('mysite/_config.php', 'wb');
108
            fclose($fh);
109
        }
110
111
        // Escape user input for safe insertion into PHP file
112
        $theme = isset($_POST['template']) ? addcslashes($_POST['template'], "\'") : 'simple';
113
        $locale = isset($_POST['locale']) ? addcslashes($_POST['locale'], "\'") : 'en_US';
114
        $type = addcslashes($config['db']['type'], "\'");
115
        $dbConfig = $config['db'][$type];
116
        foreach ($dbConfig as &$configValue) {
117
            $configValue = addcslashes($configValue, "\\\'");
118
        }
119
        if (!isset($dbConfig['path'])) {
120
            $dbConfig['path'] = '';
121
        }
122
        if (!$dbConfig) {
123
            echo "<p style=\"color: red\">Bad config submitted</p><pre>";
124
            print_r($config);
125
            echo "</pre>";
126
            die();
127
        }
128
129
        // Write the config file
130
        global $usingEnv;
131
        if ($usingEnv) {
132
            $this->statusMessage("Setting up 'mysite/_config.php' for use with environment variables...");
133
            $this->writeToFile("mysite/_config.php", "<?php\n ");
134
        } else {
135
            $this->statusMessage("Setting up 'mysite/_config.php'...");
136
            // Create databaseConfig
137
            $lines = array(
138
                $lines[] = "    'type' => '$type'"
139
            );
140
            foreach ($dbConfig as $key => $value) {
141
                $lines[] = "    '{$key}' => '$value'";
142
            }
143
            $databaseConfigContent = implode(",\n", $lines);
144
            $this->writeToFile("mysite/_config.php", <<<PHP
145
<?php
146
147
use SilverStripe\\ORM\\DB;
148
149
DB::setConfig([
150
{$databaseConfigContent}
151
]);
152
153
PHP
154
            );
155
        }
156
157
        $this->statusMessage("Setting up 'mysite/_config/config.yml'");
158
        $this->writeToFile("mysite/_config/config.yml", <<<YML
159
---
160
Name: mysite
161
---
162
# YAML configuration for SilverStripe
163
# See http://doc.silverstripe.org/framework/en/topics/configuration
164
# Caution: Indentation through two spaces, not tabs
165
SilverStripe\\View\\SSViewer:
166
  themes:
167
    - '$theme'
168
    - '\$default'
169
SilverStripe\\i18n\\i18n:
170
  default_locale: '$locale'
171
YML
172
        );
173
174
        if (!$this->checkModuleExists('cms')) {
175
            $this->writeToFile("mysite/code/RootURLController.php", <<<PHP
176
<?php
177
178
use SilverStripe\\Control\\Controller;
179
180
class RootURLController extends Controller {
181
182
    public function index() {
183
        echo "<html>Your site is now set up. Start adding controllers to mysite to get started.</html>";
184
    }
185
186
}
187
PHP
188
            );
189
        }
190
191
        // Write the appropriate web server configuration file for rewriting support
192
        if ($this->hasRewritingCapability()) {
193
            if ($isApache) {
194
                $this->statusMessage("Setting up '.htaccess' file...");
195
                $this->createHtaccess();
196
            } elseif ($isIIS) {
197
                $this->statusMessage("Setting up 'web.config' file...");
198
                $this->createWebConfig();
199
            }
200
        }
201
202
        // Mock request
203
        $session = new Session(isset($_SESSION) ? $_SESSION : array());
204
        $request = new HTTPRequest('GET', '/');
205
        $request->setSession($session);
206
207
        // Install kernel (fix to dev)
208
        $kernel = new CoreKernel(BASE_PATH);
209
        $kernel->setEnvironment(Kernel::DEV);
210
        $app = new HTTPApplication($kernel);
211
212
        // Build db within HTTPApplication
213
        $app->execute($request, function (HTTPRequest $request) use ($config) {
214
            // Start session and execute
215
            $request->getSession()->init();
0 ignored issues
show
The call to init() misses a required argument $request.

This check looks for function calls that miss required arguments.

Loading history...
216
217
            // Output status
218
            $this->statusMessage("Building database schema...");
219
220
            // Setup DB
221
            $dbAdmin = new DatabaseAdmin();
222
            $dbAdmin->setRequest($request);
223
            $dbAdmin->pushCurrent();
224
            $dbAdmin->doInit();
225
            $dbAdmin->doBuild(true);
226
227
            // Create default administrator user and group in database
228
            // (not using Security::setDefaultAdmin())
229
            $adminMember = DefaultAdminService::singleton()->findOrCreateDefaultAdmin();
230
            $adminMember->Email = $config['admin']['username'];
231
            $adminMember->Password = $config['admin']['password'];
232
            $adminMember->PasswordEncryption = Security::config()->get('encryption_algorithm');
233
234
            try {
235
                $this->statusMessage('Creating default CMS admin account...');
236
                $adminMember->write();
237
            } catch (Exception $e) {
238
                $this->statusMessage(
239
                    sprintf('Warning: Default CMS admin account could not be created (error: %s)', $e->getMessage())
240
                );
241
            }
242
243
            $request->getSession()->set('username', $config['admin']['username']);
244
            $request->getSession()->set('password', $config['admin']['password']);
245
            $request->getSession()->save();
0 ignored issues
show
The call to save() misses a required argument $request.

This check looks for function calls that miss required arguments.

Loading history...
246
        }, true);
247
248
        // Check result of install
249
        if (!$this->errors) {
250
            if (isset($_SERVER['HTTP_HOST']) && $this->hasRewritingCapability()) {
251
                $this->statusMessage("Checking that friendly URLs work...");
252
                $this->checkRewrite();
253
            } else {
254
                $token = new ParameterConfirmationToken('flush', $request);
255
                $params = http_build_query($token->params());
256
257
                $destinationURL = 'index.php/' .
258
                    ($this->checkModuleExists('cms') ? "home/successfullyinstalled?$params" : "?$params");
259
260
                echo <<<HTML
261
                <li>SilverStripe successfully installed; I am now redirecting you to your SilverStripe site...</li>
262
                <script>
263
                    setTimeout(function() {
264
                        window.location = "$destinationURL";
265
                    }, 2000);
266
                </script>
267
                <noscript>
268
                <li><a href="$destinationURL">Click here to access your site.</a></li>
269
                </noscript>
270
HTML;
271
            }
272
        }
273
274
        return $this->errors;
275
    }
276
277
    public function writeToFile($filename, $content)
278
    {
279
        $base = $this->getBaseDir();
280
        $this->statusMessage("Setting up $base$filename");
281
282
        if ((@$fh = fopen($base . $filename, 'wb')) && fwrite($fh, $content) && fclose($fh)) {
283
            return true;
284
        }
285
        $this->error("Couldn't write to file $base$filename");
286
        return false;
287
    }
288
289
    public function createHtaccess()
290
    {
291
        $start = "### SILVERSTRIPE START ###\n";
292
        $end = "\n### SILVERSTRIPE END ###";
293
294
        $base = dirname($_SERVER['SCRIPT_NAME']);
295
        if (defined('DIRECTORY_SEPARATOR')) {
296
            $base = str_replace(DIRECTORY_SEPARATOR, '/', $base);
297
        } else {
298
            $base = str_replace("\\", '/', $base);
299
        }
300
301
        if ($base != '.') {
302
            $baseClause = "RewriteBase '$base'\n";
303
        } else {
304
            $baseClause = "";
305
        }
306
        if (strpos(strtolower(php_sapi_name()), "cgi") !== false) {
307
            $cgiClause = "RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]\n";
308
        } else {
309
            $cgiClause = "";
310
        }
311
        $rewrite = <<<TEXT
312
# Deny access to templates (but allow from localhost)
313
<Files *.ss>
314
    Order deny,allow
315
    Deny from all
316
    Allow from 127.0.0.1
317
</Files>
318
319
# Deny access to IIS configuration
320
<Files web.config>
321
    Order deny,allow
322
    Deny from all
323
</Files>
324
325
# Deny access to YAML configuration files which might include sensitive information
326
<Files *.yml>
327
    Order allow,deny
328
    Deny from all
329
</Files>
330
331
# Route errors to static pages automatically generated by SilverStripe
332
ErrorDocument 404 /assets/error-404.html
333
ErrorDocument 500 /assets/error-500.html
334
335
<IfModule mod_rewrite.c>
336
337
    # Turn off index.php handling requests to the homepage fixes issue in apache >=2.4
338
    <IfModule mod_dir.c>
339
        DirectoryIndex disabled
340
    </IfModule>
341
342
    SetEnv HTTP_MOD_REWRITE On
343
    RewriteEngine On
344
    $baseClause
345
    $cgiClause
346
347
    # Deny access to potentially sensitive files and folders
348
    RewriteRule ^vendor(/|$) - [F,L,NC]
349
    RewriteRule silverstripe-cache(/|$) - [F,L,NC]
350
    RewriteRule composer\.(json|lock) - [F,L,NC]
351
352
    # Process through SilverStripe if no file with the requested name exists.
353
    # Pass through the original path as a query parameter, and retain the existing parameters.
354
    RewriteCond %{REQUEST_URI} ^(.*)$
355
    RewriteCond %{REQUEST_FILENAME} !-f
356
    RewriteRule .* framework/main.php?url=%1 [QSA]
357
</IfModule>
358
TEXT;
359
360
        if (file_exists('.htaccess')) {
361
            $htaccess = file_get_contents('.htaccess');
362
363
            if (strpos($htaccess, '### SILVERSTRIPE START ###') === false
364
                && strpos($htaccess, '### SILVERSTRIPE END ###') === false
365
            ) {
366
                $htaccess .= "\n### SILVERSTRIPE START ###\n### SILVERSTRIPE END ###\n";
367
            }
368
369
            if (strpos($htaccess, '### SILVERSTRIPE START ###') !== false
370
                && strpos($htaccess, '### SILVERSTRIPE END ###') !== false
371
            ) {
372
                $start = substr($htaccess, 0, strpos($htaccess, '### SILVERSTRIPE START ###'))
373
                    . "### SILVERSTRIPE START ###\n";
374
                $end = "\n" . substr($htaccess, strpos($htaccess, '### SILVERSTRIPE END ###'));
375
            }
376
        }
377
378
        $this->writeToFile('.htaccess', $start . $rewrite . $end);
379
    }
380
381
    /**
382
     * Writes basic configuration to the web.config for IIS
383
     * so that rewriting capability can be use.
384
     */
385
    public function createWebConfig()
386
    {
387
        $content = <<<TEXT
388
<?xml version="1.0" encoding="utf-8"?>
389
<configuration>
390
    <system.webServer>
391
        <security>
392
            <requestFiltering>
393
                <hiddenSegments applyToWebDAV="false">
394
                    <add segment="silverstripe-cache" />
395
                    <add segment="vendor" />
396
                    <add segment="composer.json" />
397
                    <add segment="composer.lock" />
398
                </hiddenSegments>
399
                <fileExtensions allowUnlisted="true" >
400
                    <add fileExtension=".ss" allowed="false"/>
401
                    <add fileExtension=".yml" allowed="false"/>
402
                </fileExtensions>
403
            </requestFiltering>
404
        </security>
405
        <rewrite>
406
            <rules>
407
                <rule name="SilverStripe Clean URLs" stopProcessing="true">
408
                    <match url="^(.*)$" />
409
                    <conditions>
410
                        <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
411
                    </conditions>
412
                    <action type="Rewrite" url="framework/main.php?url={R:1}" appendQueryString="true" />
413
                </rule>
414
            </rules>
415
        </rewrite>
416
    </system.webServer>
417
</configuration>
418
TEXT;
419
420
        $this->writeToFile('web.config', $content);
421
    }
422
423
    public function checkRewrite()
424
    {
425
        $token = new ParameterConfirmationToken('flush', new HTTPRequest('GET', '/'));
426
        $params = http_build_query($token->params());
427
428
        $destinationURL = str_replace('install.php', '', $_SERVER['SCRIPT_NAME']) .
429
            ($this->checkModuleExists('cms') ? "home/successfullyinstalled?$params" : "?$params");
430
431
        echo <<<HTML
432
<li id="ModRewriteResult">Testing...</li>
433
<script>
434
    if (typeof $ == 'undefined') {
435
        document.getElemenyById('ModeRewriteResult').innerHTML = "I can't run jQuery ajax to set rewriting; I will redirect you to the homepage to see if everything is working.";
436
        setTimeout(function() {
437
            window.location = "$destinationURL";
438
        }, 10000);
439
    } else {
440
        $.ajax({
441
            method: 'get',
442
            url: 'InstallerTest/testrewrite',
443
            complete: function(response) {
444
                var r = response.responseText.replace(/[^A-Z]?/g,"");
445
                if (r === "OK") {
446
                    $('#ModRewriteResult').html("Friendly URLs set up successfully; I am now redirecting you to your SilverStripe site...")
447
                    setTimeout(function() {
448
                        window.location = "$destinationURL";
449
                    }, 2000);
450
                } else {
451
                    $('#ModRewriteResult').html("Friendly URLs are not working. This is most likely because a rewrite module isn't configured "
452
                        + "correctly on your site. You may need to get your web host or server administrator to do this for you: "
453
                        + "<ul>"
454
                        + "<li><strong>mod_rewrite</strong> or other rewrite module is enabled on your web server</li>"
455
                        + "<li><strong>AllowOverride All</strong> is set for the directory where SilverStripe is installed</li>"
456
                        + "</ul>");
457
                }
458
            }
459
        });
460
    }
461
</script>
462
<noscript>
463
    <li><a href="$destinationURL">Click here</a> to check friendly URLs are working. If you get a 404 then something is wrong.</li>
464
</noscript>
465
HTML;
466
    }
467
468
    public function var_export_array_nokeys($array)
469
    {
470
        $retval = "array(\n";
471
        foreach ($array as $item) {
472
            $retval .= "\t'";
473
            $retval .= trim($item);
474
            $retval .= "',\n";
475
        }
476
        $retval .= ")";
477
        return $retval;
478
    }
479
480
    /**
481
     * Show an installation status message.
482
     * The output differs depending on whether this is CLI or web based
483
     *
484
     * @param string $msg
485
     */
486
    public function statusMessage($msg)
487
    {
488
        echo "<li>$msg</li>\n";
489
        flush();
490
    }
491
}
492