first version

This commit is contained in:
Bastian Allgeier
2019-01-13 23:17:34 +01:00
commit 01277f79f2
595 changed files with 82913 additions and 0 deletions

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,73 @@
<?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 = [
'type' => get_class($exception),
'message' => $exception->getMessage(),
'file' => $exception->getFile(),
'line' => $exception->getLine(),
];
if ($shouldAddTrace) {
$frames = $inspector->getFrames();
$frameData = [];
foreach ($frames as $frame) {
/** @var Frame $frame */
$frameData[] = [
'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,291 @@
<?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 = [];
/**
* @var bool
*/
protected $application;
/**
* @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__))))));
if ($dirname !== '/') {
$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'] : [];
}
/**
* 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' or '[internal]' is passed
// this would otherwise raise an exception when
// open_basedir is enabled.
if ($filePath === "Unknown" || $filePath === '[internal]') {
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[] = [
'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();
}
/**
* Returns whether this frame belongs to the application or not.
*
* @return boolean
*/
public function isApplication()
{
return $this->application;
}
/**
* Mark as an frame belonging to the application.
*
* @param boolean $application
*/
public function setApplication($application)
{
$this->application = $application;
}
}

View File

@@ -0,0 +1,203 @@
<?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_values(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);
}
/**
* Count the frames that belongs to the application.
*
* @return int
*/
public function countIsApplication()
{
return count(array_filter($this->frames, function (Frame $f) {
return $f->isApplication();
}));
}
/**
* @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,323 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Exception;
use Whoops\Util\Misc;
class Inspector
{
/**
* @var \Throwable
*/
private $exception;
/**
* @var \Whoops\Exception\FrameCollection
*/
private $frames;
/**
* @var \Whoops\Exception\Inspector
*/
private $previousExceptionInspector;
/**
* @var \Throwable[]
*/
private $previousExceptions;
/**
* @param \Throwable $exception The exception to inspect
*/
public function __construct($exception)
{
$this->exception = $exception;
}
/**
* @return \Throwable
*/
public function getException()
{
return $this->exception;
}
/**
* @return string
*/
public function getExceptionName()
{
return get_class($this->exception);
}
/**
* @return string
*/
public function getExceptionMessage()
{
return $this->extractDocrefUrl($this->exception->getMessage())['message'];
}
/**
* @return string[]
*/
public function getPreviousExceptionMessages()
{
return array_map(function ($prev) {
/** @var \Throwable $prev */
return $this->extractDocrefUrl($prev->getMessage())['message'];
}, $this->getPreviousExceptions());
}
/**
* @return int[]
*/
public function getPreviousExceptionCodes()
{
return array_map(function ($prev) {
/** @var \Throwable $prev */
return $prev->getCode();
}, $this->getPreviousExceptions());
}
/**
* Returns a url to the php-manual related to the underlying error - when available.
*
* @return string|null
*/
public function getExceptionDocrefUrl()
{
return $this->extractDocrefUrl($this->exception->getMessage())['url'];
}
private function extractDocrefUrl($message)
{
$docref = [
'message' => $message,
'url' => null,
];
// php embbeds urls to the manual into the Exception message with the following ini-settings defined
// http://php.net/manual/en/errorfunc.configuration.php#ini.docref-root
if (!ini_get('html_errors') || !ini_get('docref_root')) {
return $docref;
}
$pattern = "/\[<a href='([^']+)'>(?:[^<]+)<\/a>\]/";
if (preg_match($pattern, $message, $matches)) {
// -> strip those automatically generated links from the exception message
$docref['message'] = preg_replace($pattern, '', $message, 1);
$docref['url'] = $matches[1];
}
return $docref;
}
/**
* 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 array of all previous exceptions for this inspector's exception
* @return \Throwable[]
*/
public function getPreviousExceptions()
{
if ($this->previousExceptions === null) {
$this->previousExceptions = [];
$prev = $this->exception->getPrevious();
while ($prev !== null) {
$this->previousExceptions[] = $prev;
$prev = $prev->getPrevious();
}
}
return $this->previousExceptions;
}
/**
* Returns an iterator for the inspected exception's
* frames.
* @return \Whoops\Exception\FrameCollection
*/
public function getFrames()
{
if ($this->frames === null) {
$frames = $this->getTrace($this->exception);
// Fill empty line/file info for call_user_func_array usages (PHP Bug #44428)
foreach ($frames as $k => $frame) {
if (empty($frame['file'])) {
// Default values when file and line are missing
$file = '[internal]';
$line = 0;
$next_frame = !empty($frames[$k + 1]) ? $frames[$k + 1] : [];
if ($this->isValidNextFrame($next_frame)) {
$file = $next_frame['file'];
$line = $next_frame['line'];
}
$frames[$k]['file'] = $file;
$frames[$k]['line'] = $line;
}
}
// Find latest non-error handling frame index ($i) used to remove error handling frames
$i = 0;
foreach ($frames as $k => $frame) {
if ($frame['file'] == $this->exception->getFile() && $frame['line'] == $this->exception->getLine()) {
$i = $k;
}
}
// Remove error handling frames
if ($i > 0) {
array_splice($frames, 0, $i);
}
$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;
}
/**
* Gets the backtrace from an exception.
*
* If xdebug is installed
*
* @param \Throwable $e
* @return array
*/
protected function getTrace($e)
{
$traces = $e->getTrace();
// Get trace from xdebug if enabled, failure exceptions only trace to the shutdown handler by default
if (!$e instanceof \ErrorException) {
return $traces;
}
if (!Misc::isLevelFatal($e->getSeverity())) {
return $traces;
}
if (!extension_loaded('xdebug') || !xdebug_is_enabled()) {
return [];
}
// Use xdebug to get the full stack trace and remove the shutdown handler stack trace
$stack = array_reverse(xdebug_get_function_stack());
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
$traces = array_diff_key($stack, $trace);
return $traces;
}
/**
* Given an exception, generates an array in the format
* generated by Exception::getTrace()
* @param \Throwable $exception
* @return array
*/
protected function getFrameFromException($exception)
{
return [
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'class' => get_class($exception),
'args' => [
$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 [
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'class' => null,
'args' => [],
];
}
/**
* Determine if the frame can be used to fill in previous frame's missing info
* happens for call_user_func and call_user_func_array usages (PHP Bug #44428)
*
* @param array $frame
* @return bool
*/
protected function isValidNextFrame(array $frame)
{
if (empty($frame['file'])) {
return false;
}
if (empty($frame['line'])) {
return false;
}
if (empty($frame['function']) || !stristr($frame['function'], 'call_user_func')) {
return false;
}
return true;
}
}

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,95 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Handler;
use Whoops\Exception\Inspector;
use Whoops\RunInterface;
/**
* 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
/**
* The Handler has handled the Throwable in some way, and wishes to skip any other Handler.
* Execution will continue.
*/
const LAST_HANDLER = 0x20;
/**
* The Handler has handled the Throwable in some way, and wishes to quit/stop execution
*/
const QUIT = 0x30;
/**
* @var RunInterface
*/
private $run;
/**
* @var Inspector $inspector
*/
private $inspector;
/**
* @var \Throwable $exception
*/
private $exception;
/**
* @param RunInterface $run
*/
public function setRun(RunInterface $run)
{
$this->run = $run;
}
/**
* @return RunInterface
*/
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 \Throwable $exception
*/
public function setException($exception)
{
$this->exception = $exception;
}
/**
* @return \Throwable
*/
protected function getException()
{
return $this->exception;
}
}

View File

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

View File

@@ -0,0 +1,88 @@
<?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 $jsonApi = false;
/**
* Returns errors[[]] instead of error[] to be in compliance with the json:api spec
* @param bool $jsonApi Default is false
* @return $this
*/
public function setJsonApi($jsonApi = false)
{
$this->jsonApi = (bool) $jsonApi;
return $this;
}
/**
* @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()
{
if ($this->jsonApi === true) {
$response = [
'errors' => [
Formatter::formatExceptionAsDataArray(
$this->getInspector(),
$this->addTraceToOutput()
),
]
];
} else {
$response = [
'error' => Formatter::formatExceptionAsDataArray(
$this->getInspector(),
$this->addTraceToOutput()
),
];
}
echo json_encode($response, defined('JSON_PARTIAL_OUTPUT_ON_ERROR') ? JSON_PARTIAL_OUTPUT_ON_ERROR : 0);
return Handler::QUIT;
}
/**
* @return string
*/
public function contentType()
{
return 'application/json';
}
}

View File

@@ -0,0 +1,314 @@
<?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 callable
*/
protected $dumper;
/**
* @var bool
*/
private $addTraceToOutput = true;
/**
* @var bool|integer
*/
private $addTraceFunctionArgsToOutput = false;
/**
* @var integer
*/
private $traceFunctionArgsOutputLimit = 1024;
/**
* @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;
}
/**
* Set var dumper callback function.
*
* @param callable $dumper
* @return void
*/
public function setDumper(callable $dumper)
{
$this->dumper = $dumper;
}
/**
* 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;
}
/**
* Create plain text response and return it as a string
* @return string
*/
public function generateResponse()
{
$exception = $this->getException();
return sprintf(
"%s: %s in file %s on line %d%s\n",
get_class($exception),
$exception->getMessage(),
$exception->getFile(),
$exception->getLine(),
$this->getTraceOutput()
);
}
/**
* 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;
}
/**
* 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;
}
/**
* Test if handler can output to stdout.
* @return bool
*/
private function canOutput()
{
return !$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();
$this->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())
);
}
/**
* Dump variable.
*
* @param mixed $var
* @return void
*/
protected function dump($var)
{
if ($this->dumper) {
call_user_func($this->dumper, $var);
} else {
var_dump($var);
}
}
/**
* 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()
{
$response = $this->generateResponse();
if ($this->getLogger()) {
$this->getLogger()->error($response);
}
if (! $this->canOutput()) {
return Handler::DONE;
}
echo $response;
return Handler::QUIT;
}
/**
* @return string
*/
public function contentType()
{
return 'text/plain';
}
}

View File

