\n
" . htmlentities($val, ENT_COMPAT, 'UTF-8') . "\n"; } } return $val; } /** * Show a debugging message */ public static function message($message, $showHeader = true) { if(!Director::isLive()) { $caller = Debug::caller(); $file = basename($caller['file']); if(Director::is_cli()) { if($showHeader) echo "Debug (line $caller[line] of $file):\n "; echo $message . "\n"; } else { echo "\n"; } } } // Keep track of how many headers have been sent private static $headerCount = 0; /** * Send a debug message in an HTTP header. Only works if you are * on Dev, and headers have not yet been sent. * * @param string $msg * @param string $prefix (optional) * @return void */ public static function header($msg, $prefix = null) { if (Director::isDev() && !headers_sent()) { self::$headerCount++; header('SS-'.self::$headerCount.($prefix?'-'.$prefix:'').': '.$msg); } } /** * Log to a standard text file output. * * @param $message string to output */ public static function log($message) { if (defined('BASE_PATH')) { $path = BASE_PATH; } else { $path = dirname(__FILE__) . '/../..'; } $file = $path . '/debug.log'; $now = date('r'); $content = "\n\n== $now ==\n$message\n"; file_put_contents($file, $content, FILE_APPEND); } /** * Load error handlers into environment. * Caution: The error levels default to E_ALL is the site is in dev-mode (set in main.php). */ public static function loadErrorHandlers() { set_error_handler('errorHandler', error_reporting()); set_exception_handler('exceptionHandler'); } public static function noticeHandler($errno, $errstr, $errfile, $errline, $errcontext) { if(error_reporting() == 0) return; ini_set('display_errors', 0); // Send out the error details to the logger for writing SS_Log::log( array( 'errno' => $errno, 'errstr' => $errstr, 'errfile' => $errfile, 'errline' => $errline, 'errcontext' => $errcontext ), SS_Log::NOTICE ); if(Director::isDev()) { return self::showError($errno, $errstr, $errfile, $errline, $errcontext, "Notice"); } else { return false; } } /** * Handle a non-fatal warning error thrown by PHP interpreter. * * @param unknown_type $errno * @param unknown_type $errstr * @param unknown_type $errfile * @param unknown_type $errline * @param unknown_type $errcontext */ public static function warningHandler($errno, $errstr, $errfile, $errline, $errcontext) { if(error_reporting() == 0) return; ini_set('display_errors', 0); // Send out the error details to the logger for writing SS_Log::log( array( 'errno' => $errno, 'errstr' => $errstr, 'errfile' => $errfile, 'errline' => $errline, 'errcontext' => $errcontext ), SS_Log::WARN ); if(Director::isDev()) { return self::showError($errno, $errstr, $errfile, $errline, $errcontext, "Warning"); } else { return false; } } /** * Handle a fatal error, depending on the mode of the site (ie: Dev, Test, or Live). * * Runtime execution dies immediately once the error is generated. * * @param unknown_type $errno * @param unknown_type $errstr * @param unknown_type $errfile * @param unknown_type $errline * @param unknown_type $errcontext */ public static function fatalHandler($errno, $errstr, $errfile, $errline, $errcontext) { ini_set('display_errors', 0); // Send out the error details to the logger for writing SS_Log::log( array( 'errno' => $errno, 'errstr' => $errstr, 'errfile' => $errfile, 'errline' => $errline, 'errcontext' => $errcontext ), SS_Log::ERR ); if(Director::isDev() || Director::is_cli()) { self::showError($errno, $errstr, $errfile, $errline, $errcontext, "Error"); } else { self::friendlyError(); } return false; } /** * Render a user-facing error page, using the default HTML error template * rendered by {@link ErrorPage} if it exists. Doesn't use the standard {@link SS_HTTPResponse} class * the keep dependencies minimal. * * @uses ErrorPage * * @param int $statusCode HTTP Status Code (Default: 500) * @param string $friendlyErrorMessage User-focused error message. Should not contain code pointers * or "tech-speak". Used in the HTTP Header and ajax responses. * @param string $friendlyErrorDetail Detailed user-focused message. Is just used if no {@link ErrorPage} is found * for this specific status code. * @return string HTML error message for non-ajax requests, plaintext for ajax-request. */ public static function friendlyError($statusCode=500, $friendlyErrorMessage=null, $friendlyErrorDetail=null) { if(!$friendlyErrorMessage) { $friendlyErrorMessage = Config::inst()->get('Debug', 'friendly_error_header'); } if(!$friendlyErrorDetail) { $friendlyErrorDetail = Config::inst()->get('Debug', 'friendly_error_detail'); } if(!headers_sent()) { $currController = Controller::has_curr() ? Controller::curr() : null; // Ensure the error message complies with the HTTP 1.1 spec $msg = strip_tags(str_replace(array("\n", "\r"), '', $friendlyErrorMessage)); if($currController) { $response = $currController->getResponse(); $response->setStatusCode($statusCode, $msg); } else { header($_SERVER['SERVER_PROTOCOL'] . " $statusCode $msg"); } } if(Director::is_ajax()) { echo $friendlyErrorMessage; } else { if(class_exists('ErrorPage')){ $errorFilePath = ErrorPage::get_filepath_for_errorcode( $statusCode, class_exists('Translatable') ? Translatable::get_current_locale() : null ); if(file_exists($errorFilePath)) { $content = file_get_contents($errorFilePath); if(!headers_sent()) header('Content-Type: text/html'); // $BaseURL is left dynamic in error-###.html, so that multi-domain sites don't get broken echo str_replace('$BaseURL', Director::absoluteBaseURL(), $content); } } else { $renderer = new DebugView(); $renderer->writeHeader(); $renderer->writeInfo("Website Error", $friendlyErrorMessage, $friendlyErrorDetail); if(Email::config()->admin_email) { $mailto = Email::obfuscate(Email::config()->admin_email); $renderer->writeParagraph('Contact an administrator: ' . $mailto . ''); } $renderer->writeFooter(); } } return false; } /** * Create an instance of an appropriate DebugView object. * * @return DebugView */ public static function create_debug_view() { $service = Director::is_cli() || Director::is_ajax() ? 'CliDebugView' : 'DebugView'; return Injector::inst()->get($service); } /** * Render a developer facing error page, showing the stack trace and details * of the code where the error occured. * * @param unknown_type $errno * @param unknown_type $errstr * @param unknown_type $errfile * @param unknown_type $errline * @param unknown_type $errcontext */ public static function showError($errno, $errstr, $errfile, $errline, $errcontext, $errtype) { if(!headers_sent()) { $errText = "$errtype at line $errline of $errfile"; $errText = str_replace(array("\n","\r")," ",$errText); if(!headers_sent()) header($_SERVER['SERVER_PROTOCOL'] . " 500 $errText"); // if error is displayed through ajax with CliDebugView, use plaintext output if(Director::is_ajax()) { header('Content-Type: text/plain'); } } $reporter = self::create_debug_view(); // Coupling alert: This relies on knowledge of how the director gets its URL, it could be improved. $httpRequest = null; if(isset($_SERVER['REQUEST_URI'])) { $httpRequest = $_SERVER['REQUEST_URI']; } elseif(isset($_REQUEST['url'])) { $httpRequest = $_REQUEST['url']; } if(isset($_SERVER['REQUEST_METHOD'])) $httpRequest = $_SERVER['REQUEST_METHOD'] . ' ' . $httpRequest; $reporter->writeHeader($httpRequest); $reporter->writeError($httpRequest, $errno, $errstr, $errfile, $errline, $errcontext); if(file_exists($errfile)) { $lines = file($errfile); // Make the array 1-based array_unshift($lines,""); unset($lines[0]); $offset = $errline-10; $lines = array_slice($lines, $offset, 16, true); $reporter->writeSourceFragment($lines, $errline); } $reporter->writeTrace(($errcontext ? $errcontext : debug_backtrace())); $reporter->writeFooter(); } /** * Utility method to render a snippet of PHP source code, from selected file * and highlighting the given line number. * * @param string $errfile * @param int $errline */ public static function showLines($errfile, $errline) { $lines = file($errfile); $offset = $errline-10; $lines = array_slice($lines, $offset, 16); echo '
';
		$offset++;
		foreach($lines as $line) {
			$line = htmlentities($line, ENT_COMPAT, 'UTF-8');
			if ($offset == $errline) {
				echo "$offset $line";
			} else {
				echo "$offset $line";
			}
			$offset++;
		}
		echo '';
	}
	/**
	 * Check if the user has permissions to run URL debug tools,
	 * else redirect them to log in.
	 */
	public static function require_developer_login() {
		if(Director::isDev())	{
			return;
		}
		if(isset($_SESSION['loggedInAs'])) {
			// We have to do some raw SQL here, because this method is called in Object::defineMethods().
			// This means we have to be careful about what objects we create, as we don't want Object::defineMethods()
			// being called again.
			// This basically calls Permission::checkMember($_SESSION['loggedInAs'], 'ADMIN');
			$memberID = $_SESSION['loggedInAs'];
			$groups = DB::query("SELECT \"GroupID\" from \"Group_Members\" WHERE \"MemberID\" = " . $memberID);
			$groupCSV = implode($groups->column(), ',');
			$permission = DB::query("
				SELECT \"ID\"
				FROM \"Permission\"
				WHERE (
					\"Code\" = 'ADMIN'
					AND \"Type\" = " . Permission::GRANT_PERMISSION . "
					AND \"GroupID\" IN ($groupCSV)
				)
			")->value();
			if($permission) {
				return;
			}
		}
		// This basically does the same as
		// Security::permissionFailure(null, "You need to login with developer access to make use of debugging tools.")
		// We have to do this because of how early this method is called in execution.
		$_SESSION['Security']['Message']['message']
			= "You need to login with developer access to make use of debugging tools.";
		$_SESSION['Security']['Message']['type'] =  'warning';
		$_SESSION['BackURL'] = $_SERVER['REQUEST_URI'];
		header($_SERVER['SERVER_PROTOCOL'] . " 302 Found");
		header("Location: " . Director::baseURL() . Security::login_url());
		die();
	}
}
/**
 * Generic callback, to catch uncaught exceptions when they bubble up to the top of the call chain.
 *
 * @ignore
 * @param Exception $exception
 */
