add Whoops Library

This commit is contained in:
Thibault GUILLAUME 2015-09-25 10:26:24 +02:00
parent 79a0bf2d98
commit 988d45c2da
50 changed files with 4472 additions and 8 deletions

5
composer.json Normal file
View File

@ -0,0 +1,5 @@
{
"require": {
"filp/whoops": "^1.1"
}
}

76
composer.lock generated Normal file
View File

@ -0,0 +1,76 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "1534bef39ef39e2a695014db4d4d8247",
"packages": [
{
"name": "filp/whoops",
"version": "1.1.7",
"source": {
"type": "git",
"url": "https://github.com/filp/whoops.git",
"reference": "72538eeb70bbfb11964412a3d098d109efd012f7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/filp/whoops/zipball/72538eeb70bbfb11964412a3d098d109efd012f7",
"reference": "72538eeb70bbfb11964412a3d098d109efd012f7",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"mockery/mockery": "0.9.*"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.2-dev"
}
},
"autoload": {
"psr-0": {
"Whoops": "src/"
},
"classmap": [
"src/deprecated"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Filipe Dobreira",
"homepage": "https://github.com/filp",
"role": "Developer"
}
],
"description": "php error handling for cool kids",
"homepage": "https://github.com/filp/whoops",
"keywords": [
"error",
"exception",
"handling",
"library",
"silex-provider",
"whoops",
"zf2"
],
"time": "2015-06-29 05:42:04"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": []
}

View File

@ -34,6 +34,12 @@ if (_PS_MODE_DEV_ === true)
@ini_set('display_errors', 'on');
@error_reporting(E_ALL | E_STRICT);
define('_PS_DEBUG_SQL_', true);
require_once(dirname(__FILE__).'/../vendor/autoload.php');
$whoops = new \Whoops\Run;
$whoops->pushHandler(new \Whoops\Handler\PrettyPageHandler);
$whoops->register();
}
else
{

View File

@ -1819,7 +1819,12 @@ body .ac_results {
color: #666666;
font-size: 14px;
}
.menu_edito ul li {
border-bottom: 1px solid #f1f1f1;
padding: 3px 0;
text-indent: 5px;
}
.menu_edito ul li a { color: #e44e58 }
/*************************************************************************************************************
*************************************** HOME BOUTIQUE ****************************************

View File

@ -1,7 +1,7 @@
<div class="col-md-{$column} edito">
<div class="inner">
<h3><a href="{$link->getPostEditoLink($edito->id)}" title="{$edito->title}">{$edito->title}</a></h3>
<p>{$edito->intro|substr:0:250}</p>
<p>{$edito->intro|escape:'html':'UTF-8'|truncate:250}</p>
<div class="readmore">
<a href="{$link->getPostEditoLink($edito->id)}" title="{$edito->title}">{l s='Lire la suite ...'}</a>
</div>

View File

@ -24,7 +24,6 @@
</div>
{/if}
{/foreach}
{if $nb_page > 1}
<div class="pagination_inner">
<ul class="pagination">

View File

@ -30,7 +30,7 @@
</ul>
</div>
</div>
<div class="col-lg-9 col-md-8 col-sm-12 col-xs-12 pr0">
<div class="col-lg-9 col-md-8 col-sm-12 col-xs-12">
<h2><a href="{$link->getPostEditoLink($edito->id)}">{$edito->title}</a></h2>
<div class="intro_edito" itemprop="about" itemscope itemtype="http://schema.org/Thing">
{$edito->intro}

View File

@ -14,10 +14,9 @@
</div>
</div>
<main id="postedito" class="container">
<main id="postedito" class="container">
<div class="row article_content">
<div class="col-md-2 menu_edito">
<div class="col-md-2 menu_edito hidden-sm hidden-xs">
<ul>
<li><a href="/">{l s='Accueil'}</a></li>
<li><a href="{$link->getHomeStoreLink()}">{l s='La boutique'}</a></li>

View File

@ -52,7 +52,7 @@
{if (!$PS_CATALOG_MODE && $PS_STOCK_MANAGEMENT && ((isset($product.show_price) && $product.show_price) || (isset($product.available_for_order) && $product.available_for_order)))}
{if isset($product.available_for_order) && $product.available_for_order && !isset($restricted_country_mode)}
<span itemprop="offers" itemscope itemtype="http://schema.org/Offer" class="availability">
{if ($product.allow_oosp || $product.quantity > 0)}
{if (isset($product.allow_oosp) && $product.allow_oosp || $product.quantity > 0)}
<link itemprop="availability" href="http://schema.org/InStock" />
{elseif (isset($product.quantity_all_versions) && $product.quantity_all_versions > 0)}
<link itemprop="availability" href="http://schema.org/LimitedAvailability" />{l s='Product available with different options'}

View File

@ -11,6 +11,9 @@
<main>
{include file="$tpl_dir./errors.tpl"}
{if $errors|@count == 0}
{if !isset($allow_oosp)}
{assign var=allow_oosp value=0}
{/if}
{if !isset($priceDisplayPrecision)}
{assign var='priceDisplayPrecision' value=2}
{/if}

7
vendor/autoload.php vendored Normal file
View File

@ -0,0 +1,7 @@
<?php
// autoload.php @generated by Composer
require_once __DIR__ . '/composer' . '/autoload_real.php';
return ComposerAutoloaderInit745aac61f4fe98ed4c269e23d0de31af::getLoader();

413
vendor/composer/ClassLoader.php vendored Normal file
View File

@ -0,0 +1,413 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0 class loader
*
* See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class ClassLoader
{
// PSR-4
private $prefixLengthsPsr4 = array();
private $prefixDirsPsr4 = array();
private $fallbackDirsPsr4 = array();
// PSR-0
private $prefixesPsr0 = array();
private $fallbackDirsPsr0 = array();
private $useIncludePath = false;
private $classMap = array();
private $classMapAuthoritative = false;
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', $this->prefixesPsr0);
}
return array();
}
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array $classMap Class to filename map
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*/
public function add($prefix, $paths, $prepend = false)
{
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
(array) $paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
(array) $paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
(array) $paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-0 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
(array) $paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
(array) $paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
(array) $paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 base directories
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}
/**
* Unregisters this instance as an autoloader.
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return bool|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
includeFile($file);
return true;
}
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731
if ('\\' == $class[0]) {
$class = substr($class, 1);
}
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative) {
return false;
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if ($file === null && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if ($file === null) {
// Remember that this class does not exist.
return $this->classMap[$class] = false;
}
return $file;
}
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) {
if (0 === strpos($class, $prefix)) {
foreach ($this->prefixDirsPsr4[$prefix] as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
}
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*/
function includeFile($file)
{
include $file;
}

21
vendor/composer/LICENSE vendored Normal file
View File

@ -0,0 +1,21 @@
Copyright (c) 2015 Nils Adermann, Jordi Boggiano
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

12
vendor/composer/autoload_classmap.php vendored Normal file
View File

@ -0,0 +1,12 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Whoops\\Module' => $vendorDir . '/filp/whoops/src/deprecated/Zend/Module.php',
'Whoops\\Provider\\Zend\\ExceptionStrategy' => $vendorDir . '/filp/whoops/src/deprecated/Zend/ExceptionStrategy.php',
'Whoops\\Provider\\Zend\\RouteNotFoundStrategy' => $vendorDir . '/filp/whoops/src/deprecated/Zend/RouteNotFoundStrategy.php',
);

10
vendor/composer/autoload_namespaces.php vendored Normal file
View File

@ -0,0 +1,10 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Whoops' => array($vendorDir . '/filp/whoops/src'),
);

9
vendor/composer/autoload_psr4.php vendored Normal file
View File

@ -0,0 +1,9 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
);

50
vendor/composer/autoload_real.php vendored Normal file
View File

@ -0,0 +1,50 @@
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit745aac61f4fe98ed4c269e23d0de31af
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInit745aac61f4fe98ed4c269e23d0de31af', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInit745aac61f4fe98ed4c269e23d0de31af', 'loadClassLoader'));
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
}
$map = require __DIR__ . '/autoload_psr4.php';
foreach ($map as $namespace => $path) {
$loader->setPsr4($namespace, $path);
}
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
$loader->register(true);
return $loader;
}
}
function composerRequire745aac61f4fe98ed4c269e23d0de31af($file)
{
require $file;
}

62
vendor/composer/installed.json vendored Normal file
View File

@ -0,0 +1,62 @@
[
{
"name": "filp/whoops",
"version": "1.1.7",
"version_normalized": "1.1.7.0",
"source": {
"type": "git",
"url": "https://github.com/filp/whoops.git",
"reference": "72538eeb70bbfb11964412a3d098d109efd012f7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/filp/whoops/zipball/72538eeb70bbfb11964412a3d098d109efd012f7",
"reference": "72538eeb70bbfb11964412a3d098d109efd012f7",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"mockery/mockery": "0.9.*"
},
"time": "2015-06-29 05:42:04",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.2-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-0": {
"Whoops": "src/"
},
"classmap": [
"src/deprecated"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Filipe Dobreira",
"homepage": "https://github.com/filp",
"role": "Developer"
}
],
"description": "php error handling for cool kids",
"homepage": "https://github.com/filp/whoops",
"keywords": [
"error",
"exception",
"handling",
"library",
"silex-provider",
"whoops",
"zf2"
]
}
]

19
vendor/filp/whoops/LICENSE.md vendored Normal file
View File

@ -0,0 +1,19 @@
#The MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

33
vendor/filp/whoops/composer.json vendored Normal file
View File

@ -0,0 +1,33 @@
{
"name": "filp/whoops",
"license": "MIT",
"description": "php error handling for cool kids",
"keywords": ["library", "error", "handling", "exception", "silex-provider", "whoops", "zf2"],
"homepage": "https://github.com/filp/whoops",
"authors": [
{
"name": "Filipe Dobreira",
"homepage": "https://github.com/filp",
"role": "Developer"
}
],
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"mockery/mockery": "0.9.*"
},
"autoload": {
"psr-0": {
"Whoops": "src/"
},
"classmap": [
"src/deprecated"
]
},
"extra": {
"branch-alias": {
"dev-master": "1.2-dev"
}
}
}

View File

@ -0,0 +1,17 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Exception;
use ErrorException as BaseErrorException;
/**
* Wraps ErrorException; mostly used for typing (at least now)
* to easily cleanup the stack trace of redundant info.
*/
class ErrorException extends BaseErrorException
{
}

View File

@ -0,0 +1,74 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Exception;
class Formatter
{
/**
* Returns all basic information about the exception in a simple array
* for further convertion to other languages
* @param Inspector $inspector
* @param bool $shouldAddTrace
* @return array
*/
public static function formatExceptionAsDataArray(Inspector $inspector, $shouldAddTrace)
{
$exception = $inspector->getException();
$response = array(
'type' => get_class($exception),
'message' => $exception->getMessage(),
'file' => $exception->getFile(),
'line' => $exception->getLine(),
);
if ($shouldAddTrace) {
$frames = $inspector->getFrames();
$frameData = array();
foreach ($frames as $frame) {
/** @var Frame $frame */
$frameData[] = array(
'file' => $frame->getFile(),
'line' => $frame->getLine(),
'function' => $frame->getFunction(),
'class' => $frame->getClass(),
'args' => $frame->getArgs(),
);
}
$response['trace'] = $frameData;
}
return $response;
}
public static function formatExceptionPlain(Inspector $inspector)
{
$message = $inspector->getException()->getMessage();
$frames = $inspector->getFrames();
$plain = $inspector->getExceptionName();
$plain .= ' thrown with message "';
$plain .= $message;
$plain .= '"'."\n\n";
$plain .= "Stacktrace:\n";
foreach ($frames as $i => $frame) {
$plain .= "#". (count($frames) - $i - 1). " ";
$plain .= $frame->getClass() ?: '';
$plain .= $frame->getClass() && $frame->getFunction() ? ":" : "";
$plain .= $frame->getFunction() ?: '';
$plain .= ' in ';
$plain .= ($frame->getFile() ?: '<#unknown>');
$plain .= ':';
$plain .= (int) $frame->getLine(). "\n";
}
return $plain;
}
}

