From 58a2eb01555a778d1c8453697d91ce353b0e605b Mon Sep 17 00:00:00 2001 From: Will Rossiter Date: Fri, 19 Jul 2013 18:14:53 +1200 Subject: [PATCH 1/6] Versioned documentation typo (Thanks aragonne) --- docs/en/topics/versioning.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/topics/versioning.md b/docs/en/topics/versioning.md index 5e8d07663..351e60c9e 100644 --- a/docs/en/topics/versioning.md +++ b/docs/en/topics/versioning.md @@ -59,8 +59,8 @@ You can explicitly request a certain stage through various getters on the `Versi $liveRecords = Versioned::get_by_stage('MyRecord', 'Live'); // Fetching a single record - $stageRecord = Versioned::get_one_by_stage('MyRecord', 'Stage')->byID(99); - $liveRecord = Versioned::get_one_by_stage('MyRecord', 'Live')->byID(99); + $stageRecord = Versioned::get_by_stage('MyRecord', 'Stage')->byID(99); + $liveRecord = Versioned::get_by_stage('MyRecord', 'Live')->byID(99); ### Historical Versions From 604d9bf7dc1bc962332dd66b7bc6650fbea699df Mon Sep 17 00:00:00 2001 From: Hamish Friedlander Date: Mon, 22 Jul 2013 13:52:00 +1200 Subject: [PATCH 2/6] Split Core.php into Constants.php and Core.php and adjust main.php startup The recent flush filter fix had a problem that you couldnt set a custom BASE_PATH in _ss_environment because that file didnt get included until after checking the confirmation token. This patch pulls the part of Core.php that defines BASE_PATH into a seperate file that can be included earlier in the startup sequence so that ParameterConfirmationToken can access it. Core.php includes Constants.php with a require_once call, so for startup scripts that dont pull in Constants.php themselves (like cli-script.php) no change is needed. --- core/Constants.php | 202 +++++++++++++++++++ core/Core.php | 206 +------------------- core/startup/ErrorControlChain.php | 12 +- core/startup/ParameterConfirmationToken.php | 12 +- main.php | 84 ++++---- 5 files changed, 261 insertions(+), 255 deletions(-) create mode 100644 core/Constants.php diff --git a/core/Constants.php b/core/Constants.php new file mode 100644 index 000000000..cded92297 --- /dev/null +++ b/core/Constants.php @@ -0,0 +1,202 @@ + '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 = array_merge($serverDefaults, $_SERVER); + + /** + * If we have an HTTP_HOST value, then we're being called from the webserver and there are some things that + * need checking + */ +} else { + /** + * Fix magic quotes setting + */ + if (function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc()) { + if($_REQUEST) stripslashes_recursively($_REQUEST); + if($_GET) stripslashes_recursively($_GET); + if($_POST) stripslashes_recursively($_POST); + if($_COOKIE) stripslashes_recursively($_COOKIE); + } + + /** + * Fix HTTP_HOST from reverse proxies + */ + if (isset($_SERVER['HTTP_X_FORWARDED_HOST'])) { + // Get the first host, in case there's multiple separated through commas + $_SERVER['HTTP_HOST'] = strtok($_SERVER['HTTP_X_FORWARDED_HOST'], ','); + } +} + +/** + * Define system paths + */ +if(!defined('BASE_PATH')) { + // Assuming that this file is framework/core/Core.php we can then determine the base path + $candidateBasePath = rtrim(dirname(dirname(dirname(__FILE__))), DIRECTORY_SEPARATOR); + // We can't have an empty BASE_PATH. Making it / means that double-slashes occur in places but that's benign. + // This likely only happens on chrooted environemnts + if($candidateBasePath == '') $candidateBasePath = DIRECTORY_SEPARATOR; + define('BASE_PATH', $candidateBasePath); +} +if(!defined('BASE_URL')) { + // Determine the base URL by comparing SCRIPT_NAME to SCRIPT_FILENAME and getting common elements + $path = realpath($_SERVER['SCRIPT_FILENAME']); + if(substr($path, 0, strlen(BASE_PATH)) == BASE_PATH) { + $urlSegmentToRemove = substr($path, strlen(BASE_PATH)); + if(substr($_SERVER['SCRIPT_NAME'], -strlen($urlSegmentToRemove)) == $urlSegmentToRemove) { + $baseURL = substr($_SERVER['SCRIPT_NAME'], 0, -strlen($urlSegmentToRemove)); + define('BASE_URL', rtrim($baseURL, DIRECTORY_SEPARATOR)); + } + } + + // If that didn't work, failover to the old syntax. Hopefully this isn't necessary, and maybe + // if can be phased out? + if(!defined('BASE_URL')) { + $dir = (strpos($_SERVER['SCRIPT_NAME'], 'index.php') !== false) + ? dirname($_SERVER['SCRIPT_NAME']) + : dirname(dirname($_SERVER['SCRIPT_NAME'])); + define('BASE_URL', rtrim($dir, DIRECTORY_SEPARATOR)); + } +} +define('MODULES_DIR', 'modules'); +define('MODULES_PATH', BASE_PATH . '/' . MODULES_DIR); +define('THEMES_DIR', 'themes'); +define('THEMES_PATH', BASE_PATH . '/' . THEMES_DIR); +// Relies on this being in a subdir of the framework. +// If it isn't, or is symlinked to a folder with a different name, you must define FRAMEWORK_DIR +if(!defined('FRAMEWORK_DIR')) { + define('FRAMEWORK_DIR', basename(dirname(dirname(__FILE__)))); +} +define('FRAMEWORK_PATH', BASE_PATH . '/' . FRAMEWORK_DIR); +define('FRAMEWORK_ADMIN_DIR', FRAMEWORK_DIR . '/admin'); +define('FRAMEWORK_ADMIN_PATH', BASE_PATH . '/' . FRAMEWORK_ADMIN_DIR); + +// These are all deprecated. Use the FRAMEWORK_ versions instead. +define('SAPPHIRE_DIR', FRAMEWORK_DIR); +define('SAPPHIRE_PATH', FRAMEWORK_PATH); +define('SAPPHIRE_ADMIN_DIR', FRAMEWORK_ADMIN_DIR); +define('SAPPHIRE_ADMIN_PATH', FRAMEWORK_ADMIN_PATH); + +define('THIRDPARTY_DIR', FRAMEWORK_DIR . '/thirdparty'); +define('THIRDPARTY_PATH', BASE_PATH . '/' . THIRDPARTY_DIR); +define('ASSETS_DIR', 'assets'); +define('ASSETS_PATH', BASE_PATH . '/' . ASSETS_DIR); + +/////////////////////////////////////////////////////////////////////////////// +// INCLUDES + +if(defined('CUSTOM_INCLUDE_PATH')) { + $includePath = '.' . PATH_SEPARATOR . CUSTOM_INCLUDE_PATH . PATH_SEPARATOR + . FRAMEWORK_PATH . PATH_SEPARATOR + . FRAMEWORK_PATH . '/parsers' . PATH_SEPARATOR + . THIRDPARTY_PATH . PATH_SEPARATOR + . get_include_path(); +} else { + $includePath = '.' . PATH_SEPARATOR . FRAMEWORK_PATH . PATH_SEPARATOR + . FRAMEWORK_PATH . '/parsers' . PATH_SEPARATOR + . THIRDPARTY_PATH . PATH_SEPARATOR + . get_include_path(); +} + +set_include_path($includePath); + +/** + * Define the temporary folder if it wasn't defined yet + */ +require_once 'core/TempPath.php'; + +if(!defined('TEMP_FOLDER')) { + define('TEMP_FOLDER', getTempFolder(BASE_PATH)); +} diff --git a/core/Core.php b/core/Core.php index 27da1a28b..9071c0557 100644 --- a/core/Core.php +++ b/core/Core.php @@ -3,215 +3,29 @@ * This file is the Framework bootstrap. It will get your environment ready to call Director::direct(). * * It takes care of: - * - Including _ss_environment.php - * - Normalisation of $_SERVER values - * - Initialisation of necessary constants (mostly paths) + * - Including Constants.php to include _ss_environment and initialise necessary constants * - Checking of PHP memory limit * - Including all the files needed to get the manifest built * - Building and including the manifest - * - * Initialized constants: - * - BASE_URL: Full URL to the webroot, e.g. "http://my-host.com/my-webroot" (no trailing slash). - * - BASE_PATH: Absolute path to the webroot, e.g. "/var/www/my-webroot" (no trailing slash). - * See Director::baseFolder(). Can be overwritten by Director::setBaseFolder(). - * - TEMP_FOLDER: Absolute path to temporary folder, used for manifest and template caches. Example: "/var/tmp" - * See getTempFolder(). No trailing slash. - * - MODULES_DIR: Not used at the moment - * - MODULES_PATH: Not used at the moment - * - THEMES_DIR: Path relative to webroot, e.g. "themes" - * - THEMES_PATH: Absolute filepath, e.g. "/var/www/my-webroot/themes" - * - FRAMEWORK_DIR: Path relative to webroot, e.g. "framework" - * - FRAMEWORK_PATH:Absolute filepath, e.g. "/var/www/my-webroot/framework" - * - FRAMEWORK_ADMIN_DIR: Path relative to webroot, e.g. "framework/admin" - * - FRAMEWORK_ADMIN_PATH: Absolute filepath, e.g. "/var/www/my-webroot/framework/admin" - * - THIRDPARTY_DIR: Path relative to webroot, e.g. "framework/thirdparty" - * - THIRDPARTY_PATH: Absolute filepath, e.g. "/var/www/my-webroot/framework/thirdparty" - * + * * @todo This file currently contains a lot of bits and pieces, and its various responsibilities should probably be * moved into different subsystems. - * @todo A lot of this stuff is very order-independent; for example, the require_once calls have to happen after the - * defines.' - * This could be decoupled. + * @todo A lot of this stuff is very order-dependent. This could be decoupled. + * * @package framework * @subpackage core */ -/////////////////////////////////////////////////////////////////////////////// -// ENVIRONMENT CONFIG - -// ALL errors are reported, including E_STRICT by default *unless* the site is in -// live mode, where reporting is limited to fatal errors and warnings (see later in this file) +/** + * All errors are reported, including E_STRICT by default *unless* the site is in + * live mode, where reporting is limited to fatal errors and warnings (see later in this file) + */ error_reporting(E_ALL | E_STRICT); /** - * Include _ss_environment.php files + * Include Constants (if it hasn't already been included) to pull in BASE_PATH, etc */ -//define the name of the environment file -$envFile = '_ss_environment.php'; -//define the dir to start scanning from (have to add the trailing slash) -$dir = '.'; -//check this dir and every parent dir (until we hit the base of the drive) -do { - $dir = realpath($dir) . '/'; - //if the file exists, then we include it, set relevant vars and break out - if (file_exists($dir . $envFile)) { - define('SS_ENVIRONMENT_FILE', $dir . $envFile); - include_once(SS_ENVIRONMENT_FILE); - break; - } -//here we need to check that the real path of the last dir and the next one are -// not the same, if they are, we have hit the root of the drive -} while (realpath($dir) != realpath($dir .= '../')); - -/////////////////////////////////////////////////////////////////////////////// -// GLOBALS AND DEFINE SETTING - -/** - * A blank HTTP_HOST value is used to detect command-line execution. - * We update the $_SERVER variable to contain data consistent with the rest of the application. - */ -if(!isset($_SERVER['HTTP_HOST'])) { - // HTTP_HOST, REQUEST_PORT, SCRIPT_NAME, and PHP_SELF - if(isset($_FILE_TO_URL_MAPPING)) { - $fullPath = $testPath = realpath($_SERVER['SCRIPT_FILENAME']); - while($testPath && $testPath != '/' && !preg_match('/^[A-Z]:\\\\$/', $testPath)) { - if(isset($_FILE_TO_URL_MAPPING[$testPath])) { - $url = $_FILE_TO_URL_MAPPING[$testPath] - . str_replace(DIRECTORY_SEPARATOR, '/', substr($fullPath,strlen($testPath))); - - $components = parse_url($url); - $_SERVER['HTTP_HOST'] = $components['host']; - if(!empty($components['port'])) $_SERVER['HTTP_HOST'] .= ':' . $components['port']; - $_SERVER['SCRIPT_NAME'] = $_SERVER['PHP_SELF'] = $components['path']; - if(!empty($components['port'])) $_SERVER['REQUEST_PORT'] = $components['port']; - break; - } - $testPath = dirname($testPath); - } - } - - // Everything else - $serverDefaults = array( - 'SERVER_PROTOCOL' => '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 = array_merge($serverDefaults, $_SERVER); - -/** - * If we have an HTTP_HOST value, then we're being called from the webserver and there are some things that - * need checking - */ -} else { - /** - * Fix magic quotes setting - */ - if (function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc()) { - if($_REQUEST) stripslashes_recursively($_REQUEST); - if($_GET) stripslashes_recursively($_GET); - if($_POST) stripslashes_recursively($_POST); - if($_COOKIE) stripslashes_recursively($_COOKIE); - } - - /** - * Fix HTTP_HOST from reverse proxies - */ - if (isset($_SERVER['HTTP_X_FORWARDED_HOST'])) { - // Get the first host, in case there's multiple separated through commas - $_SERVER['HTTP_HOST'] = strtok($_SERVER['HTTP_X_FORWARDED_HOST'], ','); - } -} - -/** - * Define system paths - */ -if(!defined('BASE_PATH')) { - // Assuming that this file is framework/core/Core.php we can then determine the base path - $candidateBasePath = rtrim(dirname(dirname(dirname(__FILE__))), DIRECTORY_SEPARATOR); - // We can't have an empty BASE_PATH. Making it / means that double-slashes occur in places but that's benign. - // This likely only happens on chrooted environemnts - if($candidateBasePath == '') $candidateBasePath = DIRECTORY_SEPARATOR; - define('BASE_PATH', $candidateBasePath); -} -if(!defined('BASE_URL')) { - // Determine the base URL by comparing SCRIPT_NAME to SCRIPT_FILENAME and getting common elements - $path = realpath($_SERVER['SCRIPT_FILENAME']); - if(substr($path, 0, strlen(BASE_PATH)) == BASE_PATH) { - $urlSegmentToRemove = substr($path, strlen(BASE_PATH)); - if(substr($_SERVER['SCRIPT_NAME'], -strlen($urlSegmentToRemove)) == $urlSegmentToRemove) { - $baseURL = substr($_SERVER['SCRIPT_NAME'], 0, -strlen($urlSegmentToRemove)); - define('BASE_URL', rtrim($baseURL, DIRECTORY_SEPARATOR)); - } - } - - // If that didn't work, failover to the old syntax. Hopefully this isn't necessary, and maybe - // if can be phased out? - if(!defined('BASE_URL')) { - $dir = (strpos($_SERVER['SCRIPT_NAME'], 'index.php') !== false) - ? dirname($_SERVER['SCRIPT_NAME']) - : dirname(dirname($_SERVER['SCRIPT_NAME'])); - define('BASE_URL', rtrim($dir, DIRECTORY_SEPARATOR)); - } -} -define('MODULES_DIR', 'modules'); -define('MODULES_PATH', BASE_PATH . '/' . MODULES_DIR); -define('THEMES_DIR', 'themes'); -define('THEMES_PATH', BASE_PATH . '/' . THEMES_DIR); -// Relies on this being in a subdir of the framework. -// If it isn't, or is symlinked to a folder with a different name, you must define FRAMEWORK_DIR -if(!defined('FRAMEWORK_DIR')) { - define('FRAMEWORK_DIR', basename(dirname(dirname(__FILE__)))); -} -define('FRAMEWORK_PATH', BASE_PATH . '/' . FRAMEWORK_DIR); -define('FRAMEWORK_ADMIN_DIR', FRAMEWORK_DIR . '/admin'); -define('FRAMEWORK_ADMIN_PATH', BASE_PATH . '/' . FRAMEWORK_ADMIN_DIR); - -// These are all deprecated. Use the FRAMEWORK_ versions instead. -define('SAPPHIRE_DIR', FRAMEWORK_DIR); -define('SAPPHIRE_PATH', FRAMEWORK_PATH); -define('SAPPHIRE_ADMIN_DIR', FRAMEWORK_ADMIN_DIR); -define('SAPPHIRE_ADMIN_PATH', FRAMEWORK_ADMIN_PATH); - -define('THIRDPARTY_DIR', FRAMEWORK_DIR . '/thirdparty'); -define('THIRDPARTY_PATH', BASE_PATH . '/' . THIRDPARTY_DIR); -define('ASSETS_DIR', 'assets'); -define('ASSETS_PATH', BASE_PATH . '/' . ASSETS_DIR); - -/////////////////////////////////////////////////////////////////////////////// -// INCLUDES - -if(defined('CUSTOM_INCLUDE_PATH')) { - $includePath = '.' . PATH_SEPARATOR . CUSTOM_INCLUDE_PATH . PATH_SEPARATOR - . FRAMEWORK_PATH . PATH_SEPARATOR - . FRAMEWORK_PATH . '/parsers' . PATH_SEPARATOR - . THIRDPARTY_PATH . PATH_SEPARATOR - . get_include_path(); -} else { - $includePath = '.' . PATH_SEPARATOR . FRAMEWORK_PATH . PATH_SEPARATOR - . FRAMEWORK_PATH . '/parsers' . PATH_SEPARATOR - . THIRDPARTY_PATH . PATH_SEPARATOR - . get_include_path(); -} - -set_include_path($includePath); - -/** - * Define the temporary folder if it wasn't defined yet - */ -require_once 'core/TempPath.php'; - -if(!defined('TEMP_FOLDER')) { - define('TEMP_FOLDER', getTempFolder(BASE_PATH)); -} +require_once dirname(__FILE__).'/Constants.php'; /** * Priorities definition. These constants are used in calls to _t() as an optional argument diff --git a/core/startup/ErrorControlChain.php b/core/startup/ErrorControlChain.php index 9cd344ee0..aa2b825fa 100644 --- a/core/startup/ErrorControlChain.php +++ b/core/startup/ErrorControlChain.php @@ -9,8 +9,6 @@ * Normal errors are suppressed even past the end of the chain. Fatal errors are only suppressed until the end * of the chain - the request will then die silently. * - * The exception is if an error occurs and BASE_URL is not yet set - in that case the error is never suppressed. - * * Usage: * * $chain = new ErrorControlChain(); @@ -68,8 +66,8 @@ class ErrorControlChain { return $this->then($callback, null); } - public function handleError() { - if ($this->suppression && defined('BASE_URL')) throw new Exception('Generic Error'); + public function handleError($errno, $errstr) { + if ((error_reporting() & $errno) == $errno && $this->suppression) throw new Exception('Generic Error'); else return false; } @@ -79,7 +77,7 @@ class ErrorControlChain { } public function handleFatalError() { - if ($this->handleFatalErrors && $this->suppression && defined('BASE_URL')) { + if ($this->handleFatalErrors && $this->suppression) { if ($this->lastErrorWasFatal()) { ob_clean(); $this->error = true; @@ -89,7 +87,7 @@ class ErrorControlChain { } public function execute() { - set_error_handler(array($this, 'handleError'), error_reporting()); + set_error_handler(array($this, 'handleError')); register_shutdown_function(array($this, 'handleFatalError')); $this->handleFatalErrors = true; @@ -105,7 +103,7 @@ class ErrorControlChain { call_user_func($step['callback'], $this); } catch (Exception $e) { - if ($this->suppression && defined('BASE_URL')) $this->error = true; + if ($this->suppression) $this->error = true; else throw $e; } } diff --git a/core/startup/ParameterConfirmationToken.php b/core/startup/ParameterConfirmationToken.php index e0ccf9d87..21364f871 100644 --- a/core/startup/ParameterConfirmationToken.php +++ b/core/startup/ParameterConfirmationToken.php @@ -16,17 +16,7 @@ class ParameterConfirmationToken { protected $token = null; protected function pathForToken($token) { - if (defined('BASE_PATH')) { - $basepath = BASE_PATH; - } - else { - $basepath = rtrim(dirname(dirname(dirname(dirname(__FILE__)))), DIRECTORY_SEPARATOR); - } - - require_once(dirname(dirname(__FILE__)).'/TempPath.php'); - $tempfolder = getTempFolder($basepath ? $basepath : DIRECTORY_SEPARATOR); - - return $tempfolder.'/token_'.preg_replace('/[^a-z0-9]+/', '', $token); + return TEMP_FOLDER.'/token_'.preg_replace('/[^a-z0-9]+/', '', $token); } protected function genToken() { diff --git a/main.php b/main.php index fb76d4872..af6253fa7 100644 --- a/main.php +++ b/main.php @@ -55,6 +55,43 @@ if (version_compare(phpversion(), '5.3.2', '<')) { * @see Director::direct() */ +/** + * Include the defines that set BASE_PATH, etc + */ +require_once('core/Constants.php'); + +/** + * Figure out the request URL + */ +global $url; + +// IIS will sometimes generate this. +if(!empty($_SERVER['HTTP_X_ORIGINAL_URL'])) { + $_SERVER['REQUEST_URI'] = $_SERVER['HTTP_X_ORIGINAL_URL']; +} + +// Apache rewrite rules use this +if (isset($_GET['url'])) { + $url = $_GET['url']; + // IIS includes get variables in url + $i = strpos($url, '?'); + if($i !== false) { + $url = substr($url, 0, $i); + } + + // Lighttpd uses this +} else { + if(strpos($_SERVER['REQUEST_URI'],'?') !== false) { + list($url, $query) = explode('?', $_SERVER['REQUEST_URI'], 2); + parse_str($query, $_GET); + if ($_GET) $_REQUEST = array_merge((array)$_REQUEST, (array)$_GET); + } else { + $url = $_SERVER["REQUEST_URI"]; + } +} + +// Remove base folders from the URL if webroot is hosted in a subfolder +if (substr(strtolower($url), 0, strlen(BASE_URL)) == strtolower(BASE_URL)) $url = substr($url, strlen(BASE_URL)); /** * Include SilverStripe's core code @@ -66,68 +103,33 @@ $chain = new ErrorControlChain(); $token = new ParameterConfirmationToken('flush'); $chain - // First, if $_GET['flush'] was set, but no valid token, suppress the flush ->then(function($chain) use ($token){ + // First, if $_GET['flush'] was set, but no valid token, suppress the flush if (isset($_GET['flush']) && !$token->tokenProvided()) { unset($_GET['flush']); } else { $chain->setSuppression(false); } - }) - // Then load in core - ->then(function(){ + + // Load in core require_once('core/Core.php'); - }) - // Then build the URL (even if Core didn't load beyond setting BASE_URL) - ->thenAlways(function(){ - global $url; - // IIS will sometimes generate this. - if(!empty($_SERVER['HTTP_X_ORIGINAL_URL'])) { - $_SERVER['REQUEST_URI'] = $_SERVER['HTTP_X_ORIGINAL_URL']; - } - - // Apache rewrite rules use this - if (isset($_GET['url'])) { - $url = $_GET['url']; - // IIS includes get variables in url - $i = strpos($url, '?'); - if($i !== false) { - $url = substr($url, 0, $i); - } - - // Lighttpd uses this - } else { - if(strpos($_SERVER['REQUEST_URI'],'?') !== false) { - list($url, $query) = explode('?', $_SERVER['REQUEST_URI'], 2); - parse_str($query, $_GET); - if ($_GET) $_REQUEST = array_merge((array)$_REQUEST, (array)$_GET); - } else { - $url = $_SERVER["REQUEST_URI"]; - } - } - - // Remove base folders from the URL if webroot is hosted in a subfolder - if (substr(strtolower($url), 0, strlen(BASE_URL)) == strtolower(BASE_URL)) $url = substr($url, strlen(BASE_URL)); - }) - // Then start up the database - ->then(function(){ if (isset($_GET['debug_profile'])) { Profiler::init(); Profiler::mark('all_execution'); Profiler::mark('main.php init'); } + // Connect to database require_once('model/DB.php'); global $databaseConfig; if (isset($_GET['debug_profile'])) Profiler::mark('DB::connect'); if ($databaseConfig) DB::connect($databaseConfig); if (isset($_GET['debug_profile'])) Profiler::unmark('DB::connect'); - }) - // Then if a flush was requested, redirect to it - ->then(function($chain) use ($token){ + + // Then if a flush was requested, redirect to it if ($token->parameterProvided() && !$token->tokenProvided()) { // First, check if we're in dev mode, or the database doesn't have any security data $canFlush = Director::isDev() || !Security::database_is_ready(); From 84011aa736bb9a029b47dad5b6404f68f9674aef Mon Sep 17 00:00:00 2001 From: Hamish Friedlander Date: Mon, 22 Jul 2013 14:48:16 +1200 Subject: [PATCH 3/6] FIX Only suppress fatal errors --- core/startup/ErrorControlChain.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/core/startup/ErrorControlChain.php b/core/startup/ErrorControlChain.php index aa2b825fa..1861f4b88 100644 --- a/core/startup/ErrorControlChain.php +++ b/core/startup/ErrorControlChain.php @@ -18,6 +18,8 @@ * It will likely be heavily refactored before the release of 3.2 */ class ErrorControlChain { + public static $fatal_errors = null; // Initialised after class definition + protected $error = false; protected $steps = array(); @@ -67,8 +69,12 @@ class ErrorControlChain { } public function handleError($errno, $errstr) { - if ((error_reporting() & $errno) == $errno && $this->suppression) throw new Exception('Generic Error'); - else return false; + if ((error_reporting() & self::$fatal_errors & $errno) != 0 && $this->suppression) { + throw new Exception('Generic Error'); + } + else { + return false; + } } protected function lastErrorWasFatal() { @@ -117,3 +123,6 @@ class ErrorControlChain { } } } + +ErrorControlChain::$fatal_errors = E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR; +if (defined('E_RECOVERABLE_ERROR')) ErrorControlChain::$fatal_errors |= E_RECOVERABLE_ERROR; From b3ca4a275bc10e82e85167a842d94d694c94ae0b Mon Sep 17 00:00:00 2001 From: Damian Mooyman Date: Tue, 23 Jul 2013 11:24:48 +1200 Subject: [PATCH 4/6] BUG Fixed divide by zero with SetRatioSize on missing image file Fixes issue #2047 --- model/Image.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/model/Image.php b/model/Image.php index 22fc1f644..006135bdb 100644 --- a/model/Image.php +++ b/model/Image.php @@ -206,6 +206,9 @@ class Image extends File { */ public function SetRatioSize($width, $height) { + // Prevent divide by zero on missing/blank file + if(empty($this->width) || empty($this->height)) return null; + // Check if image is already sized to the correct dimension $widthRatio = $width / $this->width; $heightRatio = $height / $this->height; From aa6da4ee4e88cd762699c346b0df2aad130e0d55 Mon Sep 17 00:00:00 2001 From: Arno Poot Date: Tue, 23 Jul 2013 19:13:11 +0200 Subject: [PATCH 5/6] Fixed CountryDropDownField showing East Germany --- thirdparty/Zend/Locale/Data/en.xml | 1 - thirdparty/Zend/Locale/Data/lc.xml | 1 - 2 files changed, 2 deletions(-) diff --git a/thirdparty/Zend/Locale/Data/en.xml b/thirdparty/Zend/Locale/Data/en.xml index bfd0b5185..72ba3f4f5 100644 --- a/thirdparty/Zend/Locale/Data/en.xml +++ b/thirdparty/Zend/Locale/Data/en.xml @@ -745,7 +745,6 @@ Christmas Island Cyprus Czech Republic - East Germany Germany Djibouti Denmark diff --git a/thirdparty/Zend/Locale/Data/lc.xml b/thirdparty/Zend/Locale/Data/lc.xml index ae4895bb9..e9cdd73fd 100755 --- a/thirdparty/Zend/Locale/Data/lc.xml +++ b/thirdparty/Zend/Locale/Data/lc.xml @@ -745,7 +745,6 @@ Christmas Island Cyprus Czech Republic - East Germany Germany Djibouti Denmark From a1ea905ca8f38e65f6a8d98f571b3adce1c641c7 Mon Sep 17 00:00:00 2001 From: Hamish Friedlander Date: Tue, 23 Jul 2013 11:51:38 +1200 Subject: [PATCH 6/6] FIX Nice errors and allows flush on module removal --- core/startup/ErrorControlChain.php | 39 +-- tests/core/startup/ErrorControlChainTest.php | 259 +++++++++++++++---- 2 files changed, 218 insertions(+), 80 deletions(-) diff --git a/core/startup/ErrorControlChain.php b/core/startup/ErrorControlChain.php index 1861f4b88..dc32257e7 100644 --- a/core/startup/ErrorControlChain.php +++ b/core/startup/ErrorControlChain.php @@ -3,16 +3,13 @@ /** * Class ErrorControlChain * - * Runs a set of steps, optionally suppressing (but recording) any errors (even fatal ones) that occur in each step. - * If an error does occur, subsequent steps are normally skipped, but can optionally be run anyway - * - * Normal errors are suppressed even past the end of the chain. Fatal errors are only suppressed until the end - * of the chain - the request will then die silently. + * Runs a set of steps, optionally suppressing uncaught errors or exceptions which would otherwise be fatal that + * occur in each step. If an error does occur, subsequent steps are normally skipped, but can optionally be run anyway. * * Usage: * * $chain = new ErrorControlChain(); - * $chain->then($callback1)->then($callback2)->then(true, $callback3)->execute(); + * $chain->then($callback1)->then($callback2)->thenIfErrored($callback3)->execute(); * * WARNING: This class is experimental and designed specifically for use pre-startup in main.php * It will likely be heavily refactored before the release of 3.2 @@ -28,6 +25,9 @@ class ErrorControlChain { /** We can't unregister_shutdown_function, so this acts as a flag to enable handling */ protected $handleFatalErrors = false; + /** We overload display_errors to hide errors during execution, so we need to remember the original to restore to */ + protected $originalDisplayErrors = null; + public function hasErrored() { return $this->error; } @@ -38,6 +38,7 @@ class ErrorControlChain { public function setSuppression($suppression) { $this->suppression = (bool)$suppression; + if ($this->handleFatalErrors) ini_set('display_errors', !$suppression); } /** @@ -68,24 +69,14 @@ class ErrorControlChain { return $this->then($callback, null); } - public function handleError($errno, $errstr) { - if ((error_reporting() & self::$fatal_errors & $errno) != 0 && $this->suppression) { - throw new Exception('Generic Error'); - } - else { - return false; - } - } - protected function lastErrorWasFatal() { $error = error_get_last(); - return $error && $error['type'] == 1; + return $error && ($error['type'] & self::$fatal_errors) != 0; } public function handleFatalError() { if ($this->handleFatalErrors && $this->suppression) { if ($this->lastErrorWasFatal()) { - ob_clean(); $this->error = true; $this->step(); } @@ -93,10 +84,12 @@ class ErrorControlChain { } public function execute() { - set_error_handler(array($this, 'handleError')); register_shutdown_function(array($this, 'handleFatalError')); $this->handleFatalErrors = true; + $this->originalDisplayErrors = ini_get('display_errors'); + ini_set('display_errors', !$this->suppression); + $this->step(); } @@ -105,13 +98,7 @@ class ErrorControlChain { $step = array_shift($this->steps); if ($step['onErrorState'] === null || $step['onErrorState'] === $this->error) { - try { - call_user_func($step['callback'], $this); - } - catch (Exception $e) { - if ($this->suppression) $this->error = true; - else throw $e; - } + call_user_func($step['callback'], $this); } $this->step(); @@ -119,7 +106,7 @@ class ErrorControlChain { else { // Now clean up $this->handleFatalErrors = false; - restore_error_handler(); + ini_set('display_errors', $this->originalDisplayErrors); } } } diff --git a/tests/core/startup/ErrorControlChainTest.php b/tests/core/startup/ErrorControlChainTest.php index e9c2c8435..8b5a1d622 100644 --- a/tests/core/startup/ErrorControlChainTest.php +++ b/tests/core/startup/ErrorControlChainTest.php @@ -1,90 +1,241 @@ getItemPath('ErrorControlChain'); + $suppression = $this->suppression ? 'true' : 'false'; + + // Start building a PHP file that will execute the chain + $src = '<'."?php +require_once '$classpath'; + +\$chain = new ErrorControlChain(); + +\$chain->setSuppression($suppression); + +\$chain +"; + + // For each step, use reflection to pull out the call, stick in the the PHP source we're building + foreach ($this->steps as $step) { + $func = new ReflectionFunction($step['callback']); + $source = file($func->getFileName()); + + $start_line = $func->getStartLine() - 1; + $end_line = $func->getEndLine(); + $length = $end_line - $start_line; + + $src .= implode("", array_slice($source, $start_line, $length)) . "\n"; + } + + // Finally add a line to execute the chain + $src .= "->execute();"; + + // Now stick it in a temporary file & run it + $codepath = TEMP_FOLDER.'/ErrorControlChainTest_'.sha1($src).'.php'; + + $null = is_writeable('/dev/null') ? '/dev/null' : 'NUL'; + + file_put_contents($codepath, $src); + exec("php $codepath 2> $null", $stdout, $errcode); + unlink($codepath); + + return array(implode("\n", $stdout), $errcode); + } +} + class ErrorControlChainTest extends SapphireTest { - function testErrorSuppression() { - $chain = new ErrorControlChain(); + function setUp() { + // Check we can run PHP at all + $null = is_writeable('/dev/null') ? '/dev/null' : 'NUL'; + exec("php -v 2> $null", $out, $rv); - $chain - ->then(function(){ - user_error('This error should be suppressed', E_USER_ERROR); - }) - ->execute(); + if ($rv != 0) { + $this->markTestSkipped("Can't run PHP from the command line - is it in your path?"); + $this->skipTest = true; + } - $this->assertTrue($chain->hasErrored()); + parent::setUp(); } - function testMultipleErrorSuppression() { - $chain = new ErrorControlChain(); + function testErrorSuppression() { - $chain + // Fatal error + + $chain = new ErrorControlChainTest_Chain(); + + list($out, $code) = $chain ->then(function(){ - user_error('This error should be suppressed', E_USER_ERROR); + Foo::bar(); // Non-existant class causes fatal error }) - ->thenAlways(function(){ - user_error('This error should also be suppressed', E_USER_ERROR); + ->thenIfErrored(function(){ + echo "Done"; }) - ->execute(); + ->executeInSubprocess(); - $this->assertTrue($chain->hasErrored()); + $this->assertEquals('Done', $out); + + // User error + + $chain = new ErrorControlChainTest_Chain(); + + list($out, $code) = $chain + ->then(function(){ + user_error('Error', E_USER_ERROR); + }) + ->thenIfErrored(function(){ + echo "Done"; + }) + ->executeInSubprocess(); + + $this->assertEquals('Done', $out); + + // Recoverable error + + $chain = new ErrorControlChainTest_Chain(); + + list($out, $code) = $chain + ->then(function(){ + $x = function(ErrorControlChain $foo){ }; + $x(1); // Calling against type + }) + ->thenIfErrored(function(){ + echo "Done"; + }) + ->executeInSubprocess(); + + $this->assertEquals('Done', $out); } function testExceptionSuppression() { - $chain = new ErrorControlChain(); + $chain = new ErrorControlChainTest_Chain(); - $chain + list($out, $code) = $chain ->then(function(){ throw new Exception('This exception should be suppressed'); }) - ->execute(); - - $this->assertTrue($chain->hasErrored()); - } - - function testMultipleExceptionSuppression() { - $chain = new ErrorControlChain(); - - $chain - ->then(function(){ - throw new Exception('This exception should be suppressed'); + ->thenIfErrored(function(){ + echo "Done"; }) - ->thenAlways(function(){ - throw new Exception('This exception should also be suppressed'); - }) - ->execute(); + ->executeInSubprocess(); - $this->assertTrue($chain->hasErrored()); + $this->assertEquals('Done', $out); } function testErrorControl() { - $preError = $postError = array('then' => false, 'thenIfErrored' => false, 'thenAlways' => false); + $chain = new ErrorControlChainTest_Chain(); - $chain = new ErrorControlChain(); - - $chain - ->then(function() use (&$preError) { $preError['then'] = true; }) - ->thenIfErrored(function() use (&$preError) { $preError['thenIfErrored'] = true; }) - ->thenAlways(function() use (&$preError) { $preError['thenAlways'] = true; }) + list($out, $code) = $chain + ->then(function() { echo 'preThen,'; }) + ->thenIfErrored(function() { echo 'preThenIfErrored,'; }) + ->thenAlways(function() { echo 'preThenAlways,'; }) ->then(function(){ user_error('An error', E_USER_ERROR); }) - ->then(function() use (&$postError) { $postError['then'] = true; }) - ->thenIfErrored(function() use (&$postError) { $postError['thenIfErrored'] = true; }) - ->thenAlways(function() use (&$postError) { $postError['thenAlways'] = true; }) + ->then(function() { echo 'postThen,'; }) + ->thenIfErrored(function() { echo 'postThenIfErrored,'; }) + ->thenAlways(function() { echo 'postThenAlways,'; }) - ->execute(); + ->executeInSubprocess(); $this->assertEquals( - array('then' => true, 'thenIfErrored' => false, 'thenAlways' => true), - $preError, - 'Then and thenAlways callbacks called before error, thenIfErrored callback not called' - ); - - $this->assertEquals( - array('then' => false, 'thenIfErrored' => true, 'thenAlways' => true), - $postError, - 'thenIfErrored and thenAlways callbacks called after error, then callback not called' + "preThen,preThenAlways,postThenIfErrored,postThenAlways,", + $out ); } + function testSuppressionControl() { + // Turning off suppression before execution + + $chain = new ErrorControlChainTest_Chain(); + $chain->setSuppression(false); + + list($out, $code) = $chain + ->then(function($chain){ + Foo::bar(); // Non-existant class causes fatal error + }) + ->executeInSubprocess(); + + $this->assertContains("Fatal error: Class 'Foo' not found", $out); + + // Turning off suppression during execution + + $chain = new ErrorControlChainTest_Chain(); + + list($out, $code) = $chain + ->then(function($chain){ + $chain->setSuppression(false); + Foo::bar(); // Non-existent class causes fatal error + }) + ->executeInSubprocess(); + + $this->assertContains("Fatal error: Class 'Foo' not found", $out); + } + + function testDoesntAffectNonFatalErrors() { + $chain = new ErrorControlChainTest_Chain(); + + list($out, $code) = $chain + ->then(function(){ + $array = null; + if (@$array['key'] !== null) user_error('Error', E_USER_ERROR); + }) + ->then(function(){ + echo "Good"; + }) + ->thenIfErrored(function(){ + echo "Bad"; + }) + ->executeInSubprocess(); + + $this->assertContains("Good", $out); + } + + function testDoesntAffectCaughtExceptions() { + $chain = new ErrorControlChainTest_Chain(); + + list($out, $code) = $chain + ->then(function(){ + try { + throw new Exception('Error'); + } + catch (Exception $e) { + echo "Good"; + } + }) + ->thenIfErrored(function(){ + echo "Bad"; + }) + ->executeInSubprocess(); + + $this->assertContains("Good", $out); + } + + function testDoesntAffectHandledErrors() { + $chain = new ErrorControlChainTest_Chain(); + + list($out, $code) = $chain + ->then(function(){ + set_error_handler(function(){ /* NOP */ }); + user_error('Error', E_USER_ERROR); + }) + ->then(function(){ + echo "Good"; + }) + ->thenIfErrored(function(){ + echo "Bad"; + }) + ->executeInSubprocess(); + + $this->assertContains("Good", $out); + } } \ No newline at end of file