@@ -0,0 +1,713 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Handler;
use InvalidArgumentException;
use RuntimeException;
use Symfony\Component\VarDumper\Cloner\AbstractCloner;
use Symfony\Component\VarDumper\Cloner\VarCloner;
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 = [];
/**
* Fast lookup cache for known resource locations.
*
* @var array
*/
private $resourceCache = [];
/**
* The name of the custom css file.
*
* @var string
*/
private $customCss = null;
/**
* @var array[]
*/
private $extraTables = [];
/**
* @var bool
*/
private $handleUnconditionally = false;
/**
* @var string
*/
private $pageTitle = "Whoops! There was an error.";
/**
* @var array[]
*/
private $applicationPaths;
/**
* @var array[]
*/
private $blacklist = [
'_GET' => [],
'_POST' => [],
'_FILES' => [],
'_COOKIE' => [],
'_SESSION' => [],
'_SERVER' => [],
'_ENV' => [],
];
/**
* 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 = [
"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",
"idea" => "idea://open?file=%file&line=%line",
"vscode" => "vscode://file/%file:%line",
"atom" => "atom://core/open/file?filename=%file&line=%line",
];
/**
* @var TemplateHelper
*/
private $templateHelper;
/**
* 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(['%f', '%l'], [$file, $line], ini_get('xdebug.file_link_format'));
};
}
// Add the default, local resource search path:
$this->searchPaths[] = __DIR__ . "/../Resources";
// blacklist php provided auth based values
$this->blacklist('_SERVER', 'PHP_AUTH_PW');
$this->templateHelper = new TemplateHelper();
if (class_exists('Symfony\Component\VarDumper\Cloner\VarCloner')) {
$cloner = new VarCloner();
// Only dump object internals if a custom caster exists.
$cloner->addCasters(['*' => function ($obj, $a, $stub, $isNested, $filter = 0) {
$class = $stub->class;
$classes = [$class => $class] + class_parents($class) + class_implements($class);
foreach ($classes as $class) {
if (isset(AbstractCloner::$defaultCasters[$class])) {
return $a;
}
}
// Remove all internals
return [];
}]);
$this->templateHelper->setCloner($cloner);
}
}
/**
* @return int|null
*/
public function handle()
{
if (!$this->handleUnconditionally()) {
// Check conditions for outputting HTML:
// @todo: Make this more robust
if (PHP_SAPI === '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;
}
}
$templateFile = $this->getResource("views/layout.html.php");
$cssFile = $this->getResource("css/whoops.base.css");
$zeptoFile = $this->getResource("js/zepto.min.js");
$prettifyFile = $this->getResource("js/prettify.min.js");
$clipboard = $this->getResource("js/clipboard.min.js");
$jsFile = $this->getResource("js/whoops.base.js");
if ($this->customCss) {
$customCssFile = $this->getResource($this->customCss);
}
$inspector = $this->getInspector();
$frames = $this->getExceptionFrames();
$code = $this->getExceptionCode();
// List of variables that will be passed to the layout template.
$vars = [
"page_title" => $this->getPageTitle(),
// @todo: Asset compiler
"stylesheet" => file_get_contents($cssFile),
"zepto" => file_get_contents($zeptoFile),
"prettify" => file_get_contents($prettifyFile),
"clipboard" => file_get_contents($clipboard),
"javascript" => file_get_contents($jsFile),
// Template paths:
"header" => $this->getResource("views/header.html.php"),
"header_outer" => $this->getResource("views/header_outer.html.php"),
"frame_list" => $this->getResource("views/frame_list.html.php"),
"frames_description" => $this->getResource("views/frames_description.html.php"),
"frames_container" => $this->getResource("views/frames_container.html.php"),
"panel_details" => $this->getResource("views/panel_details.html.php"),
"panel_details_outer" => $this->getResource("views/panel_details_outer.html.php"),
"panel_left" => $this->getResource("views/panel_left.html.php"),
"panel_left_outer" => $this->getResource("views/panel_left_outer.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->getExceptionMessage(),
"previousMessages" => $inspector->getPreviousExceptionMessages(),
"docref_url" => $inspector->getExceptionDocrefUrl(),
"code" => $code,
"previousCodes" => $inspector->getPreviousExceptionCodes(),
"plain_exception" => Formatter::formatExceptionPlain($inspector),
"frames" => $frames,
"has_frames" => !!count($frames),
"handler" => $this,
"handlers" => $this->getRun()->getHandlers(),
"active_frames_tab" => count($frames) && $frames->offsetGet(0)->isApplication() ? 'application' : 'all',
"has_frames_tabs" => $this->getApplicationPaths(),
"tables" => [
"GET Data" => $this->masked($_GET, '_GET'),
"POST Data" => $this->masked($_POST, '_POST'),
"Files" => isset($_FILES) ? $this->masked($_FILES, '_FILES') : [],
"Cookies" => $this->masked($_COOKIE, '_COOKIE'),
"Session" => isset($_SESSION) ? $this->masked($_SESSION, '_SESSION') : [],
"Server/Request Data" => $this->masked($_SERVER, '_SERVER'),
"Environment Variables" => $this->masked($_ENV, '_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) use ($inspector) {
return $table instanceof \Closure ? $table($inspector) : $table;
}, $this->getDataTables());
$vars["tables"] = array_merge($extraTables, $vars["tables"]);
$plainTextHandler = new PlainTextHandler();
$plainTextHandler->setException($this->getException());
$plainTextHandler->setInspector($this->getInspector());
$vars["preface"] = "<!--\n\n\n" . $this->templateHelper->escape($plainTextHandler->generateResponse()) . "\n\n\n\n\n\n\n\n\n\n\n-->";
$this->templateHelper->setVariables($vars);
$this->templateHelper->render($templateFile);
return Handler::QUIT;
}
/**
* Get the stack trace frames of the exception that is currently being handled.
*
* @return \Whoops\Exception\FrameCollection;
*/
protected function getExceptionFrames()
{
$frames = $this->getInspector()->getFrames();
if ($this->getApplicationPaths()) {
foreach ($frames as $frame) {
foreach ($this->getApplicationPaths() as $path) {
if (strpos($frame->getFile(), $path) === 0) {
$frame->setApplication(true);
break;
}
}
}
}
return $frames;
}
/**
* Get the code of the exception that is currently being handled.
*
* @return string
*/
protected function getExceptionCode()
{
$exception = $this->getException();
$code = $exception->getCode();
if ($exception instanceof \ErrorException) {
// ErrorExceptions wrap the php-error types within the 'severity' property
$code = Misc::translateErrorCode($exception->getSeverity());
}
return (string) $code;
}
/**
* @return string
*/
public function contentType()
{
return 'text/html';
}
/**
* 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 (\Whoops\Exception\Inspector $inspector = null) use ($callback) {
try {
$result = call_user_func($callback, $inspector);
// Only return the result if it can be iterated over by foreach().
return is_array($result) || $result instanceof \Traversable ? $result : [];
} catch (\Exception $e) {
// Don't allow failure to break the rendering of the original exception.
return [];
}
};
}
/**
* 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] : [];
}
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 (empty($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
*
* @param string $filePath
* @param int $line
* @return array
*/
protected function getEditor($filePath, $line)
{
if (!$this->editor || (!is_string($this->editor) && !is_callable($this->editor))) {
return [];
}
if (is_string($this->editor) && isset($this->editors[$this->editor]) && !is_callable($this->editors[$this->editor])) {
return [
'ajax' => false,
'url' => $this->editors[$this->editor],
];
}
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);
}
if (empty($callback)) {
return [];
}
if (is_string($callback)) {
return [
'ajax' => false,
'url' => $callback,
];
}
return [
'ajax' => isset($callback['ajax']) ? $callback['ajax'] : false,
'url' => isset($callback['url']) ? $callback['url'] : $callback,
];
}
return [];
}
/**
* @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 InvalidArgumentException 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);
}
/**
* Return the application paths.
*
* @return array
*/
public function getApplicationPaths()
{
return $this->applicationPaths;
}
/**
* Set the application paths.
*
* @param array $applicationPaths
*/
public function setApplicationPaths($applicationPaths)
{
$this->applicationPaths = $applicationPaths;
}
/**
* Set the application root path.
*
* @param string $applicationRootPath
*/
public function setApplicationRootPath($applicationRootPath)
{
$this->templateHelper->setApplicationRootPath($applicationRootPath);
}
/**
* blacklist a sensitive value within one of the superglobal arrays.
*
* @param $superGlobalName string the name of the superglobal array, e.g. '_GET'
* @param $key string the key within the superglobal
*/
public function blacklist($superGlobalName, $key)
{
$this->blacklist[$superGlobalName][] = $key;
}
/**
* Checks all values within the given superGlobal array.
* Blacklisted values will be replaced by a equal length string cointaining only '*' characters.
*
* We intentionally dont rely on $GLOBALS as it depends on 'auto_globals_jit' php.ini setting.
*
* @param $superGlobal array One of the superglobal arrays
* @param $superGlobalName string the name of the superglobal array, e.g. '_GET'
* @return array $values without sensitive data
*/
private function masked(array $superGlobal, $superGlobalName)
{
$blacklisted = $this->blacklist[$superGlobalName];
$values = $superGlobal;
foreach ($blacklisted as $key) {
if (isset($superGlobal[$key])) {
$values[$key] = str_repeat('*', strlen($superGlobal[$key]));
}
}
return $values;
}
}

View File