View File

@ -0,0 +1,269 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Exception;
use InvalidArgumentException;
use Serializable;
class Frame implements Serializable
{
/**
* @var array
*/
protected $frame;
/**
* @var string
*/
protected $fileContentsCache;
/**
* @var array[]
*/
protected $comments = array();
/**
* @param array[]
*/
public function __construct(array $frame)
{
$this->frame = $frame;
}
/**
* @param bool $shortened
* @return string|null
*/
public function getFile($shortened = false)
{
if (empty($this->frame['file'])) {
return null;
}
$file = $this->frame['file'];
// Check if this frame occurred within an eval().
// @todo: This can be made more reliable by checking if we've entered
// eval() in a previous trace, but will need some more work on the upper
// trace collector(s).
if (preg_match('/^(.*)\((\d+)\) : (?:eval\(\)\'d|assert) code$/', $file, $matches)) {
$file = $this->frame['file'] = $matches[1];
$this->frame['line'] = (int) $matches[2];
}
if ($shortened && is_string($file)) {
// Replace the part of the path that all frames have in common, and add 'soft hyphens' for smoother line-breaks.
$dirname = dirname(dirname(dirname(dirname(dirname(dirname(__DIR__))))));
$file = str_replace($dirname, "&hellip;", $file);
$file = str_replace("/", "/&shy;", $file);
}
return $file;
}
/**
* @return int|null
*/
public function getLine()
{
return isset($this->frame['line']) ? $this->frame['line'] : null;
}
/**
* @return string|null
*/
public function getClass()
{
return isset($this->frame['class']) ? $this->frame['class'] : null;
}
/**
* @return string|null
*/
public function getFunction()
{
return isset($this->frame['function']) ? $this->frame['function'] : null;
}
/**
* @return array
*/
public function getArgs()
{
return isset($this->frame['args']) ? (array) $this->frame['args'] : array();
}
/**
* Returns the full contents of the file for this frame,
* if it's known.
* @return string|null
*/
public function getFileContents()
{
if ($this->fileContentsCache === null && $filePath = $this->getFile()) {
// Leave the stage early when 'Unknown' is passed
// this would otherwise raise an exception when
// open_basedir is enabled.
if ($filePath === "Unknown") {
return null;
}
// Return null if the file doesn't actually exist.
if (!is_file($filePath)) {
return null;
}
$this->fileContentsCache = file_get_contents($filePath);
}
return $this->fileContentsCache;
}
/**
* Adds a comment to this frame, that can be received and
* used by other handlers. For example, the PrettyPage handler
* can attach these comments under the code for each frame.
*
* An interesting use for this would be, for example, code analysis
* & annotations.
*
* @param string $comment
* @param string $context Optional string identifying the origin of the comment
*/
public function addComment($comment, $context = 'global')
{
$this->comments[] = array(
'comment' => $comment,
'context' => $context,
);
}
/**
* Returns all comments for this frame. Optionally allows
* a filter to only retrieve comments from a specific
* context.
*
* @param string $filter
* @return array[]
*/
public function getComments($filter = null)
{
$comments = $this->comments;
if ($filter !== null) {
$comments = array_filter($comments, function ($c) use ($filter) {
return $c['context'] == $filter;
});
}
return $comments;
}
/**
* Returns the array containing the raw frame data from which
* this Frame object was built
*
* @return array
*/
public function getRawFrame()
{
return $this->frame;
}
/**
* Returns the contents of the file for this frame as an
* array of lines, and optionally as a clamped range of lines.
*
* NOTE: lines are 0-indexed
*
* @example
* Get all lines for this file
* $frame->getFileLines(); // => array( 0 => '<?php', 1 => '...', ...)
* @example
* Get one line for this file, starting at line 10 (zero-indexed, remember!)
* $frame->getFileLines(9, 1); // array( 10 => '...', 11 => '...')
*
* @throws InvalidArgumentException if $length is less than or equal to 0
* @param int $start
* @param int $length
* @return string[]|null
*/
public function getFileLines($start = 0, $length = null)
{
if (null !== ($contents = $this->getFileContents())) {
$lines = explode("\n", $contents);
// Get a subset of lines from $start to $end
if ($length !== null) {
$start = (int) $start;
$length = (int) $length;
if ($start < 0) {
$start = 0;
}
if ($length <= 0) {
throw new InvalidArgumentException(
"\$length($length) cannot be lower or equal to 0"
);
}
$lines = array_slice($lines, $start, $length, true);
}
return $lines;
}
}
/**
* Implements the Serializable interface, with special
* steps to also save the existing comments.
*
* @see Serializable::serialize
* @return string
*/
public function serialize()
{
$frame = $this->frame;
if (!empty($this->comments)) {
$frame['_comments'] = $this->comments;
}
return serialize($frame);
}
/**
* Unserializes the frame data, while also preserving
* any existing comment data.
*
* @see Serializable::unserialize
* @param string $serializedFrame
*/
public function unserialize($serializedFrame)
{
$frame = unserialize($serializedFrame);
if (!empty($frame['_comments'])) {
$this->comments = $frame['_comments'];
unset($frame['_comments']);
}
$this->frame = $frame;
}
/**
* Compares Frame against one another
* @param Frame $frame
* @return bool
*/
public function equals(Frame $frame)
{
if (!$this->getFile() || $this->getFile() === 'Unknown' || !$this->getLine()) {
return false;
}
return $frame->getFile() === $this->getFile() && $frame->getLine() === $this->getLine();
}
}

View File

@ -0,0 +1,191 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Exception;
use ArrayAccess;
use ArrayIterator;
use Countable;
use IteratorAggregate;
use Serializable;
use UnexpectedValueException;
/**
* Exposes a fluent interface for dealing with an ordered list
* of stack-trace frames.
*/
class FrameCollection implements ArrayAccess, IteratorAggregate, Serializable, Countable
{
/**
* @var array[]
*/
private $frames;
/**
* @param array $frames
*/
public function __construct(array $frames)
{
$this->frames = array_map(function ($frame) {
return new Frame($frame);
}, $frames);
}
/**
* Filters frames using a callable, returns the same FrameCollection
*
* @param callable $callable
* @return FrameCollection
*/
public function filter($callable)
{
$this->frames = array_filter($this->frames, $callable);
return $this;
}
/**
* Map the collection of frames
*
* @param callable $callable
* @return FrameCollection
*/
public function map($callable)
{
// Contain the map within a higher-order callable
// that enforces type-correctness for the $callable
$this->frames = array_map(function ($frame) use ($callable) {
$frame = call_user_func($callable, $frame);
if (!$frame instanceof Frame) {
throw new UnexpectedValueException(
"Callable to " . __METHOD__ . " must return a Frame object"
);
}
return $frame;
}, $this->frames);
return $this;
}
/**
* Returns an array with all frames, does not affect
* the internal array.
*
* @todo If this gets any more complex than this,
* have getIterator use this method.
* @see FrameCollection::getIterator
* @return array
*/
public function getArray()
{
return $this->frames;
}
/**
* @see IteratorAggregate::getIterator
* @return ArrayIterator
*/
public function getIterator()
{
return new ArrayIterator($this->frames);
}
/**
* @see ArrayAccess::offsetExists
* @param int $offset
*/
public function offsetExists($offset)
{
return isset($this->frames[$offset]);
}
/**
* @see ArrayAccess::offsetGet
* @param int $offset
*/
public function offsetGet($offset)
{
return $this->frames[$offset];
}
/**
* @see ArrayAccess::offsetSet
* @param int $offset
*/
public function offsetSet($offset, $value)
{
throw new \Exception(__CLASS__ . ' is read only');
}
/**
* @see ArrayAccess::offsetUnset
* @param int $offset
*/
public function offsetUnset($offset)
{
throw new \Exception(__CLASS__ . ' is read only');
}
/**
* @see Countable::count
* @return int
*/
public function count()
{
return count($this->frames);
}
/**
* @see Serializable::serialize
* @return string
*/
public function serialize()
{
return serialize($this->frames);
}
/**
* @see Serializable::unserialize
* @param string $serializedFrames
*/
public function unserialize($serializedFrames)
{
$this->frames = unserialize($serializedFrames);
}
/**
* @param Frame[] $frames Array of Frame instances, usually from $e->getPrevious()
*/
public function prependFrames(array $frames)
{
$this->frames = array_merge($frames, $this->frames);
}
/**
* Gets the innermost part of stack trace that is not the same as that of outer exception
*
* @param FrameCollection $parentFrames Outer exception frames to compare tail against
* @return Frame[]
*/
public function topDiff(FrameCollection $parentFrames)
{
$diff = $this->frames;
$parentFrames = $parentFrames->getArray();
$p = count($parentFrames)-1;
for ($i = count($diff)-1; $i >= 0 && $p >= 0; $i--) {
/** @var Frame $tailFrame */
$tailFrame = $diff[$i];
if ($tailFrame->equals($parentFrames[$p])) {
unset($diff[$i]);
}
$p--;
}
return $diff;
}
}

View File

@ -0,0 +1,161 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Exception;
use Exception;
class Inspector
{
/**
* @var Exception
*/
private $exception;
/**
* @var \Whoops\Exception\FrameCollection
*/
private $frames;
/**
* @var \Whoops\Exception\Inspector
*/
private $previousExceptionInspector;
/**
* @param Exception $exception The exception to inspect
*/
public function __construct(Exception $exception)
{
$this->exception = $exception;
}
/**
* @return Exception
*/
public function getException()
{
return $this->exception;
}
/**
* @return string
*/
public function getExceptionName()
{
return get_class($this->exception);
}
/**
* @return string
*/
public function getExceptionMessage()
{
return $this->exception->getMessage();
}
/**
* Does the wrapped Exception has a previous Exception?
* @return bool
*/
public function hasPreviousException()
{
return $this->previousExceptionInspector || $this->exception->getPrevious();
}
/**
* Returns an Inspector for a previous Exception, if any.
* @todo Clean this up a bit, cache stuff a bit better.
* @return Inspector
*/
public function getPreviousExceptionInspector()
{
if ($this->previousExceptionInspector === null) {
$previousException = $this->exception->getPrevious();
if ($previousException) {
$this->previousExceptionInspector = new Inspector($previousException);
}
}
return $this->previousExceptionInspector;
}
/**
* Returns an iterator for the inspected exception's
* frames.
* @return \Whoops\Exception\FrameCollection
*/
public function getFrames()
{
if ($this->frames === null) {
$frames = $this->exception->getTrace();
// If we're handling an ErrorException thrown by Whoops,
// get rid of the last frame, which matches the handleError method,
// and do not add the current exception to trace. We ensure that
// the next frame does have a filename / linenumber, though.
if ($this->exception instanceof ErrorException && empty($frames[1]['line'])) {
$frames = array($this->getFrameFromError($this->exception));
} else {
$firstFrame = $this->getFrameFromException($this->exception);
array_unshift($frames, $firstFrame);
}
$this->frames = new FrameCollection($frames);
if ($previousInspector = $this->getPreviousExceptionInspector()) {
// Keep outer frame on top of the inner one
$outerFrames = $this->frames;
$newFrames = clone $previousInspector->getFrames();
// I assume it will always be set, but let's be safe
if (isset($newFrames[0])) {
$newFrames[0]->addComment(
$previousInspector->getExceptionMessage(),
'Exception message:'
);
}
$newFrames->prependFrames($outerFrames->topDiff($newFrames));
$this->frames = $newFrames;
}
}
return $this->frames;
}
/**
* Given an exception, generates an array in the format
* generated by Exception::getTrace()
* @param Exception $exception
* @return array
*/
protected function getFrameFromException(Exception $exception)
{
return array(
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'class' => get_class($exception),
'args' => array(
$exception->getMessage(),
),
);
}
/**
* Given an error, generates an array in the format
* generated by ErrorException
* @param ErrorException $exception
* @return array
*/
protected function getFrameFromError(ErrorException $exception)
{
return array(
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'class' => null,
'args' => array(),
);
}
}

View File