function exceptionHandler($exception) {
	$errno = E_USER_ERROR;
	$type = get_class($exception);
	$message = "Uncaught " . $type . ": " . $exception->getMessage();
	$file = $exception->getFile();
	$line = $exception->getLine();
	$context = $exception->getTrace();
	return Debug::fatalHandler($errno, $message, $file, $line, $context);
}
/**
 * Generic callback to catch standard PHP runtime errors thrown by the interpreter
 * or manually triggered with the user_error function.
 * Caution: The error levels default to E_ALL is the site is in dev-mode (set in main.php).
 *
 * @ignore
 * @param int $errno
 * @param string $errstr
 * @param string $errfile
 * @param int $errline
 */
function errorHandler($errno, $errstr, $errfile, $errline) {
	switch($errno) {
		case E_ERROR:
		case E_CORE_ERROR:
		case E_USER_ERROR:
			return Debug::fatalHandler($errno, $errstr, $errfile, $errline, debug_backtrace());
		case E_WARNING:
		case E_CORE_WARNING:
		case E_USER_WARNING:
			return Debug::warningHandler($errno, $errstr, $errfile, $errline, debug_backtrace());
		case E_NOTICE:
		case E_USER_NOTICE:
		case E_DEPRECATED:
		case E_USER_DEPRECATED:
		case E_STRICT:
			return Debug::noticeHandler($errno, $errstr, $errfile, $errline, debug_backtrace());
	}
}