@@ -0,0 +1,107 @@
<?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 = [
'error' => Formatter::formatExceptionAsDataArray(
$this->getInspector(),
$this->addTraceToOutput()
),
];
echo $this->toXml($response);
return Handler::QUIT;
}
/**
* @return string
*/
public function contentType()
{
return 'application/xml';
}
/**
* @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) || $data 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) || $data 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,653 @@
body {
font: 12px "Helvetica Neue", helvetica, arial, sans-serif;
color: #131313;
background: #eeeeee;
padding: 0;
margin: 0;
max-height: 100%;
text-rendering: optimizeLegibility;
}
a {
text-decoration: none;
}
.panel {
overflow-y: scroll;
height: 100%;
position: fixed;
margin: 0;
left: 0;
top: 0;
}
.branding {
position: absolute;
top: 10px;
right: 20px;
color: #777777;
font-size: 10px;
z-index: 100;
}
.branding a {
color: #e95353;
}
header {
color: white;
box-sizing: border-box;
background-color: #2a2a2a;
padding: 35px 40px;
max-height: 180px;
overflow: hidden;
transition: 0.5s;
}
header.header-expand {
max-height: 1000px;
}
.exc-title {
margin: 0;
color: #bebebe;
font-size: 14px;
}
.exc-title-primary,
.exc-title-secondary {
color: #e95353;
}
.exc-message {
font-size: 20px;
word-wrap: break-word;
margin: 4px 0 0 0;
color: white;
}
.exc-message span {
display: block;
}
.exc-message-empty-notice {
color: #a29d9d;
font-weight: 300;
}
.prev-exc-title {
margin: 10px 0;
}
.prev-exc-title + ul {
margin: 0;
padding: 0 0 0 20px;
line-height: 12px;
}
.prev-exc-title + ul li {
font: 12px "Helvetica Neue", helvetica, arial, sans-serif;
}
.prev-exc-title + ul li .prev-exc-code {
display: inline-block;
color: #bebebe;
}
.details-container {
left: 30%;
width: 70%;
background: #fafafa;
}
.details {
padding: 5px;
}
.details-heading {
color: #4288ce;
font-weight: 300;
padding-bottom: 10px;
margin-bottom: 10px;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
.details pre.sf-dump {
white-space: pre;
word-wrap: inherit;
}
.details pre.sf-dump,
.details pre.sf-dump .sf-dump-num,
.details pre.sf-dump .sf-dump-const,
.details pre.sf-dump .sf-dump-str,
.details pre.sf-dump .sf-dump-note,
.details pre.sf-dump .sf-dump-ref,
.details pre.sf-dump .sf-dump-public,
.details pre.sf-dump .sf-dump-protected,
.details pre.sf-dump .sf-dump-private,
.details pre.sf-dump .sf-dump-meta,
.details pre.sf-dump .sf-dump-key,
.details pre.sf-dump .sf-dump-index {
color: #463c54;
}
.left-panel {
width: 30%;
background: #ded8d8;
}
.frames-description {
background: rgba(0, 0, 0, 0.05);
padding: 8px 15px;
color: #a29d9d;
font-size: 11px;
}
.frames-description.frames-description-application {
text-align: center;
font-size: 12px;
}
.frames-container.frames-container-application .frame:not(.frame-application) {
display: none;
}
.frames-tab {
color: #a29d9d;
display: inline-block;
padding: 4px 8px;
margin: 0 2px;
border-radius: 3px;
}
.frames-tab.frames-tab-active {
background-color: #2a2a2a;
color: #bebebe;
}
.frame {
padding: 14px;
cursor: pointer;
transition: all 0.1s ease;
background: #eeeeee;
}
.frame:not(:last-child) {
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
}
.frame.active {
box-shadow: inset -5px 0 0 0 #4288ce;
color: #4288ce;
}
.frame:not(.active):hover {
background: #bee9ea;
}
.frame-method-info {
margin-bottom: 10px;
}
.frame-class,
.frame-function,
.frame-index {
font-size: 14px;
}
.frame-index {
float: left;
}
.frame-method-info {
margin-left: 24px;
}
.frame-index {
font-size: 11px;
color: #a29d9d;
background-color: rgba(0, 0, 0, 0.05);
height: 18px;
width: 18px;
line-height: 18px;
border-radius: 5px;
padding: 0 1px 0 1px;
text-align: center;
display: inline-block;
}
.frame-application .frame-index {
background-color: #2a2a2a;
color: #bebebe;
}
.frame-file {
font-family: "Inconsolata", "Fira Mono", "Source Code Pro", Monaco, Consolas,
"Lucida Console", monospace;
color: #a29d9d;
}
.frame-file .editor-link {
color: #a29d9d;
}
.frame-line {
font-weight: bold;
}
.frame-line:before {
content: ":";
}
.frame-code {
padding: 5px;
background: #303030;
display: none;
}
.frame-code.active {
display: block;
}
.frame-code .frame-file {
color: #a29d9d;
padding: 12px 6px;
border-bottom: none;
}
.code-block {
padding: 10px;
margin: 0;
border-radius: 6px;
box-shadow: 0 3px 0 rgba(0, 0, 0, 0.05), 0 10px 30px rgba(0, 0, 0, 0.05),
inset 0 0 1px 0 rgba(255, 255, 255, 0.07);
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
}
.linenums {
margin: 0;
margin-left: 10px;
}
.frame-comments {
border-top: none;
margin-top: 15px;
font-size: 12px;
}
.frame-comments.empty {
}
.frame-comments.empty:before {
content: "No comments for this stack frame.";
font-weight: 300;
color: #a29d9d;
}
.frame-comment {
padding: 10px;
color: #e3e3e3;
border-radius: 6px;
background-color: rgba(255, 255, 255, 0.05);
}
.frame-comment a {
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, 0.3);
}
.frame-comment-context {
font-size: 10px;
color: white;
}
.delimiter {
display: inline-block;
}
.data-table-container label {
font-size: 16px;
color: #303030;
font-weight: bold;
margin: 10px 0;
display: block;
margin-bottom: 5px;
padding-bottom: 5px;
}
.data-table {
width: 100%;
margin-bottom: 10px;
}
.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, 0.3);
font-weight: 300;
}
.data-table label.empty {
display: inline;
}
.handler {
padding: 4px 0;
font: 14px "Inconsolata", "Fira Mono", "Source Code Pro", Monaco, Consolas,
"Lucida Console", monospace;
}
/* 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.code-block,
code.code-block,
.frame-args.code-block,
.frame-args.code-block samp {
font-family: "Inconsolata", "Fira Mono", "Source Code Pro", Monaco, Consolas,
"Lucida Console", monospace;
background: #333;
color: #e9e4e5;
}
pre.code-block {
white-space: pre-wrap;
}
pre.code-block a,
code.code-block a {
text-decoration: none;
}
.linenums li {
color: #a5a5a5;
}
.linenums li.current {
background: rgba(255, 100, 100, 0.07);
}
.linenums li.current.active {
background: rgba(255, 100, 100, 0.17);
}
pre:not(.prettyprinted) {
padding-left: 60px;
}
#plain-exception {
display: none;
}
#copy-button {
cursor: pointer;
border: 0;
}
.clipboard {
opacity: 0.8;
background: none;
color: rgba(255, 255, 255, 0.1);
box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.1);
border-radius: 3px;
outline: none !important;
}
.clipboard:hover {
box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.3);
color: rgba(255, 255, 255, 0.3);
}
/* 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-style: solid;
border-width: 1px;
color: #555;
display: inline-block;
font-size: 11px;
line-height: 10px;
padding: 3px 5px;
vertical-align: middle;
}
/* == Media queries */
/* Expand the spacing in the details section */
@media (min-width: 1000px) {
.details,
.frame-code {
padding: 20px 40px;
}
.details-container {
left: 32%;
width: 68%;
}
.frames-container {
margin: 5px;
}
.left-panel {
width: 32%;
}
}
/* Stack panels */
@media (max-width: 600px) {
.panel {
position: static;
width: 100%;
}
}
/* Stack details tables */
@media (max-width: 400px) {
.data-table,
.data-table tbody,
.data-table tbody tr,
.data-table tbody td {
display: block;
width: 100%;
}
.data-table tbody tr:first-child {
padding-top: 0;
}
.data-table tbody td:first-child,
.data-table tbody td:last-child {
padding-left: 0;
padding-right: 0;
}
.data-table tbody td:last-child {
padding-top: 3px;
}
}
.tooltipped {
position: relative;
}
.tooltipped:after {
position: absolute;
z-index: 1000000;
display: none;
padding: 5px 8px;
color: #fff;
text-align: center;
text-decoration: none;
text-shadow: none;
text-transform: none;
letter-spacing: normal;
word-wrap: break-word;
white-space: pre;
pointer-events: none;
content: attr(aria-label);
background: rgba(0, 0, 0, 0.8);
border-radius: 3px;
-webkit-font-smoothing: subpixel-antialiased;
}
.tooltipped:before {
position: absolute;
z-index: 1000001;
display: none;
width: 0;
height: 0;
color: rgba(0, 0, 0, 0.8);
pointer-events: none;
content: "";
border: 5px solid transparent;
}
.tooltipped:hover:before,
.tooltipped:hover:after,
.tooltipped:active:before,
.tooltipped:active:after,
.tooltipped:focus:before,
.tooltipped:focus:after {
display: inline-block;
text-decoration: none;
}
.tooltipped-s:after {
top: 100%;
right: 50%;
margin-top: 5px;
}
.tooltipped-s:before {
top: auto;
right: 50%;
bottom: -5px;
margin-right: -5px;
border-bottom-color: rgba(0, 0, 0, 0.8);
}
pre.sf-dump {
padding: 0px !important;
margin: 0px !important;
}
.search-for-help {
width: 85%;
padding: 0;
margin: 10px 0;
list-style-type: none;
display: inline-block;
}
.search-for-help li {
display: inline-block;
margin-right: 5px;
}
.search-for-help li:last-child {
margin-right: 0;
}
.search-for-help li a {
}
.search-for-help li a i {
width: 16px;
height: 16px;
overflow: hidden;
display: block;
}
.search-for-help li a svg {
fill: #fff;
}
.search-for-help li a svg path {
background-size: contain;
}

View File

@@ -0,0 +1,523 @@
/*!
* clipboard.js v1.5.3
* https://zenorocha.github.io/clipboard.js
*
* Licensed MIT © Zeno Rocha
*/
!(function(t) {
if ("object" == typeof exports && "undefined" != typeof module)
module.exports = t();
else if ("function" == typeof define && define.amd) define([], t);
else {
var e;
(e =
"undefined" != typeof window
? window
: "undefined" != typeof global
? global
: "undefined" != typeof self
? self
: this),
(e.Clipboard = t());
}
})(function() {
var t, e, n;
return (function t(e, n, r) {
function o(a, c) {
if (!n[a]) {
if (!e[a]) {
var s = "function" == typeof require && require;
if (!c && s) return s(a, !0);
if (i) return i(a, !0);
var u = new Error("Cannot find module '" + a + "'");
throw ((u.code = "MODULE_NOT_FOUND"), u);
}
var l = (n[a] = { exports: {} });
e[a][0].call(
l.exports,
function(t) {
var n = e[a][1][t];
return o(n ? n : t);
},
l,
l.exports,
t,
e,
n,
r
);
}
return n[a].exports;
}
for (
var i = "function" == typeof require && require, a = 0;
a < r.length;
a++
)
o(r[a]);
return o;
})(
{
1: [
function(t, e, n) {
var r = t("matches-selector");
e.exports = function(t, e, n) {
for (var o = n ? t : t.parentNode; o && o !== document; ) {
if (r(o, e)) return o;
o = o.parentNode;
}
};
},
{ "matches-selector": 2 }
],
2: [
function(t, e, n) {
function r(t, e) {
if (i) return i.call(t, e);
for (
var n = t.parentNode.querySelectorAll(e), r = 0;
r < n.length;
++r
)
if (n[r] == t) return !0;
return !1;
}
var o = Element.prototype,
i =
o.matchesSelector ||
o.webkitMatchesSelector ||
o.mozMatchesSelector ||
o.msMatchesSelector ||
o.oMatchesSelector;
e.exports = r;
},
{}
],
3: [
function(t, e, n) {
function r(t, e, n, r) {
var i = o.apply(this, arguments);
return (
t.addEventListener(n, i),
{
destroy: function() {
t.removeEventListener(n, i);
}
}
);
}
function o(t, e, n, r) {
return function(n) {
var o = i(n.target, e, !0);
o &&
(Object.defineProperty(n, "target", { value: o }),
r.call(t, n));
};
}
var i = t("closest");
e.exports = r;
},
{ closest: 1 }
],
4: [
function(t, e, n) {
(n.node = function(t) {
return void 0 !== t && t instanceof HTMLElement && 1 === t.nodeType;
}),
(n.nodeList = function(t) {
var e = Object.prototype.toString.call(t);
return (
void 0 !== t &&
("[object NodeList]" === e ||
"[object HTMLCollection]" === e) &&
"length" in t &&
(0 === t.length || n.node(t[0]))
);
}),
(n.string = function(t) {
return "string" == typeof t || t instanceof String;
}),
(n.function = function(t) {
var e = Object.prototype.toString.call(t);
return "[object Function]" === e;
});
},
{}
],
5: [
function(t, e, n) {
function r(t, e, n) {
if (!t && !e && !n) throw new Error("Missing required arguments");
if (!c.string(e))
throw new TypeError("Second argument must be a String");
if (!c.function(n))
throw new TypeError("Third argument must be a Function");
if (c.node(t)) return o(t, e, n);
if (c.nodeList(t)) return i(t, e, n);
if (c.string(t)) return a(t, e, n);
throw new TypeError(
"First argument must be a String, HTMLElement, HTMLCollection, or NodeList"
);
}
function o(t, e, n) {
return (
t.addEventListener(e, n),
{
destroy: function() {
t.removeEventListener(e, n);
}
}
);
}
function i(t, e, n) {
return (
Array.prototype.forEach.call(t, function(t) {
t.addEventListener(e, n);
}),
{
destroy: function() {
Array.prototype.forEach.call(t, function(t) {
t.removeEventListener(e, n);
});
}
}
);
}
function a(t, e, n) {
return s(document.body, t, e, n);
}
var c = t("./is"),
s = t("delegate");
e.exports = r;
},
{ "./is": 4, delegate: 3 }
],
6: [
function(t, e, n) {
function r(t) {
var e;
if ("INPUT" === t.nodeName || "TEXTAREA" === t.nodeName)
t.select(), (e = t.value);
else {
var n = window.getSelection(),
r = document.createRange();
r.selectNodeContents(t),
n.removeAllRanges(),
n.addRange(r),
(e = n.toString());
}
return e;
}
e.exports = r;
},
{}
],
7: [
function(t, e, n) {
function r() {}
(r.prototype = {
on: function(t, e, n) {
var r = this.e || (this.e = {});
return (r[t] || (r[t] = [])).push({ fn: e, ctx: n }), this;
},
once: function(t, e, n) {
function r() {
o.off(t, r), e.apply(n, arguments);
}
var o = this;
return (r._ = e), this.on(t, r, n);
},
emit: function(t) {
var e = [].slice.call(arguments, 1),
n = ((this.e || (this.e = {}))[t] || []).slice(),
r = 0,
o = n.length;
for (r; o > r; r++) n[r].fn.apply(n[r].ctx, e);
return this;
},
off: function(t, e) {
var n = this.e || (this.e = {}),
r = n[t],
o = [];
if (r && e)
for (var i = 0, a = r.length; a > i; i++)
r[i].fn !== e && r[i].fn._ !== e && o.push(r[i]);
return o.length ? (n[t] = o) : delete n[t], this;
}
}),
(e.exports = r);
},
{}
],
8: [
function(t, e, n) {
"use strict";
function r(t) {
return t && t.__esModule ? t : { default: t };
}
function o(t, e) {
if (!(t instanceof e))
throw new TypeError("Cannot call a class as a function");
}
n.__esModule = !0;
var i = (function() {
function t(t, e) {
for (var n = 0; n < e.length; n++) {
var r = e[n];
(r.enumerable = r.enumerable || !1),
(r.configurable = !0),
"value" in r && (r.writable = !0),
Object.defineProperty(t, r.key, r);
}
}
return function(e, n, r) {
return n && t(e.prototype, n), r && t(e, r), e;
};
})(),
a = t("select"),
c = r(a),
s = (function() {
function t(e) {
o(this, t), this.resolveOptions(e), this.initSelection();
}
return (
(t.prototype.resolveOptions = function t() {
var e =
arguments.length <= 0 || void 0 === arguments[0]
? {}
: arguments[0];
(this.action = e.action),
(this.emitter = e.emitter),
(this.target = e.target),
(this.text = e.text),
(this.trigger = e.trigger),
(this.selectedText = "");
}),
(t.prototype.initSelection = function t() {
if (this.text && this.target)
throw new Error(
'Multiple attributes declared, use either "target" or "text"'
);
if (this.text) this.selectFake();
else {
if (!this.target)
throw new Error(
'Missing required attributes, use either "target" or "text"'
);
this.selectTarget();
}
}),
(t.prototype.selectFake = function t() {
var e = this;
this.removeFake(),
(this.fakeHandler = document.body.addEventListener(
"click",
function() {
return e.removeFake();
}
)),
(this.fakeElem = document.createElement("textarea")),
(this.fakeElem.style.position = "absolute"),
(this.fakeElem.style.left = "-9999px"),
(this.fakeElem.style.top =
(window.pageYOffset ||
document.documentElement.scrollTop) + "px"),
this.fakeElem.setAttribute("readonly", ""),
(this.fakeElem.value = this.text),
document.body.appendChild(this.fakeElem),
(this.selectedText = c.default(this.fakeElem)),
this.copyText();
}),
(t.prototype.removeFake = function t() {
this.fakeHandler &&
(document.body.removeEventListener("click"),
(this.fakeHandler = null)),
this.fakeElem &&
(document.body.removeChild(this.fakeElem),
(this.fakeElem = null));
}),
(t.prototype.selectTarget = function t() {
(this.selectedText = c.default(this.target)), this.copyText();
}),
(t.prototype.copyText = function t() {
var e = void 0;
try {
e = document.execCommand(this.action);
} catch (n) {
e = !1;
}
this.handleResult(e);
}),
(t.prototype.handleResult = function t(e) {
e
? this.emitter.emit("success", {
action: this.action,
text: this.selectedText,
trigger: this.trigger,
clearSelection: this.clearSelection.bind(this)
})
: this.emitter.emit("error", {
action: this.action,
trigger: this.trigger,
clearSelection: this.clearSelection.bind(this)
});
}),
(t.prototype.clearSelection = function t() {
this.target && this.target.blur(),
window.getSelection().removeAllRanges();
}),
(t.prototype.destroy = function t() {
this.removeFake();
}),
i(t, [
{
key: "action",
set: function t() {
var e =
arguments.length <= 0 || void 0 === arguments[0]
? "copy"
: arguments[0];
if (
((this._action = e),
"copy" !== this._action && "cut" !== this._action)
)
throw new Error(
'Invalid "action" value, use either "copy" or "cut"'
);
},
get: function t() {
return this._action;
}
},
{
key: "target",
set: function t(e) {
if (void 0 !== e) {
if (!e || "object" != typeof e || 1 !== e.nodeType)
throw new Error(
'Invalid "target" value, use a valid Element'
);
this._target = e;
}
},
get: function t() {
return this._target;
}
}
]),
t
);
})();
(n.default = s), (e.exports = n.default);
},
{ select: 6 }
],
9: [
function(t, e, n) {
"use strict";
function r(t) {
return t && t.__esModule ? t : { default: t };
}
function o(t, e) {
if (!(t instanceof e))
throw new TypeError("Cannot call a class as a function");
}
function i(t, e) {
if ("function" != typeof e && null !== e)
throw new TypeError(
"Super expression must either be null or a function, not " +
typeof e
);
(t.prototype = Object.create(e && e.prototype, {
constructor: {
value: t,
enumerable: !1,
writable: !0,
configurable: !0
}
})),
e &&
(Object.setPrototypeOf
? Object.setPrototypeOf(t, e)
: (t.__proto__ = e));
}
function a(t, e) {
var n = "data-clipboard-" + t;
if (e.hasAttribute(n)) return e.getAttribute(n);
}
n.__esModule = !0;
var c = t("./clipboard-action"),
s = r(c),
u = t("tiny-emitter"),
l = r(u),
f = t("good-listener"),
d = r(f),
h = (function(t) {
function e(n, r) {
o(this, e),
t.call(this),
this.resolveOptions(r),
this.listenClick(n);
}
return (
i(e, t),
(e.prototype.resolveOptions = function t() {
var e =
arguments.length <= 0 || void 0 === arguments[0]
? {}
: arguments[0];
(this.action =
"function" == typeof e.action
? e.action
: this.defaultAction),
(this.target =
"function" == typeof e.target
? e.target
: this.defaultTarget),
(this.text =
"function" == typeof e.text ? e.text : this.defaultText);
}),
(e.prototype.listenClick = function t(e) {
var n = this;
this.listener = d.default(e, "click", function(t) {
return n.onClick(t);
});
}),
(e.prototype.onClick = function t(e) {
this.clipboardAction && (this.clipboardAction = null),
(this.clipboardAction = new s.default({
action: this.action(e.target),
target: this.target(e.target),
text: this.text(e.target),
trigger: e.target,
emitter: this
}));
}),
(e.prototype.defaultAction = function t(e) {
return a("action", e);
}),
(e.prototype.defaultTarget = function t(e) {
var n = a("target", e);
return n ? document.querySelector(n) : void 0;
}),
(e.prototype.defaultText = function t(e) {
return a("text", e);
}),
(e.prototype.destroy = function t() {
this.listener.destroy(),
this.clipboardAction &&
(this.clipboardAction.destroy(),
(this.clipboardAction = null));
}),
e
);
})(l.default);
(n.default = h), (e.exports = n.default);
},
{ "./clipboard-action": 8, "good-listener": 5, "tiny-emitter": 7 }
]
},
{},
[9]
)(9);
});

