From 12bd31f9366327650b5c0c0f96cd0327d44faf0a Mon Sep 17 00:00:00 2001 From: Damian Mooyman Date: Tue, 20 Jun 2017 15:34:34 +1200 Subject: [PATCH] API Remove OutputMiddleware API Move environment / global / ini management into Environment class API Move getTempFolder into TempFolder class API Implement HTTPRequestBuilder / CLIRequestBuilder BUG Restore SS_ALLOWED_HOSTS check in original location API CoreKernel now requires $basePath to be passed in API Refactor installer.php to use application to bootstrap API move memstring conversion globals to Convert BUG Fix error in CoreKernel nesting not un-nesting itself properly. --- cli-script.php | 18 +- docs/en/04_Changelogs/4.0.0.md | 13 + main.php | 11 +- src/Control/CLIRequestBuilder.php | 69 + src/Control/Director.php | 40 +- src/Control/HTTPRequest.php | 174 --- src/Control/HTTPRequestBuilder.php | 146 ++ src/Core/AppKernel.php | 42 +- src/Core/Application.php | 13 + src/Core/Convert.php | 43 + src/Core/CoreKernel.php | 5 + src/Core/Environment.php | 152 ++ src/Core/Startup/OutputMiddleware.php | 44 - src/Core/TempFolder.php | 117 ++ src/Core/TestKernel.php | 4 +- src/Dev/Install/InstallRequirements.php | 1113 ++++++++++++++ src/Dev/Install/Installer.php | 501 +++++++ src/Dev/Install/install.php | 2 +- src/Dev/Install/install.php5 | 1767 ----------------------- src/Dev/Install/install5.php | 265 ++++ src/Dev/SapphireTest.php | 2 +- src/Dev/State/GlobalsTestState.php | 9 +- src/ORM/DatabaseAdmin.php | 2 +- src/includes/cli.php | 64 - src/includes/constants.php | 3 +- src/includes/functions.php | 255 ---- tests/php/Core/MemoryLimitTest.php | 108 +- 27 files changed, 2549 insertions(+), 2433 deletions(-) create mode 100644 src/Control/CLIRequestBuilder.php create mode 100644 src/Control/HTTPRequestBuilder.php create mode 100644 src/Core/Environment.php delete mode 100644 src/Core/Startup/OutputMiddleware.php create mode 100644 src/Core/TempFolder.php create mode 100644 src/Dev/Install/InstallRequirements.php create mode 100644 src/Dev/Install/Installer.php delete mode 100755 src/Dev/Install/install.php5 create mode 100755 src/Dev/Install/install5.php delete mode 100644 src/includes/cli.php diff --git a/cli-script.php b/cli-script.php index 867d8777f..e00fe27a1 100755 --- a/cli-script.php +++ b/cli-script.php @@ -1,19 +1,23 @@ addMiddleware(new OutputMiddleware()); -$app->handle($request); +$response = $app->handle($request); +$response->output(); diff --git a/docs/en/04_Changelogs/4.0.0.md b/docs/en/04_Changelogs/4.0.0.md index be06a4d67..54d3a80f4 100644 --- a/docs/en/04_Changelogs/4.0.0.md +++ b/docs/en/04_Changelogs/4.0.0.md @@ -1248,6 +1248,7 @@ After (`mysite/_config/config.yml`): #### General and Core API Additions / Changes * Minimum PHP version raised to 5.6 (with support for PHP 7.x) +* Dropped support for PHP safe mode (removed php 5.4). * Once PHP versions become [unsupported by the PHP Project](http://php.net/supported-versions.php)), we drop support for those versions in the [next minor release](/contributing/release-process This means PHP 5.6 and PHP 7.0 support will become unsupported in Dec 2018. @@ -1339,6 +1340,18 @@ After (`mysite/_config/config.yml`): #### General and Core Removed API +* Many global methods have been refactored into `Environment` or `Convert` class. + * `increase_xdebug_nesting_level_to` removed (functionality has been inlined into `AppKernel`) + * `set_increase_time_limit_max` moved to `Environment::setTimeLimitMax()` + * `get_increase_time_limit_max` moved to `Environment::getTimeLimitMax()` + * `set_increase_memory_limit_max` moved to `Environment::setMemoryLimitMax()` + * `get_increase_memory_limit_max` moved to `Environment::getMemoryLimitMax()` + * `increase_time_limit_to` moved to `Environment::increaseTimeLimitTo()` + * `increase_memory_limit_to` moved to `Environment::increaseMemoryLimitTo()` + * `translate_memstring` moved to `Convert::memstring2bytes`. + * `getTempFolder` moved to `TempFolder::getTempFolder()` + * `getTempParentFolder` removed. + * `getTempFolderUsername` removed. * `CMSMain::buildbrokenlinks()` action is removed. * `Injector::unregisterAllObjects()` has been removed. Use `unregisterObjects` to unregister groups of objects limited by type instead. diff --git a/main.php b/main.php index 9d14f8981..695e1202a 100644 --- a/main.php +++ b/main.php @@ -1,19 +1,18 @@ addMiddleware(new OutputMiddleware()); $app->addMiddleware(new ErrorControlChainMiddleware($app)); -$app->handle($request); +$response = $app->handle($request); +$response->output(); diff --git a/src/Control/CLIRequestBuilder.php b/src/Control/CLIRequestBuilder.php new file mode 100644 index 000000000..bdca68282 --- /dev/null +++ b/src/Control/CLIRequestBuilder.php @@ -0,0 +1,69 @@ + 'HTTP/1.1', + 'HTTP_ACCEPT' => 'text/plain;q=0.5', + 'HTTP_ACCEPT_LANGUAGE' => '*;q=0.5', + 'HTTP_ACCEPT_ENCODING' => '', + 'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1;q=0.5', + 'SERVER_SIGNATURE' => 'Command-line PHP/' . phpversion(), + 'SERVER_SOFTWARE' => 'PHP/' . phpversion(), + 'SERVER_ADDR' => '127.0.0.1', + 'REMOTE_ADDR' => '127.0.0.1', + 'REQUEST_METHOD' => 'GET', + 'HTTP_USER_AGENT' => 'CLI', + ), $variables['_SERVER']); + + /** + * Process arguments and load them into the $_GET and $_REQUEST arrays + * For example, + * sake my/url somearg otherarg key=val --otherkey=val third=val&fourth=val + * + * Will result in the following get data: + * args => array('somearg', 'otherarg'), + * key => val + * otherkey => val + * third => val + * fourth => val + */ + if (isset($variables['_SERVER']['argv'][2])) { + $args = array_slice($variables['_SERVER']['argv'], 2); + foreach ($args as $arg) { + if (strpos($arg, '=') == false) { + $variables['_GET']['args'][] = $arg; + } else { + $newItems = array(); + parse_str((substr($arg, 0, 2) == '--') ? substr($arg, 2) : $arg, $newItems); + $variables['_GET'] = array_merge($variables['_GET'], $newItems); + } + } + $_REQUEST = array_merge($_REQUEST, $variables['_GET']); + } + + // Set 'url' GET parameter + if (isset($variables['_SERVER']['argv'][1])) { + $variables['_GET']['url'] = $variables['_SERVER']['argv'][1]; + $variables['_SERVER']['REQUEST_URI'] = $variables['_SERVER']['argv'][1]; + } + + // Parse rest of variables as standard + return parent::cleanEnvironment($variables); + } +} diff --git a/src/Control/Director.php b/src/Control/Director.php index d0768d18d..a4043db6f 100644 --- a/src/Control/Director.php +++ b/src/Control/Director.php @@ -4,6 +4,7 @@ namespace SilverStripe\Control; use SilverStripe\CMS\Model\SiteTree; use SilverStripe\Core\Config\Configurable; +use SilverStripe\Core\Environment; use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Kernel; use SilverStripe\Dev\Deprecation; @@ -122,6 +123,14 @@ class Director implements TemplateGlobalProvider */ public static function direct(HTTPRequest $request) { + // check allowed hosts + if (getenv('SS_ALLOWED_HOSTS') && !static::is_cli()) { + $allowedHosts = explode(',', getenv('SS_ALLOWED_HOSTS')); + if (!in_array(static::host(), $allowedHosts)) { + return new HTTPResponse('Invalid Host', 400); + } + } + // Pre-request $output = RequestProcessor::singleton()->preRequest($request); if ($output === false) { @@ -231,9 +240,9 @@ class Director implements TemplateGlobalProvider }; // backup existing vars, and create new vars - $existingVars = static::envToVars(); + $existingVars = Environment::getVariables(); $finally[] = function () use ($existingVars) { - static::varsToEnv($existingVars); + Environment::setVariables($existingVars); }; $newVars = $existingVars; @@ -307,7 +316,7 @@ class Director implements TemplateGlobalProvider $newVars['_REQUEST'] = array_merge($newVars['_GET'], $newVars['_POST']); // Create new request - $request = HTTPRequest::createFromVariables($newVars, $body); + $request = HTTPRequestBuilder::createFromVariables($newVars, $body); if ($headers) { foreach ($headers as $k => $v) { $request->addHeader($k, $v); @@ -315,7 +324,7 @@ class Director implements TemplateGlobalProvider } // Apply new vars to environment - static::varsToEnv($newVars); + Environment::setVariables($newVars); try { // Normal request handling @@ -385,29 +394,6 @@ class Director implements TemplateGlobalProvider return new HTTPResponse('No URL rule was matched', 404); } - /** - * Extract env vars prior to modification - * - * @return array List of all super globals - */ - public static function envToVars() - { - // Suppress return by-ref - return array_merge($GLOBALS, []); - } - - /** - * Restore a backed up or modified list of vars to $globals - * - * @param array $vars - */ - public static function varsToEnv(array $vars) - { - foreach ($vars as $key => $value) { - $GLOBALS[$key] = $value; - } - } - /** * Return the {@link SiteTree} object that is currently being viewed. If there is no SiteTree * object to return, then this will return the current controller. diff --git a/src/Control/HTTPRequest.php b/src/Control/HTTPRequest.php index c28dd3db8..72dc820a9 100644 --- a/src/Control/HTTPRequest.php +++ b/src/Control/HTTPRequest.php @@ -23,7 +23,6 @@ use SilverStripe\ORM\ArrayLib; */ class HTTPRequest implements ArrayAccess { - /** * @var string */ @@ -149,179 +148,6 @@ class HTTPRequest implements ArrayAccess $this->body = $body; } - /** - * Create HTTPRequest instance from the current environment variables. - * May throw errors if request is invalid. - * - * @throws HTTPResponse_Exception - * @return static - */ - public static function createFromEnvironment() - { - // Health-check prior to creating environment - static::validateEnvironment(); - return self::createFromVariables(Director::envToVars(), @file_get_contents('php://input')); - } - - /** - * Takes a $_SERVER data array and extracts HTTP request headers. - * - * @param array $server - * - * @return array - */ - public static function extractRequestHeaders(array $server) - { - $headers = array(); - foreach ($server as $key => $value) { - if (substr($key, 0, 5) == 'HTTP_') { - $key = substr($key, 5); - $key = strtolower(str_replace('_', ' ', $key)); - $key = str_replace(' ', '-', ucwords($key)); - $headers[$key] = $value; - } - } - - if (isset($server['CONTENT_TYPE'])) { - $headers['Content-Type'] = $server['CONTENT_TYPE']; - } - if (isset($server['CONTENT_LENGTH'])) { - $headers['Content-Length'] = $server['CONTENT_LENGTH']; - } - - return $headers; - } - - /** - * Clean up HTTP global vars for $_GET / $_REQUEST prior to bootstrapping - * Will also populate the $_GET['url'] var safely - * - * @param array $variables - * @return array Cleaned variables - */ - public static function cleanEnvironment(array $variables) - { - // IIS will sometimes generate this. - if (!empty($variables['_SERVER']['HTTP_X_ORIGINAL_URL'])) { - $variables['_SERVER']['REQUEST_URI'] = $variables['_SERVER']['HTTP_X_ORIGINAL_URL']; - } - - // Override REQUEST_METHOD - if (isset($variables['_SERVER']['X-HTTP-Method-Override'])) { - $variables['_SERVER']['REQUEST_METHOD'] = $variables['_SERVER']['X-HTTP-Method-Override']; - } - - // Prevent injection of url= querystring argument by prioritising any leading url argument - if (isset($variables['_SERVER']['QUERY_STRING']) && - preg_match('/^(?url=[^&?]*)(?.*[&?]url=.*)$/', $variables['_SERVER']['QUERY_STRING'], $results) - ) { - $queryString = $results['query'].'&'.$results['url']; - parse_str($queryString, $variables['_GET']); - } - - // Decode url from REQUEST_URI if not passed via $_GET['url'] - if (!isset($variables['_GET']['url'])) { - $url = $variables['_SERVER']['REQUEST_URI']; - - // Querystring args need to be explicitly parsed - if (strpos($url, '?') !== false) { - list($url, $queryString) = explode('?', $url, 2); - parse_str($queryString); - } - - // Ensure $_GET['url'] is set - $variables['_GET']['url'] = urldecode($url); - } - - // Remove base folders from the URL if webroot is hosted in a subfolder - if (substr(strtolower($variables['_GET']['url']), 0, strlen(BASE_URL)) === strtolower(BASE_URL)) { - $variables['_GET']['url'] = substr($variables['_GET']['url'], strlen(BASE_URL)); - } - - // Merge $_FILES into $_POST - $variables['_POST'] = array_merge((array)$variables['_POST'], (array)$variables['_FILES']); - - // Merge $_POST, $_GET, and $_COOKIE into $_REQUEST - $variables['_REQUEST'] = array_merge( - (array)$variables['_GET'], - (array)$variables['_POST'], - (array)$variables['_COOKIE'] - ); - - return $variables; - } - - /** - * Validate environment vars prior to building HTTPRequest - * Error conditions will raise HTTPResponse_Exceptions - * - * @throws HTTPResponse_Exception - */ - protected static function validateEnvironment() - { - // Validate $_FILES array before merging it with $_POST - foreach ($_FILES as $key => $value) { - if (is_array($value['tmp_name'])) { - $tempFiles = ArrayLib::array_values_recursive($value['tmp_name']); - } else { - $tempFiles = [ $value['tmp_name'] ]; - } - foreach ($tempFiles as $tmpFile) { - if ($tmpFile && !is_uploaded_file($tmpFile)) { - throw new HTTPResponse_Exception( - "File upload '{$key}' doesn't appear to be a valid upload", - 400 - ); - } - } - } - - // Validate hostname - if (getenv('SS_ALLOWED_HOSTS') && !Director::is_cli()) { - $all_allowed_hosts = explode(',', getenv('SS_ALLOWED_HOSTS')); - if (!in_array(Director::host(), $all_allowed_hosts)) { - throw new HTTPResponse_Exception('Invalid Host', 400); - } - } - } - - /** - * Build HTTPRequest from given variables - * - * @param array $variables - * @param string $input Request body - * @return HTTPRequest - */ - public static function createFromVariables(array $variables, $input) - { - $variables = static::cleanEnvironment($variables); - - // Strip `url` out of querystring - $url = $variables['_GET']['url']; - unset($variables['_GET']['url']); - - // Build request - $request = new HTTPRequest( - $variables['_SERVER']['REQUEST_METHOD'], - $url, - $variables['_GET'], - $variables['_POST'], - $input - ); - - // Add headers - $headers = static::extractRequestHeaders($variables['_SERVER']); - foreach ($headers as $header => $value) { - $request->addHeader($header, $value); - } - - // Initiate an empty session - doesn't initialize an actual PHP session (see HTTPApplication) - $session = new Session(isset($variables['_SESSION']) ? $variables['_SESSION'] : null); - $request->setSession($session); - - return $request; - } - /** * Allow the setting of a URL * diff --git a/src/Control/HTTPRequestBuilder.php b/src/Control/HTTPRequestBuilder.php new file mode 100644 index 000000000..94159b538 --- /dev/null +++ b/src/Control/HTTPRequestBuilder.php @@ -0,0 +1,146 @@ + $value) { + $request->addHeader($header, $value); + } + + // Initiate an empty session - doesn't initialize an actual PHP session (see HTTPApplication) + $session = new Session(isset($variables['_SESSION']) ? $variables['_SESSION'] : null); + $request->setSession($session); + + return $request; + } + + /** + * Takes a $_SERVER data array and extracts HTTP request headers. + * + * @param array $server + * + * @return array + */ + protected static function extractRequestHeaders(array $server) + { + $headers = array(); + foreach ($server as $key => $value) { + if (substr($key, 0, 5) == 'HTTP_') { + $key = substr($key, 5); + $key = strtolower(str_replace('_', ' ', $key)); + $key = str_replace(' ', '-', ucwords($key)); + $headers[$key] = $value; + } + } + + if (isset($server['CONTENT_TYPE'])) { + $headers['Content-Type'] = $server['CONTENT_TYPE']; + } + if (isset($server['CONTENT_LENGTH'])) { + $headers['Content-Length'] = $server['CONTENT_LENGTH']; + } + + return $headers; + } + + /** + * Clean up HTTP global vars for $_GET / $_REQUEST prior to bootstrapping + * Will also populate the $_GET['url'] var safely + * + * @param array $variables + * @return array Cleaned variables + */ + protected static function cleanEnvironment(array $variables) + { + // IIS will sometimes generate this. + if (!empty($variables['_SERVER']['HTTP_X_ORIGINAL_URL'])) { + $variables['_SERVER']['REQUEST_URI'] = $variables['_SERVER']['HTTP_X_ORIGINAL_URL']; + } + + // Override REQUEST_METHOD + if (isset($variables['_SERVER']['X-HTTP-Method-Override'])) { + $variables['_SERVER']['REQUEST_METHOD'] = $variables['_SERVER']['X-HTTP-Method-Override']; + } + + // Prevent injection of url= querystring argument by prioritising any leading url argument + if (isset($variables['_SERVER']['QUERY_STRING']) && + preg_match('/^(?url=[^&?]*)(?.*[&?]url=.*)$/', $variables['_SERVER']['QUERY_STRING'], $results) + ) { + $queryString = $results['query'].'&'.$results['url']; + parse_str($queryString, $variables['_GET']); + } + + // Decode url from REQUEST_URI if not passed via $_GET['url'] + if (!isset($variables['_GET']['url'])) { + $url = $variables['_SERVER']['REQUEST_URI']; + + // Querystring args need to be explicitly parsed + if (strpos($url, '?') !== false) { + list($url, $queryString) = explode('?', $url, 2); + parse_str($queryString); + } + + // Ensure $_GET['url'] is set + $variables['_GET']['url'] = urldecode($url); + } + + // Remove base folders from the URL if webroot is hosted in a subfolder + if (substr(strtolower($variables['_GET']['url']), 0, strlen(BASE_URL)) === strtolower(BASE_URL)) { + $variables['_GET']['url'] = substr($variables['_GET']['url'], strlen(BASE_URL)); + } + + // Merge $_FILES into $_POST + $variables['_POST'] = array_merge((array)$variables['_POST'], (array)$variables['_FILES']); + + // Merge $_POST, $_GET, and $_COOKIE into $_REQUEST + $variables['_REQUEST'] = array_merge( + (array)$variables['_GET'], + (array)$variables['_POST'], + (array)$variables['_COOKIE'] + ); + + return $variables; + } +} diff --git a/src/Core/AppKernel.php b/src/Core/AppKernel.php index 3f759910f..ceb2dcc31 100644 --- a/src/Core/AppKernel.php +++ b/src/Core/AppKernel.php @@ -28,8 +28,17 @@ use SilverStripe\View\ThemeResourceLoader; class AppKernel extends CoreKernel { - public function __construct() + protected $basePath = null; + + /** + * Create a new kernel for this application + * + * @param string $basePath Path to base dir for this application + */ + public function __construct($basePath) { + $this->basePath = $basePath; + // Initialise the dependency injector as soon as possible, as it is // subsequently used by some of the following code $injectorLoader = InjectorLoader::inst(); @@ -42,12 +51,12 @@ class AppKernel extends CoreKernel // Class loader $classLoader = ClassLoader::inst(); - $classLoader->pushManifest(new ClassManifest(BASE_PATH, $manifestCacheFactory)); + $classLoader->pushManifest(new ClassManifest($basePath, $manifestCacheFactory)); $this->setClassLoader($classLoader); // Module loader $moduleLoader = ModuleLoader::inst(); - $moduleManifest = new ModuleManifest(BASE_PATH, $manifestCacheFactory); + $moduleManifest = new ModuleManifest($basePath, $manifestCacheFactory); $moduleLoader->pushManifest($moduleManifest); $this->setModuleLoader($moduleLoader); @@ -62,7 +71,7 @@ class AppKernel extends CoreKernel // Load template manifest $themeResourceLoader = ThemeResourceLoader::inst(); $themeResourceLoader->addSet('$default', new ThemeManifest( - BASE_PATH, + $basePath, project(), $manifestCacheFactory )); @@ -165,8 +174,8 @@ class AppKernel extends CoreKernel protected function detectLegacyEnvironment() { // Is there an _ss_environment.php file? - if (!file_exists(BASE_PATH . '/_ss_environment.php') && - !file_exists(dirname(BASE_PATH) . '/_ss_environment.php') + if (!file_exists($this->basePath . '/_ss_environment.php') && + !file_exists(dirname($this->basePath) . '/_ss_environment.php') ) { return; } @@ -197,7 +206,7 @@ class AppKernel extends CoreKernel protected function redirectToInstaller() { // Error if installer not available - if (!file_exists(BASE_PATH . '/install.php')) { + if (!file_exists($this->basePath . '/install.php')) { throw new HTTPResponse_Exception( 'SilverStripe Framework requires a $databaseConfig defined.', 500 @@ -290,7 +299,7 @@ class AppKernel extends CoreKernel if ($chooseName) { // Find directory to build name from $loopCount = (int)$chooseName; - $databaseDir = BASE_PATH; + $databaseDir = $this->basePath; for ($i = 0; $i < $loopCount-1; $i++) { $databaseDir = dirname($databaseDir); } @@ -323,12 +332,15 @@ class AppKernel extends CoreKernel /** * Ensure we have enough memory */ - increase_memory_limit_to('64M'); + Environment::increaseMemoryLimitTo('64M'); - /** - * Ensure we don't run into xdebug's fairly conservative infinite recursion protection limit - */ - increase_xdebug_nesting_level_to(200); + // Ensure we don't run into xdebug's fairly conservative infinite recursion protection limit + if (function_exists('xdebug_enable')) { + $current = ini_get('xdebug.max_nesting_level'); + if ((int)$current < 200) { + ini_set('xdebug.max_nesting_level', 200); + } + } /** * Set default encoding @@ -350,7 +362,7 @@ class AppKernel extends CoreKernel { return new ManifestCacheFactory([ 'namespace' => 'manifestcache', - 'directory' => getTempFolder(), + 'directory' => TempFolder::getTempFolder($this->basePath), ]); } @@ -407,7 +419,7 @@ class AppKernel extends CoreKernel if ($errorLog) { $logger = Injector::inst()->get(LoggerInterface::class); if ($logger instanceof Logger) { - $logger->pushHandler(new StreamHandler(BASE_PATH . '/' . $errorLog, Logger::WARNING)); + $logger->pushHandler(new StreamHandler($this->basePath . '/' . $errorLog, Logger::WARNING)); } else { user_error("SS_ERROR_LOG setting only works with Monolog, you are using another logger", E_USER_WARNING); } diff --git a/src/Core/Application.php b/src/Core/Application.php index fe4fbcabd..9306a9b96 100644 --- a/src/Core/Application.php +++ b/src/Core/Application.php @@ -2,6 +2,9 @@ namespace SilverStripe\Core; +use SilverStripe\Control\HTTPRequest; +use SilverStripe\Control\HTTPResponse; + /** * Identifies a class as a root silverstripe application */ @@ -13,4 +16,14 @@ interface Application * @return Kernel */ public function getKernel(); + + /** + * Safely boot the application and execute the given main action + * + * @param HTTPRequest $request + * @param callable $callback + * @param bool $flush + * @return HTTPResponse + */ + public function execute(HTTPRequest $request, callable $callback, $flush = false); } diff --git a/src/Core/Convert.php b/src/Core/Convert.php index da5b136fc..c8a1a2475 100644 --- a/src/Core/Convert.php +++ b/src/Core/Convert.php @@ -550,4 +550,47 @@ class Convert return $return; } + + + + /** + * Turn a memory string, such as 512M into an actual number of bytes. + * + * @param string $memString A memory limit string, such as "64M" + * @return float + */ + public static function memstring2bytes($memString) + { + switch (strtolower(substr($memString, -1))) { + case "b": + return round(substr($memString, 0, -1)); + case "k": + return round(substr($memString, 0, -1) * 1024); + case "m": + return round(substr($memString, 0, -1) * 1024 * 1024); + case "g": + return round(substr($memString, 0, -1) * 1024 * 1024 * 1024); + default: + return round($memString); + } + } + + /** + * @param float $bytes + * @param int $decimal decimal precision + * @return string + */ + public static function bytes2memstring($bytes, $decimal = 0) + { + $scales = ['b','k','m','g']; + // Get scale + $scale = (int)floor(log($bytes, 1024)); + if (!isset($scales[$scale])) { + $scale = 2; + } + + // Size + $num = round($bytes / pow(1024, $scale), $decimal); + return $num . $scales[$scale]; + } } diff --git a/src/Core/CoreKernel.php b/src/Core/CoreKernel.php index 7b73fb7b4..ac94ad6b2 100644 --- a/src/Core/CoreKernel.php +++ b/src/Core/CoreKernel.php @@ -76,6 +76,11 @@ class CoreKernel implements Kernel { $this->configLoader->activate(); $this->injectorLoader->activate(); + + // Self register + $this->getInjectorLoader() + ->getManifest() + ->registerService($this, Kernel::class); return $this; } diff --git a/src/Core/Environment.php b/src/Core/Environment.php new file mode 100644 index 000000000..c68415bde --- /dev/null +++ b/src/Core/Environment.php @@ -0,0 +1,152 @@ + $value) { + $GLOBALS[$key] = $value; + } + } + + /** + * Increase the memory limit to the given level if it's currently too low. + * Only increases up to the maximum defined in {@link set_increase_memory_limit_max()}, + * and defaults to the 'memory_limit' setting in the PHP configuration. + * + * @param string|float|int $memoryLimit A memory limit string, such as "64M". If omitted, unlimited memory will be set. + * @return bool true indicates a successful change, false a denied change. + */ + public static function increaseMemoryLimitTo($memoryLimit = -1) + { + $memoryLimit = Convert::memstring2bytes($memoryLimit); + $curLimit = Convert::memstring2bytes(ini_get('memory_limit')); + + // Can't go higher than infinite + if ($curLimit < 0) { + return true; + } + + // Check hard maximums + $max = static::getMemoryLimitMax(); + if ($max > 0 && ($memoryLimit < 0 || $memoryLimit > $max)) { + return false; + } + + // Increase the memory limit if it's too low + if ($memoryLimit < 0) { + ini_set('memory_limit', '-1'); + } elseif ($memoryLimit > $curLimit) { + ini_set('memory_limit', Convert::bytes2memstring($memoryLimit)); + } + + return true; + } + + /** + * Set the maximum allowed value for {@link increase_memory_limit_to()}. + * The same result can also be achieved through 'suhosin.memory_limit' + * if PHP is running with the Suhosin system. + * + * @param string|float $memoryLimit Memory limit string or float value + */ + static function setMemoryLimitMax($memoryLimit) + { + if (isset($memoryLimit) && !is_numeric($memoryLimit)) { + $memoryLimit = Convert::memstring2bytes($memoryLimit); + } + static::$memoryLimitMax = $memoryLimit; + } + + /** + * @return int Memory limit in bytes + */ + public static function getMemoryLimitMax() + { + if (static::$memoryLimitMax === null) { + return Convert::memstring2bytes(ini_get('memory_limit')); + } + return static::$memoryLimitMax; + } + + /** + * Increase the time limit of this script. By default, the time will be unlimited. + * Only works if 'safe_mode' is off in the PHP configuration. + * Only values up to {@link get_increase_time_limit_max()} are allowed. + * + * @param int $timeLimit The time limit in seconds. If omitted, no time limit will be set. + * @return Boolean TRUE indicates a successful change, FALSE a denied change. + */ + public static function increaseTimeLimitTo($timeLimit = null) + { + // Check vs max limit + $max = static::getTimeLimitMax(); + if ($max > 0 && $timeLimit > $max) { + return false; + } + + if (!$timeLimit) { + set_time_limit(0); + } else { + $currTimeLimit = ini_get('max_execution_time'); + // Only increase if its smaller + if ($currTimeLimit > 0 && $currTimeLimit < $timeLimit) { + set_time_limit($timeLimit); + } + } + return true; + } + + /** + * Set the maximum allowed value for {@link increase_timeLimit_to()}; + * + * @param int $timeLimit Limit in seconds + */ + public static function setTimeLimitMax($timeLimit) + { + static::$timeLimitMax = $timeLimit; + } + + /** + * @return Int Limit in seconds + */ + public static function getTimeLimitMax() + { + return static::$timeLimitMax; + } +} diff --git a/src/Core/Startup/OutputMiddleware.php b/src/Core/Startup/OutputMiddleware.php deleted file mode 100644 index f5af2637a..000000000 --- a/src/Core/Startup/OutputMiddleware.php +++ /dev/null @@ -1,44 +0,0 @@ -defaultResponse = $defaultResponse; - } - - public function process(HTTPRequest $request, callable $delegate) - { - /** @var HTTPResponse $response */ - try { - $response = call_user_func($delegate, $request); - } catch (HTTPResponse_Exception $exception) { - $response = $exception->getResponse(); - } - if ($response) { - $response->output(); - } elseif ($this->defaultResponse) { - echo $this->defaultResponse; - } - return $response; - } -} diff --git a/src/Core/TempFolder.php b/src/Core/TempFolder.php new file mode 100644 index 000000000..4556382f8 --- /dev/null +++ b/src/Core/TempFolder.php @@ -0,0 +1,117 @@ +setEnvironment(self::DEV); - parent::__construct(); + parent::__construct($basePath); } /** diff --git a/src/Dev/Install/InstallRequirements.php b/src/Dev/Install/InstallRequirements.php new file mode 100644 index 000000000..b49d0e76b --- /dev/null +++ b/src/Dev/Install/InstallRequirements.php @@ -0,0 +1,1113 @@ +requireDatabaseFunctions( + $databaseConfig, + array( + "Database Configuration", + "Database support", + "Database support in PHP", + $this->getDatabaseTypeNice($databaseConfig['type']) + ) + ) + ) { + return false; + } + + // Check if the server is available + $usePath = !empty($databaseConfig['path']) && empty($databaseConfig['server']); + if (!$this->requireDatabaseServer( + $databaseConfig, + array( + "Database Configuration", + "Database server", + $usePath + ? "I couldn't write to path '$databaseConfig[path]'" + : "I couldn't find a database server on '$databaseConfig[server]'", + $usePath ? $databaseConfig['path'] : $databaseConfig['server'] + ) + ) + ) { + return false; + } + + // Check if the connection credentials allow access to the server / database + if (!$this->requireDatabaseConnection( + $databaseConfig, + array( + "Database Configuration", + "Database access credentials", + "That username/password doesn't work" + ) + ) + ) { + return false; + } + + // Check the necessary server version is available + if (!$this->requireDatabaseVersion( + $databaseConfig, + array( + "Database Configuration", + "Database server version requirement", + '', + 'Version ' . $this->getDatabaseConfigurationHelper($databaseConfig['type'])->getDatabaseVersion($databaseConfig) + ) + ) + ) { + return false; + } + + // Check that database creation permissions are available + if (!$this->requireDatabaseOrCreatePermissions( + $databaseConfig, + array( + "Database Configuration", + "Can I access/create the database", + "I can't create new databases and the database '$databaseConfig[database]' doesn't exist" + ) + ) + ) { + return false; + } + + // Check alter permission (necessary to create tables etc) + if (!$this->requireDatabaseAlterPermissions( + $databaseConfig, + array( + "Database Configuration", + "Can I ALTER tables", + "I don't have permission to ALTER tables" + ) + ) + ) { + return false; + } + + // Success! + return true; + } + + public function checkAdminConfig($adminConfig) + { + if (!$adminConfig['username']) { + $this->error(array('', 'Please enter a username!')); + } + if (!$adminConfig['password']) { + $this->error(array('', 'Please enter a password!')); + } + } + + /** + * Check if the web server is IIS and version greater than the given version. + * + * @param int $fromVersion + * @return bool + */ + public function isIIS($fromVersion = 7) + { + if (strpos($this->findWebserver(), 'IIS/') === false) { + return false; + } + return substr(strstr($this->findWebserver(), '/'), -3, 1) >= $fromVersion; + } + + public function isApache() + { + if (strpos($this->findWebserver(), 'Apache') !== false) { + return true; + } else { + return false; + } + } + + /** + * Find the webserver software running on the PHP host. + * @return string|boolean 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 + */ + public function check() + { + $this->errors = null; + $isApache = $this->isApache(); + $isIIS = $this->isIIS(); + $webserver = $this->findWebserver(); + + $this->requirePHPVersion('5.5.0', '5.5.0', array( + "PHP Configuration", + "PHP5 installed", + null, + "PHP version " . phpversion() + )); + + // Check that we can identify the root folder successfully + $this->requireFile('framework/src/Dev/Install/config-form.html', array( + "File permissions", + "Does the webserver know where files are stored?", + "The webserver isn't letting me identify where files are stored.", + $this->getBaseDir() + )); + + $this->requireModule('mysite', array( + "File permissions", + "mysite/ directory exists?", + '' + )); + $this->requireModule('framework', array( + "File permissions", + "framework/ directory exists?", + '', + )); + + if ($isApache) { + $this->checkApacheVersion(array( + "Webserver Configuration", + "Webserver is not Apache 1.x", + "SilverStripe requires Apache version 2 or greater", + $webserver + )); + $this->requireWriteable('.htaccess', array("File permissions", "Is the .htaccess file writeable?", null)); + } elseif ($isIIS) { + $this->requireWriteable('web.config', array("File permissions", "Is the web.config file writeable?", null)); + } + + $this->requireWriteable('mysite/_config.php', array( + "File permissions", + "Is the mysite/_config.php file writeable?", + null + )); + + $this->requireWriteable('mysite/_config/config.yml', array( + "File permissions", + "Is the mysite/_config/config.yml file writeable?", + null + )); + + if (!$this->checkModuleExists('cms')) { + $this->requireWriteable('mysite/code/RootURLController.php', array( + "File permissions", + "Is the mysite/code/RootURLController.php file writeable?", + null + )); + } + $this->requireWriteable('assets', array("File permissions", "Is the assets/ directory writeable?", null)); + + try { + $tempFolder = TempFolder::getTempFolder(BASE_PATH); + } catch (Exception $e) { + $tempFolder = false; + } + + $this->requireTempFolder(array('File permissions', 'Is a temporary directory available?', null, $tempFolder)); + if ($tempFolder) { + // in addition to the temp folder being available, check it is writable + $this->requireWriteable($tempFolder, array( + "File permissions", + sprintf("Is the temporary directory writeable?", $tempFolder), + null + ), true); + } + + // Check for web server, unless we're calling the installer from the command-line + $this->isRunningWebServer(array("Webserver Configuration", "Server software", "Unknown", $webserver)); + + if ($isApache) { + $this->requireApacheRewriteModule('mod_rewrite', array( + "Webserver Configuration", + "URL rewriting support", + "You need mod_rewrite to use friendly URLs with SilverStripe, but it is not enabled." + )); + } elseif ($isIIS) { + $this->requireIISRewriteModule('IIS_UrlRewriteModule', array( + "Webserver Configuration", + "URL rewriting support", + "You need to enable the IIS URL Rewrite Module to use friendly URLs with SilverStripe, " + . "but it is not installed or enabled. Download it for IIS 7 from http://www.iis.net/expand/URLRewrite" + )); + } else { + $this->warning(array( + "Webserver Configuration", + "URL rewriting support", + "I can't tell whether any rewriting module is running. You may need to configure a rewriting rule yourself." + )); + } + + $this->requireServerVariables(array('SCRIPT_NAME', 'HTTP_HOST', 'SCRIPT_FILENAME'), array( + "Webserver Configuration", + "Recognised webserver", + "You seem to be using an unsupported webserver. " + . "The server variables SCRIPT_NAME, HTTP_HOST, SCRIPT_FILENAME need to be set." + )); + + $this->requirePostSupport(array( + "Webserver Configuration", + "POST Support", + 'I can\'t find $_POST, make sure POST is enabled.' + )); + + // Check for GD support + if (!$this->requireFunction("imagecreatetruecolor", array( + "PHP Configuration", + "GD2 support", + "PHP must have GD version 2." + )) + ) { + $this->requireFunction("imagecreate", array( + "PHP Configuration", + "GD2 support", + "GD support for PHP not included." + )); + } + + // Check for XML support + $this->requireFunction('xml_set_object', array( + "PHP Configuration", + "XML support", + "XML support not included in PHP." + )); + $this->requireClass('DOMDocument', array( + "PHP Configuration", + "DOM/XML support", + "DOM/XML support not included in PHP." + )); + $this->requireFunction('simplexml_load_file', array( + 'PHP Configuration', + 'SimpleXML support', + 'SimpleXML support not included in PHP.' + )); + + // Check for token_get_all + $this->requireFunction('token_get_all', array( + "PHP Configuration", + "Tokenizer support", + "Tokenizer support not included in PHP." + )); + + // Check for CType support + $this->requireFunction('ctype_digit', array( + 'PHP Configuration', + 'CType support', + 'CType support not included in PHP.' + )); + + // Check for session support + $this->requireFunction('session_start', array( + 'PHP Configuration', + 'Session support', + 'Session support not included in PHP.' + )); + + // Check for iconv support + $this->requireFunction('iconv', array( + 'PHP Configuration', + 'iconv support', + 'iconv support not included in PHP.' + )); + + // Check for hash support + $this->requireFunction('hash', array('PHP Configuration', 'hash support', 'hash support not included in PHP.')); + + // Check for mbstring support + $this->requireFunction('mb_internal_encoding', array( + 'PHP Configuration', + 'mbstring support', + 'mbstring support not included in PHP.' + )); + + // Check for Reflection support + $this->requireClass('ReflectionClass', array( + 'PHP Configuration', + 'Reflection support', + 'Reflection support not included in PHP.' + )); + + // Check for Standard PHP Library (SPL) support + $this->requireFunction('spl_classes', array( + 'PHP Configuration', + 'SPL support', + 'Standard PHP Library (SPL) not included in PHP.' + )); + + $this->requireDateTimezone(array( + 'PHP Configuration', + 'date.timezone setting and validity', + 'date.timezone option in php.ini must be set correctly.', + ini_get('date.timezone') + )); + + $this->suggestClass('finfo', array( + 'PHP Configuration', + 'fileinfo support', + 'fileinfo should be enabled in PHP. SilverStripe uses it for MIME type detection of files. ' + . 'SilverStripe will still operate, but email attachments and sending files to browser ' + . '(e.g. export data to CSV) may not work correctly without finfo.' + )); + + $this->suggestFunction('curl_init', array( + 'PHP Configuration', + 'curl support', + 'curl should be enabled in PHP. SilverStripe uses it for consuming web services' + . ' via the RestfulService class and many modules rely on it.' + )); + + $this->suggestClass('tidy', array( + 'PHP Configuration', + 'tidy support', + 'Tidy provides a library of code to clean up your html. ' + . 'SilverStripe will operate fine without tidy but HTMLCleaner will not be effective.' + )); + + $this->suggestPHPSetting('asp_tags', array(false), array( + 'PHP Configuration', + 'asp_tags option', + 'This should be turned off as it can cause issues with SilverStripe' + )); + $this->requirePHPSetting('magic_quotes_gpc', array(false), array( + 'PHP Configuration', + 'magic_quotes_gpc option', + 'This should be turned off, as it can cause issues with cookies. ' + . 'More specifically, unserializing data stored in cookies.' + )); + $this->suggestPHPSetting('display_errors', array(false), array( + 'PHP Configuration', + 'display_errors option', + 'Unless you\'re in a development environment, this should be turned off, ' + . 'as it can expose sensitive data to website users.' + )); + // on some weirdly configured webservers arg_separator.output is set to & + // which will results in links like ?param=value&foo=bar which will not be i + $this->suggestPHPSetting('arg_separator.output', array('&', ''), array( + 'PHP Configuration', + 'arg_separator.output option', + 'This option defines how URL parameters are concatenated. ' + . 'If not set to \'&\' this may cause issues with URL GET parameters' + )); + + // always_populate_raw_post_data should be set to -1 if PHP < 7.0 + if (version_compare(PHP_VERSION, '7.0.0', '<')) { + $this->suggestPHPSetting('always_populate_raw_post_data', ['-1'], [ + 'PHP Configuration', + 'always_populate_raw_post_data option', + 'It\'s highly recommended to set this to \'-1\' in php 5.x, as $HTTP_RAW_POST_DATA is removed in php 7' + ]); + } + + // Check memory allocation + $this->requireMemory(32 * 1024 * 1024, 64 * 1024 * 1024, array( + "PHP Configuration", + "Memory allocation (PHP config option 'memory_limit')", + "SilverStripe needs a minimum of 32M allocated to PHP, but recommends 64M.", + ini_get("memory_limit") + )); + + return $this->errors; + } + + public function suggestPHPSetting($settingName, $settingValues, $testDetails) + { + $this->testing($testDetails); + + // special case for display_errors, check the original value before + // it was changed at the start of this script. + if ($settingName == 'display_errors') { + global $originalDisplayErrorsValue; + $val = $originalDisplayErrorsValue; + } else { + $val = ini_get($settingName); + } + + if (!in_array($val, $settingValues) && $val != $settingValues) { + $this->warning($testDetails, "$settingName is set to '$val' in php.ini. $testDetails[2]"); + } + } + + public function requirePHPSetting($settingName, $settingValues, $testDetails) + { + $this->testing($testDetails); + + $val = ini_get($settingName); + if (!in_array($val, $settingValues) && $val != $settingValues) { + $this->error($testDetails, "$settingName is set to '$val' in php.ini. $testDetails[2]"); + } + } + + public function suggestClass($class, $testDetails) + { + $this->testing($testDetails); + + if (!class_exists($class)) { + $this->warning($testDetails); + } + } + + public function suggestFunction($class, $testDetails) + { + $this->testing($testDetails); + + if (!function_exists($class)) { + $this->warning($testDetails); + } + } + + public function requireDateTimezone($testDetails) + { + $this->testing($testDetails); + + $result = ini_get('date.timezone') && in_array(ini_get('date.timezone'), timezone_identifiers_list()); + if (!$result) { + $this->error($testDetails); + } + } + + public function requireMemory($min, $recommended, $testDetails) + { + $_SESSION['forcemem'] = false; + + $mem = $this->getPHPMemory(); + if ($mem < (64 * 1024 * 1024)) { + ini_set('memory_limit', '64M'); + $mem = $this->getPHPMemory(); + $testDetails[3] = ini_get("memory_limit"); + } + + $this->testing($testDetails); + + if ($mem < $min && $mem > 0) { + $message = $testDetails[2] . " You only have " . ini_get("memory_limit") . " allocated"; + $this->error($testDetails, $message); + return false; + } elseif ($mem < $recommended && $mem > 0) { + $message = $testDetails[2] . " You only have " . ini_get("memory_limit") . " allocated"; + $this->warning($testDetails, $message); + return false; + } elseif ($mem == 0) { + $message = $testDetails[2] . " We can't determine how much memory you have allocated. " + . "Install only if you're sure you've allocated at least 20 MB."; + $this->warning($testDetails, $message); + return false; + } + return true; + } + + public function getPHPMemory() + { + $memString = ini_get("memory_limit"); + + switch (strtolower(substr($memString, -1))) { + case "k": + return round(substr($memString, 0, -1) * 1024); + + case "m": + return round(substr($memString, 0, -1) * 1024 * 1024); + + case "g": + return round(substr($memString, 0, -1) * 1024 * 1024 * 1024); + + default: + return round($memString); + } + } + + + public function listErrors() + { + if ($this->errors) { + echo "