@ -0,0 +1,52 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Handler;
use InvalidArgumentException;
/**
* Wrapper for Closures passed as handlers. Can be used
* directly, or will be instantiated automagically by Whoops\Run
* if passed to Run::pushHandler
*/
class CallbackHandler extends Handler
{
/**
* @var callable
*/
protected $callable;
/**
* @throws InvalidArgumentException If argument is not callable
* @param callable $callable
*/
public function __construct($callable)
{
if (!is_callable($callable)) {
throw new InvalidArgumentException(
'Argument to ' . __METHOD__ . ' must be valid callable'
);
}
$this->callable = $callable;
}
/**
* @return int|null
*/
public function handle()
{
$exception = $this->getException();
$inspector = $this->getInspector();
$run = $this->getRun();
$callable = $this->callable;
// invoke the callable directly, to get simpler stacktraces (in comparison to call_user_func).
// this assumes that $callable is a properly typed php-callable, which we check in __construct().
return $callable($exception, $inspector, $run);
}
}

View File

@ -0,0 +1,89 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Handler;
use Exception;
use Whoops\Exception\Inspector;
use Whoops\Run;
/**
* Abstract implementation of a Handler.
*/
abstract class Handler implements HandlerInterface
{
/**
* Return constants that can be returned from Handler::handle
* to message the handler walker.
*/
const DONE = 0x10; // returning this is optional, only exists for
// semantic purposes
const LAST_HANDLER = 0x20;
const QUIT = 0x30;
/**
* @var Run
*/
private $run;
/**
* @var Inspector $inspector
*/
private $inspector;
/**
* @var Exception $exception
*/
private $exception;
/**
* @param Run $run
*/
public function setRun(Run $run)
{
$this->run = $run;
}
/**
* @return Run
*/
protected function getRun()
{
return $this->run;
}
/**
* @param Inspector $inspector
*/
public function setInspector(Inspector $inspector)
{
$this->inspector = $inspector;
}
/**
* @return Inspector
*/
protected function getInspector()
{
return $this->inspector;
}
/**
* @param Exception $exception
*/
public function setException(Exception $exception)
{
$this->exception = $exception;
}
/**
* @return Exception
*/
protected function getException()
{
return $this->exception;
}
}

View File

@ -0,0 +1,37 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Handler;
use Exception;
use Whoops\Exception\Inspector;
use Whoops\Run;
interface HandlerInterface
{
/**
* @return int|null A handler may return nothing, or a Handler::HANDLE_* constant
*/
public function handle();
/**
* @param Run $run
* @return void
*/
public function setRun(Run $run);
/**
* @param Exception $exception
* @return void
*/
public function setException(Exception $exception);
/**
* @param Inspector $inspector
* @return void
*/
public function setInspector(Inspector $inspector);
}

View File

