diff --git a/.upgrade.yml b/.upgrade.yml index a6583d97b..2158c1164 100644 --- a/.upgrade.yml +++ b/.upgrade.yml @@ -951,3 +951,5 @@ skipConfigs: excludedPaths: - '*fixtures*' - '*vendor*' +doctorTasks: + SilverStripe\Dev\Upgrade\UpgradeBootstrap: src/Dev/Upgrade/UpgradeBootstrap.php diff --git a/docs/en/04_Changelogs/4.0.0.md b/docs/en/04_Changelogs/4.0.0.md index e3bebf8ca..43eea3d5e 100644 --- a/docs/en/04_Changelogs/4.0.0.md +++ b/docs/en/04_Changelogs/4.0.0.md @@ -78,7 +78,7 @@ some automated processes that users can run to This section describes the processes which every project upgrading to 4.0 should follow. This should be followed as a standard first point of upgrade. -#### Upgrade your rewrite rules +#### Upgrade project index.php and .htaccess rewrites The location of SilverStripe's "entry file" has changed. Your project and server environment will need to adjust the path to this file from `framework/main.php` to `index.php`. @@ -87,8 +87,19 @@ please consult the [installation guides](getting_started/installation/). Since 4.0, URL rewrite capabilities are required, unless you PHP's built-in webserver through [silverstripe/serve](https://github.com/silverstripe/silverstripe-serve). -The `index.php` file should already exist in your project, -but needs its content replaced: +The [upgrader tool](https://github.com/silverstripe/silverstripe-upgrader/) has a task runner which +automates this process for you. + +``` +composer global require silverstripe/upgrader +cd ~/Project/Root +~/.composer/vendor/bin/upgrade-code doctor +``` + +This will ensure that your `.htaccess` and `index.php` are set to a reasonable default value +for a clean installation. + +After upgrade the `index.php` should look something similar to the below: ```php baseDir = $basePath; + } elseif (defined('BASE_PATH')) { + $this->baseDir = BASE_PATH; + } else { + throw new BadMethodCallException("No BASE_PATH defined"); + } + } + + public function getBaseDir() + { + return rtrim($this->baseDir, '/\\') . '/'; + } + /** * Check the database configuration. These are done one after another * starting with checking the database function exists in PHP, and @@ -217,6 +257,8 @@ class InstallRequirements '', )); + $this->requireWriteable('index.php', array("File permissions", "Is the index.php file writeable?", null)); + if ($isApache) { $this->checkApacheVersion(array( "Webserver Configuration", @@ -251,7 +293,7 @@ class InstallRequirements $this->requireWriteable('assets', array("File permissions", "Is the assets/ directory writeable?", null)); try { - $tempFolder = TempFolder::getTempFolder(BASE_PATH); + $tempFolder = TempFolder::getTempFolder($this->getBaseDir()); } catch (Exception $e) { $tempFolder = false; } @@ -732,8 +774,25 @@ class InstallRequirements */ public function checkModuleExists($dirname) { - $path = $this->getBaseDir() . $dirname; - return file_exists($path) && ($dirname == 'mysite' || file_exists($path . '/_config.php')); + // Mysite is base-only and doesn't need _config.php to be counted + if ($dirname === 'mysite') { + return file_exists($this->getBaseDir() . $dirname); + } + + $paths = [ + "vendor/silverstripe/{$dirname}/", + "{$dirname}/", + ]; + foreach ($paths as $path) { + $checks = ['_config', '_config.php']; + foreach ($checks as $check) { + if (file_exists($this->getBaseDir() . $path . $check)) { + return true; + } + } + } + + return false; } /** @@ -828,7 +887,7 @@ class InstallRequirements $this->testing($testDetails); try { - $tempFolder = TempFolder::getTempFolder(BASE_PATH); + $tempFolder = TempFolder::getTempFolder($this->getBaseDir()); } catch (Exception $e) { $tempFolder = false; } @@ -1070,14 +1129,6 @@ class InstallRequirements } } - // Must be PHP4 compatible - var $baseDir; - - public function getBaseDir() - { - return BASE_PATH . '/'; - } - public function testing($testDetails) { if (!$testDetails) { diff --git a/src/Dev/Install/Installer.php b/src/Dev/Install/Installer.php index 4811bbb31..21b4fa6d6 100644 --- a/src/Dev/Install/Installer.php +++ b/src/Dev/Install/Installer.php @@ -20,12 +20,6 @@ use SilverStripe\Security\Security; */ class Installer extends InstallRequirements { - public function __construct() - { - // Cache the baseDir value - $this->getBaseDir(); - } - protected function installHeader() { ?> @@ -86,6 +80,7 @@ class Installer extends InstallRequirements } // Write all files + $this->writeIndexPHP(); $this->writeConfigPHP($config); $this->writeConfigYaml($config); $this->writeConfigEnv($config); @@ -199,6 +194,31 @@ HTML; return $this->errors; } + protected function writeIndexPHP() + { + $content = <<<'PHP' +addMiddleware(new ErrorControlChainMiddleware($app)); +$response = $app->handle($request); +$response->output(); +PHP; + $this->writeToFile('index.php', $content); + } + /** * Write all .env files * diff --git a/src/Dev/Upgrade/UpgradeBootstrap.php b/src/Dev/Upgrade/UpgradeBootstrap.php new file mode 100644 index 000000000..3d4adce58 --- /dev/null +++ b/src/Dev/Upgrade/UpgradeBootstrap.php @@ -0,0 +1,92 @@ + true, + 'index.php' => true, + 'install.php' => false, + ]; + + /** + * @param InputInterface $input + * @param OutputInterface $output + * @param $basePath + */ + public function __invoke(InputInterface $input, OutputInterface $output, $basePath) + { + // Fail if destination isn't writable + $this->ensureWritable($basePath); + + // Check source + $source = $basePath . '/vendor/silverstripe/recipe-core'; + if (!is_dir($source)) { + throw new BadMethodCallException("silverstripe/recipe-core is not installed."); + } + + // Copy scaffolded files from recipe-core + $output->writeln("Upgrading project bootstrapping files:"); + foreach ($this->files as $file => $canCreate) { + $fileSource = $source . '/' . $file; + $fileDest = $basePath . '/' . $file; + + // Skip if we should only upgrade existing files + if (!$canCreate && !file_exists($fileDest)) { + continue; + } + $output->writeln(" - Upgrading {$file}"); + $this->copyFile( + $fileSource, + $fileDest + ); + } + } + + /** + * Ensure path is writable + * + * @param string $path + */ + protected function ensureWritable($path) + { + if (!is_writable($path)) { + throw new BadMethodCallException("Path $path is not writable"); + } + } + + /** + * Copy file + * + * @param string $source + * @param string $dest + */ + protected function copyFile($source, $dest) + { + // Ensure existing file can be overwritten + if (file_exists($dest)) { + $this->ensureWritable($dest); + } + file_put_contents($dest, file_get_contents($source)); + } +}