The following problems are preventing me from installing SilverStripe CMS:

\n\n"; + foreach ($this->errors as $error) { + echo "
  • " . htmlentities(implode(", ", $error), ENT_COMPAT, 'UTF-8') . "
  • \n"; + } + } + } + + public function showTable($section = null) + { + if ($section) { + $tests = $this->tests[$section]; + $id = strtolower(str_replace(' ', '_', $section)); + echo ""; + foreach ($tests as $test => $result) { + echo ""; + } + echo "
    $test" + . nl2br(htmlentities($result[1], ENT_COMPAT, 'UTF-8')) . "
    "; + } else { + foreach ($this->tests as $section => $tests) { + $failedRequirements = 0; + $warningRequirements = 0; + + $output = ""; + + foreach ($tests as $test => $result) { + if (isset($result['0'])) { + switch ($result['0']) { + case 'error': + $failedRequirements++; + break; + case 'warning': + $warningRequirements++; + break; + } + } + $output .= "$test" + . nl2br(htmlentities($result[1], ENT_COMPAT, 'UTF-8')) . ""; + } + $className = "good"; + $text = "All Requirements Pass"; + $pluralWarnings = ($warningRequirements == 1) ? 'Warning' : 'Warnings'; + + if ($failedRequirements > 0) { + $className = "error"; + $pluralWarnings = ($warningRequirements == 1) ? 'Warning' : 'Warnings'; + + $text = $failedRequirements . ' Failed and ' . $warningRequirements . ' ' . $pluralWarnings; + } elseif ($warningRequirements > 0) { + $className = "warning"; + $text = "All Requirements Pass but " . $warningRequirements . ' ' . $pluralWarnings; + } + + echo "
    $section Show All Requirements $text
    "; + echo ""; + echo $output; + echo "
    "; + } + } + } + + public function requireFunction($funcName, $testDetails) + { + $this->testing($testDetails); + + if (!function_exists($funcName)) { + $this->error($testDetails); + return false; + } + return true; + } + + public function requireClass($className, $testDetails) + { + $this->testing($testDetails); + if (!class_exists($className)) { + $this->error($testDetails); + return false; + } + return true; + } + + /** + * Require that the given class doesn't exist + * + * @param array $classNames + * @param array $testDetails + * @return bool + */ + public function requireNoClasses($classNames, $testDetails) + { + $this->testing($testDetails); + $badClasses = array(); + foreach ($classNames as $className) { + if (class_exists($className)) { + $badClasses[] = $className; + } + } + if ($badClasses) { + $message = $testDetails[2] . ". The following classes are at fault: " . implode(', ', $badClasses); + $this->error($testDetails, $message); + return false; + } + return true; + } + + public function checkApacheVersion($testDetails) + { + $this->testing($testDetails); + + $is1pointx = preg_match('#Apache[/ ]1\.#', $testDetails[3]); + if ($is1pointx) { + $this->error($testDetails); + } + + return true; + } + + public function requirePHPVersion($recommendedVersion, $requiredVersion, $testDetails) + { + $this->testing($testDetails); + + $installedVersion = phpversion(); + + if (version_compare($installedVersion, $requiredVersion, '<')) { + $message = "SilverStripe requires PHP version $requiredVersion or later.\n + PHP version $installedVersion is currently installed.\n + While SilverStripe requires at least PHP version $requiredVersion, upgrading to $recommendedVersion or later is recommended.\n + If you are installing SilverStripe on a shared web server, please ask your web hosting provider to upgrade PHP for you."; + $this->error($testDetails, $message); + return false; + } + + if (version_compare($installedVersion, $recommendedVersion, '<')) { + $message = "PHP version $installedVersion is currently installed.\n + Upgrading to at least PHP version $recommendedVersion is recommended.\n + SilverStripe should run, but you may run into issues. Future releases may require a later version of PHP.\n"; + $this->warning($testDetails, $message); + return false; + } + + return true; + } + + /** + * Check that a module exists + * + * @param string $dirname + * @return bool + */ + public function checkModuleExists($dirname) + { + $path = $this->getBaseDir() . $dirname; + return file_exists($path) && ($dirname == 'mysite' || file_exists($path . '/_config.php')); + } + + /** + * The same as {@link requireFile()} but does additional checks + * to ensure the module directory is intact. + * + * @param string $dirname + * @param array $testDetails + */ + public function requireModule($dirname, $testDetails) + { + $this->testing($testDetails); + $path = $this->getBaseDir() . $dirname; + 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') { + $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); + } + } + + public function requireFile($filename, $testDetails) + { + $this->testing($testDetails); + $filename = $this->getBaseDir() . $filename; + if (!file_exists($filename)) { + $testDetails[2] .= " (file '$filename' not found)"; + $this->error($testDetails); + } + } + + public function requireWriteable($filename, $testDetails, $absolute = false) + { + $this->testing($testDetails); + + if ($absolute) { + $filename = str_replace('/', DIRECTORY_SEPARATOR, $filename); + } else { + $filename = $this->getBaseDir() . str_replace('/', DIRECTORY_SEPARATOR, $filename); + } + + if (file_exists($filename)) { + $isWriteable = is_writeable($filename); + } else { + $isWriteable = is_writeable(dirname($filename)); + } + + if (!$isWriteable) { + if (function_exists('posix_getgroups')) { + $userID = posix_geteuid(); + $user = posix_getpwuid($userID); + + $currentOwnerID = fileowner(file_exists($filename) ? $filename : dirname($filename)); + $currentOwner = posix_getpwuid($currentOwnerID); + + $testDetails[2] .= "User '$user[name]' needs to be able to write to this file:\n$filename\n\nThe " + . "file is currently owned by '$currentOwner[name]'. "; + + if ($user['name'] == $currentOwner['name']) { + $testDetails[2] .= "We recommend that you make the file writeable."; + } else { + $groups = posix_getgroups(); + $groupList = array(); + foreach ($groups as $group) { + $groupInfo = posix_getgrgid($group); + if (in_array($currentOwner['name'], $groupInfo['members'])) { + $groupList[] = $groupInfo['name']; + } + } + if ($groupList) { + $testDetails[2] .= " We recommend that you make the file group-writeable " + . "and change the group to one of these groups:\n - " . implode("\n - ", $groupList) + . "\n\nFor example:\nchmod g+w $filename\nchgrp " . $groupList[0] . " $filename"; + } else { + $testDetails[2] .= " There is no user-group that contains both the web-server user and the " + . "owner of this file. Change the ownership of the file, create a new group, or " + . "temporarily make the file writeable by everyone during the install process."; + } + } + } else { + $testDetails[2] .= "The webserver user needs to be able to write to this file:\n$filename"; + } + + $this->error($testDetails); + } + } + + public function requireTempFolder($testDetails) + { + $this->testing($testDetails); + + try { + $tempFolder = TempFolder::getTempFolder(BASE_PATH); + } catch (Exception $e) { + $tempFolder = false; + } + + if (!$tempFolder) { + $testDetails[2] = "Permission problem gaining access to a temp directory. " . + "Please create a folder named silverstripe-cache in the base directory " . + "of the installation and ensure it has the adequate permissions."; + $this->error($testDetails); + } + } + + public function requireApacheModule($moduleName, $testDetails) + { + $this->testing($testDetails); + if (!in_array($moduleName, apache_get_modules())) { + $this->error($testDetails); + return false; + } else { + return true; + } + } + + 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); + if ($this->testApacheRewriteExists()) { + return true; + } else { + $this->warning($testDetails); + return false; + } + } + + /** + * 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); + if ($this->testIISRewriteModuleExists()) { + return true; + } else { + $this->warning($testDetails); + return false; + } + } + + 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); + $helper = $this->getDatabaseConfigurationHelper($databaseConfig['type']); + if (!$helper) { + $this->error($testDetails, "Couldn't load database helper code for " . $databaseConfig['type']); + return false; + } + $result = $helper->requireDatabaseFunctions($databaseConfig); + if ($result) { + return true; + } else { + $this->error($testDetails); + return false; + } + } + + public function requireDatabaseConnection($databaseConfig, $testDetails) + { + $this->testing($testDetails); + $helper = $this->getDatabaseConfigurationHelper($databaseConfig['type']); + $result = $helper->requireDatabaseConnection($databaseConfig); + if ($result['success']) { + return true; + } else { + $testDetails[2] .= ": " . $result['error']; + $this->error($testDetails); + return false; + } + } + + public function requireDatabaseVersion($databaseConfig, $testDetails) + { + $this->testing($testDetails); + $helper = $this->getDatabaseConfigurationHelper($databaseConfig['type']); + if (method_exists($helper, 'requireDatabaseVersion')) { + $result = $helper->requireDatabaseVersion($databaseConfig); + if ($result['success']) { + return true; + } else { + $testDetails[2] .= $result['error']; + $this->warning($testDetails); + return false; + } + } + // Skipped test because this database has no required version + return true; + } + + public function requireDatabaseServer($databaseConfig, $testDetails) + { + $this->testing($testDetails); + $helper = $this->getDatabaseConfigurationHelper($databaseConfig['type']); + $result = $helper->requireDatabaseServer($databaseConfig); + if ($result['success']) { + return true; + } else { + $message = $testDetails[2] . ": " . $result['error']; + $this->error($testDetails, $message); + return false; + } + } + + public function requireDatabaseOrCreatePermissions($databaseConfig, $testDetails) + { + $this->testing($testDetails); + $helper = $this->getDatabaseConfigurationHelper($databaseConfig['type']); + $result = $helper->requireDatabaseOrCreatePermissions($databaseConfig); + if ($result['success']) { + if ($result['alreadyExists']) { + $testDetails[3] = "Database $databaseConfig[database]"; + } else { + $testDetails[3] = "Able to create a new database"; + } + $this->testing($testDetails); + return true; + } else { + if (empty($result['cannotCreate'])) { + $message = $testDetails[2] . ". Please create the database manually."; + } else { + $message = $testDetails[2] . " (user '$databaseConfig[username]' doesn't have CREATE DATABASE permissions.)"; + } + + $this->error($testDetails, $message); + return false; + } + } + + public function requireDatabaseAlterPermissions($databaseConfig, $testDetails) + { + $this->testing($testDetails); + $helper = $this->getDatabaseConfigurationHelper($databaseConfig['type']); + $result = $helper->requireDatabaseAlterPermissions($databaseConfig); + if ($result['success']) { + return true; + } else { + $message = "Silverstripe cannot alter tables. This won't prevent installation, however it may " + . "cause issues if you try to run a /dev/build once installed."; + $this->warning($testDetails, $message); + return false; + } + } + + public function requireServerVariables($varNames, $testDetails) + { + $this->testing($testDetails); + $missing = array(); + + foreach ($varNames as $varName) { + if (!isset($_SERVER[$varName]) || !$_SERVER[$varName]) { + $missing[] = '$_SERVER[' . $varName . ']'; + } + } + + if (!$missing) { + return true; + } + + $message = $testDetails[2] . " (the following PHP variables are missing: " . implode(", ", $missing) . ")"; + $this->error($testDetails, $message); + return false; + } + + + public function requirePostSupport($testDetails) + { + $this->testing($testDetails); + + if (!isset($_POST)) { + $this->error($testDetails); + + return false; + } + + return true; + } + + public function isRunningWebServer($testDetails) + { + $this->testing($testDetails); + if ($testDetails[3]) { + return true; + } else { + $this->warning($testDetails); + return false; + } + } + + // Must be PHP4 compatible + var $baseDir; + + public function getBaseDir() + { + return BASE_PATH . '/'; + } + + public function testing($testDetails) + { + if (!$testDetails) { + return; + } + + $section = $testDetails[0]; + $test = $testDetails[1]; + + $message = "OK"; + if (isset($testDetails[3])) { + $message .= " ($testDetails[3])"; + } + + $this->tests[$section][$test] = array("good", $message); + } + + public function error($testDetails, $message = null) + { + if (!is_array($testDetails)) { + throw new InvalidArgumentException("Invalid error"); + } + $section = $testDetails[0]; + $test = $testDetails[1]; + if (!$message && isset($testDetails[2])) { + $message = $testDetails[2]; + } + + $this->tests[$section][$test] = array("error", $message); + $this->errors[] = $testDetails; + } + + public function warning($testDetails, $message = null) + { + if (!is_array($testDetails)) { + throw new InvalidArgumentException("Invalid warning"); + } + $section = $testDetails[0]; + $test = $testDetails[1]; + if (!$message && isset($testDetails[2])) { + $message = $testDetails[2]; + } + + $this->tests[$section][$test] = array("warning", $message); + $this->warnings[] = $testDetails; + } + + public function hasErrors() + { + return sizeof($this->errors); + } + + public function hasWarnings() + { + return sizeof($this->warnings); + } +} diff --git a/src/Dev/Install/Installer.php b/src/Dev/Install/Installer.php new file mode 100644 index 000000000..ef256b60f --- /dev/null +++ b/src/Dev/Install/Installer.php @@ -0,0 +1,501 @@ +getBaseDir(); + } + + protected function installHeader() + { + ?> + + + + Installing SilverStripe... + + + + +
    +
    +
    + + +

    SilverStripe

    +
    +
    +
    + + +
    + +
    +
    +

    Installing SilverStripe...

    + +

    I am now running through the installation steps (this should take about 30 seconds)

    + +

    If you receive a fatal error, refresh this page to continue the installation

    +
      + installHeader(); + + $webserver = $this->findWebserver(); + $isIIS = $this->isIIS(); + $isApache = $this->isApache(); + + flush(); + + if (isset($config['stats'])) { + if (file_exists(FRAMEWORK_PATH . '/silverstripe_version')) { + $silverstripe_version = file_get_contents(FRAMEWORK_PATH . '/silverstripe_version'); + } else { + $silverstripe_version = "unknown"; + } + + $phpVersion = urlencode(phpversion()); + $encWebserver = urlencode($webserver); + $dbType = $config['db']['type']; + + // Try to determine the database version from the helper + $databaseVersion = $config['db']['type']; + $helper = $this->getDatabaseConfigurationHelper($dbType); + if ($helper && method_exists($helper, 'getDatabaseVersion')) { + $versionConfig = $config['db'][$dbType]; + $versionConfig['type'] = $dbType; + $databaseVersion = urlencode($dbType . ': ' . $helper->getDatabaseVersion($versionConfig)); + } + + $url = "http://ss2stat.silverstripe.com/Installation/add?SilverStripe=$silverstripe_version&PHP=$phpVersion&Database=$databaseVersion&WebServer=$encWebserver"; + + if (isset($_SESSION['StatsID']) && $_SESSION['StatsID']) { + $url .= '&ID=' . $_SESSION['StatsID']; + } + + @$_SESSION['StatsID'] = file_get_contents($url); + } + + if (file_exists('mysite/_config.php')) { + // 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'); + fclose($fh); + } + + // Escape user input for safe insertion into PHP file + $theme = isset($_POST['template']) ? addcslashes($_POST['template'], "\'") : 'simple'; + $locale = isset($_POST['locale']) ? addcslashes($_POST['locale'], "\'") : 'en_US'; + $type = addcslashes($config['db']['type'], "\'"); + $dbConfig = $config['db'][$type]; + foreach ($dbConfig as &$configValue) { + $configValue = addcslashes($configValue, "\\\'"); + } + if (!isset($dbConfig['path'])) { + $dbConfig['path'] = ''; + } + if (!$dbConfig) { + echo "

      Bad config submitted

      ";
      +            print_r($config);
      +            echo "
      "; + die(); + } + + // Write the config file + global $usingEnv; + if ($usingEnv) { + $this->statusMessage("Setting up 'mysite/_config.php' for use with environment variables..."); + $this->writeToFile("mysite/_config.php", <<statusMessage("Setting up 'mysite/_config.php'..."); + // Create databaseConfig + $lines = array( + $lines[] = "\t'type' => '$type'" + ); + foreach ($dbConfig as $key => $value) { + $lines[] = "\t'{$key}' => '$value'"; + } + $databaseConfigContent = implode(",\n", $lines); + $this->writeToFile("mysite/_config.php", <<statusMessage("Setting up 'mysite/_config/config.yml'"); + $this->writeToFile("mysite/_config/config.yml", <<checkModuleExists('cms')) { + $this->writeToFile("mysite/code/RootURLController.php", <<Your site is now set up. Start adding controllers to mysite to get started."; + } + +} +PHP + ); + } + + // Write the appropriate web server configuration file for rewriting support + if ($this->hasRewritingCapability()) { + if ($isApache) { + $this->statusMessage("Setting up '.htaccess' file..."); + $this->createHtaccess(); + } elseif ($isIIS) { + $this->statusMessage("Setting up 'web.config' file..."); + $this->createWebConfig(); + } + } + + // Mock request + $session = new Session(isset($_SESSION) ? $_SESSION : array()); + $request = new HTTPRequest('GET', '/'); + $request->setSession($session); + + // Install kernel (fix to dev) + $kernel = new AppKernel(BASE_PATH); + $kernel->setEnvironment(Kernel::DEV); + $app = new HTTPApplication($kernel); + + // Build db within HTTPApplication + $app->execute($request, function (HTTPRequest $request) use ($config) { + // Start session and execute + $request->getSession()->init(); + + // Output status + $this->statusMessage("Building database schema..."); + + // Setup DB + $dbAdmin = new DatabaseAdmin(); + $dbAdmin->setRequest($request); + $dbAdmin->pushCurrent(); + $dbAdmin->doInit(); + $dbAdmin->doBuild(true); + + // Create default administrator user and group in database + // (not using Security::setDefaultAdmin()) + $adminMember = DefaultAdminService::singleton()->findOrCreateDefaultAdmin(); + $adminMember->Email = $config['admin']['username']; + $adminMember->Password = $config['admin']['password']; + $adminMember->PasswordEncryption = Security::config()->get('encryption_algorithm'); + + try { + $this->statusMessage('Creating default CMS admin account...'); + $adminMember->write(); + } catch (Exception $e) { + $this->statusMessage( + sprintf('Warning: Default CMS admin account could not be created (error: %s)', $e->getMessage()) + ); + } + + $request->getSession()->set('username', $config['admin']['username']); + $request->getSession()->set('password', $config['admin']['password']); + $request->getSession()->save(); + }, true); + + // Check result of install + if (!$this->errors) { + if (isset($_SERVER['HTTP_HOST']) && $this->hasRewritingCapability()) { + $this->statusMessage("Checking that friendly URLs work..."); + $this->checkRewrite(); + } else { + $token = new ParameterConfirmationToken('flush', $request); + $params = http_build_query($token->params()); + + $destinationURL = 'index.php/' . + ($this->checkModuleExists('cms') ? "home/successfullyinstalled?$params" : "?$params"); + + echo <<SilverStripe successfully installed; I am now redirecting you to your SilverStripe site... + + +HTML; + } + } + + return $this->errors; + } + + public function writeToFile($filename, $content) + { + $base = $this->getBaseDir(); + $this->statusMessage("Setting up $base$filename"); + + if ((@$fh = fopen($base . $filename, 'wb')) && fwrite($fh, $content) && fclose($fh)) { + return true; + } + $this->error("Couldn't write to file $base$filename"); + return false; + } + + public function createHtaccess() + { + $start = "### SILVERSTRIPE START ###\n"; + $end = "\n### SILVERSTRIPE END ###"; + + $base = dirname($_SERVER['SCRIPT_NAME']); + if (defined('DIRECTORY_SEPARATOR')) { + $base = str_replace(DIRECTORY_SEPARATOR, '/', $base); + } else { + $base = str_replace("\\", '/', $base); + } + + if ($base != '.') { + $baseClause = "RewriteBase '$base'\n"; + } else { + $baseClause = ""; + } + if (strpos(strtolower(php_sapi_name()), "cgi") !== false) { + $cgiClause = "RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]\n"; + } else { + $cgiClause = ""; + } + $rewrite = << + Order deny,allow + Deny from all + Allow from 127.0.0.1 + + +# Deny access to IIS configuration + + Order deny,allow + Deny from all + + +# Deny access to YAML configuration files which might include sensitive information + + Order allow,deny + Deny from all + + +# Route errors to static pages automatically generated by SilverStripe +ErrorDocument 404 /assets/error-404.html +ErrorDocument 500 /assets/error-500.html + + + + # Turn off index.php handling requests to the homepage fixes issue in apache >=2.4 + + DirectoryIndex disabled + + + SetEnv HTTP_MOD_REWRITE On + RewriteEngine On + $baseClause + $cgiClause + + # Deny access to potentially sensitive files and folders + RewriteRule ^vendor(/|$) - [F,L,NC] + RewriteRule silverstripe-cache(/|$) - [F,L,NC] + RewriteRule composer\.(json|lock) - [F,L,NC] + + # Process through SilverStripe if no file with the requested name exists. + # Pass through the original path as a query parameter, and retain the existing parameters. + RewriteCond %{REQUEST_URI} ^(.*)$ + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule .* framework/main.php?url=%1 [QSA] + +TEXT; + + if (file_exists('.htaccess')) { + $htaccess = file_get_contents('.htaccess'); + + if (strpos($htaccess, '### SILVERSTRIPE START ###') === false + && strpos($htaccess, '### SILVERSTRIPE END ###') === false + ) { + $htaccess .= "\n### SILVERSTRIPE START ###\n### SILVERSTRIPE END ###\n"; + } + + if (strpos($htaccess, '### SILVERSTRIPE START ###') !== false + && strpos($htaccess, '### SILVERSTRIPE END ###') !== false + ) { + $start = substr($htaccess, 0, strpos($htaccess, '### SILVERSTRIPE START ###')) + . "### SILVERSTRIPE START ###\n"; + $end = "\n" . substr($htaccess, strpos($htaccess, '### SILVERSTRIPE END ###')); + } + } + + $this->writeToFile('.htaccess', $start . $rewrite . $end); + } + + /** + * Writes basic configuration to the web.config for IIS + * so that rewriting capability can be use. + */ + public function createWebConfig() + { + $content = << + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +TEXT; + + $this->writeToFile('web.config', $content); + } + + public function checkRewrite() + { + $token = new ParameterConfirmationToken('flush', new HTTPRequest('GET', '/')); + $params = http_build_query($token->params()); + + $destinationURL = str_replace('install.php', '', $_SERVER['SCRIPT_NAME']) . + ($this->checkModuleExists('cms') ? "home/successfullyinstalled?$params" : "?$params"); + + echo <<Testing... + + +HTML; + } + + public function var_export_array_nokeys($array) + { + $retval = "array(\n"; + foreach ($array as $item) { + $retval .= "\t'"; + $retval .= trim($item); + $retval .= "',\n"; + } + $retval .= ")"; + return $retval; + } + + /** + * Show an installation status message. + * The output differs depending on whether this is CLI or web based + * + * @param string $msg + */ + public function statusMessage($msg) + { + echo "
    • $msg
    • \n"; + flush(); + } +} diff --git a/src/Dev/Install/install.php b/src/Dev/Install/install.php index 6f1ab4b8d..69304eab5 100644 --- a/src/Dev/Install/install.php +++ b/src/Dev/Install/install.php @@ -24,4 +24,4 @@ if (version_compare(phpversion(), '5.5.0', '<')) { die(); } -include(__DIR__ . '/install.php5'); +include(__DIR__ . '/install5.php'); diff --git a/src/Dev/Install/install.php5 b/src/Dev/Install/install.php5 deleted file mode 100755 index d3a8a5349..000000000 --- a/src/Dev/Install/install.php5 +++ /dev/null @@ -1,1767 +0,0 @@ - 'Afrikaans (South Africa)', - 'ar_EG' => 'Arabic (Egypt)', - 'hy_AM' => 'Armenian (Armenia)', - 'ast_ES' => 'Asturian (Spain)', - 'az_AZ' => 'Azerbaijani (Azerbaijan)', - 'bs_BA' => 'Bosnian (Bosnia and Herzegovina)', - 'bg_BG' => 'Bulgarian (Bulgaria)', - 'ca_ES' => 'Catalan (Spain)', - 'zh_CN' => 'Chinese (China)', - 'zh_TW' => 'Chinese (Taiwan)', - 'hr_HR' => 'Croatian (Croatia)', - 'cs_CZ' => 'Czech (Czech Republic)', - 'da_DK' => 'Danish (Denmark)', - 'nl_NL' => 'Dutch (Netherlands)', - 'en_GB' => 'English (United Kingdom)', - 'en_US' => 'English (United States)', - 'eo_XX' => 'Esperanto', - 'et_EE' => 'Estonian (Estonia)', - 'fo_FO' => 'Faroese (Faroe Islands)', - 'fi_FI' => 'Finnish (Finland)', - 'fr_FR' => 'French (France)', - 'de_DE' => 'German (Germany)', - 'el_GR' => 'Greek (Greece)', - 'he_IL' => 'Hebrew (Israel)', - 'hu_HU' => 'Hungarian (Hungary)', - 'is_IS' => 'Icelandic (Iceland)', - 'id_ID' => 'Indonesian (Indonesia)', - 'it_IT' => 'Italian (Italy)', - 'ja_JP' => 'Japanese (Japan)', - 'km_KH' => 'Khmer (Cambodia)', - 'lc_XX' => 'LOLCAT', - 'lv_LV' => 'Latvian (Latvia)', - 'lt_LT' => 'Lithuanian (Lithuania)', - 'ms_MY' => 'Malay (Malaysia)', - 'mi_NZ' => 'Maori (New Zealand)', - 'ne_NP' => 'Nepali (Nepal)', - 'nb_NO' => 'Norwegian', - 'fa_IR' => 'Persian (Iran)', - 'pl_PL' => 'Polish (Poland)', - 'pt_BR' => 'Portuguese (Brazil)', - 'pa_IN' => 'Punjabi (India)', - 'ro_RO' => 'Romanian (Romania)', - 'ru_RU' => 'Russian (Russia)', - 'sr_RS' => 'Serbian (Serbia)', - 'si_LK' => 'Sinhalese (Sri Lanka)', - 'sk_SK' => 'Slovak (Slovakia)', - 'sl_SI' => 'Slovenian (Slovenia)', - 'es_AR' => 'Spanish (Argentina)', - 'es_MX' => 'Spanish (Mexico)', - 'es_ES' => 'Spanish (Spain)', - 'sv_SE' => 'Swedish (Sweden)', - 'th_TH' => 'Thai (Thailand)', - 'tr_TR' => 'Turkish (Turkey)', - 'uk_UA' => 'Ukrainian (Ukraine)', - 'uz_UZ' => 'Uzbek (Uzbekistan)', - 'vi_VN' => 'Vietnamese (Vietnam)', -); - -// Discover which databases are available -DatabaseAdapterRegistry::autodiscover(); - -// Determine which external database modules are USABLE -$databaseClasses = DatabaseAdapterRegistry::get_adapters(); -foreach($databaseClasses as $class => $details) { - $helper = DatabaseAdapterRegistry::getDatabaseConfigurationHelper($class); - $databaseClasses[$class]['hasModule'] = !empty($helper); -} - -// Load database config -if(isset($_REQUEST['db'])) { - if(isset($_REQUEST['db']['type'])) { - $type = $_REQUEST['db']['type']; - } else { - if ($type = getenv('SS_DATABASE_CLASS')) { - $_REQUEST['db']['type'] = $type; - } elseif( $databaseClasses['MySQLPDODatabase']['supported'] ) { - $type = $_REQUEST['db']['type'] = 'MySQLPDODatabase'; - } elseif( $databaseClasses['MySQLDatabase']['supported'] ) { - $type = $_REQUEST['db']['type'] = 'MySQLDatabase'; - } else { - // handle error - } - } - - // Disabled inputs don't submit anything - we need to use the environment (except the database name) - if($usingEnv) { - $_REQUEST['db'][$type] = $databaseConfig = array( - "type" => getenv('SS_DATABASE_CLASS') ?: $type, - "server" => getenv('SS_DATABASE_SERVER') ?: "localhost", - "username" => getenv('SS_DATABASE_USERNAME') ?: "root", - "password" => getenv('SS_DATABASE_PASSWORD') ?: "", - "database" => $_REQUEST['db'][$type]['database'], - ); - - } else { - // Normal behaviour without the environment - $databaseConfig = $_REQUEST['db'][$type]; - $databaseConfig['type'] = $type; - } -} else { - if($type = getenv('SS_DATABASE_CLASS')) { - $_REQUEST['db']['type'] = $type; - } elseif( $databaseClasses['MySQLPDODatabase']['supported'] ) { - $type = $_REQUEST['db']['type'] = 'MySQLPDODatabase'; - } elseif( $databaseClasses['MySQLDatabase']['supported'] ) { - $type = $_REQUEST['db']['type'] = 'MySQLDatabase'; - } else { - // handle error - } - $_REQUEST['db'][$type] = $databaseConfig = array( - "type" => $type, - "server" => getenv('SS_DATABASE_SERVER') ?: "localhost", - "username" => getenv('SS_DATABASE_USERNAME') ?: "root", - "password" => getenv('SS_DATABASE_PASSWORD') ?: "", - "database" => isset($_SERVER['argv'][2]) ? $_SERVER['argv'][2] : "SS_mysite", - ); -} - -if(isset($_REQUEST['admin'])) { - // Disabled inputs don't submit anything - we need to use the environment (except the database name) - if($usingEnv) { - $_REQUEST['admin'] = $adminConfig = array( - 'username' => getenv('SS_DEFAULT_ADMIN_USERNAME') ?: 'admin', - 'password' => getenv('SS_DEFAULT_ADMIN_PASSWORD') ?: '', - ); - } else { - $adminConfig = $_REQUEST['admin']; - } -} else { - $_REQUEST['admin'] = $adminConfig = array( - 'username' => getenv('SS_DEFAULT_ADMIN_USERNAME') ?: 'admin', - 'password' => getenv('SS_DEFAULT_ADMIN_PASSWORD') ?: '', - ); -} - -$alreadyInstalled = false; -if(file_exists('mysite/_config.php')) { - // Find the $database variable in the relevant config file without having to execute the config file - if(preg_match("/\\\$database\s*=\s*[^\n\r]+[\n\r]/", file_get_contents("mysite/_config.php"), $parts)) { - eval($parts[0]); - if(!empty($database)) { - $alreadyInstalled = true; - } - // Assume that if $databaseConfig is defined in mysite/_config.php, then a non-environment-based installation has - // already gone ahead - } else if(preg_match("/\\\$databaseConfig\s*=\s*[^\n\r]+[\n\r]/", file_get_contents("mysite/_config.php"), $parts)) { - $alreadyInstalled = true; - } -} - -if(file_exists(FRAMEWORK_NAME . '/silverstripe_version')) { - $silverstripe_version = file_get_contents(FRAMEWORK_NAME . '/silverstripe_version'); -} else { - $silverstripe_version = "unknown"; -} - -// Check requirements -$req = new InstallRequirements(); -$req->check(); - -$webserverConfigFile = ''; -if($req->isIIS()) { - $webserverConfigFile = 'web.config'; -} else { - $webserverConfigFile = '.htaccess'; -} - -if($req->hasErrors()) { - $hasErrorOtherThanDatabase = true; - $phpIniLocation = php_ini_loaded_file(); -} - -if($databaseConfig) { - $dbReq = new InstallRequirements(); - $dbReq->checkDatabase($databaseConfig); -} - -if($adminConfig) { - $adminReq = new InstallRequirements(); - $adminReq->checkAdminConfig($adminConfig); -} - -// Actual processor -$installFromCli = (isset($_SERVER['argv'][1]) && $_SERVER['argv'][1] == 'install'); - -// CLI-install error message. exit(1) will halt any makefile. -if($installFromCli && ($req->hasErrors() || $dbReq->hasErrors())) { - echo "Cannot install due to errors:\n"; - $req->listErrors(); - $dbReq->listErrors(); - exit(1); -} - -if((isset($_REQUEST['go']) || $installFromCli) && !$req->hasErrors() && !$dbReq->hasErrors() && $adminConfig['username'] && $adminConfig['password']) { - // Confirm before reinstalling - if(!$installFromCli && $alreadyInstalled) { - include(__DIR__ . '/config-form.html'); - - } else { - $inst = new Installer(); - if($_REQUEST) $inst->install($_REQUEST); - else $inst->install(array( - 'db' => $databaseConfig, - 'admin' => $adminConfig, - )); - } - -// Show the config form -} else { - include(__DIR__ . '/config-form.html'); -} - -/** - * This class checks requirements - * Each of the requireXXX functions takes an argument which gives a user description of the test. - * It's an array of 3 parts: - * $description[0] - The test catetgory - * $description[1] - The test title - * $description[2] - The test error to show, if it goes wrong - */ -class InstallRequirements { - var $errors, $warnings, $tests; - - /** - * Check the database configuration. These are done one after another - * starting with checking the database function exists in PHP, and - * continuing onto more difficult checks like database permissions. - * - * @param array $databaseConfig The list of database parameters - * @return boolean Validity of database configuration details - */ - public function checkDatabase($databaseConfig) { - // Check if support is available - if(!$this->requireDatabaseFunctions( - $databaseConfig, - array( - "Database Configuration", - "Database support", - "Database support in PHP", - $this->getDatabaseTypeNice($databaseConfig['type']) - ) - )) return false; - - // Check if the server is available - $usePath = !empty($databaseConfig['path']) && empty($databaseConfig['server']); - if(!$this->requireDatabaseServer( - $databaseConfig, - array( - "Database Configuration", - "Database server", - $usePath - ? "I couldn't write to path '$databaseConfig[path]'" - : "I couldn't find a database server on '$databaseConfig[server]'", - $usePath ? $databaseConfig['path'] : $databaseConfig['server'] - ) - )) return false; - - // Check if the connection credentials allow access to the server / database - if(!$this->requireDatabaseConnection( - $databaseConfig, - array( - "Database Configuration", - "Database access credentials", - "That username/password doesn't work" - ) - )) return false; - - // Check the necessary server version is available - if(!$this->requireDatabaseVersion( - $databaseConfig, - array( - "Database Configuration", - "Database server version requirement", - '', - 'Version ' . $this->getDatabaseConfigurationHelper($databaseConfig['type'])->getDatabaseVersion($databaseConfig) - ) - )) return false; - - // Check that database creation permissions are available - if(!$this->requireDatabaseOrCreatePermissions( - $databaseConfig, - array( - "Database Configuration", - "Can I access/create the database", - "I can't create new databases and the database '$databaseConfig[database]' doesn't exist" - ) - )) return false; - - // Check alter permission (necessary to create tables etc) - if(!$this->requireDatabaseAlterPermissions( - $databaseConfig, - array( - "Database Configuration", - "Can I ALTER tables", - "I don't have permission to ALTER tables" - ) - )) return false; - - // Success! - return true; - } - - public function checkAdminConfig($adminConfig) { - if(!$adminConfig['username']) { - $this->error(array('', 'Please enter a username!')); - } - if(!$adminConfig['password']) { - $this->error(array('', 'Please enter a password!')); - } - } - - /** - * Check if the web server is IIS and version greater than the given version. - * - * @param int $fromVersion - * @return bool - */ - public function isIIS($fromVersion = 7) { - if(strpos($this->findWebserver(), 'IIS/') === false) { - return false; - } - return substr(strstr($this->findWebserver(), '/'), -3, 1) >= $fromVersion; - } - - public function isApache() { - if(strpos($this->findWebserver(), 'Apache') !== false) { - return true; - } else { - return false; - } - } - - /** - * Find the webserver software running on the PHP host. - * @return string|boolean 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 - */ - public function check() { - $this->errors = null; - $isApache = $this->isApache(); - $isIIS = $this->isIIS(); - $webserver = $this->findWebserver(); - - $this->requirePHPVersion('5.5.0', '5.5.0', array( - "PHP Configuration", - "PHP5 installed", - null, - "PHP version " . phpversion() - )); - - // Check that we can identify the root folder successfully - $this->requireFile(FRAMEWORK_NAME . '/src/Dev/Install/config-form.html', array("File permissions", - "Does the webserver know where files are stored?", - "The webserver isn't letting me identify where files are stored.", - $this->getBaseDir() - )); - - $this->requireModule('mysite', array("File permissions", "mysite/ directory exists?")); - $this->requireModule(FRAMEWORK_NAME, array("File permissions", FRAMEWORK_NAME . "/ directory exists?")); - - if($isApache) { - $this->checkApacheVersion(array( - "Webserver Configuration", - "Webserver is not Apache 1.x", "SilverStripe requires Apache version 2 or greater", - $webserver - )); - $this->requireWriteable('.htaccess', array("File permissions", "Is the .htaccess file writeable?", null)); - } elseif($isIIS) { - $this->requireWriteable('web.config', array("File permissions", "Is the web.config file writeable?", null)); - } - - $this->requireWriteable('mysite/_config.php', array( - "File permissions", - "Is the mysite/_config.php file writeable?", - null - )); - - $this->requireWriteable('mysite/_config/config.yml', array( - "File permissions", - "Is the mysite/_config/config.yml file writeable?", - null - )); - - if(!$this->checkModuleExists('cms')) { - $this->requireWriteable('mysite/code/RootURLController.php', array( - "File permissions", - "Is the mysite/code/RootURLController.php file writeable?", - null - )); - } - $this->requireWriteable('assets', array("File permissions", "Is the assets/ directory writeable?", null)); - - try { - $tempFolder = getTempFolder(); - } catch(Exception $e) { - $tempFolder = false; - } - - $this->requireTempFolder(array('File permissions', 'Is a temporary directory available?', null, $tempFolder)); - if($tempFolder) { - // in addition to the temp folder being available, check it is writable - $this->requireWriteable($tempFolder, array( - "File permissions", - sprintf("Is the temporary directory writeable?", $tempFolder), - null - ), true); - } - - // Check for web server, unless we're calling the installer from the command-line - $this->isRunningWebServer(array("Webserver Configuration", "Server software", "Unknown", $webserver)); - - if($isApache) { - $this->requireApacheRewriteModule('mod_rewrite', array( - "Webserver Configuration", - "URL rewriting support", - "You need mod_rewrite to use friendly URLs with SilverStripe, but it is not enabled." - )); - } elseif($isIIS) { - $this->requireIISRewriteModule('IIS_UrlRewriteModule', array( - "Webserver Configuration", - "URL rewriting support", - "You need to enable the IIS URL Rewrite Module to use friendly URLs with SilverStripe, " - . "but it is not installed or enabled. Download it for IIS 7 from http://www.iis.net/expand/URLRewrite" - )); - } else { - $this->warning(array( - "Webserver Configuration", - "URL rewriting support", - "I can't tell whether any rewriting module is running. You may need to configure a rewriting rule yourself.")); - } - - $this->requireServerVariables(array('SCRIPT_NAME', 'HTTP_HOST', 'SCRIPT_FILENAME'), array( - "Webserver Configuration", - "Recognised webserver", - "You seem to be using an unsupported webserver. " - . "The server variables SCRIPT_NAME, HTTP_HOST, SCRIPT_FILENAME need to be set." - )); - - $this->requirePostSupport(array( - "Webserver Configuration", - "POST Support", - 'I can\'t find $_POST, make sure POST is enabled.' - )); - - // Check for GD support - if(!$this->requireFunction("imagecreatetruecolor", array( - "PHP Configuration", - "GD2 support", - "PHP must have GD version 2." - ))) { - $this->requireFunction("imagecreate", array( - "PHP Configuration", - "GD2 support", - "GD support for PHP not included." - )); - } - - // Check for XML support - $this->requireFunction('xml_set_object', array( - "PHP Configuration", - "XML support", - "XML support not included in PHP." - )); - $this->requireClass('DOMDocument', array( - "PHP Configuration", - "DOM/XML support", - "DOM/XML support not included in PHP." - )); - $this->requireFunction('simplexml_load_file', array( - 'PHP Configuration', - 'SimpleXML support', - 'SimpleXML support not included in PHP.' - )); - - // Check for token_get_all - $this->requireFunction('token_get_all', array( - "PHP Configuration", - "Tokenizer support", - "Tokenizer support not included in PHP." - )); - - // Check for CType support - $this->requireFunction('ctype_digit', array( - 'PHP Configuration', - 'CType support', - 'CType support not included in PHP.' - )); - - // Check for session support - $this->requireFunction('session_start', array( - 'PHP Configuration', - 'Session support', - 'Session support not included in PHP.' - )); - - // Check for iconv support - $this->requireFunction('iconv', array( - 'PHP Configuration', - 'iconv support', - 'iconv support not included in PHP.' - )); - - // Check for hash support - $this->requireFunction('hash', array('PHP Configuration', 'hash support', 'hash support not included in PHP.')); - - // Check for mbstring support - $this->requireFunction('mb_internal_encoding', array( - 'PHP Configuration', - 'mbstring support', - 'mbstring support not included in PHP.' - )); - - // Check for Reflection support - $this->requireClass('ReflectionClass', array( - 'PHP Configuration', - 'Reflection support', - 'Reflection support not included in PHP.' - )); - - // Check for Standard PHP Library (SPL) support - $this->requireFunction('spl_classes', array( - 'PHP Configuration', - 'SPL support', - 'Standard PHP Library (SPL) not included in PHP.' - )); - - $this->requireDateTimezone(array( - 'PHP Configuration', - 'date.timezone setting and validity', - 'date.timezone option in php.ini must be set correctly.', - ini_get('date.timezone') - )); - - $this->suggestClass('finfo', array( - 'PHP Configuration', - 'fileinfo support', - 'fileinfo should be enabled in PHP. SilverStripe uses it for MIME type detection of files. ' - . 'SilverStripe will still operate, but email attachments and sending files to browser ' - . '(e.g. export data to CSV) may not work correctly without finfo.' - )); - - $this->suggestFunction('curl_init', array( - 'PHP Configuration', - 'curl support', - 'curl should be enabled in PHP. SilverStripe uses it for consuming web services' - . ' via the RestfulService class and many modules rely on it.' - )); - - $this->suggestClass('tidy', array( - 'PHP Configuration', - 'tidy support', - 'Tidy provides a library of code to clean up your html. ' - . 'SilverStripe will operate fine without tidy but HTMLCleaner will not be effective.' - )); - - $this->suggestPHPSetting('asp_tags', array(false), array( - 'PHP Configuration', - 'asp_tags option', - 'This should be turned off as it can cause issues with SilverStripe' - )); - $this->requirePHPSetting('magic_quotes_gpc', array(false), array( - 'PHP Configuration', - 'magic_quotes_gpc option', - 'This should be turned off, as it can cause issues with cookies. ' - . 'More specifically, unserializing data stored in cookies.' - )); - $this->suggestPHPSetting('display_errors', array(false), array( - 'PHP Configuration', - 'display_errors option', - 'Unless you\'re in a development environment, this should be turned off, ' - . 'as it can expose sensitive data to website users.' - )); - // on some weirdly configured webservers arg_separator.output is set to & - // which will results in links like ?param=value&foo=bar which will not be i - $this->suggestPHPSetting('arg_separator.output', array('&', ''), array( - 'PHP Configuration', - 'arg_separator.output option', - 'This option defines how URL parameters are concatenated. ' - . 'If not set to \'&\' this may cause issues with URL GET parameters' - )); - - // always_populate_raw_post_data should be set to -1 if PHP < 7.0 - if (version_compare(PHP_VERSION, '7.0.0', '<')) { - $this->suggestPHPSetting('always_populate_raw_post_data', ['-1'], [ - 'PHP Configuration', - 'always_populate_raw_post_data option', - 'It\'s highly recommended to set this to \'-1\' in php 5.x, as $HTTP_RAW_POST_DATA is removed in php 7' - ]); - } - - // Check memory allocation - $this->requireMemory(32 * 1024 * 1024, 64 * 1024 * 1024, array( - "PHP Configuration", - "Memory allocation (PHP config option 'memory_limit')", - "SilverStripe needs a minimum of 32M allocated to PHP, but recommends 64M.", - ini_get("memory_limit") - )); - - return $this->errors; - } - - public function suggestPHPSetting($settingName, $settingValues, $testDetails) { - $this->testing($testDetails); - - // special case for display_errors, check the original value before - // it was changed at the start of this script. - if($settingName == 'display_errors') { - global $originalDisplayErrorsValue; - $val = $originalDisplayErrorsValue; - } else { - $val = ini_get($settingName); - } - - if(!in_array($val, $settingValues) && $val != $settingValues) { - $this->warning($testDetails, "$settingName is set to '$val' in php.ini. $testDetails[2]"); - } - } - - public function requirePHPSetting($settingName, $settingValues, $testDetails) { - $this->testing($testDetails); - - $val = ini_get($settingName); - if(!in_array($val, $settingValues) && $val != $settingValues) { - $this->error($testDetails, "$settingName is set to '$val' in php.ini. $testDetails[2]"); - } - } - - public function suggestClass($class, $testDetails) { - $this->testing($testDetails); - - if(!class_exists($class)) { - $this->warning($testDetails); - } - } - - public function suggestFunction($class, $testDetails) { - $this->testing($testDetails); - - if(!function_exists($class)) { - $this->warning($testDetails); - } - } - - public function requireDateTimezone($testDetails) { - $this->testing($testDetails); - - $result = ini_get('date.timezone') && in_array(ini_get('date.timezone'), timezone_identifiers_list()); - if(!$result) { - $this->error($testDetails); - } - } - - public function requireMemory($min, $recommended, $testDetails) { - $_SESSION['forcemem'] = false; - - $mem = $this->getPHPMemory(); - if($mem < (64 * 1024 * 1024)) { - ini_set('memory_limit', '64M'); - $mem = $this->getPHPMemory(); - $testDetails[3] = ini_get("memory_limit"); - } - - $this->testing($testDetails); - - if($mem < $min && $mem > 0) { - $message = $testDetails[2] . " You only have " . ini_get("memory_limit") . " allocated"; - $this->error($testDetails, $message); - return false; - } else if($mem < $recommended && $mem > 0) { - $message = $testDetails[2] . " You only have " . ini_get("memory_limit") . " allocated"; - $this->warning($testDetails, $message); - return false; - } elseif($mem == 0) { - $message = $testDetails[2] . " We can't determine how much memory you have allocated. " - . "Install only if you're sure you've allocated at least 20 MB."; - $this->warning($testDetails, $message); - return false; - } - return true; - } - - public function getPHPMemory() { - $memString = ini_get("memory_limit"); - - switch(strtolower(substr($memString, -1))) { - case "k": - return round(substr($memString, 0, -1) * 1024); - - case "m": - return round(substr($memString, 0, -1) * 1024 * 1024); - - case "g": - return round(substr($memString, 0, -1) * 1024 * 1024 * 1024); - - default: - return round($memString); - } - } - - public function listErrors() { - if($this->errors) { - echo "

      The following problems are preventing me from installing SilverStripe CMS:

      \n\n"; - foreach($this->errors as $error) { - echo "
    • " . htmlentities(implode(", ", $error), ENT_COMPAT, 'UTF-8') . "
    • \n"; - } - } - } - - public function showTable($section = null) { - if($section) { - $tests = $this->tests[$section]; - $id = strtolower(str_replace(' ', '_', $section)); - echo ""; - foreach($tests as $test => $result) { - echo ""; - } - echo "
      $test" - . nl2br(htmlentities($result[1], ENT_COMPAT, 'UTF-8')) . "
      "; - - } else { - foreach($this->tests as $section => $tests) { - $failedRequirements = 0; - $warningRequirements = 0; - - $output = ""; - - foreach($tests as $test => $result) { - if(isset($result['0'])) { - switch($result['0']) { - case 'error': - $failedRequirements++; - break; - case 'warning': - $warningRequirements++; - break; - } - } - $output .= "$test" - . nl2br(htmlentities($result[1], ENT_COMPAT, 'UTF-8')) . ""; - } - $className = "good"; - $text = "All Requirements Pass"; - $pluralWarnings = ($warningRequirements == 1) ? 'Warning' : 'Warnings'; - - if($failedRequirements > 0) { - $className = "error"; - $pluralWarnings = ($warningRequirements == 1) ? 'Warning' : 'Warnings'; - - $text = $failedRequirements . ' Failed and ' . $warningRequirements . ' ' . $pluralWarnings; - } else if($warningRequirements > 0) { - $className = "warning"; - $text = "All Requirements Pass but " . $warningRequirements . ' ' . $pluralWarnings; - } - - echo "
      $section Show All Requirements $text
      "; - echo ""; - echo $output; - echo "
      "; - } - } - } - - public function requireFunction($funcName, $testDetails) { - $this->testing($testDetails); - - if(!function_exists($funcName)) { - $this->error($testDetails); - return false; - } - return true; - } - - public function requireClass($className, $testDetails) { - $this->testing($testDetails); - if(!class_exists($className)) { - $this->error($testDetails); - return false; - } - return true; - } - - /** - * Require that the given class doesn't exist - * - * @param array $classNames - * @param array $testDetails - * @return bool - */ - public function requireNoClasses($classNames, $testDetails) { - $this->testing($testDetails); - $badClasses = array(); - foreach($classNames as $className) { - if(class_exists($className)) { - $badClasses[] = $className; - } - } - if($badClasses) { - $message = $testDetails[2] . ". The following classes are at fault: " . implode(', ', $badClasses); - $this->error($testDetails, $message); - return false; - } - return true; - } - - public function checkApacheVersion($testDetails) { - $this->testing($testDetails); - - $is1pointx = preg_match('#Apache[/ ]1\.#', $testDetails[3]); - if($is1pointx) { - $this->error($testDetails); - } - - return true; - } - - public function requirePHPVersion($recommendedVersion, $requiredVersion, $testDetails) { - $this->testing($testDetails); - - $installedVersion = phpversion(); - - if(version_compare($installedVersion, $requiredVersion, '<')) { - $message = "SilverStripe requires PHP version $requiredVersion or later.\n - PHP version $installedVersion is currently installed.\n - While SilverStripe requires at least PHP version $requiredVersion, upgrading to $recommendedVersion or later is recommended.\n - If you are installing SilverStripe on a shared web server, please ask your web hosting provider to upgrade PHP for you."; - $this->error($testDetails, $message); - return false; - } - - if(version_compare($installedVersion, $recommendedVersion, '<')) { - $message = "PHP version $installedVersion is currently installed.\n - Upgrading to at least PHP version $recommendedVersion is recommended.\n - SilverStripe should run, but you may run into issues. Future releases may require a later version of PHP.\n"; - $this->warning($testDetails, $message); - return false; - } - - return true; - } - - /** - * Check that a module exists - * - * @param string $dirname - * @return bool - */ - public function checkModuleExists($dirname) { - $path = $this->getBaseDir() . $dirname; - return file_exists($path) && ($dirname == 'mysite' || file_exists($path . '/_config.php')); - } - - /** - * The same as {@link requireFile()} but does additional checks - * to ensure the module directory is intact. - * - * @param string $dirname - * @param array $testDetails - */ - public function requireModule($dirname, $testDetails) { - $this->testing($testDetails); - $path = $this->getBaseDir() . $dirname; - 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') { - $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); - } - } - - public function requireFile($filename, $testDetails) { - $this->testing($testDetails); - $filename = $this->getBaseDir() . $filename; - if(!file_exists($filename)) { - $testDetails[2] .= " (file '$filename' not found)"; - $this->error($testDetails); - } - } - - public function requireWriteable($filename, $testDetails, $absolute = false) { - $this->testing($testDetails); - - if($absolute) { - $filename = str_replace('/', DIRECTORY_SEPARATOR, $filename); - } else { - $filename = $this->getBaseDir() . str_replace('/', DIRECTORY_SEPARATOR, $filename); - } - - if(file_exists($filename)) $isWriteable = is_writeable($filename); - else $isWriteable = is_writeable(dirname($filename)); - - if(!$isWriteable) { - if(function_exists('posix_getgroups')) { - $userID = posix_geteuid(); - $user = posix_getpwuid($userID); - - $currentOwnerID = fileowner(file_exists($filename) ? $filename : dirname($filename)); - $currentOwner = posix_getpwuid($currentOwnerID); - - $testDetails[2] .= "User '$user[name]' needs to be able to write to this file:\n$filename\n\nThe " - . "file is currently owned by '$currentOwner[name]'. "; - - if($user['name'] == $currentOwner['name']) { - $testDetails[2] .= "We recommend that you make the file writeable."; - } else { - - $groups = posix_getgroups(); - $groupList = array(); - foreach($groups as $group) { - $groupInfo = posix_getgrgid($group); - if(in_array($currentOwner['name'], $groupInfo['members'])) $groupList[] = $groupInfo['name']; - } - if($groupList) { - $testDetails[2] .= " We recommend that you make the file group-writeable " - . "and change the group to one of these groups:\n - " . implode("\n - ", $groupList) - . "\n\nFor example:\nchmod g+w $filename\nchgrp " . $groupList[0] . " $filename"; - } else { - $testDetails[2] .= " There is no user-group that contains both the web-server user and the " - . "owner of this file. Change the ownership of the file, create a new group, or " - . "temporarily make the file writeable by everyone during the install process."; - } - } - - } else { - $testDetails[2] .= "The webserver user needs to be able to write to this file:\n$filename"; - } - - $this->error($testDetails); - } - } - - public function requireTempFolder($testDetails) { - $this->testing($testDetails); - - try { - $tempFolder = getTempFolder(); - } catch(Exception $e) { - $tempFolder = false; - } - - if(!$tempFolder) { - $testDetails[2] = "Permission problem gaining access to a temp directory. " . - "Please create a folder named silverstripe-cache in the base directory " . - "of the installation and ensure it has the adequate permissions."; - $this->error($testDetails); - } - } - - public function requireApacheModule($moduleName, $testDetails) { - $this->testing($testDetails); - if(!in_array($moduleName, apache_get_modules())) { - $this->error($testDetails); - return false; - } else { - return true; - } - } - - public function testApacheRewriteExists($moduleName = 'mod_rewrite') { - if(function_exists('apache_get_modules') && in_array($moduleName, apache_get_modules())) { - return true; - } elseif(isset($_SERVER['HTTP_MOD_REWRITE']) && $_SERVER['HTTP_MOD_REWRITE'] == 'On') { - return true; - } elseif(isset($_SERVER['REDIRECT_HTTP_MOD_REWRITE']) && $_SERVER['REDIRECT_HTTP_MOD_REWRITE'] == 'On') { - return true; - } else { - 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); - if($this->testApacheRewriteExists()) { - return true; - } else { - $this->warning($testDetails); - return false; - } - } - - /** - * 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); - if($this->testIISRewriteModuleExists()) { - return true; - } else { - $this->warning($testDetails); - return false; - } - } - - 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); - $helper = $this->getDatabaseConfigurationHelper($databaseConfig['type']); - if (!$helper) { - $this->error($testDetails, "Couldn't load database helper code for ". $databaseConfig['type']); - return false; - } - $result = $helper->requireDatabaseFunctions($databaseConfig); - if($result) { - return true; - } else { - $this->error($testDetails); - return false; - } - } - - public function requireDatabaseConnection($databaseConfig, $testDetails) { - $this->testing($testDetails); - $helper = $this->getDatabaseConfigurationHelper($databaseConfig['type']); - $result = $helper->requireDatabaseConnection($databaseConfig); - if($result['success']) { - return true; - } else { - $testDetails[2] .= ": " . $result['error']; - $this->error($testDetails); - return false; - } - } - - public function requireDatabaseVersion($databaseConfig, $testDetails) { - $this->testing($testDetails); - $helper = $this->getDatabaseConfigurationHelper($databaseConfig['type']); - if(method_exists($helper, 'requireDatabaseVersion')) { - $result = $helper->requireDatabaseVersion($databaseConfig); - if($result['success']) { - return true; - } else { - $testDetails[2] .= $result['error']; - $this->warning($testDetails); - return false; - } - } - // Skipped test because this database has no required version - return true; - } - - public function requireDatabaseServer($databaseConfig, $testDetails) { - $this->testing($testDetails); - $helper = $this->getDatabaseConfigurationHelper($databaseConfig['type']); - $result = $helper->requireDatabaseServer($databaseConfig); - if($result['success']) { - return true; - } else { - $message = $testDetails[2] . ": " . $result['error']; - $this->error($testDetails, $message); - return false; - } - } - - public function requireDatabaseOrCreatePermissions($databaseConfig, $testDetails) { - $this->testing($testDetails); - $helper = $this->getDatabaseConfigurationHelper($databaseConfig['type']); - $result = $helper->requireDatabaseOrCreatePermissions($databaseConfig); - if($result['success']) { - if($result['alreadyExists']) { - $testDetails[3] = "Database $databaseConfig[database]"; - } else { - $testDetails[3] = "Able to create a new database"; - } - $this->testing($testDetails); - return true; - } else { - if(empty($result['cannotCreate'])) { - $message = $testDetails[2] . ". Please create the database manually."; - } else { - $message = $testDetails[2] . " (user '$databaseConfig[username]' doesn't have CREATE DATABASE permissions.)"; - } - - $this->error($testDetails, $message); - return false; - } - } - - public function requireDatabaseAlterPermissions($databaseConfig, $testDetails) { - $this->testing($testDetails); - $helper = $this->getDatabaseConfigurationHelper($databaseConfig['type']); - $result = $helper->requireDatabaseAlterPermissions($databaseConfig); - if ($result['success']) { - return true; - } else { - $message = "Silverstripe cannot alter tables. This won't prevent installation, however it may " - . "cause issues if you try to run a /dev/build once installed."; - $this->warning($testDetails, $message); - return false; - } - } - - public function requireServerVariables($varNames, $testDetails) { - $this->testing($testDetails); - $missing = array(); - - foreach($varNames as $varName) { - if(!isset($_SERVER[$varName]) || !$_SERVER[$varName]) { - $missing[] = '$_SERVER[' . $varName . ']'; - } - } - - if(!$missing) { - return true; - } - - $message = $testDetails[2] . " (the following PHP variables are missing: " . implode(", ", $missing) . ")"; - $this->error($testDetails, $message); - return false; - } - - - public function requirePostSupport($testDetails) { - $this->testing($testDetails); - - if(!isset($_POST)) { - $this->error($testDetails); - - return false; - } - - return true; - } - - public function isRunningWebServer($testDetails) { - $this->testing($testDetails); - if($testDetails[3]) { - return true; - } else { - $this->warning($testDetails); - return false; - } - } - - // Must be PHP4 compatible - var $baseDir; - - public function getBaseDir() { - // Cache the value so that when the installer mucks with SCRIPT_FILENAME half way through, this method - // still returns the correct value. - if(!$this->baseDir) $this->baseDir = realpath(dirname($_SERVER['SCRIPT_FILENAME'])) . DIRECTORY_SEPARATOR; - return $this->baseDir; - } - - public function testing($testDetails) { - if(!$testDetails) return; - - $section = $testDetails[0]; - $test = $testDetails[1]; - - $message = "OK"; - if(isset($testDetails[3])) $message .= " ($testDetails[3])"; - - $this->tests[$section][$test] = array("good", $message); - } - - public function error($testDetails, $message = null) { - if (!is_array($testDetails)) { - throw new InvalidArgumentException("Invalid error"); - } - $section = $testDetails[0]; - $test = $testDetails[1]; - if (!$message && isset($testDetails[2])) { - $message = $testDetails[2]; - } - - $this->tests[$section][$test] = array("error", $message); - $this->errors[] = $testDetails; - } - - public function warning($testDetails, $message = null) { - if (!is_array($testDetails)) { - throw new InvalidArgumentException("Invalid warning"); - } - $section = $testDetails[0]; - $test = $testDetails[1]; - if (!$message && isset($testDetails[2])) { - $message = $testDetails[2]; - } - - $this->tests[$section][$test] = array("warning", $message); - $this->warnings[] = $testDetails; - } - - public function hasErrors() { - return sizeof($this->errors); - } - - public function hasWarnings() { - return sizeof($this->warnings); - } - -} - -class Installer extends InstallRequirements { - public function __construct() { - // Cache the baseDir value - $this->getBaseDir(); - } - - public function install($config) { - ?> - - - - Installing SilverStripe... - - - - -
      -
      -
      - - -

      SilverStripe

      -
      -
      -
      - - -
      - -
      -
      -

      Installing SilverStripe...

      - -

      I am now running through the installation steps (this should take about 30 seconds)

      - -

      If you receive a fatal error, refresh this page to continue the installation

      -
        -findWebserver(); - $isIIS = $this->isIIS(); - $isApache = $this->isApache(); - - flush(); - - if(isset($config['stats'])) { - if(file_exists(FRAMEWORK_NAME . '/silverstripe_version')) { - $silverstripe_version = file_get_contents(FRAMEWORK_NAME . '/silverstripe_version'); - } else { - $silverstripe_version = "unknown"; - } - - $phpVersion = urlencode(phpversion()); - $encWebserver = urlencode($webserver); - $dbType = $config['db']['type']; - - // Try to determine the database version from the helper - $databaseVersion = $config['db']['type']; - $helper = $this->getDatabaseConfigurationHelper($dbType); - if($helper && method_exists($helper, 'getDatabaseVersion')) { - $versionConfig = $config['db'][$dbType]; - $versionConfig['type'] = $dbType; - $databaseVersion = urlencode($dbType . ': ' . $helper->getDatabaseVersion($versionConfig)); - } - - $url = "http://ss2stat.silverstripe.com/Installation/add?SilverStripe=$silverstripe_version&PHP=$phpVersion&Database=$databaseVersion&WebServer=$encWebserver"; - - if(isset($_SESSION['StatsID']) && $_SESSION['StatsID']) { - $url .= '&ID=' . $_SESSION['StatsID']; - } - - @$_SESSION['StatsID'] = file_get_contents($url); - } - - if(file_exists('mysite/_config.php')) { - // 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'); - fclose($fh); - } - - // Escape user input for safe insertion into PHP file - $theme = isset($_POST['template']) ? addcslashes($_POST['template'], "\'") : 'simple'; - $locale = isset($_POST['locale']) ? addcslashes($_POST['locale'], "\'") : 'en_US'; - $type = addcslashes($config['db']['type'], "\'"); - $dbConfig = $config['db'][$type]; - foreach ($dbConfig as &$configValue) { - $configValue = addcslashes($configValue, "\\\'"); - } - if(!isset($dbConfig['path'])) $dbConfig['path'] = ''; - if(!$dbConfig) { - echo "

        Bad config submitted

        ";
        -			print_r($config);
        -			echo "
        "; - die(); - } - - // Write the config file - global $usingEnv; - if($usingEnv) { - $this->statusMessage("Setting up 'mysite/_config.php' for use with environment variables..."); - $this->writeToFile("mysite/_config.php", <<statusMessage("Setting up 'mysite/_config.php'..."); - // Create databaseConfig - $lines = array( - $lines[] = "\t'type' => '$type'" - ); - foreach($dbConfig as $key => $value) { - $lines[] = "\t'{$key}' => '$value'"; - } - $databaseConfigContent = implode(",\n", $lines); - $this->writeToFile("mysite/_config.php", <<statusMessage("Setting up 'mysite/_config/config.yml'"); - $this->writeToFile("mysite/_config/config.yml", <<checkModuleExists('cms')) { - $this->writeToFile("mysite/code/RootURLController.php", <<Your site is now set up. Start adding controllers to mysite to get started."; - } - -} -PHP - ); - } - - // Write the appropriate web server configuration file for rewriting support - if($this->hasRewritingCapability()) { - if($isApache) { - $this->statusMessage("Setting up '.htaccess' file..."); - $this->createHtaccess(); - } elseif($isIIS) { - $this->statusMessage("Setting up 'web.config' file..."); - $this->createWebConfig(); - } - } - - // Load the SilverStripe runtime - $_SERVER['SCRIPT_FILENAME'] = dirname(realpath($_SERVER['SCRIPT_FILENAME'])) . '/' . FRAMEWORK_NAME . '/main.php'; - chdir(FRAMEWORK_NAME); - - // Rebuild the manifest - $_GET['flush'] = true; - // Show errors as if you're in development mode - $_SESSION['isDev'] = 1; - - $this->statusMessage("Building database schema..."); - - require_once 'Core/Core.php'; - - // Build database - $request = new HTTPRequest('GET', '/'); - $request->setSession(new Session([])); - $con = new Controller(); - $con->setRequest($request); - $con->pushCurrent(); - - global $databaseConfig; - DB::connect($databaseConfig); - - $dbAdmin = new DatabaseAdmin(); - $dbAdmin->doInit(); - - $dbAdmin->doBuild(true); - - // Create default administrator user and group in database - // (not using Security::setDefaultAdmin()) - $adminMember = DefaultAdminService::singleton()->findOrCreateDefaultAdmin(); - $adminMember->Email = $config['admin']['username']; - $adminMember->Password = $config['admin']['password']; - $adminMember->PasswordEncryption = Security::config()->encryption_algorithm; - - try { - $this->statusMessage('Creating default CMS admin account...'); - $adminMember->write(); - } catch(Exception $e) { - $this->statusMessage( - sprintf('Warning: Default CMS admin account could not be created (error: %s)', $e->getMessage()) - ); - } - - $_SESSION['username'] = $config['admin']['username']; - $_SESSION['password'] = $config['admin']['password']; - - if(!$this->errors) { - if(isset($_SERVER['HTTP_HOST']) && $this->hasRewritingCapability()) { - $this->statusMessage("Checking that friendly URLs work..."); - $this->checkRewrite(); - } else { - require_once 'Core/Startup/ParameterConfirmationToken.php'; - $token = new ParameterConfirmationToken('flush'); - $params = http_build_query($token->params()); - - $destinationURL = 'index.php/' . - ($this->checkModuleExists('cms') ? "home/successfullyinstalled?$params" : "?$params"); - - echo <<SilverStripe successfully installed; I am now redirecting you to your SilverStripe site... - - -HTML; - } - } - - return $this->errors; - } - - public function writeToFile($filename, $content) { - $base = $this->getBaseDir(); - $this->statusMessage("Setting up $base$filename"); - - if((@$fh = fopen($base . $filename, 'wb')) && fwrite($fh, $content) && fclose($fh)) { - return true; - } - $this->error("Couldn't write to file $base$filename"); - return false; - } - - public function createHtaccess() { - $start = "### SILVERSTRIPE START ###\n"; - $end = "\n### SILVERSTRIPE END ###"; - - $base = dirname($_SERVER['SCRIPT_NAME']); - if(defined('DIRECTORY_SEPARATOR')) $base = str_replace(DIRECTORY_SEPARATOR, '/', $base); - else $base = str_replace("\\", '/', $base); - - if($base != '.') $baseClause = "RewriteBase '$base'\n"; - else $baseClause = ""; - if(strpos(strtolower(php_sapi_name()), "cgi") !== false) $cgiClause = "RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]\n"; - else $cgiClause = ""; - $modulePath = FRAMEWORK_NAME; - $rewrite = << - Order deny,allow - Deny from all - Allow from 127.0.0.1 - - -# Deny access to IIS configuration - - Order deny,allow - Deny from all - - -# Deny access to YAML configuration files which might include sensitive information - - Order allow,deny - Deny from all - - -# Route errors to static pages automatically generated by SilverStripe -ErrorDocument 404 /assets/error-404.html -ErrorDocument 500 /assets/error-500.html - - - - # Turn off index.php handling requests to the homepage fixes issue in apache >=2.4 - - DirectoryIndex disabled - - - SetEnv HTTP_MOD_REWRITE On - RewriteEngine On - $baseClause - $cgiClause - - # Deny access to potentially sensitive files and folders - RewriteRule ^vendor(/|$) - [F,L,NC] - RewriteRule silverstripe-cache(/|$) - [F,L,NC] - RewriteRule composer\.(json|lock) - [F,L,NC] - - # Process through SilverStripe if no file with the requested name exists. - # Pass through the original path as a query parameter, and retain the existing parameters. - RewriteCond %{REQUEST_URI} ^(.*)$ - RewriteCond %{REQUEST_FILENAME} !-f - RewriteRule .* $modulePath/main.php?url=%1 [QSA] - -TEXT; - - if(file_exists('.htaccess')) { - $htaccess = file_get_contents('.htaccess'); - - if(strpos($htaccess, '### SILVERSTRIPE START ###') === false && strpos($htaccess, '### SILVERSTRIPE END ###') === false) { - $htaccess .= "\n### SILVERSTRIPE START ###\n### SILVERSTRIPE END ###\n"; - } - - if(strpos($htaccess, '### SILVERSTRIPE START ###') !== false && strpos($htaccess, '### SILVERSTRIPE END ###') !== false) { - $start = substr($htaccess, 0, strpos($htaccess, '### SILVERSTRIPE START ###')) . "### SILVERSTRIPE START ###\n"; - $end = "\n" . substr($htaccess, strpos($htaccess, '### SILVERSTRIPE END ###')); - } - } - - $this->writeToFile('.htaccess', $start . $rewrite . $end); - } - - /** - * Writes basic configuration to the web.config for IIS - * so that rewriting capability can be use. - */ - public function createWebConfig() { - $modulePath = FRAMEWORK_NAME; - $content = << - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -TEXT; - - $this->writeToFile('web.config', $content); - } - - public function checkRewrite() { - require_once 'Core/Startup/ParameterConfirmationToken.php'; - $token = new ParameterConfirmationToken('flush'); - $params = http_build_query($token->params()); - - $destinationURL = str_replace('install.php', '', $_SERVER['SCRIPT_NAME']) . - ($this->checkModuleExists('cms') ? "home/successfullyinstalled?$params" : "?$params"); - - echo <<Testing... - - -HTML; - } - - public function var_export_array_nokeys($array) { - $retval = "array(\n"; - foreach($array as $item) { - $retval .= "\t'"; - $retval .= trim($item); - $retval .= "',\n"; - } - $retval .= ")"; - return $retval; - } - - /** - * Show an installation status message. - * The output differs depending on whether this is CLI or web based - * - * @param string $msg - */ - public function statusMessage($msg) { - echo "
      • $msg
      • \n"; - flush(); - } -} diff --git a/src/Dev/Install/install5.php b/src/Dev/Install/install5.php new file mode 100755 index 000000000..5694aa242 --- /dev/null +++ b/src/Dev/Install/install5.php @@ -0,0 +1,265 @@ + 'Afrikaans (South Africa)', + 'ar_EG' => 'Arabic (Egypt)', + 'hy_AM' => 'Armenian (Armenia)', + 'ast_ES' => 'Asturian (Spain)', + 'az_AZ' => 'Azerbaijani (Azerbaijan)', + 'bs_BA' => 'Bosnian (Bosnia and Herzegovina)', + 'bg_BG' => 'Bulgarian (Bulgaria)', + 'ca_ES' => 'Catalan (Spain)', + 'zh_CN' => 'Chinese (China)', + 'zh_TW' => 'Chinese (Taiwan)', + 'hr_HR' => 'Croatian (Croatia)', + 'cs_CZ' => 'Czech (Czech Republic)', + 'da_DK' => 'Danish (Denmark)', + 'nl_NL' => 'Dutch (Netherlands)', + 'en_GB' => 'English (United Kingdom)', + 'en_US' => 'English (United States)', + 'eo_XX' => 'Esperanto', + 'et_EE' => 'Estonian (Estonia)', + 'fo_FO' => 'Faroese (Faroe Islands)', + 'fi_FI' => 'Finnish (Finland)', + 'fr_FR' => 'French (France)', + 'de_DE' => 'German (Germany)', + 'el_GR' => 'Greek (Greece)', + 'he_IL' => 'Hebrew (Israel)', + 'hu_HU' => 'Hungarian (Hungary)', + 'is_IS' => 'Icelandic (Iceland)', + 'id_ID' => 'Indonesian (Indonesia)', + 'it_IT' => 'Italian (Italy)', + 'ja_JP' => 'Japanese (Japan)', + 'km_KH' => 'Khmer (Cambodia)', + 'lc_XX' => 'LOLCAT', + 'lv_LV' => 'Latvian (Latvia)', + 'lt_LT' => 'Lithuanian (Lithuania)', + 'ms_MY' => 'Malay (Malaysia)', + 'mi_NZ' => 'Maori (New Zealand)', + 'ne_NP' => 'Nepali (Nepal)', + 'nb_NO' => 'Norwegian', + 'fa_IR' => 'Persian (Iran)', + 'pl_PL' => 'Polish (Poland)', + 'pt_BR' => 'Portuguese (Brazil)', + 'pa_IN' => 'Punjabi (India)', + 'ro_RO' => 'Romanian (Romania)', + 'ru_RU' => 'Russian (Russia)', + 'sr_RS' => 'Serbian (Serbia)', + 'si_LK' => 'Sinhalese (Sri Lanka)', + 'sk_SK' => 'Slovak (Slovakia)', + 'sl_SI' => 'Slovenian (Slovenia)', + 'es_AR' => 'Spanish (Argentina)', + 'es_MX' => 'Spanish (Mexico)', + 'es_ES' => 'Spanish (Spain)', + 'sv_SE' => 'Swedish (Sweden)', + 'th_TH' => 'Thai (Thailand)', + 'tr_TR' => 'Turkish (Turkey)', + 'uk_UA' => 'Ukrainian (Ukraine)', + 'uz_UZ' => 'Uzbek (Uzbekistan)', + 'vi_VN' => 'Vietnamese (Vietnam)', +); + +// Discover which databases are available +DatabaseAdapterRegistry::autodiscover(); + +// Determine which external database modules are USABLE +$databaseClasses = DatabaseAdapterRegistry::get_adapters(); +foreach ($databaseClasses as $class => $details) { + $helper = DatabaseAdapterRegistry::getDatabaseConfigurationHelper($class); + $databaseClasses[$class]['hasModule'] = !empty($helper); +} + +// Load database config +if (isset($_REQUEST['db'])) { + if (isset($_REQUEST['db']['type'])) { + $type = $_REQUEST['db']['type']; + } else { + if ($type = getenv('SS_DATABASE_CLASS')) { + $_REQUEST['db']['type'] = $type; + } elseif ($databaseClasses['MySQLPDODatabase']['supported']) { + $type = $_REQUEST['db']['type'] = 'MySQLPDODatabase'; + } elseif ($databaseClasses['MySQLDatabase']['supported']) { + $type = $_REQUEST['db']['type'] = 'MySQLDatabase'; + } else { + // handle error + } + } + + // Disabled inputs don't submit anything - we need to use the environment (except the database name) + if ($usingEnv) { + $_REQUEST['db'][$type] = $databaseConfig = array( + "type" => getenv('SS_DATABASE_CLASS') ?: $type, + "server" => getenv('SS_DATABASE_SERVER') ?: "localhost", + "username" => getenv('SS_DATABASE_USERNAME') ?: "root", + "password" => getenv('SS_DATABASE_PASSWORD') ?: "", + "database" => $_REQUEST['db'][$type]['database'], + ); + } else { + // Normal behaviour without the environment + $databaseConfig = $_REQUEST['db'][$type]; + $databaseConfig['type'] = $type; + } +} else { + if ($type = getenv('SS_DATABASE_CLASS')) { + $_REQUEST['db']['type'] = $type; + } elseif ($databaseClasses['MySQLPDODatabase']['supported']) { + $type = $_REQUEST['db']['type'] = 'MySQLPDODatabase'; + } elseif ($databaseClasses['MySQLDatabase']['supported']) { + $type = $_REQUEST['db']['type'] = 'MySQLDatabase'; + } else { + // handle error + } + $_REQUEST['db'][$type] = $databaseConfig = array( + "type" => $type, + "server" => getenv('SS_DATABASE_SERVER') ?: "localhost", + "username" => getenv('SS_DATABASE_USERNAME') ?: "root", + "password" => getenv('SS_DATABASE_PASSWORD') ?: "", + "database" => isset($_SERVER['argv'][2]) ? $_SERVER['argv'][2] : "SS_mysite", + ); +} + +if (isset($_REQUEST['admin'])) { + // Disabled inputs don't submit anything - we need to use the environment (except the database name) + if ($usingEnv) { + $_REQUEST['admin'] = $adminConfig = array( + 'username' => getenv('SS_DEFAULT_ADMIN_USERNAME') ?: 'admin', + 'password' => getenv('SS_DEFAULT_ADMIN_PASSWORD') ?: '', + ); + } else { + $adminConfig = $_REQUEST['admin']; + } +} else { + $_REQUEST['admin'] = $adminConfig = array( + 'username' => getenv('SS_DEFAULT_ADMIN_USERNAME') ?: 'admin', + 'password' => getenv('SS_DEFAULT_ADMIN_PASSWORD') ?: '', + ); +} + +$alreadyInstalled = false; +if (file_exists('mysite/_config.php')) { + // Find the $database variable in the relevant config file without having to execute the config file + if (preg_match("/\\\$database\s*=\s*[^\n\r]+[\n\r]/", file_get_contents("mysite/_config.php"), $parts)) { + eval($parts[0]); + if (!empty($database)) { + $alreadyInstalled = true; + } + // Assume that if $databaseConfig is defined in mysite/_config.php, then a non-environment-based installation has + // already gone ahead + } elseif (preg_match( + "/\\\$databaseConfig\s*=\s*[^\n\r]+[\n\r]/", + file_get_contents("mysite/_config.php"), + $parts + )) { + $alreadyInstalled = true; + } +} + +if (file_exists(FRAMEWORK_NAME . '/silverstripe_version')) { + $silverstripe_version = file_get_contents(FRAMEWORK_NAME . '/silverstripe_version'); +} else { + $silverstripe_version = "unknown"; +} + +// Check requirements +$req = new InstallRequirements(); +$req->check(); + +$webserverConfigFile = ''; +if ($req->isIIS()) { + $webserverConfigFile = 'web.config'; +} else { + $webserverConfigFile = '.htaccess'; +} + +if ($req->hasErrors()) { + $hasErrorOtherThanDatabase = true; + $phpIniLocation = php_ini_loaded_file(); +} + +$dbReq = new InstallRequirements(); +if ($databaseConfig) { + $dbReq->checkDatabase($databaseConfig); +} + +$adminReq = new InstallRequirements(); +if ($adminConfig) { + $adminReq->checkAdminConfig($adminConfig); +} + +// Actual processor +$installFromCli = (isset($_SERVER['argv'][1]) && $_SERVER['argv'][1] == 'install'); + +// CLI-install error message. exit(1) will halt any makefile. +if ($installFromCli && ($req->hasErrors() || $dbReq->hasErrors())) { + echo "Cannot install due to errors:\n"; + $req->listErrors(); + $dbReq->listErrors(); + exit(1); +} + +if ((isset($_REQUEST['go']) || $installFromCli) + && !$req->hasErrors() + && !$dbReq->hasErrors() + && $adminConfig['username'] + && $adminConfig['password'] +) { + // Confirm before reinstalling + if (!$installFromCli && $alreadyInstalled) { + include(__DIR__ . '/config-form.html'); + } else { + $inst = new Installer(); + if ($_REQUEST) { + $inst->install($_REQUEST); + } else { + $inst->install(array( + 'db' => $databaseConfig, + 'admin' => $adminConfig, + )); + } + } + +// Show the config form +} else { + include(__DIR__ . '/config-form.html'); +} + diff --git a/src/Dev/SapphireTest.php b/src/Dev/SapphireTest.php index 6a0dd94cb..dd80b2210 100644 --- a/src/Dev/SapphireTest.php +++ b/src/Dev/SapphireTest.php @@ -894,7 +894,7 @@ class SapphireTest extends PHPUnit_Framework_TestCase $request->setSession($session); // Test application - $kernel = new TestKernel(); + $kernel = new TestKernel(BASE_PATH); $app = new HTTPApplication($kernel); // Custom application diff --git a/src/Dev/State/GlobalsTestState.php b/src/Dev/State/GlobalsTestState.php index df94e7842..a29b139aa 100644 --- a/src/Dev/State/GlobalsTestState.php +++ b/src/Dev/State/GlobalsTestState.php @@ -3,6 +3,7 @@ namespace SilverStripe\Dev\State; use SilverStripe\Control\Director; +use SilverStripe\Core\Environment; use SilverStripe\Dev\SapphireTest; /** @@ -24,21 +25,21 @@ class GlobalsTestState implements TestState public function setUp(SapphireTest $test) { - $this->vars = Director::envToVars(); + $this->vars = Environment::getVariables(); } public function tearDown(SapphireTest $test) { - Director::varsToEnv($this->vars); + Environment::setVariables($this->vars); } public function setUpOnce($class) { - $this->staticVars = Director::envToVars(); + $this->staticVars = Environment::getVariables(); } public function tearDownOnce($class) { - Director::varsToEnv($this->staticVars); + Environment::setVariables($this->staticVars); } } diff --git a/src/ORM/DatabaseAdmin.php b/src/ORM/DatabaseAdmin.php index 0419378e2..efb09fd70 100644 --- a/src/ORM/DatabaseAdmin.php +++ b/src/ORM/DatabaseAdmin.php @@ -122,7 +122,7 @@ class DatabaseAdmin extends Controller increase_time_limit_to(600); // Get all our classes - ClassLoader::inst()->getManifest()->regenerate(); + ClassLoader::inst()->getManifest()->regenerate(false); $url = $this->getReturnURL(); if ($url) { diff --git a/src/includes/cli.php b/src/includes/cli.php deleted file mode 100644 index 2b24f78b8..000000000 --- a/src/includes/cli.php +++ /dev/null @@ -1,64 +0,0 @@ - 'HTTP/1.1', - 'HTTP_ACCEPT' => 'text/plain;q=0.5', - 'HTTP_ACCEPT_LANGUAGE' => '*;q=0.5', - 'HTTP_ACCEPT_ENCODING' => '', - 'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1;q=0.5', - 'SERVER_SIGNATURE' => 'Command-line PHP/' . phpversion(), - 'SERVER_SOFTWARE' => 'PHP/' . phpversion(), - 'SERVER_ADDR' => '127.0.0.1', - 'REMOTE_ADDR' => '127.0.0.1', - 'REQUEST_METHOD' => 'GET', - 'HTTP_USER_AGENT' => 'CLI', -), $_SERVER); - -/** - * Process arguments and load them into the $_GET and $_REQUEST arrays - * For example, - * sake my/url somearg otherarg key=val --otherkey=val third=val&fourth=val - * - * Will result in the following get data: - * args => array('somearg', 'otherarg'), - * key => val - * otherkey => val - * third => val - * fourth => val - */ -if (isset($_SERVER['argv'][2])) { - call_user_func(function () { - $args = array_slice($_SERVER['argv'], 2); - if (!isset($_GET)) { - $_GET = array(); - } - if (!isset($_REQUEST)) { - $_REQUEST = array(); - } - foreach ($args as $arg) { - if (strpos($arg, '=') == false) { - $_GET['args'][] = $arg; - } else { - $newItems = array(); - parse_str((substr($arg, 0, 2) == '--') ? substr($arg, 2) : $arg, $newItems); - $_GET = array_merge($_GET, $newItems); - } - } - $_REQUEST = array_merge($_REQUEST, $_GET); - }); -} - -// Set 'url' GET parameter -if (isset($_SERVER['argv'][1])) { - $_REQUEST['url'] = $_SERVER['argv'][1]; - $_GET['url'] = $_SERVER['argv'][1]; -} diff --git a/src/includes/constants.php b/src/includes/constants.php index 62042f915..2eee0dacc 100644 --- a/src/includes/constants.php +++ b/src/includes/constants.php @@ -3,6 +3,7 @@ use Dotenv\Dotenv; use Dotenv\Exception\InvalidPathException; use SilverStripe\Control\Util\IPUtils; +use SilverStripe\Core\TempFolder; /** * This file is the Framework constants bootstrap. It will prepare some basic common constants. @@ -148,5 +149,5 @@ if (defined('CUSTOM_INCLUDE_PATH')) { // Define the temporary folder if it wasn't defined yet if (!defined('TEMP_FOLDER')) { - define('TEMP_FOLDER', getTempFolder(BASE_PATH)); + define('TEMP_FOLDER', TempFolder::getTempFolder(BASE_PATH)); } diff --git a/src/includes/functions.php b/src/includes/functions.php index 2411f3073..83f1a61d4 100644 --- a/src/includes/functions.php +++ b/src/includes/functions.php @@ -62,258 +62,3 @@ function _t($entity, $arg = null) // Pass args directly to handle deprecation return call_user_func_array([i18n::class, '_t'], func_get_args()); } - -/** - * Increase the memory limit to the given level if it's currently too low. - * Only increases up to the maximum defined in {@link set_increase_memory_limit_max()}, - * and defaults to the 'memory_limit' setting in the PHP configuration. - * - * @param string|int $memoryLimit A memory limit string, such as "64M". If omitted, unlimited memory will be set. - * @return Boolean TRUE indicates a successful change, FALSE a denied change. - */ -function increase_memory_limit_to($memoryLimit = -1) -{ - $curLimit = ini_get('memory_limit'); - - // Can't go higher than infinite - if ($curLimit == -1) { - return true; - } - - // Check hard maximums - $max = get_increase_memory_limit_max(); - - if ($max && $max != -1 && translate_memstring($memoryLimit) > translate_memstring($max)) { - return false; - } - - // Increase the memory limit if it's too low - if ($memoryLimit == -1 || translate_memstring($memoryLimit) > translate_memstring($curLimit)) { - ini_set('memory_limit', $memoryLimit); - } - - return true; -} - -$_increase_memory_limit_max = ini_get('memory_limit'); - -/** - * Set the maximum allowed value for {@link increase_memory_limit_to()}. - * The same result can also be achieved through 'suhosin.memory_limit' - * if PHP is running with the Suhosin system. - * - * @param string $memoryLimit Memory limit string - */ -function set_increase_memory_limit_max($memoryLimit) -{ - global $_increase_memory_limit_max; - $_increase_memory_limit_max = $memoryLimit; -} - -/** - * @return string Memory limit string - */ -function get_increase_memory_limit_max() -{ - global $_increase_memory_limit_max; - return $_increase_memory_limit_max; -} - -/** - * Increases the XDebug parameter max_nesting_level, which limits how deep recursion can go. - * Only does anything if (a) xdebug is installed and (b) the new limit is higher than the existing limit - * - * @param int $limit - The new limit to increase to - */ -function increase_xdebug_nesting_level_to($limit) -{ - if (function_exists('xdebug_enable')) { - $current = ini_get('xdebug.max_nesting_level'); - if ((int)$current < $limit) { - ini_set('xdebug.max_nesting_level', $limit); - } - } -} - -/** - * Turn a memory string, such as 512M into an actual number of bytes. - * - * @param string $memString A memory limit string, such as "64M" - * @return float - */ -function translate_memstring($memString) -{ - switch (strtolower(substr($memString, -1))) { - case "k": - return round(substr($memString, 0, -1) * 1024); - case "m": - return round(substr($memString, 0, -1) * 1024 * 1024); - case "g": - return round(substr($memString, 0, -1) * 1024 * 1024 * 1024); - default: - return round($memString); - } -} - -/** - * Increase the time limit of this script. By default, the time will be unlimited. - * Only works if 'safe_mode' is off in the PHP configuration. - * Only values up to {@link get_increase_time_limit_max()} are allowed. - * - * @param int $timeLimit The time limit in seconds. If omitted, no time limit will be set. - * @return Boolean TRUE indicates a successful change, FALSE a denied change. - */ -function increase_time_limit_to($timeLimit = null) -{ - $max = get_increase_time_limit_max(); - if ($max != -1 && $max != null && $timeLimit > $max) { - return false; - } - - if (!ini_get('safe_mode')) { - if (!$timeLimit) { - set_time_limit(0); - return true; - } else { - $currTimeLimit = ini_get('max_execution_time'); - // Only increase if its smaller - if ($currTimeLimit && $currTimeLimit < $timeLimit) { - set_time_limit($timeLimit); - } - return true; - } - } else { - return false; - } -} - -/** - * Set the maximum allowed value for {@link increase_timeLimit_to()}; - * - * @param int $timeLimit Limit in seconds - */ -function set_increase_time_limit_max($timeLimit) -{ - global $_increase_time_limit_max; - $_increase_time_limit_max = $timeLimit; -} - -/** - * @return Int Limit in seconds - */ -function get_increase_time_limit_max() -{ - global $_increase_time_limit_max; - return $_increase_time_limit_max; -} - - -/** - * Returns the temporary folder path that silverstripe should use for its cache files. - * - * @param string $base The base path to use for determining the temporary path - * @return string Path to temp - */ -function getTempFolder($base = null) -{ - $parent = getTempParentFolder($base); - - // The actual temp folder is a subfolder of getTempParentFolder(), named by username - $subfolder = $parent . DIRECTORY_SEPARATOR . getTempFolderUsername(); - - if (!@file_exists($subfolder)) { - mkdir($subfolder); - } - - return $subfolder; -} - -/** - * Returns as best a representation of the current username as we can glean. - * - * @return string - */ -function getTempFolderUsername() -{ - $user = getenv('APACHE_RUN_USER'); - if (!$user) { - $user = getenv('USER'); - } - if (!$user) { - $user = getenv('USERNAME'); - } - if (!$user && function_exists('posix_getpwuid') && function_exists('posix_getuid')) { - $userDetails = posix_getpwuid(posix_getuid()); - $user = $userDetails['name']; - } - if (!$user) { - $user = 'unknown'; - } - $user = preg_replace('/[^A-Za-z0-9_\-]/', '', $user); - return $user; -} - -/** - * Return the parent folder of the temp folder. - * The temp folder will be a subfolder of this, named by username. - * This structure prevents permission problems. - * - * @param string $base - * @return string - * @throws Exception - */ -function getTempParentFolder($base = null) -{ - if (!$base && defined('BASE_PATH')) { - $base = BASE_PATH; - } - - // first, try finding a silverstripe-cache dir built off the base path - $tempPath = $base . DIRECTORY_SEPARATOR . 'silverstripe-cache'; - if (@file_exists($tempPath)) { - if ((fileperms($tempPath) & 0777) != 0777) { - @chmod($tempPath, 0777); - } - return $tempPath; - } - - // failing the above, try finding a namespaced silverstripe-cache dir in the system temp - $tempPath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . - 'silverstripe-cache-php' . preg_replace('/[^\w-\.+]+/', '-', PHP_VERSION) . - str_replace(array(' ', '/', ':', '\\'), '-', $base); - if (!@file_exists($tempPath)) { - $oldUMask = umask(0); - @mkdir($tempPath, 0777); - umask($oldUMask); - - // if the folder already exists, correct perms - } else { - if ((fileperms($tempPath) & 0777) != 0777) { - @chmod($tempPath, 0777); - } - } - - $worked = @file_exists($tempPath) && @is_writable($tempPath); - - // failing to use the system path, attempt to create a local silverstripe-cache dir - if (!$worked) { - $tempPath = $base . DIRECTORY_SEPARATOR . 'silverstripe-cache'; - if (!@file_exists($tempPath)) { - $oldUMask = umask(0); - @mkdir($tempPath, 0777); - umask($oldUMask); - } - - $worked = @file_exists($tempPath) && @is_writable($tempPath); - } - - if (!$worked) { - throw new Exception( - 'Permission problem gaining access to a temp folder. ' . - 'Please create a folder named silverstripe-cache in the base folder ' . - 'of the installation and ensure it has the correct permissions' - ); - } - - return $tempPath; -} diff --git a/tests/php/Core/MemoryLimitTest.php b/tests/php/Core/MemoryLimitTest.php index f5717fd58..b144b0335 100644 --- a/tests/php/Core/MemoryLimitTest.php +++ b/tests/php/Core/MemoryLimitTest.php @@ -2,35 +2,64 @@ namespace SilverStripe\Core\Tests; +use SilverStripe\Core\Environment; use SilverStripe\Dev\SapphireTest; class MemoryLimitTest extends SapphireTest { + protected $origMemLimitMax; + protected $origTimeLimitMax; + protected $origMemLimit; + protected $origTimeLimit; + + protected function setUp() + { + parent::setUp(); + + // see http://www.hardened-php.net/suhosin/configuration.html#suhosin.memory_limit + if (in_array('suhosin', get_loaded_extensions())) { + $this->markTestSkipped("This test cannot be run with suhosin installed"); + } else { + $this->origMemLimit = ini_get('memory_limit'); + $this->origTimeLimit = ini_get('max_execution_time'); + $this->origMemLimitMax = Environment::getMemoryLimitMax(); + $this->origTimeLimitMax = Environment::getTimeLimitMax(); + Environment::setMemoryLimitMax(null); + Environment::setTimeLimitMax(null); + } + } + + protected function tearDown() + { + if (!in_array('suhosin', get_loaded_extensions())) { + ini_set('memory_limit', $this->origMemLimit); + set_time_limit($this->origTimeLimit); + Environment::setMemoryLimitMax($this->origMemLimitMax); + Environment::setTimeLimitMax($this->origTimeLimitMax); + } + parent::tearDown(); + } public function testIncreaseMemoryLimitTo() { - if (!$this->canChangeMemory()) { - return; - } - ini_set('memory_limit', '64M'); // It can go up - increase_memory_limit_to('128M'); + Environment::increaseMemoryLimitTo('128M'); $this->assertEquals('128M', ini_get('memory_limit')); // But not down - increase_memory_limit_to('64M'); + Environment::increaseMemoryLimitTo('64M'); $this->assertEquals('128M', ini_get('memory_limit')); // Test the different kinds of syntaxes - increase_memory_limit_to(1024*1024*200); + Environment::increaseMemoryLimitTo(1024*1024*200); $this->assertEquals(1024*1024*200, ini_get('memory_limit')); - increase_memory_limit_to('409600K'); + Environment::increaseMemoryLimitTo('409600K'); $this->assertEquals('409600K', ini_get('memory_limit')); - increase_memory_limit_to('1G'); + Environment::increaseMemoryLimitTo('1G'); // If memory limit was left at 409600K, that means that the current testbox doesn't have // 1G of memory available. That's okay; let's not report a failure for that. @@ -39,80 +68,31 @@ class MemoryLimitTest extends SapphireTest } // No argument means unlimited - increase_memory_limit_to(); + Environment::increaseMemoryLimitTo(); $this->assertEquals(-1, ini_get('memory_limit')); } public function testIncreaseTimeLimitTo() { - if (!$this->canChangeMemory()) { - return; - } - // Can't change time limit if (!set_time_limit(6000)) { - return; + $this->markTestSkipped("Cannot change time limit"); } // It can go up - $this->assertTrue(increase_time_limit_to(7000)); + $this->assertTrue(Environment::increaseTimeLimitTo(7000)); $this->assertEquals(7000, ini_get('max_execution_time')); // But not down - $this->assertTrue(increase_time_limit_to(5000)); + $this->assertTrue(Environment::increaseTimeLimitTo(5000)); $this->assertEquals(7000, ini_get('max_execution_time')); // 0/nothing means infinity - $this->assertTrue(increase_time_limit_to()); + $this->assertTrue(Environment::increaseTimeLimitTo()); $this->assertEquals(0, ini_get('max_execution_time')); // Can't go down from there - $this->assertTrue(increase_time_limit_to(10000)); + $this->assertTrue(Environment::increaseTimeLimitTo(10000)); $this->assertEquals(0, ini_get('max_execution_time')); } - - - /////////////////// - - private $origMemLimit, $origTimeLimit; - - protected function setUp() - { - $this->origMemLimit = ini_get('memory_limit'); - $this->origTimeLimit = ini_get('max_execution_time'); - $this->origMemLimitMax = get_increase_memory_limit_max(); - $this->origTimeLimitMax = get_increase_time_limit_max(); - set_increase_memory_limit_max(-1); - set_increase_time_limit_max(-1); - } - - protected function tearDown() - { - ini_set('memory_limit', $this->origMemLimit); - set_time_limit($this->origTimeLimit); - set_increase_memory_limit_max($this->origMemLimitMax); - set_increase_time_limit_max($this->origTimeLimitMax); - } - - /** - * Determines wether the environment generally allows - * to change the memory limits, which is not always the case. - * - * @return Boolean - */ - protected function canChangeMemory() - { - $exts = get_loaded_extensions(); - // see http://www.hardened-php.net/suhosin/configuration.html#suhosin.memory_limit - if (in_array('suhosin', $exts)) { - return false; - } - - // We can't change memory limit in safe mode - if (ini_get('safe_mode')) { - return false; - } - - return true; - } }