diff --git a/src/Dev/Install/InstallConfig.php b/src/Dev/Install/InstallConfig.php index 21465392b..d36c7b47d 100644 --- a/src/Dev/Install/InstallConfig.php +++ b/src/Dev/Install/InstallConfig.php @@ -2,6 +2,7 @@ namespace SilverStripe\Dev\Install; +use BadMethodCallException; use SilverStripe\Core\Environment; /** @@ -11,6 +12,8 @@ use SilverStripe\Core\Environment; */ class InstallConfig { + use InstallEnvironmentAware; + /** * List of preferred DB classes in order * @@ -21,6 +24,11 @@ class InstallConfig 'MySQLDatabase', ]; + public function __construct($basePath = null) + { + $this->initBaseDir($basePath); + } + /** * Get database config from the current environment * @@ -119,7 +127,7 @@ class InstallConfig */ protected function getConfigPath() { - return BASE_PATH . '/mysite/_config.php'; + return $this->getBaseDir() . $this->getProjectDir() . DIRECTORY_SEPARATOR . '_config.php'; } /** @@ -127,7 +135,7 @@ class InstallConfig */ protected function getEnvPath() { - return BASE_PATH . '/.env'; + return $this->getBaseDir() . '.env'; } /** diff --git a/src/Dev/Install/InstallEnvironmentAware.php b/src/Dev/Install/InstallEnvironmentAware.php new file mode 100644 index 000000000..2a603419b --- /dev/null +++ b/src/Dev/Install/InstallEnvironmentAware.php @@ -0,0 +1,231 @@ +setBaseDir($basePath); + } elseif (defined('BASE_PATH')) { + $this->setBaseDir(BASE_PATH); + } else { + throw new BadMethodCallException("No BASE_PATH defined"); + } + } + + /** + * @param string $base + * @return $this + */ + protected function setBaseDir($base) + { + $this->baseDir = $base; + return $this; + } + + /** + * Get base path for this installation + * + * @return string + */ + public function getBaseDir() + { + return Path::normalise($this->baseDir) . DIRECTORY_SEPARATOR; + } + + /** + * Get path to public directory + * + * @return string + */ + public function getPublicDir() + { + $base = $this->getBaseDir(); + $public = Path::join($base, 'public') . DIRECTORY_SEPARATOR; + if (file_exists($public)) { + return $public; + } + return $base; + } + + /** + * Check that a module exists + * + * @param string $dirname + * @return bool + */ + public function checkModuleExists($dirname) + { + // Mysite is base-only and doesn't need _config.php to be counted + if (in_array($dirname, ['mysite', 'app'])) { + 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; + } + + /** + * Get project dir name. + * + * @return string 'app', or 'mysite' (deprecated) + */ + protected function getProjectDir() + { + $base = $this->getBaseDir(); + if (is_dir($base . 'mysite')) { + /** @deprecated 4.2..5.0 */ + return 'mysite'; + } + + // Default + return 'app'; + } + + /** + * Get src dir name for project + * + * @return string + */ + protected function getProjectSrcDir() + { + $projectDir = $this->getProjectDir(); + if ($projectDir === 'mysite') { + /** @deprecated 4.2..5.0 */ + return $projectDir . DIRECTORY_SEPARATOR . 'code'; + } + + // Default + return $projectDir . DIRECTORY_SEPARATOR . 'src'; + } + + /** + * Check if the web server is IIS and version greater than the given version. + * + * @param int $fromVersion + * @return bool + */ + public function isIIS($fromVersion = 7) + { + $webserver = $this->findWebserver(); + if (preg_match('#.*IIS/(?[.\\d]+)$#', $webserver, $matches)) { + return version_compare($matches['version'], $fromVersion, '>='); + } + return false; + } + + /** + * @return bool + */ + public function isApache() + { + return strpos($this->findWebserver(), 'Apache') !== false; + } + + /** + * Find the webserver software running on the PHP host. + * + * @return string|false Server software or boolean FALSE + */ + public function findWebserver() + { + // Try finding from SERVER_SIGNATURE or SERVER_SOFTWARE + if (!empty($_SERVER['SERVER_SIGNATURE'])) { + $webserver = $_SERVER['SERVER_SIGNATURE']; + } elseif (!empty($_SERVER['SERVER_SOFTWARE'])) { + $webserver = $_SERVER['SERVER_SOFTWARE']; + } else { + return false; + } + + return strip_tags(trim($webserver)); + } + + public function testApacheRewriteExists($moduleName = 'mod_rewrite') + { + if (function_exists('apache_get_modules') && in_array($moduleName, apache_get_modules())) { + return true; + } + if (isset($_SERVER['HTTP_MOD_REWRITE']) && $_SERVER['HTTP_MOD_REWRITE'] == 'On') { + return true; + } + if (isset($_SERVER['REDIRECT_HTTP_MOD_REWRITE']) && $_SERVER['REDIRECT_HTTP_MOD_REWRITE'] == 'On') { + return true; + } + return false; + } + + public function testIISRewriteModuleExists($moduleName = 'IIS_UrlRewriteModule') + { + if (isset($_SERVER[$moduleName]) && $_SERVER[$moduleName]) { + return true; + } else { + return false; + } + } + + /** + * Determines if the web server has any rewriting capability. + * + * @return bool + */ + public function hasRewritingCapability() + { + return ($this->testApacheRewriteExists() || $this->testIISRewriteModuleExists()); + } + + /** + * Get "nice" database name without "Database" suffix + * + * @param string $databaseClass + * @return string + */ + public function getDatabaseTypeNice($databaseClass) + { + return substr($databaseClass, 0, -8); + } + + /** + * Get an instance of a helper class for the specific database. + * + * @param string $databaseClass e.g. MySQLDatabase or MSSQLDatabase + * @return DatabaseConfigurationHelper + */ + public function getDatabaseConfigurationHelper($databaseClass) + { + return DatabaseAdapterRegistry::getDatabaseConfigurationHelper($databaseClass); + } +} diff --git a/src/Dev/Install/InstallRequirements.php b/src/Dev/Install/InstallRequirements.php index df5817f70..ed2529278 100644 --- a/src/Dev/Install/InstallRequirements.php +++ b/src/Dev/Install/InstallRequirements.php @@ -21,6 +21,8 @@ use SplFileInfo; */ class InstallRequirements { + use InstallEnvironmentAware; + /** * List of errors * @@ -48,46 +50,9 @@ class InstallRequirements */ protected $originalIni = []; - /** - * Base path - * @var - */ - protected $baseDir; - public function __construct($basePath = null) { - if ($basePath) { - $this->baseDir = $basePath; - } elseif (defined('BASE_PATH')) { - $this->baseDir = BASE_PATH; - } else { - throw new BadMethodCallException("No BASE_PATH defined"); - } - } - - /** - * Get base path for this installation - * - * @return string - */ - public function getBaseDir() - { - return Path::normalise($this->baseDir) . DIRECTORY_SEPARATOR; - } - - /** - * Get path to public directory - * - * @return string - */ - public function getPublicDir() - { - $base = $this->getBaseDir(); - $public = Path::join($base, 'public') . DIRECTORY_SEPARATOR; - if (file_exists($public)) { - return $public; - } - return $base; + $this->initBaseDir($basePath); } /** @@ -203,50 +168,11 @@ class InstallRequirements } } - /** - * Check if the web server is IIS and version greater than the given version. - * - * @param int $fromVersion - * @return bool - */ - public function isIIS($fromVersion = 7) - { - $webserver = $this->findWebserver(); - if (preg_match('#.*IIS/(?[.\\d]+)$#', $webserver, $matches)) { - return version_compare($matches['version'], $fromVersion, '>='); - } - return false; - } - - /** - * @return bool - */ - public function isApache() - { - return strpos($this->findWebserver(), 'Apache') !== false; - } - - /** - * Find the webserver software running on the PHP host. - * - * @return string|false Server software or boolean FALSE - */ - public function findWebserver() - { - // Try finding from SERVER_SIGNATURE or SERVER_SOFTWARE - if (!empty($_SERVER['SERVER_SIGNATURE'])) { - $webserver = $_SERVER['SERVER_SIGNATURE']; - } elseif (!empty($_SERVER['SERVER_SOFTWARE'])) { - $webserver = $_SERVER['SERVER_SOFTWARE']; - } else { - return false; - } - - return strip_tags(trim($webserver)); - } - /** * Check everything except the database + * + * @param array $originalIni + * @return array */ public function check($originalIni) { @@ -256,6 +182,10 @@ class InstallRequirements $isIIS = $this->isIIS(); $webserver = $this->findWebserver(); + // Get project dirs to inspect + $projectDir = $this->getProjectDir(); + $projectSrcDir = $this->getProjectSrcDir(); + $this->requirePHPVersion('5.5.0', '5.5.0', array( "PHP Configuration", "PHP5 installed", @@ -271,11 +201,11 @@ class InstallRequirements $this->getBaseDir() )); - $this->requireModule('mysite', array( + $this->requireModule($projectDir, [ "File permissions", - "mysite/ directory exists?", + "{$projectDir}/ directory exists?", '' - )); + ]); $this->requireModule('vendor/silverstripe/framework', array( "File permissions", "vendor/silverstripe/framework/ directory exists?", @@ -283,7 +213,15 @@ class InstallRequirements )); - $this->requireWriteable($this->getPublicDir() . 'index.php', array("File permissions", "Is the index.php file writeable?", null), true); + $this->requireWriteable( + $this->getPublicDir() . 'index.php', + [ + "File permissions", + "Is the index.php file writeable?", + null, + ], + true + ); $this->requireWriteable('.env', ["File permissions", "Is the .env file writeable?", null], false, false); @@ -294,29 +232,37 @@ class InstallRequirements "SilverStripe requires Apache version 2 or greater", $webserver )); - $this->requireWriteable($this->getPublicDir() . '.htaccess', array("File permissions", "Is the .htaccess file writeable?", null), true); + $this->requireWriteable( + $this->getPublicDir() . '.htaccess', + array("File permissions", "Is the .htaccess file writeable?", null), + true + ); } elseif ($isIIS) { - $this->requireWriteable($this->getPublicDir() . 'web.config', array("File permissions", "Is the web.config file writeable?", null), true); + $this->requireWriteable( + $this->getPublicDir() . 'web.config', + array("File permissions", "Is the web.config file writeable?", null), + true + ); } - $this->requireWriteable('mysite/_config.php', array( + $this->requireWriteable("{$projectDir}/_config.php", [ "File permissions", - "Is the mysite/_config.php file writeable?", - null - )); + "Is the {$projectDir}/_config.php file writeable?", + null, + ]); - $this->requireWriteable('mysite/_config/theme.yml', array( + $this->requireWriteable("{$projectDir}/_config/theme.yml", [ "File permissions", - "Is the mysite/_config/theme.yml file writeable?", - null - )); + "Is the {$projectDir}/_config/theme.yml file writeable?", + null, + ]); if (!$this->checkModuleExists('cms')) { - $this->requireWriteable('mysite/code/RootURLController.php', array( + $this->requireWriteable("{$projectSrcDir}/RootURLController.php", [ "File permissions", - "Is the mysite/code/RootURLController.php file writeable?", - null - )); + "Is the {$projectSrcDir}/RootURLController.php file writeable?", + null, + ]); } // Check public folder exists @@ -332,7 +278,11 @@ class InstallRequirements ); // Ensure root assets dir is writable - $this->requireWriteable(ASSETS_PATH, array("File permissions", "Is the assets/ directory writeable?", null), true); + $this->requireWriteable( + ASSETS_PATH, + array("File permissions", "Is the assets/ directory writeable?", null), + true + ); // Ensure all assets files are writable $innerIterator = new RecursiveDirectoryIterator(ASSETS_PATH, RecursiveDirectoryIterator::SKIP_DOTS); @@ -824,35 +774,6 @@ class InstallRequirements return true; } - /** - * Check that a module exists - * - * @param string $dirname - * @return bool - */ - public function checkModuleExists($dirname) - { - // 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; - } - /** * The same as {@link requireFile()} but does additional checks * to ensure the module directory is intact. @@ -867,7 +788,7 @@ class InstallRequirements if (!file_exists($path)) { $testDetails[2] .= " Directory '$path' not found. Please make sure you have uploaded the SilverStripe files to your webserver correctly."; $this->error($testDetails); - } elseif (!file_exists($path . '/_config.php') && $dirname != 'mysite') { + } elseif (!file_exists($path . '/_config.php') && !in_array($dirname, ['mysite', 'app'])) { $testDetails[2] .= " Directory '$path' exists, but is missing files. Please make sure you have uploaded " . "the SilverStripe files to your webserver correctly."; $this->error($testDetails); @@ -985,29 +906,6 @@ class InstallRequirements } } - public function testApacheRewriteExists($moduleName = 'mod_rewrite') - { - if (function_exists('apache_get_modules') && in_array($moduleName, apache_get_modules())) { - return true; - } - if (isset($_SERVER['HTTP_MOD_REWRITE']) && $_SERVER['HTTP_MOD_REWRITE'] == 'On') { - return true; - } - if (isset($_SERVER['REDIRECT_HTTP_MOD_REWRITE']) && $_SERVER['REDIRECT_HTTP_MOD_REWRITE'] == 'On') { - return true; - } - return false; - } - - public function testIISRewriteModuleExists($moduleName = 'IIS_UrlRewriteModule') - { - if (isset($_SERVER[$moduleName]) && $_SERVER[$moduleName]) { - return true; - } else { - return false; - } - } - public function requireApacheRewriteModule($moduleName, $testDetails) { $this->testing($testDetails); @@ -1019,15 +917,6 @@ class InstallRequirements } } - /** - * Determines if the web server has any rewriting capability. - * @return boolean - */ - public function hasRewritingCapability() - { - return ($this->testApacheRewriteExists() || $this->testIISRewriteModuleExists()); - } - public function requireIISRewriteModule($moduleName, $testDetails) { $this->testing($testDetails); @@ -1039,22 +928,6 @@ class InstallRequirements } } - public function getDatabaseTypeNice($databaseClass) - { - return substr($databaseClass, 0, -8); - } - - /** - * Get an instance of a helper class for the specific database. - * - * @param string $databaseClass e.g. MySQLDatabase or MSSQLDatabase - * @return DatabaseConfigurationHelper - */ - public function getDatabaseConfigurationHelper($databaseClass) - { - return DatabaseAdapterRegistry::getDatabaseConfigurationHelper($databaseClass); - } - public function requireDatabaseFunctions($databaseConfig, $testDetails) { $this->testing($testDetails); diff --git a/src/Dev/Install/Installer.php b/src/Dev/Install/Installer.php index 0636fd2c0..22d377c06 100644 --- a/src/Dev/Install/Installer.php +++ b/src/Dev/Install/Installer.php @@ -2,6 +2,7 @@ namespace SilverStripe\Dev\Install; +use BadMethodCallException; use Exception; use SilverStripe\Control\Cookie; use SilverStripe\Control\HTTPApplication; @@ -11,6 +12,7 @@ use SilverStripe\Core\Convert; use SilverStripe\Core\CoreKernel; use SilverStripe\Core\EnvironmentLoader; use SilverStripe\Core\Kernel; +use SilverStripe\Core\Path; use SilverStripe\Core\Startup\ParameterConfirmationToken; use SilverStripe\ORM\DatabaseAdmin; use SilverStripe\Security\DefaultAdminService; @@ -19,13 +21,37 @@ use SilverStripe\Security\Security; /** * This installer doesn't use any of the fancy SilverStripe stuff in case it's unsupported. */ -class Installer extends InstallRequirements +class Installer { + use InstallEnvironmentAware; + + /** + * Errors during install + * + * @var array + */ + protected $errors = []; + /** * value='' attribute placeholder for password fields */ const PASSWORD_PLACEHOLDER = '********'; + public function __construct($basePath = null) + { + $this->initBaseDir($basePath); + } + + /** + * Installer error + * + * @param string $message + */ + protected function error($message = null) + { + $this->errors[] = $message; + } + protected function installHeader() { $clientPath = PUBLIC_DIR @@ -43,8 +69,6 @@ class Installer extends InstallRequirements
- -

SilverStripe

@@ -68,9 +92,10 @@ class Installer extends InstallRequirements { // Render header $this->installHeader(); - $isIIS = $this->isIIS(); $isApache = $this->isApache(); + $projectDir = $this->getProjectDir(); + $projectSrcDir = $this->getProjectSrcDir(); flush(); @@ -80,10 +105,12 @@ class Installer extends InstallRequirements } // Cleanup _config.php - if (file_exists('mysite/_config.php')) { + $basePath = $this->getBaseDir(); + $appConfigPath = $basePath . "{$projectDir}/_config.php"; + if (file_exists($appConfigPath)) { // Truncate the contents of _config instead of deleting it - we can't re-create it because Windows handles permissions slightly // differently to UNIX based filesystems - it takes the permissions from the parent directory instead of retaining them - $fh = fopen('mysite/_config.php', 'wb'); + $fh = fopen($appConfigPath, 'wb'); fclose($fh); } @@ -95,17 +122,18 @@ class Installer extends InstallRequirements // Write other stuff if (!$this->checkModuleExists('cms')) { - $this->writeToFile("mysite/code/RootURLController.php", <<writeToFile($rootURLControllerPath, <<Your site is now set up. Start adding controllers to mysite to get started."; +class RootURLController extends Controller +{ + public function index() + { + echo "Your site is now set up. Start adding controllers to app/src to get started."; } - } PHP ); @@ -124,7 +152,7 @@ PHP $request = HTTPRequestBuilder::createFromEnvironment(); // Install kernel (fix to dev) - $kernel = new CoreKernel(BASE_PATH); + $kernel = new CoreKernel(Path::normalise($basePath)); $kernel->setEnvironment(Kernel::DEV); $app = new HTTPApplication($kernel); @@ -197,6 +225,15 @@ PHP HTML; } + } else { + // Output all errors + $this->statusMessage('Encountered ' . count($this->errors) . ' errors during install:'); + echo "
    "; + foreach ($this->errors as $error) { + $this->statusMessage($error); + } + echo "
"; + $this->statusMessage('Please Click here to return to the installer.'); } return $this->errors; @@ -310,8 +347,9 @@ PHP; */ protected function writeConfigPHP($config) { + $configPath = $this->getProjectDir() . DIRECTORY_SEPARATOR . "_config.php"; if ($config['usingEnv']) { - $this->writeToFile("mysite/_config.php", "writeToFile($configPath, "writeToFile("mysite/_config.php", <<writeToFile($configPath, <<ymlString($config['locale']); + $projectDir = $this->getProjectDir(); // Set either specified, or no theme if ($config['theme'] && $config['theme'] !== 'tutorial') { @@ -364,7 +403,7 @@ YML; } // Write theme.yml - $this->writeToFile("mysite/_config/theme.yml", <<writeToFile("{$projectDir}/_config/theme.yml", <<getBaseDir() . $filename; - $this->statusMessage("Setting up $path"); + // Get absolute / relative paths by either combining or removing base from path + list($absolutePath, $relativePath) = $absolute + ? [ + $filename, + substr($filename, strlen($this->getBaseDir()))] + : [ + $this->getBaseDir() . $filename, + $filename + ]; + $this->statusMessage("Setting up $relativePath"); - if ((@$fh = fopen($path, 'wb')) && fwrite($fh, $content) && fclose($fh)) { + if ((@$fh = fopen($absolutePath, 'wb')) && fwrite($fh, $content) && fclose($fh)) { // Set permissions to writable - @chmod($path, 0775); + @chmod($absolutePath, 0775); return true; } - $this->error("Couldn't write to file $path"); + $this->error("Couldn't write to file $relativePath"); return false; } diff --git a/tests/behat/features/manage-users.feature b/tests/behat/features/manage-users.feature index e23d0a192..d03fd677e 100644 --- a/tests/behat/features/manage-users.feature +++ b/tests/behat/features/manage-users.feature @@ -58,7 +58,7 @@ Feature: Manage users When I click the "Users" CMS tab And I click "staffmember@example.org" in the "#Root_Users" element And I select "ADMIN group" from "Groups" - And I press the "Save" button + And I press the "Apply changes" button Then I should see a "Saved Member" message When I go to "admin/security" diff --git a/tests/behat/features/security-permissions.feature b/tests/behat/features/security-permissions.feature index 9ddbf8735..ba57b25e0 100644 --- a/tests/behat/features/security-permissions.feature +++ b/tests/behat/features/security-permissions.feature @@ -29,7 +29,7 @@ Feature: Manage Security Permissions for Groups Then the "Access to 'Security' section" checkbox should be checked # Save so the driver can reset without having to deal with the popup alert. - Then I press the "Save" button + Then I press the "Apply changes" button Scenario: I can see sub-permissions being properly set and restored when using "Full administrative rights" When I check "Access to 'Security' section" @@ -46,11 +46,11 @@ Feature: Manage Security Permissions for Groups And the "Access to 'Security' section" field should be enabled # Save so the driver can reset without having to deal with the popup alert. - Then I press the "Save" button + Then I press the "Apply changes" button Scenario: I can see sub-permissions being handled properly between reloads when using "Full administrative rights" When I check "Full administrative rights" - And I press the "Save" button + And I press the "Apply changes" button And I click the "Permissions" CMS tab Then the "Full administrative rights" checkbox should be checked And the "Access to 'Security' section" checkbox should be checked @@ -60,7 +60,7 @@ Feature: Manage Security Permissions for Groups Then the "Access to 'Security' section" checkbox should not be checked And the "Access to 'Security' section" field should be enabled - When I press the "Save" button + When I press the "Apply changes" button And I click the "Permissions" CMS tab Then the "Full administrative rights" checkbox should not be checked And the "Access to 'Security' section" checkbox should not be checked @@ -68,7 +68,7 @@ Feature: Manage Security Permissions for Groups Scenario: I can see sub-permissions being handled properly between reloads when using "Access to all CMS sections" When I check "Access to all CMS sections" - And I press the "Save" button + And I press the "Apply changes" button And I click the "Permissions" CMS tab Then the "Access to all CMS sections" checkbox should be checked And the "Access to 'Security' section" checkbox should be checked @@ -78,7 +78,7 @@ Feature: Manage Security Permissions for Groups Then the "Access to 'Security' section" checkbox should not be checked And the "Access to 'Security' section" field should be enabled - When I press the "Save" button + When I press the "Apply changes" button And I click the "Permissions" CMS tab Then the "Access to all CMS sections" checkbox should not be checked And the "Access to 'Security' section" checkbox should not be checked