@ -0,0 +1,90 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Handler;
use Whoops\Exception\Formatter;
/**
* Catches an exception and converts it to a JSON
* response. Additionally can also return exception
* frames for consumption by an API.
*/
class JsonResponseHandler extends Handler
{
/**
* @var bool
*/
private $returnFrames = false;
/**
* @var bool
*/
private $onlyForAjaxRequests = false;
/**
* @param bool|null $returnFrames
* @return bool|$this
*/
public function addTraceToOutput($returnFrames = null)
{
if (func_num_args() == 0) {
return $this->returnFrames;
}
$this->returnFrames = (bool) $returnFrames;
return $this;
}
/**
* @param bool|null $onlyForAjaxRequests
* @return null|bool
*/
public function onlyForAjaxRequests($onlyForAjaxRequests = null)
{
if (func_num_args() == 0) {
return $this->onlyForAjaxRequests;
}
$this->onlyForAjaxRequests = (bool) $onlyForAjaxRequests;
}
/**
* Check, if possible, that this execution was triggered by an AJAX request.
*
* @return bool
*/
private function isAjaxRequest()
{
return (
!empty($_SERVER['HTTP_X_REQUESTED_WITH'])
&& strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest');
}
/**
* @return int
*/
public function handle()
{
if ($this->onlyForAjaxRequests() && !$this->isAjaxRequest()) {
return Handler::DONE;
}
$response = array(
'error' => Formatter::formatExceptionAsDataArray(
$this->getInspector(),
$this->addTraceToOutput()
),
);
if (\Whoops\Util\Misc::canSendHeaders()) {
header('Content-Type: application/json');
}
echo json_encode($response);
return Handler::QUIT;
}
}

View File

@ -0,0 +1,331 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
* Plaintext handler for command line and logs.
* @author Pierre-Yves Landuré <https://howto.biapy.com/>
*/
namespace Whoops\Handler;
use InvalidArgumentException;
use Psr\Log\LoggerInterface;
use Whoops\Exception\Frame;
/**
* Handler outputing plaintext error messages. Can be used
* directly, or will be instantiated automagically by Whoops\Run
* if passed to Run::pushHandler
*/
class PlainTextHandler extends Handler
{
const VAR_DUMP_PREFIX = ' | ';
/**
* @var \Psr\Log\LoggerInterface
*/
protected $logger;
/**
* @var bool
*/
private $addTraceToOutput = true;
/**
* @var bool|integer
*/
private $addTraceFunctionArgsToOutput = false;
/**
* @var integer
*/
private $traceFunctionArgsOutputLimit = 1024;
/**
* @var bool
*/
private $onlyForCommandLine = false;
/**
* @var bool
*/
private $outputOnlyIfCommandLine = true;
/**
* @var bool
*/
private $loggerOnly = false;
/**
* Constructor.
* @throws InvalidArgumentException If argument is not null or a LoggerInterface
* @param \Psr\Log\LoggerInterface|null $logger
*/
public function __construct($logger = null)
{
$this->setLogger($logger);
}
/**
* Set the output logger interface.
* @throws InvalidArgumentException If argument is not null or a LoggerInterface
* @param \Psr\Log\LoggerInterface|null $logger
*/
public function setLogger($logger = null)
{
if (! (is_null($logger)
|| $logger instanceof LoggerInterface)) {
throw new InvalidArgumentException(
'Argument to ' . __METHOD__ .
" must be a valid Logger Interface (aka. Monolog), " .
get_class($logger) . ' given.'
);
}
$this->logger = $logger;
}
/**
* @return \Psr\Log\LoggerInterface|null
*/
public function getLogger()
{
return $this->logger;
}
/**
* Add error trace to output.
* @param bool|null $addTraceToOutput
* @return bool|$this
*/
public function addTraceToOutput($addTraceToOutput = null)
{
if (func_num_args() == 0) {
return $this->addTraceToOutput;
}
$this->addTraceToOutput = (bool) $addTraceToOutput;
return $this;
}
/**
* Add error trace function arguments to output.
* Set to True for all frame args, or integer for the n first frame args.
* @param bool|integer|null $addTraceFunctionArgsToOutput
* @return null|bool|integer
*/
public function addTraceFunctionArgsToOutput($addTraceFunctionArgsToOutput = null)
{
if (func_num_args() == 0) {
return $this->addTraceFunctionArgsToOutput;
}
if (! is_integer($addTraceFunctionArgsToOutput)) {
$this->addTraceFunctionArgsToOutput = (bool) $addTraceFunctionArgsToOutput;
} else {
$this->addTraceFunctionArgsToOutput = $addTraceFunctionArgsToOutput;
}
}
/**
* Set the size limit in bytes of frame arguments var_dump output.
* If the limit is reached, the var_dump output is discarded.
* Prevent memory limit errors.
* @var integer
*/
public function setTraceFunctionArgsOutputLimit($traceFunctionArgsOutputLimit)
{
$this->traceFunctionArgsOutputLimit = (integer) $traceFunctionArgsOutputLimit;
}
/**
* Get the size limit in bytes of frame arguments var_dump output.
* If the limit is reached, the var_dump output is discarded.
* Prevent memory limit errors.
* @return integer
*/
public function getTraceFunctionArgsOutputLimit()
{
return $this->traceFunctionArgsOutputLimit;
}
/**
* Restrict error handling to command line calls.
* @param bool|null $onlyForCommandLine
* @return null|bool
*/
public function onlyForCommandLine($onlyForCommandLine = null)
{
if (func_num_args() == 0) {
return $this->onlyForCommandLine;
}
$this->onlyForCommandLine = (bool) $onlyForCommandLine;
}
/**
* Output the error message only if using command line.
* else, output to logger if available.
* Allow to safely add this handler to web pages.
* @param bool|null $outputOnlyIfCommandLine
* @return null|bool
*/
public function outputOnlyIfCommandLine($outputOnlyIfCommandLine = null)
{
if (func_num_args() == 0) {
return $this->outputOnlyIfCommandLine;
}
$this->outputOnlyIfCommandLine = (bool) $outputOnlyIfCommandLine;
}
/**
* Only output to logger.
* @param bool|null $loggerOnly
* @return null|bool
*/
public function loggerOnly($loggerOnly = null)
{
if (func_num_args() == 0) {
return $this->loggerOnly;
}
$this->loggerOnly = (bool) $loggerOnly;
}
/**
* Check, if possible, that this execution was triggered by a command line.
* @return bool
*/
private function isCommandLine()
{
return PHP_SAPI == 'cli';
}
/**
* Test if handler can process the exception..
* @return bool
*/
private function canProcess()
{
return $this->isCommandLine() || !$this->onlyForCommandLine();
}
/**
* Test if handler can output to stdout.
* @return bool
*/
private function canOutput()
{
return ($this->isCommandLine() || ! $this->outputOnlyIfCommandLine())
&& ! $this->loggerOnly();
}
/**
* Get the frame args var_dump.
* @param \Whoops\Exception\Frame $frame [description]
* @param integer $line [description]
* @return string
*/
private function getFrameArgsOutput(Frame $frame, $line)
{
if ($this->addTraceFunctionArgsToOutput() === false
|| $this->addTraceFunctionArgsToOutput() < $line) {
return '';
}
// Dump the arguments:
ob_start();
var_dump($frame->getArgs());
if (ob_get_length() > $this->getTraceFunctionArgsOutputLimit()) {
// The argument var_dump is to big.
// Discarded to limit memory usage.
ob_clean();
return sprintf(
"\n%sArguments dump length greater than %d Bytes. Discarded.",
self::VAR_DUMP_PREFIX,
$this->getTraceFunctionArgsOutputLimit()
);
}
return sprintf("\n%s",
preg_replace('/^/m', self::VAR_DUMP_PREFIX, ob_get_clean())
);
}
/**
* Get the exception trace as plain text.
* @return string
*/
private function getTraceOutput()
{
if (! $this->addTraceToOutput()) {
return '';
}
$inspector = $this->getInspector();
$frames = $inspector->getFrames();
$response = "\nStack trace:";
$line = 1;
foreach ($frames as $frame) {
/** @var Frame $frame */
$class = $frame->getClass();
$template = "\n%3d. %s->%s() %s:%d%s";
if (! $class) {
// Remove method arrow (->) from output.
$template = "\n%3d. %s%s() %s:%d%s";
}
$response .= sprintf(
$template,
$line,
$class,
$frame->getFunction(),
$frame->getFile(),
$frame->getLine(),
$this->getFrameArgsOutput($frame, $line)
);
$line++;
}
return $response;
}
/**
* @return int
*/
public function handle()
{
if (! $this->canProcess()) {
return Handler::DONE;
}
$exception = $this->getException();
$response = sprintf("%s: %s in file %s on line %d%s\n",
get_class($exception),
$exception->getMessage(),
$exception->getFile(),
$exception->getLine(),
$this->getTraceOutput()
);
if ($this->getLogger()) {
$this->getLogger()->error($response);
}
if (! $this->canOutput()) {
return Handler::DONE;
}
if (class_exists('\Whoops\Util\Misc')
&& \Whoops\Util\Misc::canSendHeaders()) {
header('Content-Type: text/plain');
}
echo $response;
return Handler::QUIT;
}
}

View File

@ -0,0 +1,529 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Handler;
use InvalidArgumentException;
use RuntimeException;
use UnexpectedValueException;
use Whoops\Exception\Formatter;
use Whoops\Util\Misc;
use Whoops\Util\TemplateHelper;
class PrettyPageHandler extends Handler
{
/**
* Search paths to be scanned for resources, in the reverse
* order they're declared.
*
* @var array
*/
private $searchPaths = array();
/**
* Fast lookup cache for known resource locations.
*
* @var array
*/
private $resourceCache = array();
/**
* The name of the custom css file.
*
* @var string
*/
private $customCss = null;
/**
* @var array[]
*/
private $extraTables = array();
/**
* @var bool
*/
private $handleUnconditionally = false;
/**
* @var string
*/
private $pageTitle = "Whoops! There was an error.";
/**
* A string identifier for a known IDE/text editor, or a closure
* that resolves a string that can be used to open a given file
* in an editor. If the string contains the special substrings
* %file or %line, they will be replaced with the correct data.
*
* @example
* "txmt://open?url=%file&line=%line"
* @var mixed $editor
*/
protected $editor;
/**
* A list of known editor strings
* @var array
*/
protected $editors = array(
"sublime" => "subl://open?url=file://%file&line=%line",
"textmate" => "txmt://open?url=file://%file&line=%line",
"emacs" => "emacs://open?url=file://%file&line=%line",
"macvim" => "mvim://open/?url=file://%file&line=%line",
"phpstorm" => "phpstorm://open?file=%file&line=%line",
);
/**
* Constructor.
*/
public function __construct()
{
if (ini_get('xdebug.file_link_format') || extension_loaded('xdebug')) {
// Register editor using xdebug's file_link_format option.
$this->editors['xdebug'] = function ($file, $line) {
return str_replace(array('%f', '%l'), array($file, $line), ini_get('xdebug.file_link_format'));
};
}
// Add the default, local resource search path:
$this->searchPaths[] = __DIR__ . "/../Resources";
}
/**
* @return int|null
*/
public function handle()
{
if (!$this->handleUnconditionally()) {
// Check conditions for outputting HTML:
// @todo: Make this more robust
if (php_sapi_name() === 'cli') {
// Help users who have been relying on an internal test value
// fix their code to the proper method
if (isset($_ENV['whoops-test'])) {
throw new \Exception(
'Use handleUnconditionally instead of whoops-test'
.' environment variable'
);
}
return Handler::DONE;
}
}
// @todo: Make this more dynamic
$helper = new TemplateHelper();
$templateFile = $this->getResource("views/layout.html.php");
$cssFile = $this->getResource("css/whoops.base.css");
$zeptoFile = $this->getResource("js/zepto.min.js");
$jsFile = $this->getResource("js/whoops.base.js");
if ($this->customCss) {
$customCssFile = $this->getResource($this->customCss);
}
$inspector = $this->getInspector();
$frames = $inspector->getFrames();
$code = $inspector->getException()->getCode();
if ($inspector->getException() instanceof \ErrorException) {
// ErrorExceptions wrap the php-error types within the "severity" property
$code = Misc::translateErrorCode($inspector->getException()->getSeverity());
}
// List of variables that will be passed to the layout template.
$vars = array(
"page_title" => $this->getPageTitle(),
// @todo: Asset compiler
"stylesheet" => file_get_contents($cssFile),
"zepto" => file_get_contents($zeptoFile),
"javascript" => file_get_contents($jsFile),
// Template paths:
"header" => $this->getResource("views/header.html.php"),
"frame_list" => $this->getResource("views/frame_list.html.php"),
"frame_code" => $this->getResource("views/frame_code.html.php"),
"env_details" => $this->getResource("views/env_details.html.php"),
"title" => $this->getPageTitle(),
"name" => explode("\\", $inspector->getExceptionName()),
"message" => $inspector->getException()->getMessage(),
"code" => $code,
"plain_exception" => Formatter::formatExceptionPlain($inspector),
"frames" => $frames,
"has_frames" => !!count($frames),
"handler" => $this,
"handlers" => $this->getRun()->getHandlers(),
"tables" => array(
"GET Data" => $_GET,
"POST Data" => $_POST,
"Files" => $_FILES,
"Cookies" => $_COOKIE,
"Session" => isset($_SESSION) ? $_SESSION : array(),
"Server/Request Data" => $_SERVER,
"Environment Variables" => $_ENV,
),
);
if (isset($customCssFile)) {
$vars["stylesheet"] .= file_get_contents($customCssFile);
}
// Add extra entries list of data tables:
// @todo: Consolidate addDataTable and addDataTableCallback
$extraTables = array_map(function ($table) {
return $table instanceof \Closure ? $table() : $table;
}, $this->getDataTables());
$vars["tables"] = array_merge($extraTables, $vars["tables"]);
$helper->setVariables($vars);
$helper->render($templateFile);
return Handler::QUIT;
}
/**
* Adds an entry to the list of tables displayed in the template.
* The expected data is a simple associative array. Any nested arrays
* will be flattened with print_r
* @param string $label
* @param array $data
*/
public function addDataTable($label, array $data)
{
$this->extraTables[$label] = $data;
}
/**
* Lazily adds an entry to the list of tables displayed in the table.
* The supplied callback argument will be called when the error is rendered,
* it should produce a simple associative array. Any nested arrays will
* be flattened with print_r.
*
* @throws InvalidArgumentException If $callback is not callable
* @param string $label
* @param callable $callback Callable returning an associative array
*/
public function addDataTableCallback($label, /* callable */ $callback)
{
if (!is_callable($callback)) {
throw new InvalidArgumentException('Expecting callback argument to be callable');
}
$this->extraTables[$label] = function () use ($callback) {
try {
$result = call_user_func($callback);
// Only return the result if it can be iterated over by foreach().
return is_array($result) || $result instanceof \Traversable ? $result : array();
} catch (\Exception $e) {
// Don't allow failure to break the rendering of the original exception.
return array();
}
};
}
/**
* Returns all the extra data tables registered with this handler.
* Optionally accepts a 'label' parameter, to only return the data
* table under that label.
* @param string|null $label
* @return array[]|callable
*/
public function getDataTables($label = null)
{
if ($label !== null) {
return isset($this->extraTables[$label]) ?
$this->extraTables[$label] : array();
}
return $this->extraTables;
}
/**
* Allows to disable all attempts to dynamically decide whether to
* handle or return prematurely.
* Set this to ensure that the handler will perform no matter what.
* @param bool|null $value
* @return bool|null
*/
public function handleUnconditionally($value = null)
{
if (func_num_args() == 0) {
return $this->handleUnconditionally;
}
$this->handleUnconditionally = (bool) $value;
}
/**
* Adds an editor resolver, identified by a string
* name, and that may be a string path, or a callable
* resolver. If the callable returns a string, it will
* be set as the file reference's href attribute.
*
* @example
* $run->addEditor('macvim', "mvim://open?url=file://%file&line=%line")
* @example
* $run->addEditor('remove-it', function($file, $line) {
* unlink($file);
* return "http://stackoverflow.com";
* });
* @param string $identifier
* @param string $resolver
*/
public function addEditor($identifier, $resolver)
{
$this->editors[$identifier] = $resolver;
}
/**
* Set the editor to use to open referenced files, by a string
* identifier, or a callable that will be executed for every
* file reference, with a $file and $line argument, and should
* return a string.
*
* @example
* $run->setEditor(function($file, $line) { return "file:///{$file}"; });
* @example
* $run->setEditor('sublime');
*
* @throws InvalidArgumentException If invalid argument identifier provided
* @param string|callable $editor
*/
public function setEditor($editor)
{
if (!is_callable($editor) && !isset($this->editors[$editor])) {
throw new InvalidArgumentException(
"Unknown editor identifier: $editor. Known editors:" .
implode(",", array_keys($this->editors))
);
}
$this->editor = $editor;
}
/**
* Given a string file path, and an integer file line,
* executes the editor resolver and returns, if available,
* a string that may be used as the href property for that
* file reference.
*
* @throws InvalidArgumentException If editor resolver does not return a string
* @param string $filePath
* @param int $line
* @return string|bool
*/
public function getEditorHref($filePath, $line)
{
$editor = $this->getEditor($filePath, $line);
if (!$editor) {
return false;
}
// Check that the editor is a string, and replace the
// %line and %file placeholders:
if (!isset($editor['url']) || !is_string($editor['url'])) {
throw new UnexpectedValueException(
__METHOD__ . " should always resolve to a string or a valid editor array; got something else instead."
);
}
$editor['url'] = str_replace("%line", rawurlencode($line), $editor['url']);
$editor['url'] = str_replace("%file", rawurlencode($filePath), $editor['url']);
return $editor['url'];
}
/**
* Given a boolean if the editor link should
* act as an Ajax request. The editor must be a
* valid callable function/closure
*
* @throws UnexpectedValueException If editor resolver does not return a boolean
* @param string $filePath
* @param int $line
* @return bool
*/
public function getEditorAjax($filePath, $line)
{
$editor = $this->getEditor($filePath, $line);
// Check that the ajax is a bool
if (!isset($editor['ajax']) || !is_bool($editor['ajax'])) {
throw new UnexpectedValueException(
__METHOD__ . " should always resolve to a bool; got something else instead."
);
}
return $editor['ajax'];
}
/**
* Given a boolean if the editor link should
* act as an Ajax request. The editor must be a
* valid callable function/closure
*
* @throws UnexpectedValueException If editor resolver does not return a boolean
* @param string $filePath
* @param int $line
* @return mixed
*/
protected function getEditor($filePath, $line)
{
if ($this->editor === null && !is_string($this->editor) && !is_callable($this->editor))
{
return false;
}
else if(is_string($this->editor) && isset($this->editors[$this->editor]) && !is_callable($this->editors[$this->editor]))
{
return array(
'ajax' => false,
'url' => $this->editors[$this->editor],
);
}
else if(is_callable($this->editor) || (isset($this->editors[$this->editor]) && is_callable($this->editors[$this->editor])))
{
if(is_callable($this->editor))
{
$callback = call_user_func($this->editor, $filePath, $line);
}
else
{
$callback = call_user_func($this->editors[$this->editor], $filePath, $line);
}
return array(
'ajax' => isset($callback['ajax']) ? $callback['ajax'] : false,
'url' => (is_array($callback) ? $callback['url'] : $callback),
);
}
return false;
}
/**
* @param string $title
* @return void
*/
public function setPageTitle($title)
{
$this->pageTitle = (string) $title;
}
/**
* @return string
*/
public function getPageTitle()
{
return $this->pageTitle;
}
/**
* Adds a path to the list of paths to be searched for
* resources.
*
* @throws InvalidArgumnetException If $path is not a valid directory
*
* @param string $path
* @return void
*/
public function addResourcePath($path)
{
if (!is_dir($path)) {
throw new InvalidArgumentException(
"'$path' is not a valid directory"
);
}
array_unshift($this->searchPaths, $path);
}
/**
* Adds a custom css file to be loaded.
*
* @param string $name
* @return void
*/
public function addCustomCss($name)
{
$this->customCss = $name;
}
/**
* @return array
*/
public function getResourcePaths()
{
return $this->searchPaths;
}
/**
* Finds a resource, by its relative path, in all available search paths.
* The search is performed starting at the last search path, and all the
* way back to the first, enabling a cascading-type system of overrides
* for all resources.
*
* @throws RuntimeException If resource cannot be found in any of the available paths
*
* @param string $resource
* @return string
*/
protected function getResource($resource)
{
// If the resource was found before, we can speed things up
// by caching its absolute, resolved path:
if (isset($this->resourceCache[$resource])) {
return $this->resourceCache[$resource];
}
// Search through available search paths, until we find the
// resource we're after:
foreach ($this->searchPaths as $path) {
$fullPath = $path . "/$resource";
if (is_file($fullPath)) {
// Cache the result:
$this->resourceCache[$resource] = $fullPath;
return $fullPath;
}
}
// If we got this far, nothing was found.
throw new RuntimeException(
"Could not find resource '$resource' in any resource paths."
. "(searched: " . join(", ", $this->searchPaths). ")"
);
}
/**
* @deprecated
*
* @return string
*/
public function getResourcesPath()
{
$allPaths = $this->getResourcePaths();
// Compat: return only the first path added
return end($allPaths) ?: null;
}
/**
* @deprecated
*
* @param string $resourcesPath
* @return void
*/
public function setResourcesPath($resourcesPath)
{
$this->addResourcePath($resourcesPath);
}
}

View File

@ -0,0 +1,49 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Handler;
/**
* Catches an exception and converts it to an Soap XML
* response.
*
* @author Markus Staab <http://github.com/staabm>
*/
class SoapResponseHandler extends Handler
{
/**
* @return int
*/
public function handle()
{
$exception = $this->getException();
echo $this->toXml($exception);
return Handler::QUIT;
}
/**
* Converts a Exception into a SoapFault XML
*/
private function toXml(\Exception $exception)
{
$xml = '';
$xml .= '<?xml version="1.0" encoding="UTF-8"?>';
$xml .= '<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">';
$xml .= ' <SOAP-ENV:Body>';
$xml .= ' <SOAP-ENV:Fault>';
$xml .= ' <faultcode>'. htmlspecialchars($exception->getCode()) .'</faultcode>';
$xml .= ' <faultstring>'. htmlspecialchars($exception->getMessage()) .'</faultstring>';
$xml .= ' <detail><trace>'. htmlspecialchars($exception->getTraceAsString()) .'</trace></detail>';
$xml .= ' </SOAP-ENV:Fault>';
$xml .= ' </SOAP-ENV:Body>';
$xml .= '</SOAP-ENV:Envelope>';
return $xml;
}
}

View File

@ -0,0 +1,99 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Handler;
use SimpleXMLElement;
use Whoops\Exception\Formatter;
/**
* Catches an exception and converts it to an XML
* response. Additionally can also return exception
* frames for consumption by an API.
*/
class XmlResponseHandler extends Handler
{
/**
* @var bool
*/
private $returnFrames = false;
/**
* @param bool|null $returnFrames
* @return bool|$this
*/
public function addTraceToOutput($returnFrames = null)
{
if (func_num_args() == 0) {
return $this->returnFrames;
}
$this->returnFrames = (bool) $returnFrames;
return $this;
}
/**
* @return int
*/
public function handle()
{
$response = array(
'error' => Formatter::formatExceptionAsDataArray(
$this->getInspector(),
$this->addTraceToOutput()
),
);
echo $this->toXml($response);
return Handler::QUIT;
}
/**
* @param SimpleXMLElement $node Node to append data to, will be modified in place
* @param array|Traversable $data
* @return SimpleXMLElement The modified node, for chaining
*/
private static function addDataToNode(\SimpleXMLElement $node, $data)
{
assert('is_array($data) || $node instanceof Traversable');
foreach ($data as $key => $value) {
if (is_numeric($key)) {
// Convert the key to a valid string
$key = "unknownNode_". (string) $key;
}
// Delete any char not allowed in XML element names
$key = preg_replace('/[^a-z0-9\-\_\.\:]/i', '', $key);
if (is_array($value)) {
$child = $node->addChild($key);
self::addDataToNode($child, $value);
} else {
$value = str_replace('&', '&amp;', print_r($value, true));
$node->addChild($key, $value);
}
}
return $node;
}
/**
* The main function for converting to an XML document.
*
* @param array|Traversable $data
* @return string XML
*/
private static function toXml($data)
{
assert('is_array($data) || $node instanceof Traversable');
$node = simplexml_load_string("<?xml version='1.0' encoding='utf-8'?><root />");
return self::addDataToNode($node, $data)->asXML();
}
}

View File

@ -0,0 +1,78 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Provider\Phalcon;
use Phalcon\DI;
use Phalcon\DI\Exception;
use Whoops\Handler\JsonResponseHandler;
use Whoops\Handler\PrettyPageHandler;
use Whoops\Run;
class WhoopsServiceProvider
{
/**
* @param DI $di
*/
public function __construct(DI $di = null)
{
if (!$di) {
$di = DI::getDefault();
}
// There's only ever going to be one error page...right?
$di->setShared('whoops.pretty_page_handler', function () {
return new PrettyPageHandler();
});
// There's only ever going to be one error page...right?
$di->setShared('whoops.json_response_handler', function () {
$jsonHandler = new JsonResponseHandler();
$jsonHandler->onlyForAjaxRequests(true);
return $jsonHandler;
});
// Retrieves info on the Phalcon environment and ships it off
// to the PrettyPageHandler's data tables:
// This works by adding a new handler to the stack that runs
// before the error page, retrieving the shared page handler
// instance, and working with it to add new data tables
$phalcon_info_handler = function () use ($di) {
try {
$request = $di['request'];
} catch (Exception $e) {
// This error occurred too early in the application's life
// and the request instance is not yet available.
return;
}
// Request info:
$di['whoops.pretty_page_handler']->addDataTable('Phalcon Application (Request)', array(
'URI' => $request->getScheme().'://'.$request->getServer('HTTP_HOST').$request->getServer('REQUEST_URI'),
'Request URI' => $request->getServer('REQUEST_URI'),
'Path Info' => $request->getServer('PATH_INFO'),
'Query String' => $request->getServer('QUERY_STRING') ?: '<none>',
'HTTP Method' => $request->getMethod(),
'Script Name' => $request->getServer('SCRIPT_NAME'),
//'Base Path' => $request->getBasePath(),
//'Base URL' => $request->getBaseUrl(),
'Scheme' => $request->getScheme(),
'Port' => $request->getServer('SERVER_PORT'),
'Host' => $request->getServerName(),
));
};
$di->setShared('whoops', function () use ($di,$phalcon_info_handler) {
$run = new Run();
$run->pushHandler($di['whoops.pretty_page_handler']);
$run->pushHandler($phalcon_info_handler);
$run->pushHandler($di['whoops.json_response_handler']);
return $run;
});
$di['whoops']->register();
}
}

View File

@ -0,0 +1,111 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Provider\Silex;
use RuntimeException;
use Silex\Application;
use Silex\ServiceProviderInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Whoops\Handler\Handler;
use Whoops\Handler\PlainTextHandler;
use Whoops\Handler\PrettyPageHandler;
use Whoops\Run;
class WhoopsServiceProvider implements ServiceProviderInterface
{
/**
* @param Application $app
*/
public function register(Application $app)
{
// There's only ever going to be one error page...right?
$app['whoops.error_page_handler'] = $app->share(function () {
if (PHP_SAPI === 'cli') {
return new PlainTextHandler();
} else {
return new PrettyPageHandler();
}
});
// Retrieves info on the Silex environment and ships it off
// to the PrettyPageHandler's data tables:
// This works by adding a new handler to the stack that runs
// before the error page, retrieving the shared page handler
// instance, and working with it to add new data tables
$app['whoops.silex_info_handler'] = $app->protect(function () use ($app) {
try {
/** @var Request $request */
$request = $app['request'];
} catch (RuntimeException $e) {
// This error occurred too early in the application's life
// and the request instance is not yet available.
return;
}
/** @var Handler $errorPageHandler */
$errorPageHandler = $app["whoops.error_page_handler"];
if ($errorPageHandler instanceof PrettyPageHandler) {
/** @var PrettyPageHandler $errorPageHandler */
// General application info:
$errorPageHandler->addDataTable('Silex Application', array(
'Charset' => $app['charset'],
'Locale' => $app['locale'],
'Route Class' => $app['route_class'],
'Dispatcher Class' => $app['dispatcher_class'],
'Application Class' => get_class($app),
));
// Request info:
$errorPageHandler->addDataTable('Silex Application (Request)', array(
'URI' => $request->getUri(),
'Request URI' => $request->getRequestUri(),
'Path Info' => $request->getPathInfo(),
'Query String' => $request->getQueryString() ?: '<none>',
'HTTP Method' => $request->getMethod(),
'Script Name' => $request->getScriptName(),
'Base Path' => $request->getBasePath(),
'Base URL' => $request->getBaseUrl(),
'Scheme' => $request->getScheme(),
'Port' => $request->getPort(),
'Host' => $request->getHost(),
));
}
});
$app['whoops'] = $app->share(function () use ($app) {
$run = new Run();
$run->allowQuit(false);
$run->pushHandler($app['whoops.error_page_handler']);
$run->pushHandler($app['whoops.silex_info_handler']);
return $run;
});
$app->error(function ($e) use ($app) {
$method = Run::EXCEPTION_HANDLER;
ob_start();
$app['whoops']->$method($e);
$response = ob_get_clean();
$code = $e instanceof HttpException ? $e->getStatusCode() : 500;
return new Response($response, $code);
});
$app['whoops']->register();
}
/**
* @see Silex\ServiceProviderInterface::boot
*/
public function boot(Application $app)
{
}
}

View File

@ -0,0 +1,427 @@
.cf:before, .cf:after {content: " ";display: table;} .cf:after {clear: both;} .cf {*zoom: 1;}
body {
font: 14px helvetica, arial, sans-serif;
color: #2B2B2B;
background-color: #D4D4D4;
padding:0;
margin: 0;
max-height: 100%;
}
a {
text-decoration: none;
}
.container{
height: 100%;
width: 100%;
position: fixed;
margin: 0;
padding: 0;
left: 0;
top: 0;
}
.branding {
position: absolute;
top: 10px;
right: 20px;
color: #777777;
font-size: 10px;
z-index: 100;
}
.branding a {
color: #CD3F3F;
}
header {
padding: 30px 20px;
color: white;
background: #272727;
box-sizing: border-box;
border-left: 5px solid #CD3F3F;
}
.exc-title {
margin: 0;
color: #616161;
text-shadow: 0 1px 2px rgba(0, 0, 0, .1);
}
.exc-title-primary { color: #CD3F3F; }
.exc-message {
font-size: 32px;
margin: 5px 0;
word-wrap: break-word;
}
.stack-container {
height: 100%;
position: relative;
}
.details-container {
height: 100%;
overflow: auto;
float: right;
width: 70%;
background: #DADADA;
}
.details {
padding: 10px;
padding-left: 5px;
border-left: 5px solid rgba(0, 0, 0, .1);
}
.frames-container {
height: 100%;
overflow: auto;
float: left;
width: 30%;
background: #FFF;
}
.frame {
padding: 14px;
background: #F3F3F3;
border-right: 1px solid rgba(0, 0, 0, .2);
cursor: pointer;
}
.frame.active {
background-color: #4288CE;
color: #F3F3F3;
box-shadow: inset -2px 0 0 rgba(255, 255, 255, .1);
text-shadow: 0 1px 0 rgba(0, 0, 0, .2);
}
.frame:not(.active):hover {
background: #BEE9EA;
}
.frame-class, .frame-function, .frame-index {
font-weight: bold;
}
.frame-index {
font-size: 11px;
color: #BDBDBD;
}
.frame-class {
color: #4288CE;
}
.active .frame-class {
color: #BEE9EA;
}
.frame-file {
font-family: "Inconsolata", "Fira Mono", "Source Code Pro", Monaco, Consolas, "Lucida Console", monospace;
word-wrap:break-word;
}
.frame-file .editor-link {
color: #272727;
}
.frame-line {
font-weight: bold;
color: #4288CE;
}
.active .frame-line { color: #BEE9EA; }
.frame-line:before {
content: ":";
}
.frame-code {
padding: 10px;
padding-left: 5px;
background: #BDBDBD;
display: none;
border-left: 5px solid #4288CE;
}
.frame-code.active {
display: block;
}
.frame-code .frame-file {
background: #C6C6C6;
color: #525252;
text-shadow: 0 1px 0 #E7E7E7;
padding: 10px 10px 5px 10px;
border-top-right-radius: 6px;
border-top-left-radius: 6px;
border: 1px solid rgba(0, 0, 0, .1);
border-bottom: none;
box-shadow: inset 0 1px 0 #DADADA;
}
.code-block {
padding: 10px;
margin: 0;
box-shadow: inset 0 0 6px rgba(0, 0, 0, .3);
}
.linenums {
margin: 0;
margin-left: 10px;
}
.frame-comments {
box-shadow: inset 0 0 6px rgba(0, 0, 0, .3);
border: 1px solid rgba(0, 0, 0, .2);
border-top: none;
border-bottom-right-radius: 6px;
border-bottom-left-radius: 6px;
padding: 5px;
font-size: 12px;
background: #404040;
}
.frame-comments.empty {
padding: 8px 15px;
}
.frame-comments.empty:before {
content: "No comments for this stack frame.";
font-style: italic;
color: #828282;
}
.frame-comment {
padding: 10px;
color: #D2D2D2;
}
.frame-comment a {
color: #BEE9EA;
font-weight: bold;
text-decoration: none;
}
.frame-comment a:hover {
color: #4bb1b1;
}
.frame-comment:not(:last-child) {
border-bottom: 1px dotted rgba(0, 0, 0, .3);
}
.frame-comment-context {
font-size: 10px;
font-weight: bold;
color: #86D2B6;
}
.data-table-container label {
font-size: 16px;
font-weight: bold;
color: #4288CE;
margin: 10px 0;
padding: 10px 0;
display: block;
margin-bottom: 5px;
padding-bottom: 5px;
border-bottom: 1px dotted rgba(0, 0, 0, .2);
}
.data-table {
width: 100%;
margin: 10px 0;
}
.data-table tbody {
font: 13px "Inconsolata", "Fira Mono", "Source Code Pro", Monaco, Consolas, "Lucida Console", monospace;
}
.data-table thead {
display: none;
}
.data-table tr {
padding: 5px 0;
}
.data-table td:first-child {
width: 20%;
min-width: 130px;
overflow: hidden;
font-weight: bold;
color: #463C54;
padding-right: 5px;
}
.data-table td:last-child {
width: 80%;
-ms-word-break: break-all;
word-break: break-all;
word-break: break-word;
-webkit-hyphens: auto;
-moz-hyphens: auto;
hyphens: auto;
}
.data-table span.empty {
color: rgba(0, 0, 0, .3);
font-style: italic;
}
.data-table label.empty {
display: inline;
}
.handler {
padding: 10px;
font: 14px "Inconsolata", "Fira Mono", "Source Code Pro", Monaco, Consolas, "Lucida Console", monospace;
}
.handler.active {
color: #BBBBBB;
background: #989898;
font-weight: bold;
}
/* prettify code style
Uses the Doxy theme as a base */
pre .str, code .str { color: #BCD42A; } /* string */
pre .kwd, code .kwd { color: #4bb1b1; font-weight: bold; } /* keyword*/
pre .com, code .com { color: #888; font-weight: bold; } /* comment */
pre .typ, code .typ { color: #ef7c61; } /* type */
pre .lit, code .lit { color: #BCD42A; } /* literal */
pre .pun, code .pun { color: #fff; font-weight: bold; } /* punctuation */
pre .pln, code .pln { color: #e9e4e5; } /* plaintext */
pre .tag, code .tag { color: #4bb1b1; } /* html/xml tag */
pre .htm, code .htm { color: #dda0dd; } /* html tag */
pre .xsl, code .xsl { color: #d0a0d0; } /* xslt tag */
pre .atn, code .atn { color: #ef7c61; font-weight: normal;} /* html/xml attribute name */
pre .atv, code .atv { color: #bcd42a; } /* html/xml attribute value */
pre .dec, code .dec { color: #606; } /* decimal */
pre.prettyprint, code.prettyprint {
font-family: "Inconsolata", "Fira Mono", "Source Code Pro", Monaco, Consolas, "Lucida Console", monospace;
background: #333;
color: #e9e4e5;
}
pre.prettyprint {
white-space: pre-wrap;
}
pre.prettyprint a, code.prettyprint a {
text-decoration:none;
}
.linenums li {
color: #A5A5A5;
}
.linenums li.current{
background: rgba(255, 100, 100, .07);
padding-top: 4px;
padding-left: 1px;
}
.linenums li.current.active {
background: rgba(255, 100, 100, .17);
}
#plain-exception {
display: none;
}
#copy-button {
display: none;
float: right;
cursor: pointer;
border: 0;
}
.clipboard {
width: 29px;
height: 28px;
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB0AAAAcCAYAAACdz7SqAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gUUByMD0ZSoGQAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAACAklEQVRIx72Wv0sbYRjHP29y1VoXxR9UskjpXTaHoBUcpGgKkS5OZ1Ec/QeKOIhalEghoOCqQsGhFAWbISKYyaFDS1BKKW0TCYrQSdElXSReh9bkksu9iZdLbnrvvee9z/N83+d9nldo2gvjzd5Hxp4246W6J5tJszsxwvxPIbXzwBQDLgABvM1P6JsAwzCkdopl5vqIuWev2K4QpH/4QjjQci/nPCVny3iaNzMcrVUsC1sChFMpwtTu8dTqx7J9dR3a2BngUb0j7Xr+jtjasBR8f+jpNqqqoqoqmqblxjOJq/8GTfhCK8TWhmmykdhRpEIIhBCWMcD51wQXN3KwY3nvYGYgQPbXOMHJKOlMA77QCvsbugXsOFLZ+5+jGULBtyQuFB4PzlrAVSWSGWaptpdbjAcniaZv6RhcIL6VByvVZqsQouBMdutJkrrVrr1/gdjqN4Ze/3DvyBwcnnF9I7N4gC8YYdqNSHP7uD5G/7pdJRrl/ecIva1t9IRcgpolLk6qQic8eB+6GOkdrDjSf/OiTD91CS4r+jXrMqWkrgvUtuDbeVNTKGzw6SRDto5QBc5Yehlg0WbTc8mwHCeld1u+yZSySySlspTHFmZUeIkrgBYvtvPcyBdXkqWKq5OLmbk/luqVYjPOd3lxLXf/J/P7mJ0oCL/fX1Yfs4RO5CxW8C97dLBw2Q3fUwAAAABJRU5ErkJggg==');
background-repeat: no-repeat;
}
.help button {
cursor: help;
height: 28px;
float: right;
margin-left: 10px;
}
.help button:hover + #help-overlay {
display: block;
}
#help-overlay {
pointer-events: none;
display: none;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(54, 54, 54, 0.5);
}
#help-overlay div {
width: 200px;
padding: 5px;
color: #463c54;
background-color: white;
border-radius: 10px;
}
#help-clipboard {
position: absolute;
right: 30px;
top: 90px;
}
#help-framestack {
position: absolute;
left: 200px;
top: 50px;
}
#help-exc-message {
position: absolute;
left: 65%;
top: 10px;
}
#help-code {
position: absolute;
right: 30px;
top: 250px;
}
#help-request {
position: absolute;
right: 30px;
top: 480px;
}
#help-appinfo {
position: absolute;
right: 30px;
top: 550px;
}
/* inspired by githubs kbd styles */
kbd {
-moz-border-bottom-colors: none;
-moz-border-left-colors: none;
-moz-border-right-colors: none;
-moz-border-top-colors: none;
background-color: #fcfcfc;
border-color: #ccc #ccc #bbb;
border-image: none;
border-radius: 3px;
border-style: solid;
border-width: 1px;
box-shadow: 0 -1px 0 #bbb inset;
color: #555;
display: inline-block;
font-size: 11px;
line-height: 10px;
padding: 3px 5px;
vertical-align: middle;
}

View File

@ -0,0 +1,86 @@
Zepto(function($) {
prettyPrint();
var $frameContainer = $('.frames-container');
var $container = $('.details-container');
var $activeLine = $frameContainer.find('.frame.active');
var $activeFrame = $container.find('.frame-code.active');
var $ajaxEditors = $('.editor-link[data-ajax]');
var headerHeight = $('header').height();
var highlightCurrentLine = function() {
// Highlight the active and neighboring lines for this frame:
var activeLineNumber = +($activeLine.find('.frame-line').text());
var $lines = $activeFrame.find('.linenums li');
var firstLine = +($lines.first().val());
$($lines[activeLineNumber - firstLine - 1]).addClass('current');
$($lines[activeLineNumber - firstLine]).addClass('current active');
$($lines[activeLineNumber - firstLine + 1]).addClass('current');
}
// Highlight the active for the first frame:
highlightCurrentLine();
$frameContainer.on('click', '.frame', function() {
var $this = $(this);
var id = /frame\-line\-([\d]*)/.exec($this.attr('id'))[1];
var $codeFrame = $('#frame-code-' + id);
if ($codeFrame) {
$activeLine.removeClass('active');
$activeFrame.removeClass('active');
$this.addClass('active');
$codeFrame.addClass('active');
$activeLine = $this;
$activeFrame = $codeFrame;
highlightCurrentLine();
$container.scrollTop(headerHeight);
}
});
if (typeof ZeroClipboard !== "undefined") {
ZeroClipboard.config({
moviePath: '//ajax.cdnjs.com/ajax/libs/zeroclipboard/1.3.5/ZeroClipboard.swf',
});
var clipEl = document.getElementById("copy-button");
var clip = new ZeroClipboard( clipEl );
var $clipEl = $(clipEl);
// show the button, when swf could be loaded successfully from CDN
clip.on("load", function() {
$clipEl.show();
});
}
$(document).on('keydown', function(e) {
if(e.ctrlKey) {
// CTRL+Arrow-UP/Arrow-Down support:
// 1) select the next/prev element
// 2) make sure the newly selected element is within the view-scope
// 3) focus the (right) container, so arrow-up/down (without ctrl) scroll the details
if (e.which === 38 /* arrow up */) {
$activeLine.prev('.frame').click();
$activeLine[0].scrollIntoView();
$container.focus();
e.preventDefault();
} else if (e.which === 40 /* arrow down */) {
$activeLine.next('.frame').click();
$activeLine[0].scrollIntoView();
$container.focus();
e.preventDefault();
}
}
});
// Avoid to quit the page with some protocol (e.g. IntelliJ Platform REST API)
$ajaxEditors.on('click', function(e){
e.preventDefault();
$.get(this.href);
});
});

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,40 @@
<?php /* List data-table values, i.e: $_SERVER, $_GET, .... */ ?>
<div class="details">
<div class="data-table-container" id="data-tables">
<?php foreach ($tables as $label => $data): ?>
<div class="data-table" id="sg-<?php echo $tpl->escape($tpl->slug($label)) ?>">
<?php if (!empty($data)): ?>
<label><?php echo $tpl->escape($label) ?></label>
<table class="data-table">
<thead>
<tr>
<td class="data-table-k">Key</td>
<td class="data-table-v">Value</td>
</tr>
</thead>
<?php foreach ($data as $k => $value): ?>
<tr>
<td><?php echo $tpl->escape($k) ?></td>
<td><?php echo $tpl->escape(print_r($value, true)) ?></td>
</tr>
<?php endforeach ?>
</table>
<?php else: ?>
<label class="empty"><?php echo $tpl->escape($label) ?></label>
<span class="empty">empty</span>
<?php endif ?>
</div>
<?php endforeach ?>
</div>
<?php /* List registered handlers, in order of first to last registered */ ?>
<div class="data-table-container" id="handlers">
<label>Registered Handlers</label>
<?php foreach ($handlers as $i => $handler): ?>
<div class="handler <?php echo ($handler === $handler) ? 'active' : ''?>">
<?php echo $i ?>. <?php echo $tpl->escape(get_class($handler)) ?>
</div>
<?php endforeach ?>
</div>
</div>

View File

@ -0,0 +1,52 @@
<?php /* Display a code block for all frames in the stack.
* @todo: This should PROBABLY be done on-demand, lest
* we get 200 frames to process. */ ?>
<div class="frame-code-container <?php echo (!$has_frames ? 'empty' : '') ?>">
<?php foreach ($frames as $i => $frame): ?>
<?php $line = $frame->getLine(); ?>
<div class="frame-code <?php echo ($i == 0 ) ? 'active' : '' ?>" id="frame-code-<?php echo $i ?>">
<div class="frame-file">
<?php $filePath = $frame->getFile(); ?>
<?php if ($filePath && $editorHref = $handler->getEditorHref($filePath, (int) $line)): ?>
Open:
<a href="<?php echo $editorHref ?>" class="editor-link"<?php echo ($handler->getEditorAjax($filePath, (int) $line) ? ' data-ajax' : '') ?>>
<strong><?php echo $tpl->escape($filePath ?: '<#unknown>') ?></strong>
</a>
<?php else: ?>
<strong><?php echo $tpl->escape($filePath ?: '<#unknown>') ?></strong>
<?php endif ?>
</div>
<?php
// Do nothing if there's no line to work off
if ($line !== null):
// the $line is 1-indexed, we nab -1 where needed to account for this
$range = $frame->getFileLines($line - 8, 10);
// getFileLines can return null if there is no source code
if ($range):
$range = array_map(function ($line) { return empty($line) ? ' ' : $line;}, $range);
$start = key($range) + 1;
$code = join("\n", $range);
?>
<pre class="code-block prettyprint linenums:<?php echo $start ?>"><?php echo $tpl->escape($code) ?></pre>
<?php endif ?>
<?php endif ?>
<?php
// Append comments for this frame
$comments = $frame->getComments();
?>
<div class="frame-comments <?php echo empty($comments) ? 'empty' : '' ?>">
<?php foreach ($comments as $commentNo => $comment): ?>
<?php extract($comment) ?>
<div class="frame-comment" id="comment-<?php echo $i . '-' . $commentNo ?>">
<span class="frame-comment-context"><?php echo $tpl->escape($context) ?></span>
<?php echo $tpl->escapeButPreserveUris($comment) ?>
</div>
<?php endforeach ?>
</div>
</div>
<?php endforeach ?>
</div>

View File

@ -0,0 +1,17 @@
<?php /* List file names & line numbers for all stack frames;
clicking these links/buttons will display the code view
for that particular frame */ ?>
<?php foreach ($frames as $i => $frame): ?>
<div class="frame <?php echo ($i == 0 ? 'active' : '') ?>" id="frame-line-<?php echo $i ?>">
<div class="frame-method-info">
<span class="frame-index"><?php echo (count($frames) - $i - 1) ?>.</span>
<span class="frame-class"><?php echo $tpl->escape($frame->getClass() ?: '') ?></span>
<span class="frame-function"><?php echo $tpl->escape($frame->getFunction() ?: '') ?></span>
</div>
<span class="frame-file">
<?php echo ($frame->getFile(true) ?: '<#unknown>') ?><!--
--><span class="frame-line"><?php echo (int) $frame->getLine() ?></span>
</span>
</div>
<?php endforeach ?>

View File

@ -0,0 +1,34 @@
<div class="exception">
<h3 class="exc-title">
<?php foreach ($name as $i => $nameSection): ?>
<?php if ($i == count($name) - 1): ?>
<span class="exc-title-primary"><?php echo $tpl->escape($nameSection) ?></span>
<?php else: ?>
<?php echo $tpl->escape($nameSection) . ' \\' ?>
<?php endif ?>
<?php endforeach ?>
<?php if ($code): ?>
<span title="Exception Code">(<?php echo $tpl->escape($code) ?>)</span>
<?php endif ?>
</h3>
<div class="help">
<button title="show help">HELP</button>
<div id="help-overlay">
<div id="help-framestack">Callstack information; navigate with mouse or keyboard using <kbd>Ctrl+&uparrow;</kbd> or <kbd>Ctrl+&downarrow;</kbd></div>
<div id="help-clipboard">Copy-to-clipboard button</div>
<div id="help-exc-message">Exception message and its type</div>
<div id="help-code">Code snippet where the error was thrown</div>
<div id="help-request">Server state information</div>
<div id="help-appinfo">Application provided context information</div>
</div>
</div>
<button id="copy-button" class="clipboard" data-clipboard-target="plain-exception" title="copy exception into clipboard"></button>
<span id="plain-exception"><?php echo $tpl->escape($plain_exception) ?></span>
<p class="exc-message">
<?php echo $tpl->escape($message) ?>
</p>
</div>

View File

@ -0,0 +1,37 @@
<?php
/**
* Layout template file for Whoops's pretty error output.
*/
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title><?php echo $tpl->escape($page_title) ?></title>
<style><?php echo $stylesheet ?></style>
</head>
<body>
<div class="Whoops container">
<div class="stack-container">
<div class="frames-container cf <?php echo (!$has_frames ? 'empty' : '') ?>">
<?php $tpl->render($frame_list) ?>
</div>
<div class="details-container cf">
<header>
<?php $tpl->render($header) ?>
</header>
<?php $tpl->render($frame_code) ?>
<?php $tpl->render($env_details) ?>
</div>
</div>
</div>
<script src="//cdnjs.cloudflare.com/ajax/libs/zeroclipboard/1.3.5/ZeroClipboard.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.js"></script>
<script><?php echo $zepto ?></script>
<script><?php echo $javascript ?></script>
</body>
</html>

408
vendor/filp/whoops/src/Whoops/Run.php vendored Normal file
View File

@ -0,0 +1,408 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops;
use Exception;
use InvalidArgumentException;
use Whoops\Exception\ErrorException;
use Whoops\Exception\Inspector;
use Whoops\Handler\CallbackHandler;
use Whoops\Handler\Handler;
use Whoops\Handler\HandlerInterface;
class Run
{
const EXCEPTION_HANDLER = "handleException";
const ERROR_HANDLER = "handleError";
const SHUTDOWN_HANDLER = "handleShutdown";
protected $isRegistered;
protected $allowQuit = true;
protected $sendOutput = true;
/**
* @var integer|false
*/
protected $sendHttpCode = 500;
/**
* @var HandlerInterface[]
*/
protected $handlerStack = array();
protected $silencedPatterns = array();
/**
* Pushes a handler to the end of the stack
*
* @throws InvalidArgumentException If argument is not callable or instance of HandlerInterface
* @param Callable|HandlerInterface $handler
* @return Run
*/
public function pushHandler($handler)
{
if (is_callable($handler)) {
$handler = new CallbackHandler($handler);
}
if (!$handler instanceof HandlerInterface) {
throw new InvalidArgumentException(
"Argument to " . __METHOD__ . " must be a callable, or instance of"
. "Whoops\\Handler\\HandlerInterface"
);
}
$this->handlerStack[] = $handler;
return $this;
}
/**
* Removes the last handler in the stack and returns it.
* Returns null if there"s nothing else to pop.
* @return null|HandlerInterface
*/
public function popHandler()
{
return array_pop($this->handlerStack);
}
/**
* Returns an array with all handlers, in the
* order they were added to the stack.
* @return array
*/
public function getHandlers()
{
return $this->handlerStack;
}
/**
* Clears all handlers in the handlerStack, including
* the default PrettyPage handler.
* @return Run
*/
public function clearHandlers()
{
$this->handlerStack = array();
return $this;
}
/**
* @param Exception $exception
* @return Inspector
*/
protected function getInspector(Exception $exception)
{
return new Inspector($exception);
}
/**
* Registers this instance as an error handler.
* @return Run
*/
public function register()
{
if (!$this->isRegistered) {
// Workaround PHP bug 42098
// https://bugs.php.net/bug.php?id=42098
class_exists("\\Whoops\\Exception\\ErrorException");
class_exists("\\Whoops\\Exception\\FrameCollection");
class_exists("\\Whoops\\Exception\\Frame");
class_exists("\\Whoops\\Exception\\Inspector");
set_error_handler(array($this, self::ERROR_HANDLER));
set_exception_handler(array($this, self::EXCEPTION_HANDLER));
register_shutdown_function(array($this, self::SHUTDOWN_HANDLER));
$this->isRegistered = true;
}
return $this;
}
/**
* Unregisters all handlers registered by this Whoops\Run instance
* @return Run
*/
public function unregister()
{
if ($this->isRegistered) {
restore_exception_handler();
restore_error_handler();
$this->isRegistered = false;
}
return $this;
}
/**
* Should Whoops allow Handlers to force the script to quit?
* @param bool|int $exit
* @return bool
*/
public function allowQuit($exit = null)
{
if (func_num_args() == 0) {
return $this->allowQuit;
}
return $this->allowQuit = (bool) $exit;
}
/**
* Silence particular errors in particular files
* @param array|string $patterns List or a single regex pattern to match
* @param int $levels Defaults to E_STRICT | E_DEPRECATED
* @return \Whoops\Run
*/
public function silenceErrorsInPaths($patterns, $levels = 10240)
{
$this->silencedPatterns = array_merge(
$this->silencedPatterns,
array_map(
function ($pattern) use ($levels) {
return array(
"pattern" => $pattern,
"levels" => $levels,
);
},
(array) $patterns
)
);
return $this;
}
/*
* Should Whoops send HTTP error code to the browser if possible?
* Whoops will by default send HTTP code 500, but you may wish to
* use 502, 503, or another 5xx family code.
*
* @param bool|int $code
* @return int|false
*/
public function sendHttpCode($code = null)
{
if (func_num_args() == 0) {
return $this->sendHttpCode;
}
if (!$code) {
return $this->sendHttpCode = false;
}
if ($code === true) {
$code = 500;
}
if ($code < 400 || 600 <= $code) {
throw new InvalidArgumentException(
"Invalid status code '$code', must be 4xx or 5xx"
);
}
return $this->sendHttpCode = $code;
}
/**
* Should Whoops push output directly to the client?
* If this is false, output will be returned by handleException
* @param bool|int $send
* @return bool
*/
public function writeToOutput($send = null)
{
if (func_num_args() == 0) {
return $this->sendOutput;
}
return $this->sendOutput = (bool) $send;
}
/**
* Handles an exception, ultimately generating a Whoops error
* page.
*
* @param Exception $exception
* @return string Output generated by handlers
*/
public function handleException(Exception $exception)
{
// Walk the registered handlers in the reverse order
// they were registered, and pass off the exception
$inspector = $this->getInspector($exception);
// Capture output produced while handling the exception,
// we might want to send it straight away to the client,
// or return it silently.
ob_start();
// Just in case there are no handlers:
$handlerResponse = null;
foreach (array_reverse($this->handlerStack) as $handler) {
$handler->setRun($this);
$handler->setInspector($inspector);
$handler->setException($exception);
// The HandlerInterface does not require an Exception passed to handle()
// and neither of our bundled handlers use it.
// However, 3rd party handlers may have already relied on this parameter,
// and removing it would be possibly breaking for users.
$handlerResponse = $handler->handle($exception);
if (in_array($handlerResponse, array(Handler::LAST_HANDLER, Handler::QUIT))) {
// The Handler has handled the exception in some way, and
// wishes to quit execution (Handler::QUIT), or skip any
// other handlers (Handler::LAST_HANDLER). If $this->allowQuit
// is false, Handler::QUIT behaves like Handler::LAST_HANDLER
break;
}
}
$willQuit = $handlerResponse == Handler::QUIT && $this->allowQuit();
$output = ob_get_clean();
// If we're allowed to, send output generated by handlers directly
// to the output, otherwise, and if the script doesn't quit, return
// it so that it may be used by the caller
if ($this->writeToOutput()) {
// @todo Might be able to clean this up a bit better
// If we're going to quit execution, cleanup all other output
// buffers before sending our own output:
if ($willQuit) {
while (ob_get_level() > 0) {
ob_end_clean();
}
}
$this->writeToOutputNow($output);
}
if ($willQuit) {
flush(); // HHVM fix for https://github.com/facebook/hhvm/issues/4055
exit(1);
}
return $output;
}
/**
* Converts generic PHP errors to \ErrorException
* instances, before passing them off to be handled.
*
* This method MUST be compatible with set_error_handler.
*
* @param int $level
* @param string $message
* @param string $file
* @param int $line
*
* @return bool
* @throws ErrorException
*/
public function handleError($level, $message, $file = null, $line = null)
{
if ($level & error_reporting()) {
foreach ($this->silencedPatterns as $entry) {
$pathMatches = (bool) preg_match($entry["pattern"], $file);
$levelMatches = $level & $entry["levels"];
if ($pathMatches && $levelMatches) {
// Ignore the error, abort handling
return true;
}
}
// XXX we pass $level for the "code" param only for BC reasons.
// see https://github.com/filp/whoops/issues/267
$exception = new ErrorException($message, /*code*/ $level, /*severity*/ $level, $file, $line);
if ($this->canThrowExceptions) {
throw $exception;
} else {
$this->handleException($exception);
}
// Do not propagate errors which were already handled by Whoops.
return true;
}
// Propagate error to the next handler, allows error_get_last() to
// work on silenced errors.
return false;
}
/**
* Special case to deal with Fatal errors and the like.
*/
public function handleShutdown()
{
// If we reached this step, we are in shutdown handler.
// An exception thrown in a shutdown handler will not be propagated
// to the exception handler. Pass that information along.
$this->canThrowExceptions = false;
$error = error_get_last();
if ($error && $this->isLevelFatal($error['type'])) {
// If there was a fatal error,
// it was not handled in handleError yet.
$this->handleError(
$error['type'],
$error['message'],
$error['file'],
$error['line']
);
}
}
/**
* In certain scenarios, like in shutdown handler, we can not throw exceptions
* @var bool
*/
private $canThrowExceptions = true;
/**
* Echo something to the browser
* @param string $output
* @return $this
*/
private function writeToOutputNow($output)
{
if ($this->sendHttpCode() && \Whoops\Util\Misc::canSendHeaders()) {
$httpCode = $this->sendHttpCode();
if (function_exists('http_response_code')) {
http_response_code($httpCode);
} else {
// http_response_code is added in 5.4.
// For compatibility with 5.3 we use the third argument in header call
// First argument must be a real header.
// If it is empty, PHP will ignore the third argument.
// If it is invalid, such as a single space, Apache will handle it well,
// but the PHP development server will hang.
// Setting a full status line would require us to hardcode
// string values for all different status code, and detect the protocol.
// which is an extra error-prone complexity.
header('X-Ignore-This: 1', true, $httpCode);
}
}
echo $output;
return $this;
}
private static function isLevelFatal($level)
{
$errors = E_ERROR;
$errors |= E_PARSE;
$errors |= E_CORE_ERROR;
$errors |= E_CORE_WARNING;
$errors |= E_COMPILE_ERROR;
$errors |= E_COMPILE_WARNING;
return ($level & $errors) > 0;
}
}

View File

@ -0,0 +1,44 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Util;
class Misc
{
/**
* Can we at this point in time send HTTP headers?
*
* Currently this checks if we are even serving an HTTP request,
* as opposed to running from a command line.
*
* If we are serving an HTTP request, we check if it's not too late.
*
* @return bool
*/
public static function canSendHeaders()
{
return isset($_SERVER["REQUEST_URI"]) && !headers_sent();
}
/**
* Translate ErrorException code into the represented constant.
*
* @param int $error_code
* @return string
*/
public static function translateErrorCode($error_code)
{
$constants = get_defined_constants(true);
if (array_key_exists('Core' , $constants)) {
foreach ($constants['Core'] as $constant => $value) {
if (substr($constant, 0, 2) == 'E_' && $value == $error_code) {
return $constant;
}
}
}
return "E_UNKNOWN";
}
}

View File

@ -0,0 +1,154 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Util;
/**
* Exposes useful tools for working with/in templates
*/
class TemplateHelper
{
/**
* An array of variables to be passed to all templates
* @var array
*/
private $variables = array();
/**
* Escapes a string for output in an HTML document
*
* @param string $raw
* @return string
*/
public function escape($raw)
{
$flags = ENT_QUOTES;
// HHVM has all constants defined, but only ENT_IGNORE
// works at the moment
if (defined("ENT_SUBSTITUTE") && !defined("HHVM_VERSION")) {
$flags |= ENT_SUBSTITUTE;
} else {
// This is for 5.3.
// The documentation warns of a potential security issue,
// but it seems it does not apply in our case, because
// we do not blacklist anything anywhere.
$flags |= ENT_IGNORE;
}
return htmlspecialchars($raw, $flags, "UTF-8");
}
/**
* Escapes a string for output in an HTML document, but preserves
* URIs within it, and converts them to clickable anchor elements.
*
* @param string $raw
* @return string
*/
public function escapeButPreserveUris($raw)
{
$escaped = $this->escape($raw);
return preg_replace(
"@([A-z]+?://([-\w\.]+[-\w])+(:\d+)?(/([\w/_\.#-]*(\?\S+)?[^\.\s])?)?)@",
"<a href=\"$1\" target=\"_blank\">$1</a>", $escaped
);
}
/**
* Convert a string to a slug version of itself
*
* @param string $original
* @return string
*/
public function slug($original)
{
$slug = str_replace(" ", "-", $original);
$slug = preg_replace('/[^\w\d\-\_]/i', '', $slug);
return strtolower($slug);
}
/**
* Given a template path, render it within its own scope. This
* method also accepts an array of additional variables to be
* passed to the template.
*
* @param string $template
* @param array $additionalVariables
*/
public function render($template, array $additionalVariables = null)
{
$variables = $this->getVariables();
// Pass the helper to the template:
$variables["tpl"] = $this;
if ($additionalVariables !== null) {
$variables = array_replace($variables, $additionalVariables);
}
call_user_func(function () {
extract(func_get_arg(1));
require func_get_arg(0);
}, $template, $variables);
}
/**
* Sets the variables to be passed to all templates rendered
* by this template helper.
*
* @param array $variables
*/
public function setVariables(array $variables)
{
$this->variables = $variables;
}
/**
* Sets a single template variable, by its name:
*
* @param string $variableName
* @param mixd $variableValue
*/
public function setVariable($variableName, $variableValue)
{
$this->variables[$variableName] = $variableValue;
}
/**
* Gets a single template variable, by its name, or
* $defaultValue if the variable does not exist
*
* @param string $variableName
* @param mixed $defaultValue
* @return mixed
*/
public function getVariable($variableName, $defaultValue = null)
{
return isset($this->variables[$variableName]) ?
$this->variables[$variableName] : $defaultValue;
}
/**
* Unsets a single template variable, by its name
*
* @param string $variableName
*/
public function delVariable($variableName)
{
unset($this->variables[$variableName]);
}
/**
* Returns all variables for this helper
*
* @return array
*/
public function getVariables()
{
return $this->variables;
}
}

View File

@ -0,0 +1,63 @@
<?php
/**
* ZF2 Integration for Whoops
* @author Balázs Németh <zsilbi@zsilbi.hu>
*/
namespace Whoops\Provider\Zend;
use Whoops\Run;
use Zend\Http\Response;
use Zend\Mvc\Application;
use Zend\Mvc\MvcEvent;
use Zend\Mvc\View\Http\ExceptionStrategy as BaseExceptionStrategy;
/**
* @deprecated Use https://github.com/ghislainf/zf2-whoops
*/
class ExceptionStrategy extends BaseExceptionStrategy
{
protected $run;
public function __construct(Run $run)
{
$this->run = $run;
return $this;
}
public function prepareExceptionViewModel(MvcEvent $event)
{
// Do nothing if no error in the event
$error = $event->getError();
if (empty($error)) {
return;
}
// Do nothing if the result is a response object
$result = $event->getResult();
if ($result instanceof Response) {
return;
}
switch ($error) {
case Application::ERROR_CONTROLLER_NOT_FOUND:
case Application::ERROR_CONTROLLER_INVALID:
case Application::ERROR_ROUTER_NO_MATCH:
// Specifically not handling these
return;
case Application::ERROR_EXCEPTION:
default:
$exception = $event->getParam('exception');
if ($exception) {
$response = $event->getResponse();
if (!$response || $response->getStatusCode() === 200) {
header('HTTP/1.0 500 Internal Server Error', true, 500);
}
ob_clean();
$this->run->handleException($event->getParam('exception'));
}
break;
}
}
}

View File

@ -0,0 +1,107 @@
<?php
/**
* ZF2 Integration for Whoops
* @author Balázs Németh <zsilbi@zsilbi.hu>
*
* The Whoops directory should be added as a module to ZF2 (/vendor/Whoops)
*
* Whoops must be added as the first module
* For example:
* 'modules' => array(
* 'Whoops',
* 'Application',
* ),
*
* This file should be moved next to Whoops/Run.php (/vendor/Whoops/Module.php)
*
*/
namespace Whoops;
use Whoops\Handler\JsonResponseHandler;
use Whoops\Handler\PrettyPageHandler;
use Whoops\Provider\Zend\ExceptionStrategy;
use Whoops\Provider\Zend\RouteNotFoundStrategy;
use Zend\Console\Request as ConsoleRequest;
use Zend\EventManager\EventInterface;
/**
* @deprecated Use https://github.com/ghislainf/zf2-whoops
*/
class Module
{
protected $run;
public function onBootstrap(EventInterface $event)
{
$prettyPageHandler = new PrettyPageHandler();
// Set editor
$config = $event->getApplication()->getServiceManager()->get('Config');
if (isset($config['view_manager']['editor'])) {
$prettyPageHandler->setEditor($config['view_manager']['editor']);
}
$this->run = new Run();
$this->run->register();
$this->run->pushHandler($prettyPageHandler);
$this->attachListeners($event);
}
public function getAutoloaderConfig()
{
return array(
'Zend\Loader\StandardAutoloader' => array(
'namespaces' => array(
__NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
),
),
);
}
private function attachListeners(EventInterface $event)
{
$request = $event->getRequest();
$application = $event->getApplication();
$services = $application->getServiceManager();
$events = $application->getEventManager();
$config = $services->get('Config');
//Display exceptions based on configuration and console mode
if ($request instanceof ConsoleRequest || empty($config['view_manager']['display_exceptions'])) {
return;
}
$jsonHandler = new JsonResponseHandler();
if (!empty($config['view_manager']['json_exceptions']['show_trace'])) {
//Add trace to the JSON output
$jsonHandler->addTraceToOutput(true);
}
if (!empty($config['view_manager']['json_exceptions']['ajax_only'])) {
//Only return JSON response for AJAX requests
$jsonHandler->onlyForAjaxRequests(true);
}
if (!empty($config['view_manager']['json_exceptions']['display'])) {
//Turn on JSON handler
$this->run->pushHandler($jsonHandler);
}
//Attach the Whoops ExceptionStrategy
$exceptionStrategy = new ExceptionStrategy($this->run);
$exceptionStrategy->attach($events);
//Attach the Whoops RouteNotFoundStrategy
$routeNotFoundStrategy = new RouteNotFoundStrategy($this->run);
$routeNotFoundStrategy->attach($events);
//Detach default ExceptionStrategy
$services->get('Zend\Mvc\View\Http\ExceptionStrategy')->detach($events);
//Detach default RouteNotFoundStrategy
$services->get('Zend\Mvc\View\Http\RouteNotFoundStrategy')->detach($events);
}
}

View File

@ -0,0 +1,67 @@
<?php
/**
* ZF2 Integration for Whoops
* @author Balázs Németh <zsilbi@zsilbi.hu>
*/
namespace Whoops\Provider\Zend;
use Whoops\Run;
use Zend\Mvc\MvcEvent;
use Zend\Mvc\View\Http\RouteNotFoundStrategy as BaseRouteNotFoundStrategy;
use Zend\Stdlib\ResponseInterface as Response;
use Zend\View\Model\ViewModel;
/**
* @deprecated Use https://github.com/ghislainf/zf2-whoops
*/
class RouteNotFoundStrategy extends BaseRouteNotFoundStrategy
{
protected $run;
public function __construct(Run $run)
{
$this->run = $run;
}
public function prepareNotFoundViewModel(MvcEvent $e)
{
$vars = $e->getResult();
if ($vars instanceof Response) {
// Already have a response as the result
return;
}
$response = $e->getResponse();
if ($response->getStatusCode() != 404) {
// Only handle 404 responses
return;
}
if (!$vars instanceof ViewModel) {
$model = new ViewModel();
if (is_string($vars)) {
$model->setVariable('message', $vars);
} else {
$model->setVariable('message', 'Page not found.');
}
} else {
$model = $vars;
if ($model->getVariable('message') === null) {
$model->setVariable('message', 'Page not found.');
}
}
// If displaying reasons, inject the reason
$this->injectNotFoundReason($model, $e);
// If displaying exceptions, inject
$this->injectException($model, $e);
// Inject controller if we're displaying either the reason or the exception
$this->injectController($model, $e);
ob_clean();
throw new \Exception($model->getVariable('message') . ' ' . $model->getVariable('reason'));
}
}

View File

@ -0,0 +1,20 @@
<?php
/**
* ZF2 Integration for Whoops
* @author Balázs Németh <zsilbi@zsilbi.hu>
*
* Example controller configuration
*/
return array(
'view_manager' => array(
'editor' => 'sublime',
'display_not_found_reason' => true,
'display_exceptions' => true,
'json_exceptions' => array(
'display' => true,
'ajax_only' => true,
'show_trace' => true,
),
),
);