View File

@@ -0,0 +1,753 @@
var r = null;
window.PR_SHOULD_USE_CONTINUATION = !0;
(function() {
function O(a) {
function i(d) {
var a = d.charCodeAt(0);
if (a !== 92) return a;
var f = d.charAt(1);
return (a = s[f])
? a
: "0" <= f && f <= "7"
? parseInt(d.substring(1), 8)
: f === "u" || f === "x"
? parseInt(d.substring(2), 16)
: d.charCodeAt(1);
}
function g(d) {
if (d < 32) return (d < 16 ? "\\x0" : "\\x") + d.toString(16);
d = String.fromCharCode(d);
return d === "\\" || d === "-" || d === "]" || d === "^" ? "\\" + d : d;
}
function j(d) {
var a = d
.substring(1, d.length - 1)
.match(
/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g
),
d = [],
f = a[0] === "^",
b = ["["];
f && b.push("^");
for (var f = f ? 1 : 0, c = a.length; f < c; ++f) {
var h = a[f];
if (/\\[bdsw]/i.test(h)) b.push(h);
else {
var h = i(h),
e;
f + 2 < c && "-" === a[f + 1]
? ((e = i(a[f + 2])), (f += 2))
: (e = h);
d.push([h, e]);
e < 65 ||
h > 122 ||
(e < 65 ||
h > 90 ||
d.push([Math.max(65, h) | 32, Math.min(e, 90) | 32]),
e < 97 ||
h > 122 ||
d.push([Math.max(97, h) & -33, Math.min(e, 122) & -33]));
}
}
d.sort(function(d, a) {
return d[0] - a[0] || a[1] - d[1];
});
a = [];
c = [];
for (f = 0; f < d.length; ++f)
(h = d[f]),
h[0] <= c[1] + 1 ? (c[1] = Math.max(c[1], h[1])) : a.push((c = h));
for (f = 0; f < a.length; ++f)
(h = a[f]),
b.push(g(h[0])),
h[1] > h[0] && (h[1] + 1 > h[0] && b.push("-"), b.push(g(h[1])));
b.push("]");
return b.join("");
}
function t(d) {
for (
var a = d.source.match(
/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g
),
b = a.length,
i = [],
c = 0,
h = 0;
c < b;
++c
) {
var e = a[c];
e === "("
? ++h
: "\\" === e.charAt(0) &&
(e = +e.substring(1)) &&
(e <= h ? (i[e] = -1) : (a[c] = g(e)));
}
for (c = 1; c < i.length; ++c) -1 === i[c] && (i[c] = ++z);
for (h = c = 0; c < b; ++c)
(e = a[c]),
e === "("
? (++h, i[h] || (a[c] = "(?:"))
: "\\" === e.charAt(0) &&
(e = +e.substring(1)) &&
e <= h &&
(a[c] = "\\" + i[e]);
for (c = 0; c < b; ++c) "^" === a[c] && "^" !== a[c + 1] && (a[c] = "");
if (d.ignoreCase && w)
for (c = 0; c < b; ++c)
(e = a[c]),
(d = e.charAt(0)),
e.length >= 2 && d === "["
? (a[c] = j(e))
: d !== "\\" &&
(a[c] = e.replace(/[A-Za-z]/g, function(d) {
d = d.charCodeAt(0);
return "[" + String.fromCharCode(d & -33, d | 32) + "]";
}));
return a.join("");
}
for (var z = 0, w = !1, k = !1, m = 0, b = a.length; m < b; ++m) {
var o = a[m];
if (o.ignoreCase) k = !0;
else if (
/[a-z]/i.test(
o.source.replace(/\\u[\da-f]{4}|\\x[\da-f]{2}|\\[^UXux]/gi, "")
)
) {
w = !0;
k = !1;
break;
}
}
for (
var s = {
b: 8,
t: 9,
n: 10,
v: 11,
f: 12,
r: 13
},
q = [],
m = 0,
b = a.length;
m < b;
++m
) {
o = a[m];
if (o.global || o.multiline) throw Error("" + o);
q.push("(?:" + t(o) + ")");
}
return RegExp(q.join("|"), k ? "gi" : "g");
}
function P(a, i) {
function g(a) {
switch (a.nodeType) {
case 1:
if (j.test(a.className)) break;
for (var b = a.firstChild; b; b = b.nextSibling) g(b);
b = a.nodeName.toLowerCase();
if ("br" === b || "li" === b)
(t[k] = "\n"), (w[k << 1] = z++), (w[(k++ << 1) | 1] = a);
break;
case 3:
case 4:
(b = a.nodeValue),
b.length &&
((b = i
? b.replace(/\r\n?/g, "\n")
: b.replace(/[\t\n\r ]+/g, " ")),
(t[k] = b),
(w[k << 1] = z),
(z += b.length),
(w[(k++ << 1) | 1] = a));
}
}
var j = /(?:^|\s)nocode(?:\s|$)/,
t = [],
z = 0,
w = [],
k = 0;
g(a);
return { a: t.join("").replace(/\n$/, ""), d: w };
}
function E(a, i, g, j) {
i && ((a = { a: i, e: a }), g(a), j.push.apply(j, a.g));
}
function x(a, i) {
function g(a) {
for (
var k = a.e,
m = [k, "pln"],
b = 0,
o = a.a.match(t) || [],
s = {},
q = 0,
d = o.length;
q < d;
++q
) {
var v = o[q],
f = s[v],
u = void 0,
c;
if (typeof f === "string") c = !1;
else {
var h = j[v.charAt(0)];
if (h) (u = v.match(h[1])), (f = h[0]);
else {
for (c = 0; c < z; ++c)
if (((h = i[c]), (u = v.match(h[1])))) {
f = h[0];
break;
}
u || (f = "pln");
}
if (
(c = f.length >= 5 && "lang-" === f.substring(0, 5)) &&
!(u && typeof u[1] === "string")
)
(c = !1), (f = "src");
c || (s[v] = f);
}
h = b;
b += v.length;
if (c) {
c = u[1];
var e = v.indexOf(c),
p = e + c.length;
u[2] && ((p = v.length - u[2].length), (e = p - c.length));
f = f.substring(5);
E(k + h, v.substring(0, e), g, m);
E(k + h + e, c, F(f, c), m);
E(k + h + p, v.substring(p), g, m);
} else m.push(k + h, f);
}
a.g = m;
}
var j = {},
t;
(function() {
for (
var g = a.concat(i), k = [], m = {}, b = 0, o = g.length;
b < o;
++b
) {
var s = g[b],
q = s[3];
if (q) for (var d = q.length; --d >= 0; ) j[q.charAt(d)] = s;
s = s[1];
q = "" + s;
m.hasOwnProperty(q) || (k.push(s), (m[q] = r));
}
k.push(/[\S\s]/);
t = O(k);
})();
var z = i.length;
return g;
}
function l(a) {
var i = [],
g = [];
a.tripleQuotedStrings
? i.push([
"str",
/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,
r,
"'\""
])
: a.multiLineStrings
? i.push([
"str",
/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/,
r,
"'\"`"
])
: i.push([
"str",
/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,
r,
"\"'"
]);
a.verbatimStrings && g.push(["str", /^@"(?:[^"]|"")*(?:"|$)/, r]);
var j = a.hashComments;
j &&
(a.cStyleComments
? (j > 1
? i.push(["com", /^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/, r, "#"])
: i.push([
"com",
/^#(?:(?:define|e(?:l|nd)if|else|error|ifn?def|include|line|pragma|undef|warning)\b|[^\n\r]*)/,
r,
"#"
]),
g.push([
"str",
/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h(?:h|pp|\+\+)?|[a-z]\w*)>/,
r
]))
: i.push(["com", /^#[^\n\r]*/, r, "#"]));
a.cStyleComments &&
(g.push(["com", /^\/\/[^\n\r]*/, r]),
g.push(["com", /^\/\*[\S\s]*?(?:\*\/|$)/, r]));
a.regexLiterals &&
g.push([
"lang-regex",
/^(?:^^\.?|[+-]|[!=]={0,2}|#|%=?|&&?=?|\(|\*=?|[+-]=|->|\/=?|::?|<<?=?|>{1,3}=?|[,;?@[{~]|\^\^?=?|\|\|?=?|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/
]);
(j = a.types) && g.push(["typ", j]);
a = ("" + a.keywords).replace(/^ | $/g, "");
a.length &&
g.push(["kwd", RegExp("^(?:" + a.replace(/[\s,]+/g, "|") + ")\\b"), r]);
i.push(["pln", /^\s+/, r, " \r\n\t\u00a0"]);
g.push(
["lit", /^@[$_a-z][\w$@]*/i, r],
["typ", /^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/, r],
["pln", /^[$_a-z][\w$@]*/i, r],
[
"lit",
/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,
r,
"0123456789"
],
["pln", /^\\[\S\s]?/, r],
["pun", /^.[^\s\w"$'./@\\`]*/, r]
);
return x(i, g);
}
function G(a, i, g) {
function j(a) {
switch (a.nodeType) {
case 1:
if (z.test(a.className)) break;
if ("br" === a.nodeName)
t(a), a.parentNode && a.parentNode.removeChild(a);
else for (a = a.firstChild; a; a = a.nextSibling) j(a);
break;
case 3:
case 4:
if (g) {
var b = a.nodeValue,
f = b.match(n);
if (f) {
var i = b.substring(0, f.index);
a.nodeValue = i;
(b = b.substring(f.index + f[0].length)) &&
a.parentNode.insertBefore(k.createTextNode(b), a.nextSibling);
t(a);
i || a.parentNode.removeChild(a);
}
}
}
}
function t(a) {
function i(a, b) {
var d = b ? a.cloneNode(!1) : a,
e = a.parentNode;
if (e) {
var e = i(e, 1),
f = a.nextSibling;
e.appendChild(d);
for (var g = f; g; g = f) (f = g.nextSibling), e.appendChild(g);
}
return d;
}
for (; !a.nextSibling; ) if (((a = a.parentNode), !a)) return;
for (
var a = i(a.nextSibling, 0), f;
(f = a.parentNode) && f.nodeType === 1;
)
a = f;
b.push(a);
}
for (
var z = /(?:^|\s)nocode(?:\s|$)/,
n = /\r\n?|\n/,
k = a.ownerDocument,
m = k.createElement("li");
a.firstChild;
)
m.appendChild(a.firstChild);
for (var b = [m], o = 0; o < b.length; ++o) j(b[o]);
i === (i | 0) && b[0].setAttribute("value", i);
var s = k.createElement("ol");
s.className = "linenums";
for (var i = Math.max(0, (i - 1) | 0) || 0, o = 0, q = b.length; o < q; ++o)
(m = b[o]),
(m.className = "L" + (o + i) % 10),
m.firstChild || m.appendChild(k.createTextNode("\u00a0")),
s.appendChild(m);
a.appendChild(s);
}
function n(a, i) {
for (var g = i.length; --g >= 0; ) {
var j = i[g];
A.hasOwnProperty(j)
? C.console && console.warn("cannot override language handler %s", j)
: (A[j] = a);
}
}
function F(a, i) {
if (!a || !A.hasOwnProperty(a))
a = /^\s*</.test(i) ? "default-markup" : "default-code";
return A[a];
}
function H(a) {
var i = a.h;
try {
var g = P(a.c, a.i),
j = g.a;
a.a = j;
a.d = g.d;
a.e = 0;
F(i, j)(a);
var t = /\bMSIE\s(\d+)/.exec(navigator.userAgent),
t = t && +t[1] <= 8,
i = /\n/g,
n = a.a,
w = n.length,
g = 0,
k = a.d,
m = k.length,
j = 0,
b = a.g,
o = b.length,
s = 0;
b[o] = w;
var q, d;
for (d = q = 0; d < o; )
b[d] !== b[d + 2] ? ((b[q++] = b[d++]), (b[q++] = b[d++])) : (d += 2);
o = q;
for (d = q = 0; d < o; ) {
for (
var v = b[d], f = b[d + 1], u = d + 2;
u + 2 <= o && b[u + 1] === f;
)
u += 2;
b[q++] = v;
b[q++] = f;
d = u;
}
b.length = q;
var c = a.c,
h;
if (c) (h = c.style.display), (c.style.display = "none");
try {
for (; j < m; ) {
var e = k[j + 2] || w,
p = b[s + 2] || w,
u = Math.min(e, p),
l = k[j + 1],
D;
if (l.nodeType !== 1 && (D = n.substring(g, u))) {
t && (D = D.replace(i, "\r"));
l.nodeValue = D;
var y = l.ownerDocument,
x = y.createElement("span");
x.className = b[s + 1];
var B = l.parentNode;
B.replaceChild(x, l);
x.appendChild(l);
g < e &&
((k[j + 1] = l = y.createTextNode(n.substring(u, e))),
B.insertBefore(l, x.nextSibling));
}
g = u;
g >= e && (j += 2);
g >= p && (s += 2);
}
} finally {
if (c) c.style.display = h;
}
} catch (A) {
C.console && console.log(A && A.stack ? A.stack : A);
}
}
var C = window,
y = ["break,continue,do,else,for,if,return,while"],
B = [
[
y,
"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"
],
"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"
],
I = [
B,
"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"
],
J = [
B,
"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"
],
K = [
J,
"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,let,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var,virtual,where"
],
B = [
B,
"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"
],
L = [
y,
"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"
],
M = [
y,
"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"
],
y = [y, "case,done,elif,esac,eval,fi,function,in,local,set,then,until"],
N = /^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)\b/,
Q = /\S/,
R = l({
keywords: [
I,
K,
B,
"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END" +
L,
M,
y
],
hashComments: !0,
cStyleComments: !0,
multiLineStrings: !0,
regexLiterals: !0
}),
A = {};
n(R, ["default-code"]);
n(
x(
[],
[
["pln", /^[^<?]+/],
["dec", /^<!\w[^>]*(?:>|$)/],
["com", /^<\!--[\S\s]*?(?:--\>|$)/],
["lang-", /^<\?([\S\s]+?)(?:\?>|$)/],
["lang-", /^<%([\S\s]+?)(?:%>|$)/],
["pun", /^(?:<[%?]|[%?]>)/],
["lang-", /^<xmp\b[^>]*>([\S\s]+?)<\/xmp\b[^>]*>/i],
["lang-js", /^<script\b[^>]*>([\S\s]*?)(<\/script\b[^>]*>)/i],
["lang-css", /^<style\b[^>]*>([\S\s]*?)(<\/style\b[^>]*>)/i],
["lang-in.tag", /^(<\/?[a-z][^<>]*>)/i]
]
),
["default-markup", "htm", "html", "mxml", "xhtml", "xml", "xsl"]
);
n(
x(
[
["pln", /^\s+/, r, " \t\r\n"],
["atv", /^(?:"[^"]*"?|'[^']*'?)/, r, "\"'"]
],
[
["tag", /^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],
["atn", /^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],
["lang-uq.val", /^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],
["pun", /^[/<->]+/],
["lang-js", /^on\w+\s*=\s*"([^"]+)"/i],
["lang-js", /^on\w+\s*=\s*'([^']+)'/i],
["lang-js", /^on\w+\s*=\s*([^\s"'>]+)/i],
["lang-css", /^style\s*=\s*"([^"]+)"/i],
["lang-css", /^style\s*=\s*'([^']+)'/i],
["lang-css", /^style\s*=\s*([^\s"'>]+)/i]
]
),
["in.tag"]
);
n(x([], [["atv", /^[\S\s]+/]]), ["uq.val"]);
n(l({ keywords: I, hashComments: !0, cStyleComments: !0, types: N }), [
"c",
"cc",
"cpp",
"cxx",
"cyc",
"m"
]);
n(l({ keywords: "null,true,false" }), ["json"]);
n(
l({
keywords: K,
hashComments: !0,
cStyleComments: !0,
verbatimStrings: !0,
types: N
}),
["cs"]
);
n(l({ keywords: J, cStyleComments: !0 }), ["java"]);
n(l({ keywords: y, hashComments: !0, multiLineStrings: !0 }), [
"bsh",
"csh",
"sh"
]);
n(
l({
keywords: L,
hashComments: !0,
multiLineStrings: !0,
tripleQuotedStrings: !0
}),
["cv", "py"]
);
n(
l({
keywords:
"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",
hashComments: !0,
multiLineStrings: !0,
regexLiterals: !0
}),
["perl", "pl", "pm"]
);
n(
l({
keywords: M,
hashComments: !0,
multiLineStrings: !0,
regexLiterals: !0
}),
["rb"]
);
n(l({ keywords: B, cStyleComments: !0, regexLiterals: !0 }), ["js"]);
n(
l({
keywords:
"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,throw,true,try,unless,until,when,while,yes",
hashComments: 3,
cStyleComments: !0,
multilineStrings: !0,
tripleQuotedStrings: !0,
regexLiterals: !0
}),
["coffee"]
);
n(x([], [["str", /^[\S\s]+/]]), ["regex"]);
var S = (C.PR = {
createSimpleLexer: x,
registerLangHandler: n,
sourceDecorator: l,
PR_ATTRIB_NAME: "atn",
PR_ATTRIB_VALUE: "atv",
PR_COMMENT: "com",
PR_DECLARATION: "dec",
PR_KEYWORD: "kwd",
PR_LITERAL: "lit",
PR_NOCODE: "nocode",
PR_PLAIN: "pln",
PR_PUNCTUATION: "pun",
PR_SOURCE: "src",
PR_STRING: "str",
PR_TAG: "tag",
PR_TYPE: "typ",
prettyPrintOne: (C.prettyPrintOne = function(a, i, g) {
var j = document.createElement("pre");
j.innerHTML = a;
g && G(j, g, !0);
H({ h: i, j: g, c: j, i: 1 });
return j.innerHTML;
}),
prettyPrint: (C.prettyPrint = function(a) {
function i() {
var u;
for (
var g = C.PR_SHOULD_USE_CONTINUATION ? k.now() + 250 : Infinity;
m < j.length && k.now() < g;
m++
) {
var c = j[m],
h = c.className;
if (s.test(h) && !q.test(h)) {
for (var e = !1, p = c.parentNode; p; p = p.parentNode)
if (f.test(p.tagName) && p.className && s.test(p.className)) {
e = !0;
break;
}
if (!e) {
c.className += " prettyprinted";
var h = h.match(o),
n;
if ((e = !h)) {
for (
var e = c, p = void 0, l = e.firstChild;
l;
l = l.nextSibling
)
var t = l.nodeType,
p =
t === 1
? p
? e
: l
: t === 3
? Q.test(l.nodeValue)
? e
: p
: p;
e = (n = p === e ? void 0 : p) && v.test(n.tagName);
}
e && (h = n.className.match(o));
h && (h = h[1]);
(u = d.test(c.tagName)
? 1
: (e = (e = c.currentStyle)
? e.whiteSpace
: document.defaultView &&
document.defaultView.getComputedStyle
? document.defaultView
.getComputedStyle(c, r)
.getPropertyValue("white-space")
: 0) && "pre" === e.substring(0, 3)),
(e = u);
(p = (p = c.className.match(/\blinenums\b(?::(\d+))?/))
? p[1] && p[1].length
? +p[1]
: !0
: !1) && G(c, p, e);
b = { h: h, c: c, j: p, i: e };
H(b);
}
}
}
m < j.length ? setTimeout(i, 250) : a && a();
}
for (
var g = [
document.getElementsByTagName("pre"),
document.getElementsByTagName("code"),
document.getElementsByTagName("xmp")
],
j = [],
n = 0;
n < g.length;
++n
)
for (var l = 0, w = g[n].length; l < w; ++l) j.push(g[n][l]);
var g = r,
k = Date;
k.now ||
(k = {
now: function() {
return +new Date();
}
});
var m = 0,
b,
o = /\blang(?:uage)?-([\w.]+)(?!\S)/,
s = /\bprettyprint\b/,
q = /\bprettyprinted\b/,
d = /pre|xmp/i,
v = /^code$/i,
f = /^(?:pre|code|xmp)$/i;
i();
})
});
typeof define === "function" &&
define.amd &&
define("google-code-prettify", [], function() {
return S;
});
})();

View File

@@ -0,0 +1,208 @@
Zepto(function($) {
var $leftPanel = $(".left-panel");
var $frameContainer = $(".frames-container");
var $appFramesTab = $("#application-frames-tab");
var $allFramesTab = $("#all-frames-tab");
var $container = $(".details-container");
var $activeLine = $frameContainer.find(".frame.active");
var $activeFrame = $container.find(".frame-code.active");
var $ajaxEditors = $(".editor-link[data-ajax]");
var $header = $("header");
$header.on("mouseenter", function() {
if ($header.find(".exception").height() >= 145) {
$header.addClass("header-expand");
}
});
$header.on("mouseleave", function() {
$header.removeClass("header-expand");
});
/*
* add prettyprint classes to our current active codeblock
* run prettyPrint() to highlight the active code
* scroll to the line when prettyprint is done
* highlight the current line
*/
var renderCurrentCodeblock = function(id) {
// remove previous codeblocks so we only render the active one
$(".code-block").removeClass("prettyprint");
// pass the id in when we can for speed
if (typeof id === "undefined" || typeof id === "object") {
var id = /frame\-line\-([\d]*)/.exec($activeLine.attr("id"))[1];
}
$("#frame-code-linenums-" + id).addClass("prettyprint");
$("#frame-code-args-" + id).addClass("prettyprint");
prettyPrint(highlightCurrentLine);
};
/*
* Highlight the active and neighboring lines for the current frame
* Adjust the offset to make sure that line is veritcally centered
*/
var highlightCurrentLine = function() {
var activeLineNumber = +$activeLine.find(".frame-line").text();
var $lines = $activeFrame.find(".linenums li");
var firstLine = +$lines.first().val();
// We show more code than needed, purely for proper syntax highlighting
// Lets hide a big chunk of that code and then scroll the remaining block
$activeFrame
.find(".code-block")
.first()
.css({
maxHeight: 345,
overflow: "hidden"
});
var $offset = $($lines[activeLineNumber - firstLine - 10]);
if ($offset.length > 0) {
$offset[0].scrollIntoView();
}
$($lines[activeLineNumber - firstLine - 1]).addClass("current");
$($lines[activeLineNumber - firstLine]).addClass("current active");
$($lines[activeLineNumber - firstLine + 1]).addClass("current");
$container.scrollTop(0);
};
/*
* click handler for loading codeblocks
*/
$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;
renderCurrentCodeblock(id);
}
});
var clipboard = new Clipboard(".clipboard");
var showTooltip = function(elem, msg) {
elem.setAttribute("class", "clipboard tooltipped tooltipped-s");
elem.setAttribute("aria-label", msg);
};
clipboard.on("success", function(e) {
e.clearSelection();
showTooltip(e.trigger, "Copied!");
});
clipboard.on("error", function(e) {
showTooltip(e.trigger, fallbackMessage(e.action));
});
var btn = document.querySelector(".clipboard");
btn.addEventListener("mouseleave", function(e) {
e.currentTarget.setAttribute("class", "clipboard");
e.currentTarget.removeAttribute("aria-label");
});
function fallbackMessage(action) {
var actionMsg = "";
var actionKey = action === "cut" ? "X" : "C";
if (/Mac/i.test(navigator.userAgent)) {
actionMsg = "Press ⌘-" + actionKey + " to " + action;
} else {
actionMsg = "Press Ctrl-" + actionKey + " to " + action;
}
return actionMsg;
}
function scrollIntoView($node, $parent) {
var nodeOffset = $node.offset();
var nodeTop = nodeOffset.top;
var nodeBottom = nodeTop + nodeOffset.height;
var parentScrollTop = $parent.scrollTop();
var parentHeight = $parent.height();
if (nodeTop < 0) {
$parent.scrollTop(parentScrollTop + nodeTop);
} else if (nodeBottom > parentHeight) {
$parent.scrollTop(parentScrollTop + nodeBottom - parentHeight);
}
}
$(document).on("keydown", function(e) {
var applicationFrames = $frameContainer.hasClass(
"frames-container-application"
),
frameClass = applicationFrames ? ".frame.frame-application" : ".frame";
if (e.ctrlKey || e.which === 74 || e.which === 75) {
// CTRL+Arrow-UP/k and Arrow-Down/j 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 */ || e.which === 75 /* k */) {
$activeLine.prev(frameClass).click();
scrollIntoView($activeLine, $leftPanel);
$container.focus();
e.preventDefault();
} else if (e.which === 40 /* arrow down */ || e.which === 74 /* j */) {
$activeLine.next(frameClass).click();
scrollIntoView($activeLine, $leftPanel);
$container.focus();
e.preventDefault();
}
} else if (e.which == 78 /* n */) {
if ($appFramesTab.length) {
setActiveFramesTab($(".frames-tab:not(.frames-tab-active)"));
}
}
});
// Render late enough for highlightCurrentLine to be ready
renderCurrentCodeblock();
// Avoid to quit the page with some protocol (e.g. IntelliJ Platform REST API)
$ajaxEditors.on("click", function(e) {
e.preventDefault();
$.get(this.href);
});
// Symfony VarDumper: Close the by default expanded objects
$(".sf-dump-expanded")
.removeClass("sf-dump-expanded")
.addClass("sf-dump-compact");
$(".sf-dump-toggle span").html("&#9654;");
// Make the given frames-tab active
function setActiveFramesTab($tab) {
$tab.addClass("frames-tab-active");
if ($tab.attr("id") == "application-frames-tab") {
$frameContainer.addClass("frames-container-application");
$allFramesTab.removeClass("frames-tab-active");
} else {
$frameContainer.removeClass("frames-container-application");
$appFramesTab.removeClass("frames-tab-active");
}
}
$("a.frames-tab").on("click", function(e) {
e.preventDefault();
setActiveFramesTab($(this));
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,42 @@
<?php /* List data-table values, i.e: $_SERVER, $_GET, .... */ ?>
<div class="details">
<h2 class="details-heading">Environment &amp; details:</h2>
<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->dump($value) ?></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,63 @@
<?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)): ?>
<a href="<?php echo $editorHref ?>" class="editor-link"<?php echo ($handler->getEditorAjax($filePath, (int) $line) ? ' data-ajax' : '') ?>>
Open:
<strong><?php echo $tpl->breakOnDelimiter('/', $tpl->escape($filePath ?: '<#unknown>')) ?></strong>
</a>
<?php else: ?>
<strong><?php echo $tpl->breakOnDelimiter('/', $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 - 20, 40);
// 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 id="frame-code-linenums-<?=$i?>" class="code-block linenums:<?php echo $start ?>"><?php echo $tpl->escape($code) ?></pre>
<?php endif ?>
<?php endif ?>
<?php $frameArgs = $tpl->dumpArgs($frame); ?>
<?php if ($frameArgs): ?>
<div class="frame-file">
Arguments
</div>
<div id="frame-code-args-<?=$i?>" class="code-block frame-args">
<?php echo $frameArgs; ?>
</div>
<?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' : '') ?> <?php echo ($frame->isApplication() ? 'frame-application' : '') ?>" id="frame-line-<?php echo $i ?>">
<span class="frame-index"><?php echo (count($frames) - $i - 1) ?></span>
<div class="frame-method-info">
<span class="frame-class"><?php echo $tpl->breakOnDelimiter('\\', $tpl->escape($frame->getClass() ?: '')) ?></span>
<span class="frame-function"><?php echo $tpl->breakOnDelimiter('\\', $tpl->escape($frame->getFunction() ?: '')) ?></span>
</div>
<div class="frame-file">
<?php echo $frame->getFile() ? $tpl->breakOnDelimiter('/', $tpl->shorten($tpl->escape($frame->getFile()))) : '<#unknown>' ?><!--
--><span class="frame-line"><?php echo (int) $frame->getLine() ?></span>
</div>
</div>
<?php endforeach;

View File

@@ -0,0 +1,3 @@
<div class="frames-container <?php echo $active_frames_tab == 'application' ? 'frames-container-application' : '' ?>">
<?php $tpl->render($frame_list) ?>
</div>

View File

@@ -0,0 +1,20 @@
<div class="frames-description <?php echo $has_frames_tabs ? 'frames-description-application' : '' ?>">
<?php if ($has_frames_tabs): ?>
<?php if ($active_frames_tab == 'application'): ?>
<span href="#" id="application-frames-tab" class="frames-tab frames-tab-active">
Application frames (<?php echo $frames->countIsApplication() ?>)
</span>
<?php else: ?>
<a href="#" id="application-frames-tab" class="frames-tab">
Application frames (<?php echo $frames->countIsApplication() ?>)
</a>
<?php endif; ?>
<a href="#" id="all-frames-tab" class="frames-tab <?php echo $active_frames_tab == 'all' ? 'frames-tab-active' : '' ?>">
All frames (<?php echo count($frames) ?>)
</a>
<?php else: ?>
<span>
Stack frames (<?php echo count($frames) ?>)
</span>
<?php endif; ?>
</div>

View File

@@ -0,0 +1,93 @@
<div class="exception">
<div 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 ?>
</div>
<div class="exc-message">
<?php if (!empty($message)): ?>
<span><?php echo $tpl->escape($message) ?></span>
<?php if (count($previousMessages)): ?>
<div class="exc-title prev-exc-title">
<span class="exc-title-secondary">Previous exceptions</span>
</div>
<ul>
<?php foreach ($previousMessages as $i => $previousMessage): ?>
<li>
<?php echo $tpl->escape($previousMessage) ?>
<span class="prev-exc-code">(<?php echo $previousCodes[$i] ?>)</span>
</li>
<?php endforeach; ?>
</ul>
<?php endif ?>
<?php else: ?>
<span class="exc-message-empty-notice">No message</span>
<?php endif ?>
<ul class="search-for-help">
<?php if (!empty($docref_url)): ?>
<li>
<a rel="noopener noreferrer" target="_blank" href="<?php echo $docref_url; ?>" title="Search for help in the PHP manual.">
<!-- PHP icon by Icons Solid -->
<!-- https://www.iconfinder.com/icons/322421/book_icon -->
<!-- Free for commercial use -->
<svg height="16px" id="Layer_1" style="enable-background:new 0 0 32 32;" version="1.1" viewBox="0 0 32 32" width="16px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g transform="translate(240 0)"><path d="M-211,4v26h-24c-1.104,0-2-0.895-2-2s0.896-2,2-2h22V0h-22c-2.209,0-4,1.791-4,4v24c0,2.209,1.791,4,4,4h26V4H-211z M-235,8V2h20v22h-20V8z M-219,6h-12V4h12V6z M-223,10h-8V8h8V10z M-227,14h-4v-2h4V14z"/></g></svg>
</a>
</li>
<?php endif ?>
<li>
<a rel="noopener noreferrer" target="_blank" href="https://google.com/search?q=<?php echo urlencode(implode('\\', $name).' '.$message) ?>" title="Search for help on Google.">
<!-- Google icon by Alfredo H, from https://www.iconfinder.com/alfredoh -->
<!-- Creative Commons (Attribution 3.0 Unported) -->
<!-- http://creativecommons.org/licenses/by/3.0/ -->
<svg class="google" height="16" viewBox="0 0 512 512" width="16" xmlns="http://www.w3.org/2000/svg">
<path d="M457.732 216.625c2.628 14.04 4.063 28.743 4.063 44.098C461.795 380.688 381.48 466 260.205 466c-116.024 0-210-93.977-210-210s93.976-210 210-210c56.703 0 104.076 20.867 140.44 54.73l-59.205 59.197v-.135c-22.046-21.002-50-31.762-81.236-31.762-69.297 0-125.604 58.537-125.604 127.84 0 69.29 56.306 127.97 125.604 127.97 62.87 0 105.653-35.966 114.46-85.313h-114.46v-81.902h197.528z"/>
</svg>
</a>
</li>
<li>
<a rel="noopener noreferrer" target="_blank" href="https://duckduckgo.com/?q=<?php echo urlencode(implode('\\', $name).' '.$message) ?>" title="Search for help on DuckDuckGo.">
<!-- DuckDuckGo icon by IconBaandar Team, from https://www.iconfinder.com/iconbaandar -->
<!-- Creative Commons (Attribution 3.0 Unported) -->
<!-- http://creativecommons.org/licenses/by/3.0/ -->
<svg class="duckduckgo" height="16" viewBox="150 150 1675 1675" width="16" xmlns="http://www.w3.org/2000/svg">
<path d="M1792 1024c0 204.364-80.472 398.56-224.955 543.04-144.483 144.48-338.68 224.95-543.044 224.95-204.36 0-398.56-80.47-543.04-224.95-144.48-144.482-224.95-338.676-224.95-543.04 0-204.365 80.47-398.562 224.96-543.045C625.44 336.47 819.64 256 1024 256c204.367 0 398.565 80.47 543.05 224.954C1711.532 625.437 1792 819.634 1792 1024zm-270.206 497.787C1654.256 1389.327 1728 1211.36 1728 1024c0-187.363-73.74-365.332-206.203-497.796C1389.332 393.74 1211.363 320 1024 320s-365.33 73.742-497.795 206.205C393.742 658.67 320 836.637 320 1024c0 187.36 73.744 365.326 206.206 497.787C658.67 1654.25 836.638 1727.99 1024 1727.99c187.362 0 365.33-73.74 497.794-206.203z"/>
<path d="M1438.64 1177.41c0-.03-.005-.017-.01.004l.01-.004z"/>
<path d="M1499.8 976.878c.03-.156-.024-.048-.11.107l.11-.107z"/>
<path d="M1105.19 991.642zm-68.013-376.128c-8.087-10.14-18.028-19.965-29.89-29.408-13.29-10.582-29-20.76-47.223-30.443-35.07-18.624-74.482-31.61-115.265-38.046-39.78-6.28-80.84-6.256-120.39.917l1.37 31.562c1.8.164 7.7 3.9 14.36 8.32-20.68 5.94-39.77 14.447-39.48 39.683l.2 17.48 17.3-1.73c29.38-2.95 60.17-2.06 90.32 2.61 9.21 1.42 18.36 3.2 27.38 5.32l-4.33 1.15c-20.45 5.58-38.93 12.52-54.25 20.61-46.28 24.32-75.51 60.85-90.14 108.37-14.14 45.95-14.27 101.81-2.72 166.51l.06.06c15.14 84.57 64.16 316.39 104.11 505.39 19.78 93.59 37.38 176.83 47.14 224.4 3.26 15.84 5.03 31.02 5.52 45.52.3 9.08.09 17.96-.58 26.62-.45 5.8-1.11 11.51-1.96 17.112l31.62 4.75c.71-4.705 1.3-9.494 1.76-14.373 48.964 10.517 99.78 16.05 151.88 16.05 60.68 0 119.61-7.505 175.91-21.64 3.04 6.08 6.08 12.19 9.11 18.32l28.62-14.128c-2.11-4.27-4.235-8.55-6.37-12.84-23.005-46.124-47.498-93.01-68.67-133.534-15.39-29.466-29.01-55.53-39.046-75.58-26.826-53.618-53.637-119.47-68.28-182.368-8.78-37.705-13.128-74.098-10.308-105.627-15.31-6.28-26.69-11.8-31.968-15.59l-.01.015c-14.22-10.2-31.11-28.12-41.82-49.717-8.618-17.376-13.4-37.246-10.147-57.84 3.17-19.84 27.334-46.714 57.843-67.46v-.063c26.554-18.05 58.75-32.506 86.32-34.31 7.835-.51 16.31-1.008 23.99-1.45 33.45-1.95 50.243-2.93 84.475-11.42 10.88-2.697 26.19-6.56 43.53-11.09 2.364-40.7-5.947-87.596-21.04-133.234-22.004-66.53-58.68-131.25-97.627-170.21-12.543-12.55-28.17-22.79-45.9-30.933-16.88-7.753-35.64-13.615-55.436-17.782zm-10.658 178.553s6.77-42.485 58.39-33.977c27.96 4.654 37.89 29.833 37.89 29.833s-25.31-14.46-44.95-14.198c-40.33.53-51.35 18.342-51.35 18.342zm-240.45-18.802c48.49-19.853 72.11 11.298 72.11 11.298s-35.21-15.928-69.46 5.59c-34.19 21.477-32.92 43.452-32.92 43.452s-18.17-40.5 30.26-60.34zm296.5 95.4c0-6.677 2.68-12.694 7.01-17.02 4.37-4.37 10.42-7.074 17.1-7.074 6.73 0 12.79 2.7 17.15 7.05 4.33 4.33 7.01 10.36 7.01 17.05 0 6.74-2.7 12.81-7.07 17.18-4.33 4.33-10.37 7.01-17.1 7.01-6.68 0-12.72-2.69-17.05-7.03-4.36-4.37-7.07-10.43-7.07-17.16zm-268.42 51.27c0-8.535 3.41-16.22 8.93-21.738 5.55-5.55 13.25-8.982 21.81-8.982 8.51 0 16.18 3.415 21.7 8.934 5.55 5.55 8.98 13.25 8.98 21.78 0 8.53-3.44 16.23-8.98 21.79-5.52 5.52-13.19 8.93-21.71 8.93-8.55 0-16.26-3.43-21.82-8.99-5.52-5.52-8.93-13.2-8.93-21.74z"/>
<path d="M1102.48 986.34zm390.074-64.347c-28.917-11.34-74.89-12.68-93.32-3.778-11.5 5.567-35.743 13.483-63.565 21.707-25.75 7.606-53.9 15.296-78.15 21.702-17.69 4.67-33.3 8.66-44.4 11.435-34.92 8.76-52.05 9.77-86.17 11.78-7.84.46-16.48.97-24.48 1.5-28.12 1.86-60.97 16.77-88.05 35.4v.06c-31.12 21.4-55.77 49.12-59.01 69.59-3.32 21.24 1.56 41.74 10.35 59.67 10.92 22.28 28.15 40.77 42.66 51.29l.01-.02c5.38 3.9 16.98 9.6 32.6 16.08 26.03 10.79 63.2 23.76 101.25 34.23 43.6 11.99 89.11 21.05 121.69 20.41 34.26-.69 77.73-10.52 114.54-24.67 22.15-8.52 42.21-18.71 56.88-29.58 17.85-13.22 28.7-28.42 28.4-44.74-.07-3.89-.72-7.63-1.97-11.21l-.02.01c-11.6-33.06-50.37-23.59-105.53-10.12-46.86 11.445-107.94 26.365-169.01 20.434-32.56-3.167-54.45-10.61-67.88-20.133-5.96-4.224-9.93-8.67-12.18-13.11-1.96-3.865-2.68-7.84-2.33-11.714.39-4.42 2.17-9.048 5.1-13.57l-.05-.03c7.86-12.118 23.082-9.72 43.93-6.43 25.91 4.08 58.2 9.172 99.013-3.61 39.63-12.378 87.76-29.9 131.184-47.39 42.405-17.08 80.08-34.078 100.74-46.18 25.46-14.87 37.57-29.428 40.59-42.866 2.725-12.152-.89-22.48-8.903-31.07-5.87-6.29-14.254-11.31-23.956-15.115z"/>
</svg>
</a>
</li>
<li>
<a rel="noopener noreferrer" target="_blank" href="https://stackoverflow.com/search?q=<?php echo urlencode(implode('\\', $name).' '.$message) ?>" title="Search for help on Stack Overflow.">
<!-- Stack Overflow icon by Picons.me, from https://www.iconfinder.com/Picons -->
<!-- Free for commercial use -->
<svg class="stackoverflow" height="16" viewBox="-1163 1657.697 56.693 56.693" width="16" xmlns="http://www.w3.org/2000/svg">
<path d="M-1126.04 1689.533l-16.577-9.778 2.088-3.54 16.578 9.778zM-1127.386 1694.635l-18.586-4.996 1.068-3.97 18.586 4.995zM-1127.824 1700.137l-19.165-1.767.378-4.093 19.165 1.767zM-1147.263 1701.293h19.247v4.11h-19.247z"/>
<path d="M-1121.458 1710.947s0 .96-.032.96v.016h-30.796s-.96 0-.96-.016h-.032v-20.03h3.288v16.805h25.244v-16.804h3.288v19.07zM-1130.667 1667.04l10.844 15.903-3.396 2.316-10.843-15.903zM-1118.313 1663.044l3.29 18.963-4.05.703-3.29-18.963z"/>
</svg>
</a>
</li>
</ul>
<span id="plain-exception"><?php echo $tpl->escape($plain_exception) ?></span>
<button id="copy-button" class="clipboard" data-clipboard-text="<?php echo $tpl->escape($plain_exception) ?>" title="Copy exception details to clipboard">
COPY
</button>
</div>
</div>

View File

@@ -0,0 +1,3 @@
<header>
<?php $tpl->render($header) ?>
</header>

View File

@@ -0,0 +1,33 @@
<?php
/**
* Layout template file for Whoops's pretty error output.
*/
?>
<!DOCTYPE html><?php echo $preface; ?>
<html>
<head>
<meta charset="utf-8">
<meta name="robots" content="noindex,nofollow"/>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
<title><?php echo $tpl->escape($page_title) ?></title>
<style><?php echo $stylesheet ?></style>
</head>
<body>
<div class="Whoops container">
<div class="stack-container">
<?php $tpl->render($panel_left_outer) ?>
<?php $tpl->render($panel_details_outer) ?>
</div>
</div>
<script><?php echo $prettify ?></script>
<script><?php echo $zepto ?></script>
<script><?php echo $clipboard ?></script>
<script><?php echo $javascript ?></script>
</body>
</html>

View File

@@ -0,0 +1,2 @@
<?php $tpl->render($frame_code) ?>
<?php $tpl->render($env_details) ?>

View File

@@ -0,0 +1,3 @@
<div class="panel details-container cf">
<?php $tpl->render($panel_details) ?>
</div>

View File

@@ -0,0 +1,4 @@
<?php
$tpl->render($header_outer);
$tpl->render($frames_description);
$tpl->render($frames_container);

View File

@@ -0,0 +1,3 @@
<div class="panel left-panel cf <?php echo (!$has_frames ? 'empty' : '') ?>">
<?php $tpl->render($panel_left) ?>
</div>

410
kirby/vendor/filp/whoops/src/Whoops/Run.php vendored Executable file
View File

@@ -0,0 +1,410 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops;
use InvalidArgumentException;
use Whoops\Exception\ErrorException;
use Whoops\Exception\Inspector;
use Whoops\Handler\CallbackHandler;
use Whoops\Handler\Handler;
use Whoops\Handler\HandlerInterface;
use Whoops\Util\Misc;
use Whoops\Util\SystemFacade;
final class Run implements RunInterface
{
private $isRegistered;
private $allowQuit = true;
private $sendOutput = true;
/**
* @var integer|false
*/
private $sendHttpCode = 500;
/**
* @var HandlerInterface[]
*/
private $handlerStack = [];
private $silencedPatterns = [];
private $system;
public function __construct(SystemFacade $system = null)
{
$this->system = $system ?: new SystemFacade;
}
/**
* 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 = [];
return $this;
}
/**
* @param \Throwable $exception
* @return Inspector
*/
private function getInspector($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");
$this->system->setErrorHandler([$this, self::ERROR_HANDLER]);
$this->system->setExceptionHandler([$this, self::EXCEPTION_HANDLER]);
$this->system->registerShutdownFunction([$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) {
$this->system->restoreExceptionHandler();
$this->system->restoreErrorHandler();
$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 [
"pattern" => $pattern,
"levels" => $levels,
];
},
(array) $patterns
)
);
return $this;
}
/**
* Returns an array with silent errors in path configuration
*
* @return array
*/
public function getSilenceErrorsInPaths()
{
return $this->silencedPatterns;
}
/*
* 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 \Throwable $exception
* @return string Output generated by handlers
*/
public function handleException($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.
$this->system->startOutputBuffering();
// Just in case there are no handlers:
$handlerResponse = null;
$handlerContentType = 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);
// Collect the content type for possible sending in the headers.
$handlerContentType = method_exists($handler, 'contentType') ? $handler->contentType() : null;
if (in_array($handlerResponse, [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 = $this->system->cleanOutputBuffer();
// 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 ($willQuit) {
// Cleanup all other output buffers before sending our output:
while ($this->system->getOutputBufferLevel() > 0) {
$this->system->endOutputBuffering();
}
// Send any headers if needed:
if (Misc::canSendHeaders() && $handlerContentType) {
header("Content-Type: {$handlerContentType}");
}
}
$this->writeToOutputNow($output);
}
if ($willQuit) {
// HHVM fix for https://github.com/facebook/hhvm/issues/4055
$this->system->flushOutputBuffer();
$this->system->stopExecution(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 & $this->system->getErrorReportingLevel()) {
foreach ($this->silencedPatterns as $entry) {
$pathMatches = (bool) preg_match($entry["pattern"], $file);
$levelMatches = $level & $entry["levels"];
if ($pathMatches && $levelMatches) {
// Ignore the error, abort handling
// See https://github.com/filp/whoops/issues/418
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 = $this->system->getLastError();
if ($error && Misc::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()) {
$this->system->setHttpResponseCode(
$this->sendHttpCode()
);
}
echo $output;
return $this;
}
}

View File

@@ -0,0 +1,131 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops;
use InvalidArgumentException;
use Whoops\Exception\ErrorException;
use Whoops\Handler\HandlerInterface;
interface RunInterface
{
const EXCEPTION_HANDLER = "handleException";
const ERROR_HANDLER = "handleError";
const SHUTDOWN_HANDLER = "handleShutdown";
/**
* 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);
/**
* 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();
/**
* Returns an array with all handlers, in the
* order they were added to the stack.
*
* @return array
*/
public function getHandlers();
/**
* Clears all handlers in the handlerStack, including
* the default PrettyPage handler.
*
* @return Run
*/
public function clearHandlers();
/**
* Registers this instance as an error handler.
*
* @return Run
*/
public function register();
/**
* Unregisters all handlers registered by this Whoops\Run instance
*
* @return Run
*/
public function unregister();
/**
* Should Whoops allow Handlers to force the script to quit?
*
* @param bool|int $exit
* @return bool
*/
public function allowQuit($exit = null);
/**
* 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);
/**
* 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);
/**
* 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);
/**
* Handles an exception, ultimately generating a Whoops error
* page.
*
* @param \Throwable $exception
* @return string Output generated by handlers
*/
public function handleException($exception);
/**
* 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);
/**
* Special case to deal with Fatal errors and the like.
*/
public function handleShutdown();
}

View File

@@ -0,0 +1,36 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Util;
/**
* Used as output callable for Symfony\Component\VarDumper\Dumper\HtmlDumper::dump()
*
* @see TemplateHelper::dump()
*/
class HtmlDumperOutput
{
private $output;
public function __invoke($line, $depth)
{
// A negative depth means "end of dump"
if ($depth >= 0) {
// Adds a two spaces indentation to the line
$this->output .= str_repeat(' ', $depth) . $line . "\n";
}
}
public function getOutput()
{
return $this->output;
}
public function clear()
{
$this->output = null;
}
}

View File

@@ -0,0 +1,77 @@
<?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();
}
public static function isAjaxRequest()
{
return (
!empty($_SERVER['HTTP_X_REQUESTED_WITH'])
&& strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest');
}
/**
* Check, if possible, that this execution was triggered by a command line.
* @return bool
*/
public static function isCommandLine()
{
return PHP_SAPI == 'cli';
}
/**
* 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";
}
/**
* Determine if an error level is fatal (halts execution)
*
* @param int $level
* @return bool
*/
public 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,137 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Util;
class SystemFacade
{
/**
* Turns on output buffering.
*
* @return bool
*/
public function startOutputBuffering()
{
return ob_start();
}
/**
* @param callable $handler
* @param int $types
*
* @return callable|null
*/
public function setErrorHandler(callable $handler, $types = 'use-php-defaults')
{
// Workaround for PHP 5.5
if ($types === 'use-php-defaults') {
$types = E_ALL | E_STRICT;
}
return set_error_handler($handler, $types);
}
/**
* @param callable $handler
*
* @return callable|null
*/
public function setExceptionHandler(callable $handler)
{
return set_exception_handler($handler);
}
/**
* @return void
*/
public function restoreExceptionHandler()
{
restore_exception_handler();
}
/**
* @return void
*/
public function restoreErrorHandler()
{
restore_error_handler();
}
/**
* @param callable $function
*
* @return void
*/
public function registerShutdownFunction(callable $function)
{
register_shutdown_function($function);
}
/**
* @return string|false
*/
public function cleanOutputBuffer()
{
return ob_get_clean();
}
/**
* @return int
*/
public function getOutputBufferLevel()
{
return ob_get_level();
}
/**
* @return bool
*/
public function endOutputBuffering()
{
return ob_end_clean();
}
/**
* @return void
*/
public function flushOutputBuffer()
{
flush();
}
/**
* @return int
*/
public function getErrorReportingLevel()
{
return error_reporting();
}
/**
* @return array|null
*/
public function getLastError()
{
return error_get_last();
}
/**
* @param int $httpCode
*
* @return int
*/
public function setHttpResponseCode($httpCode)
{
return http_response_code($httpCode);
}
/**
* @param int $exitStatus
*/
public function stopExecution($exitStatus)
{
exit($exitStatus);
}
}

View File

@@ -0,0 +1,352 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Util;
use Symfony\Component\VarDumper\Caster\Caster;
use Symfony\Component\VarDumper\Cloner\AbstractCloner;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
use Whoops\Exception\Frame;
/**
* Exposes useful tools for working with/in templates
*/
class TemplateHelper
{
/**
* An array of variables to be passed to all templates
* @var array
*/
private $variables = [];
/**
* @var HtmlDumper
*/
private $htmlDumper;
/**
* @var HtmlDumperOutput
*/
private $htmlDumperOutput;
/**
* @var AbstractCloner
*/
private $cloner;
/**
* @var string
*/
private $applicationRootPath;
public function __construct()
{
// root path for ordinary composer projects
$this->applicationRootPath = dirname(dirname(dirname(dirname(dirname(dirname(__DIR__))))));
}
/**
* 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;
}
$raw = str_replace(chr(9), ' ', $raw);
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\" rel=\"noreferrer noopener\">$1</a>",
$escaped
);
}
/**
* Makes sure that the given string breaks on the delimiter.
*
* @param string $delimiter
* @param string $s
* @return string
*/
public function breakOnDelimiter($delimiter, $s)
{
$parts = explode($delimiter, $s);
foreach ($parts as &$part) {
$part = '<div class="delimiter">' . $part . '</div>';
}
return implode($delimiter, $parts);
}
/**
* Replace the part of the path that all files have in common.
*
* @param string $path
* @return string
*/
public function shorten($path)
{
if ($this->applicationRootPath != "/") {
$path = str_replace($this->applicationRootPath, '&hellip;', $path);
}
return $path;
}
private function getDumper()
{
if (!$this->htmlDumper && class_exists('Symfony\Component\VarDumper\Cloner\VarCloner')) {
$this->htmlDumperOutput = new HtmlDumperOutput();
// re-use the same var-dumper instance, so it won't re-render the global styles/scripts on each dump.
$this->htmlDumper = new HtmlDumper($this->htmlDumperOutput);
$styles = [
'default' => 'color:#FFFFFF; line-height:normal; font:12px "Inconsolata", "Fira Mono", "Source Code Pro", Monaco, Consolas, "Lucida Console", monospace !important; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:99999; word-break: normal',
'num' => 'color:#BCD42A',
'const' => 'color: #4bb1b1;',
'str' => 'color:#BCD42A',
'note' => 'color:#ef7c61',
'ref' => 'color:#A0A0A0',
'public' => 'color:#FFFFFF',
'protected' => 'color:#FFFFFF',
'private' => 'color:#FFFFFF',
'meta' => 'color:#FFFFFF',
'key' => 'color:#BCD42A',
'index' => 'color:#ef7c61',
];
$this->htmlDumper->setStyles($styles);
}
return $this->htmlDumper;
}
/**
* Format the given value into a human readable string.
*
* @param mixed $value
* @return string
*/
public function dump($value)
{
$dumper = $this->getDumper();
if ($dumper) {
// re-use the same DumpOutput instance, so it won't re-render the global styles/scripts on each dump.
// exclude verbose information (e.g. exception stack traces)
if (class_exists('Symfony\Component\VarDumper\Caster\Caster')) {
$cloneVar = $this->getCloner()->cloneVar($value, Caster::EXCLUDE_VERBOSE);
// Symfony VarDumper 2.6 Caster class dont exist.
} else {
$cloneVar = $this->getCloner()->cloneVar($value);
}
$dumper->dump(
$cloneVar,
$this->htmlDumperOutput
);
$output = $this->htmlDumperOutput->getOutput();
$this->htmlDumperOutput->clear();
return $output;
}
return htmlspecialchars(print_r($value, true));
}
/**
* Format the args of the given Frame as a human readable html string
*
* @param Frame $frame
* @return string the rendered html
*/
public function dumpArgs(Frame $frame)
{
// we support frame args only when the optional dumper is available
if (!$this->getDumper()) {
return '';
}
$html = '';
$numFrames = count($frame->getArgs());
if ($numFrames > 0) {
$html = '<ol class="linenums">';
foreach ($frame->getArgs() as $j => $frameArg) {
$html .= '<li>'. $this->dump($frameArg) .'</li>';
}
$html .= '</ol>';
}
return $html;
}
/**
* 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 mixed $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;
}
/**
* Set the cloner used for dumping variables.
*
* @param AbstractCloner $cloner
*/
public function setCloner($cloner)
{
$this->cloner = $cloner;
}
/**
* Get the cloner used for dumping variables.
*
* @return AbstractCloner
*/
public function getCloner()
{
if (!$this->cloner) {
$this->cloner = new VarCloner();
}
return $this->cloner;
}
/**
* Set the application root path.
*
* @param string $applicationRootPath
*/
public function setApplicationRootPath($applicationRootPath)
{
$this->applicationRootPath = $applicationRootPath;
}
/**
* Return the application root path.
*
* @return string
*/
public function getApplicationRootPath()
{
return $this->applicationRootPath;
}
}