Updated Rector to commit 7f873f0311679c23feacabd3089c8185b9a93c74

7f873f0311 [DX] Add back node helper functions to ease debugging (#3354)
This commit is contained in:
Tomas Votruba 2023-02-07 13:11:11 +00:00
parent 76b2c64b5f
commit 1f4ea6f9b2
66 changed files with 6724 additions and 14 deletions

View File

@ -133,6 +133,24 @@ Or with Xdebug:
vendor/bin/rector process src/Controller --dry-run --xdebug
```
To assist with simple debugging Rector provides 2 helpers to pretty-print AST-nodes:
```php
use PhpParser\Node\Scalar\String_;
$node = new String_('hello world!');
// prints node to string, as PHP code displays it
print_node($node);
// dump nested node object with nested properties
dump_node($node);
// 2nd argument is how deep the nesting is - this makes sure the dump is short and useful
dump_node($node, 1);
```
<br>
## Known Drawbacks
### How to Apply Coding Standards?

View File

@ -101,7 +101,7 @@ return static function (RectorConfig $rectorConfig) : void {
}
// require only in dev
$rectorConfig->import(__DIR__ . '/../utils/compiler/config/config.php', null, 'not_found');
$services->load('Rector\\Core\\', __DIR__ . '/../src')->exclude([__DIR__ . '/../src/Rector', __DIR__ . '/../src/Console/Style/RectorConsoleOutputStyle.php', __DIR__ . '/../src/Exception', __DIR__ . '/../src/DependencyInjection/CompilerPass', __DIR__ . '/../src/DependencyInjection/Loader', __DIR__ . '/../src/Kernel', __DIR__ . '/../src/ValueObject', __DIR__ . '/../src/Bootstrap', __DIR__ . '/../src/Enum', __DIR__ . '/../src/PhpParser/Node/CustomNode', __DIR__ . '/../src/PhpParser/ValueObject', __DIR__ . '/../src/constants.php']);
$services->load('Rector\\Core\\', __DIR__ . '/../src')->exclude([__DIR__ . '/../src/Rector', __DIR__ . '/../src/Console/Style/RectorConsoleOutputStyle.php', __DIR__ . '/../src/Exception', __DIR__ . '/../src/DependencyInjection/CompilerPass', __DIR__ . '/../src/DependencyInjection/Loader', __DIR__ . '/../src/Kernel', __DIR__ . '/../src/ValueObject', __DIR__ . '/../src/Bootstrap', __DIR__ . '/../src/Enum', __DIR__ . '/../src/functions', __DIR__ . '/../src/PhpParser/Node/CustomNode', __DIR__ . '/../src/PhpParser/ValueObject', __DIR__ . '/../src/constants.php']);
$services->alias(Application::class, ConsoleApplication::class);
$services->set(EmptyConfigurableRectorCollector::class)->arg('$containerBuilder', service('service_container'));
$services->set(SimpleCallableNodeTraverser::class);

View File

@ -19,12 +19,12 @@ final class VersionResolver
* @api
* @var string
*/
public const PACKAGE_VERSION = 'e68c8d23cbbdb9dbfef66133af6bb4f1857015ad';
public const PACKAGE_VERSION = '7f873f0311679c23feacabd3089c8185b9a93c74';
/**
* @api
* @var string
*/
public const RELEASE_DATE = '2023-02-07 12:21:54';
public const RELEASE_DATE = '2023-02-07 20:06:43';
/**
* @var int
*/

View File

@ -0,0 +1,49 @@
<?php
declare (strict_types=1);
namespace RectorPrefix202302;
use PhpParser\Node;
use PhpParser\PrettyPrinter\Standard;
use RectorPrefix202302\Tracy\Dumper;
if (!\function_exists('dump_with_depth')) {
/**
* @param mixed $value
*/
function dump_with_depth($value, int $depth = 2) : void
{
Dumper::dump($value, [Dumper::DEPTH => $depth]);
}
}
if (!\function_exists('dn')) {
function dn(Node $node, int $depth = 2) : void
{
\RectorPrefix202302\dump_node($node, $depth);
}
}
if (!\function_exists('dump_node')) {
/**
* @param \PhpParser\Node|mixed[] $node
*/
function dump_node($node, int $depth = 2) : void
{
$nodes = \is_array($node) ? $node : [$node];
foreach ($nodes as $node) {
Dumper::dump($node, [Dumper::DEPTH => $depth]);
}
}
}
if (!\function_exists('print_node')) {
/**
* @param \PhpParser\Node|mixed[] $node
*/
function print_node($node) : void
{
$standard = new Standard();
$nodes = \is_array($node) ? $node : [$node];
foreach ($nodes as $node) {
$printedContent = $standard->prettyPrint([$node]);
Dumper::dump($printedContent);
}
}
}

2
vendor/autoload.php vendored
View File

@ -22,4 +22,4 @@ if (PHP_VERSION_ID < 50600) {
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit182ffbcec7715a7c92e1953ca71c39fb::getLoader();
return ComposerAutoloaderInit1e12be2eb93c8583a8ed506af09bf6f6::getLoader();

View File

@ -1072,6 +1072,21 @@ return array(
'RectorPrefix202302\\Symplify\\EasyParallel\\ValueObject\\ParallelProcess' => $vendorDir . '/symplify/easy-parallel/src/ValueObject/ParallelProcess.php',
'RectorPrefix202302\\Symplify\\EasyParallel\\ValueObject\\ProcessPool' => $vendorDir . '/symplify/easy-parallel/src/ValueObject/ProcessPool.php',
'RectorPrefix202302\\Symplify\\EasyParallel\\ValueObject\\Schedule' => $vendorDir . '/symplify/easy-parallel/src/ValueObject/Schedule.php',
'RectorPrefix202302\\Tracy\\BlueScreen' => $vendorDir . '/tracy/tracy/src/Tracy/BlueScreen/BlueScreen.php',
'RectorPrefix202302\\Tracy\\Debugger' => $vendorDir . '/tracy/tracy/src/Tracy/Debugger/Debugger.php',
'RectorPrefix202302\\Tracy\\DeferredContent' => $vendorDir . '/tracy/tracy/src/Tracy/Debugger/DeferredContent.php',
'RectorPrefix202302\\Tracy\\DevelopmentStrategy' => $vendorDir . '/tracy/tracy/src/Tracy/Debugger/DevelopmentStrategy.php',
'RectorPrefix202302\\Tracy\\Dumper' => $vendorDir . '/tracy/tracy/src/Tracy/Dumper/Dumper.php',
'RectorPrefix202302\\Tracy\\Dumper\\Describer' => $vendorDir . '/tracy/tracy/src/Tracy/Dumper/Describer.php',
'RectorPrefix202302\\Tracy\\Dumper\\Exposer' => $vendorDir . '/tracy/tracy/src/Tracy/Dumper/Exposer.php',
'RectorPrefix202302\\Tracy\\Dumper\\Renderer' => $vendorDir . '/tracy/tracy/src/Tracy/Dumper/Renderer.php',
'RectorPrefix202302\\Tracy\\Dumper\\Value' => $vendorDir . '/tracy/tracy/src/Tracy/Dumper/Value.php',
'RectorPrefix202302\\Tracy\\FireLogger' => $vendorDir . '/tracy/tracy/src/Tracy/Logger/FireLogger.php',
'RectorPrefix202302\\Tracy\\Helpers' => $vendorDir . '/tracy/tracy/src/Tracy/Helpers.php',
'RectorPrefix202302\\Tracy\\ILogger' => $vendorDir . '/tracy/tracy/src/Tracy/Logger/ILogger.php',
'RectorPrefix202302\\Tracy\\Logger' => $vendorDir . '/tracy/tracy/src/Tracy/Logger/Logger.php',
'RectorPrefix202302\\Tracy\\OutputDebugger' => $vendorDir . '/tracy/tracy/src/Tracy/OutputDebugger/OutputDebugger.php',
'RectorPrefix202302\\Tracy\\ProductionStrategy' => $vendorDir . '/tracy/tracy/src/Tracy/Debugger/ProductionStrategy.php',
'RectorPrefix202302\\Triun\\LongestCommonSubstring\\Solver' => $vendorDir . '/triun/longest-common-substring/src/Solver.php',
'RectorPrefix202302\\Triun\\LongestCommonSubstring\\SolverInterface' => $vendorDir . '/triun/longest-common-substring/src/SolverInterface.php',
'RectorPrefix202302\\Webmozart\\Assert\\Assert' => $vendorDir . '/webmozart/assert/src/Assert.php',

View File

@ -13,5 +13,7 @@ return array(
'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php',
'9b38cf48e83f5d8f60375221cd213eee' => $vendorDir . '/phpstan/phpstan/bootstrap.php',
'2324d0e5cadd603331d27de142371f0b' => $vendorDir . '/symfony/contracts/Deprecation/function.php',
'd507e002f7fce7f0c6dbf1f22edcb902' => $vendorDir . '/tracy/tracy/src/Tracy/functions.php',
'4508688f3cab21375725e8d11ca194a2' => $baseDir . '/src/constants.php',
'30bca7fff093e8069bed7c55247e2bf8' => $baseDir . '/src/functions/node_helper.php',
);

View File

@ -2,7 +2,7 @@
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit182ffbcec7715a7c92e1953ca71c39fb
class ComposerAutoloaderInit1e12be2eb93c8583a8ed506af09bf6f6
{
private static $loader;
@ -22,17 +22,17 @@ class ComposerAutoloaderInit182ffbcec7715a7c92e1953ca71c39fb
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInit182ffbcec7715a7c92e1953ca71c39fb', 'loadClassLoader'), true, true);
spl_autoload_register(array('ComposerAutoloaderInit1e12be2eb93c8583a8ed506af09bf6f6', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
spl_autoload_unregister(array('ComposerAutoloaderInit182ffbcec7715a7c92e1953ca71c39fb', 'loadClassLoader'));
spl_autoload_unregister(array('ComposerAutoloaderInit1e12be2eb93c8583a8ed506af09bf6f6', 'loadClassLoader'));
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit182ffbcec7715a7c92e1953ca71c39fb::getInitializer($loader));
call_user_func(\Composer\Autoload\ComposerStaticInit1e12be2eb93c8583a8ed506af09bf6f6::getInitializer($loader));
$loader->setClassMapAuthoritative(true);
$loader->register(true);
$filesToLoad = \Composer\Autoload\ComposerStaticInit182ffbcec7715a7c92e1953ca71c39fb::$files;
$filesToLoad = \Composer\Autoload\ComposerStaticInit1e12be2eb93c8583a8ed506af09bf6f6::$files;
$requireFile = \Closure::bind(static function ($fileIdentifier, $file) {
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;

View File

@ -4,7 +4,7 @@
namespace Composer\Autoload;
class ComposerStaticInit182ffbcec7715a7c92e1953ca71c39fb
class ComposerStaticInit1e12be2eb93c8583a8ed506af09bf6f6
{
public static $files = array (
'ad155f8f1cf0d418fe49e248db8c661b' => __DIR__ . '/..' . '/react/promise/src/functions_include.php',
@ -14,7 +14,9 @@ class ComposerStaticInit182ffbcec7715a7c92e1953ca71c39fb
'b6b991a57620e2fb6b2f66f03fe9ddc2' => __DIR__ . '/..' . '/symfony/string/Resources/functions.php',
'9b38cf48e83f5d8f60375221cd213eee' => __DIR__ . '/..' . '/phpstan/phpstan/bootstrap.php',
'2324d0e5cadd603331d27de142371f0b' => __DIR__ . '/..' . '/symfony/contracts/Deprecation/function.php',
'd507e002f7fce7f0c6dbf1f22edcb902' => __DIR__ . '/..' . '/tracy/tracy/src/Tracy/functions.php',
'4508688f3cab21375725e8d11ca194a2' => __DIR__ . '/../..' . '/src/constants.php',
'30bca7fff093e8069bed7c55247e2bf8' => __DIR__ . '/../..' . '/src/functions/node_helper.php',
);
public static $prefixLengthsPsr4 = array (
@ -1322,6 +1324,21 @@ class ComposerStaticInit182ffbcec7715a7c92e1953ca71c39fb
'RectorPrefix202302\\Symplify\\EasyParallel\\ValueObject\\ParallelProcess' => __DIR__ . '/..' . '/symplify/easy-parallel/src/ValueObject/ParallelProcess.php',
'RectorPrefix202302\\Symplify\\EasyParallel\\ValueObject\\ProcessPool' => __DIR__ . '/..' . '/symplify/easy-parallel/src/ValueObject/ProcessPool.php',
'RectorPrefix202302\\Symplify\\EasyParallel\\ValueObject\\Schedule' => __DIR__ . '/..' . '/symplify/easy-parallel/src/ValueObject/Schedule.php',
'RectorPrefix202302\\Tracy\\BlueScreen' => __DIR__ . '/..' . '/tracy/tracy/src/Tracy/BlueScreen/BlueScreen.php',
'RectorPrefix202302\\Tracy\\Debugger' => __DIR__ . '/..' . '/tracy/tracy/src/Tracy/Debugger/Debugger.php',
'RectorPrefix202302\\Tracy\\DeferredContent' => __DIR__ . '/..' . '/tracy/tracy/src/Tracy/Debugger/DeferredContent.php',
'RectorPrefix202302\\Tracy\\DevelopmentStrategy' => __DIR__ . '/..' . '/tracy/tracy/src/Tracy/Debugger/DevelopmentStrategy.php',
'RectorPrefix202302\\Tracy\\Dumper' => __DIR__ . '/..' . '/tracy/tracy/src/Tracy/Dumper/Dumper.php',
'RectorPrefix202302\\Tracy\\Dumper\\Describer' => __DIR__ . '/..' . '/tracy/tracy/src/Tracy/Dumper/Describer.php',
'RectorPrefix202302\\Tracy\\Dumper\\Exposer' => __DIR__ . '/..' . '/tracy/tracy/src/Tracy/Dumper/Exposer.php',
'RectorPrefix202302\\Tracy\\Dumper\\Renderer' => __DIR__ . '/..' . '/tracy/tracy/src/Tracy/Dumper/Renderer.php',
'RectorPrefix202302\\Tracy\\Dumper\\Value' => __DIR__ . '/..' . '/tracy/tracy/src/Tracy/Dumper/Value.php',
'RectorPrefix202302\\Tracy\\FireLogger' => __DIR__ . '/..' . '/tracy/tracy/src/Tracy/Logger/FireLogger.php',
'RectorPrefix202302\\Tracy\\Helpers' => __DIR__ . '/..' . '/tracy/tracy/src/Tracy/Helpers.php',
'RectorPrefix202302\\Tracy\\ILogger' => __DIR__ . '/..' . '/tracy/tracy/src/Tracy/Logger/ILogger.php',
'RectorPrefix202302\\Tracy\\Logger' => __DIR__ . '/..' . '/tracy/tracy/src/Tracy/Logger/Logger.php',
'RectorPrefix202302\\Tracy\\OutputDebugger' => __DIR__ . '/..' . '/tracy/tracy/src/Tracy/OutputDebugger/OutputDebugger.php',
'RectorPrefix202302\\Tracy\\ProductionStrategy' => __DIR__ . '/..' . '/tracy/tracy/src/Tracy/Debugger/ProductionStrategy.php',
'RectorPrefix202302\\Triun\\LongestCommonSubstring\\Solver' => __DIR__ . '/..' . '/triun/longest-common-substring/src/Solver.php',
'RectorPrefix202302\\Triun\\LongestCommonSubstring\\SolverInterface' => __DIR__ . '/..' . '/triun/longest-common-substring/src/SolverInterface.php',
'RectorPrefix202302\\Webmozart\\Assert\\Assert' => __DIR__ . '/..' . '/webmozart/assert/src/Assert.php',
@ -3089,9 +3106,9 @@ class ComposerStaticInit182ffbcec7715a7c92e1953ca71c39fb
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit182ffbcec7715a7c92e1953ca71c39fb::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit182ffbcec7715a7c92e1953ca71c39fb::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInit182ffbcec7715a7c92e1953ca71c39fb::$classMap;
$loader->prefixLengthsPsr4 = ComposerStaticInit1e12be2eb93c8583a8ed506af09bf6f6::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit1e12be2eb93c8583a8ed506af09bf6f6::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInit1e12be2eb93c8583a8ed506af09bf6f6::$classMap;
}, null, ClassLoader::class);
}

View File

@ -3172,6 +3172,83 @@
],
"install-path": "..\/symplify\/rule-doc-generator-contracts"
},
{
"name": "tracy\/tracy",
"version": "v2.9.6",
"version_normalized": "2.9.6.0",
"source": {
"type": "git",
"url": "https:\/\/github.com\/nette\/tracy.git",
"reference": "80533f4bda19ce8138c0fc984743533e2a0e1c5c"
},
"dist": {
"type": "zip",
"url": "https:\/\/api.github.com\/repos\/nette\/tracy\/zipball\/80533f4bda19ce8138c0fc984743533e2a0e1c5c",
"reference": "80533f4bda19ce8138c0fc984743533e2a0e1c5c",
"shasum": ""
},
"require": {
"ext-json": "*",
"ext-session": "*",
"php": ">=7.2 <8.3"
},
"conflict": {
"nette\/di": "<3.0"
},
"require-dev": {
"latte\/latte": "^2.5",
"nette\/di": "^3.0",
"nette\/mail": "^3.0",
"nette\/tester": "^2.2",
"nette\/utils": "^3.0",
"phpstan\/phpstan": "^1.0",
"psr\/log": "^1.0 || ^2.0 || ^3.0"
},
"time": "2023-02-06T15:31:40+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.9-dev"
}
},
"installation-source": "dist",
"autoload": {
"files": [
"src\/Tracy\/functions.php"
],
"classmap": [
"src"
]
},
"notification-url": "https:\/\/packagist.org\/downloads\/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "David Grudl",
"homepage": "https:\/\/davidgrudl.com"
},
{
"name": "Nette Community",
"homepage": "https:\/\/nette.org\/contributors"
}
],
"description": "\ud83d\ude0e Tracy: the addictive tool to ease debugging PHP code for cool developers. Friendly design, logging, profiler, advanced features like debugging AJAX calls or CLI support. You will love it.",
"homepage": "https:\/\/tracy.nette.org",
"keywords": [
"Xdebug",
"debug",
"debugger",
"nette",
"profiler"
],
"support": {
"issues": "https:\/\/github.com\/nette\/tracy\/issues",
"source": "https:\/\/github.com\/nette\/tracy\/tree\/v2.9.6"
},
"install-path": "..\/tracy\/tracy"
},
{
"name": "triun\/longest-common-substring",
"version": "1.0.0",

File diff suppressed because one or more lines are too long

View File

@ -6,6 +6,26 @@ $loader = require_once __DIR__.'/autoload.php';
// Exposed functions. For more information see:
// https://github.com/humbug/php-scoper/blob/master/docs/configuration.md#exposing-functions
if (!function_exists('dump_with_depth')) {
function dump_with_depth() {
return \RectorPrefix202302\dump_with_depth(...func_get_args());
}
}
if (!function_exists('dn')) {
function dn() {
return \RectorPrefix202302\dn(...func_get_args());
}
}
if (!function_exists('dump_node')) {
function dump_node() {
return \RectorPrefix202302\dump_node(...func_get_args());
}
}
if (!function_exists('print_node')) {
function print_node() {
return \RectorPrefix202302\print_node(...func_get_args());
}
}
if (!function_exists('trigger_deprecation')) {
function trigger_deprecation() {
return \RectorPrefix202302\trigger_deprecation(...func_get_args());

60
vendor/tracy/tracy/composer.json vendored Normal file
View File

@ -0,0 +1,60 @@
{
"name": "tracy\/tracy",
"description": "\ud83d\ude0e Tracy: the addictive tool to ease debugging PHP code for cool developers. Friendly design, logging, profiler, advanced features like debugging AJAX calls or CLI support. You will love it.",
"keywords": [
"debug",
"debugger",
"nette",
"profiler",
"xdebug"
],
"homepage": "https:\/\/tracy.nette.org",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "David Grudl",
"homepage": "https:\/\/davidgrudl.com"
},
{
"name": "Nette Community",
"homepage": "https:\/\/nette.org\/contributors"
}
],
"require": {
"php": ">=7.2 <8.3",
"ext-session": "*",
"ext-json": "*"
},
"require-dev": {
"nette\/utils": "^3.0",
"nette\/di": "^3.0",
"nette\/mail": "^3.0",
"nette\/tester": "^2.2",
"latte\/latte": "^2.5",
"psr\/log": "^1.0 || ^2.0 || ^3.0",
"phpstan\/phpstan": "^1.0"
},
"conflict": {
"nette\/di": "<3.0"
},
"autoload": {
"classmap": [
"src"
],
"files": [
"src\/Tracy\/functions.php"
]
},
"minimum-stability": "dev",
"scripts": {
"phpstan": "phpstan analyse",
"tester": "tester tests"
},
"extra": {
"branch-alias": {
"dev-master": "2.9-dev"
}
}
}

55
vendor/tracy/tracy/license.md vendored Normal file
View File

@ -0,0 +1,55 @@
Licenses
========
Good news! You may use Tracy under the terms of either the New BSD License
or the GNU General Public License (GPL) version 2 or 3.
The BSD License is recommended for most projects. It is easy to understand and it
places almost no restrictions on what you can do with the framework. If the GPL
fits better to your project, you can use the framework under this license.
You don't have to notify anyone which license you are using. You can freely
use Tracy in commercial projects as long as the copyright header
remains intact.
New BSD License
---------------
Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com)
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of "Tracy" nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
This software is provided by the copyright holders and contributors "as is" and
any express or implied warranties, including, but not limited to, the implied
warranties of merchantability and fitness for a particular purpose are
disclaimed. In no event shall the copyright owner or contributors be liable for
any direct, indirect, incidental, special, exemplary, or consequential damages
(including, but not limited to, procurement of substitute goods or services;
loss of use, data, or profits; or business interruption) however caused and on
any theory of liability, whether in contract, strict liability, or tort
(including negligence or otherwise) arising in any way out of the use of this
software, even if advised of the possibility of such damage.
GNU General Public License
--------------------------
GPL licenses are very very long, so instead of including them here we offer
you URLs with full text:
- [GPL version 2](http://www.gnu.org/licenses/gpl-2.0.html)
- [GPL version 3](http://www.gnu.org/licenses/gpl-3.0.html)

434
vendor/tracy/tracy/readme.md vendored Normal file
View File

@ -0,0 +1,434 @@
[Tracy](https://tracy.nette.org) - PHP debugger
==============================================
[![Downloads this Month](https://img.shields.io/packagist/dm/tracy/tracy.svg)](https://packagist.org/packages/tracy/tracy)
[![Tests](https://github.com/nette/tracy/workflows/Tests/badge.svg?branch=master)](https://github.com/nette/tracy/actions)
[![Build Status Windows](https://ci.appveyor.com/api/projects/status/github/nette/tracy?branch=master&svg=true)](https://ci.appveyor.com/project/dg/tracy/branch/master)
[![Latest Stable Version](https://poser.pugx.org/tracy/tracy/v/stable)](https://github.com/nette/tracy/releases)
[![License](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://github.com/nette/tracy/blob/master/license.md)
Introduction
------------
Tracy library is a useful helper for everyday PHP programmers. It helps you to:
- quickly detect and correct errors
- log errors
- dump variables
- measure execution time of scripts/queries
- see memory consumption
PHP is a perfect language for making hardly detectable errors because it gives great flexibility to programmers. Tracy\Debugger is more valuable because of that. It is an ultimate tool among the diagnostic ones.
If you are meeting Tracy for the first time, believe me, your life starts to be divided into one before the Tracy and the one with her. Welcome to the good part!
Documentation can be found on the [website](https://tracy.nette.org).
[Support Tracy](https://github.com/sponsors/dg)
-----------------------------------------------
Do you like Tracy? Are you looking forward to the new features?
[![Buy me a coffee](https://files.nette.org/icons/donation-3.svg)](https://github.com/sponsors/dg)
Thank you!
Installation and requirements
-----------------------------
The recommended way to is via Composer:
```shell
composer require tracy/tracy
```
Alternatively, you can download the whole package or [tracy.phar](https://github.com/nette/tracy/releases) file.
| Tracy | compatible with PHP | compatible with browsers
|-----------|---------------|----------
| Tracy 2.9 | PHP 7.2 8.2 | Chrome 64+, Firefox 69+, Safari 13.1+ and iOS Safari 13.4+
| Tracy 2.8 | PHP 7.2 8.1 | Chrome 55+, Firefox 53+, Safari 11+ and iOS Safari 11+
| Tracy 2.7 | PHP 7.1 8.0 | Chrome 55+, Firefox 53+, MS Edge 16+, Safari 11+ and iOS Safari 11+
| Tracy 2.6 | PHP 7.1 8.0 | Chrome 49+, Firefox 45+, MS Edge 14+, Safari 10+ and iOS Safari 10.2+
| Tracy 2.5 | PHP 5.4 7.4 | Chrome 49+, Firefox 45+, MS Edge 12+, Safari 10+ and iOS Safari 10.2+
| Tracy 2.4 | PHP 5.4 7.2 | Chrome 29+, Firefox 28+, IE 11+ (except AJAX), MS Edge 12+, Safari 9+ and iOS Safari 9.2+
Usage
-----
Activating Tracy is easy. Simply add these two lines of code, preferably just after library loading (like `require 'vendor/autoload.php'`) and before any output is sent to browser:
```php
use Tracy\Debugger;
Debugger::enable();
```
The first thing you will notice on the website is a Debugger Bar.
(If you do not see anything, it means that Tracy is running in production mode. For security reasons, Tracy is visible only on localhost.
You may force Tracy to run in development mode by passing the `Debugger::Development` as the first parameter of `enable()` method.)
The `enable()` involves changing the error reporting level to E_ALL.
Debugger Bar
------------
The Debugger Bar is a floating panel. It is displayed in the bottom right corner of a page. You can move it using the mouse. It will remember its position after the page reloading.
[![Debugger-Bar](https://nette.github.io/tracy/images/tracy-bar.png)](https://nette.github.io/tracy/tracy-debug-bar.html)
You can add other useful panels to the Debugger Bar. You can find interesting ones in [addons](https://componette.org) or you can [create your own](https://tracy.nette.org/en/extensions).
If you do not want to show Debugger Bar, set:
```php
Debugger::$showBar = false;
```
Visualization of errors and exceptions
--------------------------------------
Surely, you know how PHP reports errors: there is something like this in the page source code:
```pre
<b>Parse error</b>: syntax error, unexpected '}' in <b>HomepagePresenter.php</b> on line <b>15</b>
```
or uncaught exception:
```pre
<b>Fatal error</b>: Uncaught Nette\MemberAccessException: Call to undefined method Nette\Application\UI\Form::addTest()? in /sandbox/vendor/nette/utils/src/Utils/ObjectMixin.php:100
Stack trace:
#0 /sandbox/vendor/nette/utils/src/Utils/Object.php(75): Nette\Utils\ObjectMixin::call(Object(Nette\Application\UI\Form), 'addTest', Array)
#1 /sandbox/app/forms/SignFormFactory.php(32): Nette\Object-&gt;__call('addTest', Array)
#2 /sandbox/app/presenters/SignPresenter.php(21): App\Forms\SignFormFactory-&gt;create()
#3 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(181): App\Presenters\SignPresenter-&gt;createComponentSignInForm('signInForm')
#4 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(139): Nette\ComponentModel\Container-&gt;createComponent('signInForm')
#5 /sandbox/temp/cache/latte/15206b353f351f6bfca2c36cc.php(17): Nette\ComponentModel\Co in <b>/sandbox/vendor/nette/utils/src/Utils/ObjectMixin.php</b> on line <b>100</b><br />
```
It is not so easy to navigate through this output. If you enable Tracy, both errors and exceptions are displayed in a completely different form:
[![Uncaught exception rendered by Tracy](https://nette.github.io/tracy/images/tracy-exception.png)](https://nette.github.io/tracy/tracy-exception.html)
The error message literally screams. You can see a part of the source code with the highlighted line where the error occurred. A message clearly explains an error. The entire site is [interactive, try it](https://nette.github.io/tracy/tracy-exception.html).
And you know what? Fatal errors are captured and displayed in the same way. No need to install any extension (click for live example):
[![Fatal error rendered by Tracy](https://nette.github.io/tracy/images/tracy-error.png)](https://nette.github.io/tracy/tracy-error.html)
Errors like a typo in a variable name or an attempt to open a nonexistent file generate reports of E_NOTICE or E_WARNING level. These can be easily overlooked and/or can be completely hidden in a web page graphic layout. Let Tracy manage them:
[![Notice rendered by Tracy](https://nette.github.io/tracy/images/tracy-notice2.png)](https://nette.github.io/tracy/tracy-debug-bar.html)
Or they may be displayed like errors:
```php
Debugger::$strictMode = true; // display all errors
Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // all errors except deprecated notices
```
[![Notice rendered by Tracy](https://nette.github.io/tracy/images/tracy-notice.png)](https://nette.github.io/tracy/tracy-notice.html)
In order to detect misspellings when assigning to an object, we use [trait Nette\SmartObject](https://doc.nette.org/en/3.0/smartobject).
Content Security Policy
-----------------------
If your site uses Content Security Policy, you'll need to add `'nonce-<value>'` to `script-src` for Tracy to work properly. Some 3rd plugins may require additional directives.
Nonce is not supported in the `style-src` directive, if you use this directive you need to add `'unsafe-inline'`, but this should be avoided in production mode.
Configuration example for [Nette Framework](https://nette.org):
```neon
http:
csp:
script-src: nonce
```
Example in pure PHP:
```php
$nonce = base64_encode(random_bytes(20));
header("Content-Security-Policy: script-src 'nonce-$nonce';");
```
Faster loading
--------------
The basic integration is straightforward, however if you have slow blocking scripts in web page, they can slow the Tracy loading.
The solution is to place `<?php Tracy\Debugger::renderLoader() ?>` into your template before
any scripts:
```html
<!DOCTYPE html>
<html>
<head>
<title>...<title>
<?php Tracy\Debugger::renderLoader() ?>
<link rel="stylesheet" href="assets/style.css">
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
</head>
```
AJAX and redirected requests
----------------------------
Tracy is able to show Debug bar and Bluescreens for AJAX and redirected requests. Tracy keeps the data in a temporary files and uses the `tracy-session` cookie. Tracy can be configured to use a standard PHP session:
```php
Debugger::setSessionStorage(new Tracy\NativeSession);
Debugger::enable();
```
In case you use non-standard session handler, you can start Tracy immediately (in order to handle any errors), then initialize your session handler
and then inform Tracy that session is ready to use via `dispatch()`:
```php
Debugger::setSessionStorage(new Tracy\NativeSession);
Debugger::enable();
// initialize session handler
session_start();
Debugger::dispatch();
```
Opening files in the editor
---------------------------
When the error page is displayed, you can click on file names and they will open in your editor with the cursor on the corresponding line. Files can also be created (action `create file`) or bug fixed in them (action `fix it`). In order to do this, you need to [configure the browser and the system](https://tracy.nette.org/cs/open-files-in-ide).
Production mode and error logging
---------------------------------
As you can see, Tracy is quite eloquent. It is appreciated in a development environment, but on a production server, it would cause a disaster. Any debugging information cannot be listed there. Therefore Tracy has an environment autodetection and logging functionality. Instead of showing herself, Tracy stores information into a log file and shows the visitor a user-comprehensible server error message:
[![Server Error 500](https://nette.github.io/tracy/images/tracy-error2.png)](https://nette.github.io/tracy/tracy-production.html)
Production output mode suppresses all debugging information which is sent out via `dump()` or `Debugger::fireLog()`, and of course all error messages generated by PHP. So, even if you forget `dump($obj)` in the source code, you do not have to worry about it on your production server. Nothing will be seen.
The output mode is set by the first parameter of `Debugger::enable()`. You can specify either a constant `Debugger::Production` or `Debugger::Development`. Other option is to set it up in a way, that development mode will be on when the application is accessed from a defined IP address with a defined value of `tracy-debug` cookie. The syntax used to achieve this is `cookie-value@ip-address`.
If it is not specified, the default value `Debugger::Detect` is used. In this case, the system detects a server by IP address. The production mode is chosen if an application is accessed via a public IP address. A local IP address leads to development mode. It is not necessary to set the mode in most cases. The mode is correctly recognized when you are launching the application on your local server or in production.
In the production mode, Tracy automatically captures all errors and exceptions into a text log. Unless you specify otherwise, it will be stored in log/error.log. This error logging is extremely useful. Imagine, that all users of your application are actually betatesters. They are doing cutting-edge work for free when hunting bugs and you would be silly if you threw away their valuable reports to a recycle bin unnoticed.
If you need to log your own messages or caught exceptions, use the method `log()`:
```php
Debugger::log('Unexpected error'); // text message
try {
criticalOperation();
} catch (Exception $e) {
Debugger::log($e); // log exception
// or
Debugger::log($e, Debugger::ERROR); // also sends an email notification
}
```
A directory for errors logging can be set by the second parameter of the enable() method:
```php
Debugger::enable(Debugger::Detect, __DIR__ . '/mylog');
```
If you want Tracy to log PHP errors like `E_NOTICE` or `E_WARNING` with detailed information (HTML report), set `Debugger::$logSeverity`:
```php
Debugger::$logSeverity = E_NOTICE | E_WARNING;
```
For a real professional the error log is a crucial source of information and he or she wants to be notified about any new error immediately. Tracy helps him. She is capable of sending an email for every new error record. The variable $email identifies where to send these e-mails:
```php
Debugger::$email = 'admin@example.com';
```
If you use the Nette Framework, you can set this and others in the configuration file.
To protect your e-mail box from flood, Tracy sends **only one message** and creates a file `email-sent`. When a developer receives the e-mail notification, he checks the log, corrects his application and deletes the `email-sent` monitoring file. This activates the e-mail sending again.
Variable dumping
----------------
Every debugging developer is a good friend with the function `var_dump`, which lists all contents of any variable in detail. Unfortunately, its output is without HTML formatting and outputs the dump into a single line of HTML code, not to mention context escaping. It is necessary to replace the `var_dump` with a more handy function. That is just what `dump()` is.
```php
$arr = [10, 20.2, true, null, 'hello'];
dump($arr);
// or Tracy\Debugger::dump($arr);
```
generates the output:
![dump](https://nette.github.io/tracy/images/tracy-dump.png)
You can also change the nesting depth by `Debugger::$maxDepth` and displayed strings length by `Debugger::$maxLength`. Naturally, lower values accelerate Tracy rendering.
```php
Debugger::$maxDepth = 2; // default: 7
Debugger::$maxLength = 50; // default: 150
Debugger::$dumpTheme = 'dark'; // default: light
```
The `dump()` function can display useful location information:
```php
Debugger::$showLocation = true; // shows tooltip with path to the file, where the dump() was called, and tooltips for every dumped objects
Debugger::$showLocation = Tracy\Dumper::LOCATION_CLASS; // shows only tooltips for every dumped object containing path to the file
Debugger::$showLocation = false; // hides all location information
```
Very handy alternative to `dump()` is `dumpe()` (ie. dump and exit) and `bdump()`. This allows us to dump variables in Debugger Bar. This is useful, because dumps don't mess up the output and we can also add a title to the dump.
```php
bdump([2, 4, 6, 8], 'even numbers up to ten');
bdump([1, 3, 5, 7, 9], 'odd numbers up to ten');
```
![bar dump](https://nette.github.io/tracy/images/tracy-bardump.png)
Timing
------
Another useful tool is the debugger stopwatch with a precision of microseconds:
```php
Debugger::timer();
// sweet dreams my cherrie
sleep(2);
$elapsed = Debugger::timer();
// $elapsed = 2
```
Multiple measurements at once can be achieved by an optional parameter.
```php
Debugger::timer('page-generating');
// some code
Debugger::timer('rss-generating');
// some code
$rssElapsed = Debugger::timer('rss-generating');
$pageElapsed = Debugger::timer('page-generating');
```
```php
Debugger::timer(); // runs the timer
... // some time-consuming operation
echo Debugger::timer(); // elapsed time in seconds
```
FireLogger
----------
You cannot always send debugging information to the browser window. This applies to AJAX requests or generating XML files to output. In such cases, you can send the messages by a separate channel into FireLogger. Error, Notice and Warning levels are sent to FireLogger window automatically. It is also possible to log suppressed exceptions in running application when attention to them is important.
How to do it?
- install extension [FireLogger for Chrome](https://chrome.google.com/webstore/detail/firelogger-for-chrome/hmagilfopmdjkeomnjpchokglfdfjfeh)
- turn on Chrome DevTools (using Ctrl-Shift-I key) and open Console
Navigate to the [demo page](https://examples.nette.org/tracy/) and you will see messages sent from PHP.
Because Tracy\Debugger communicates with FireLogger via HTTP headers, you must call the logging function before the PHP script sends anything to output. It is also possible to enable output buffering and delay the output.
```php
use Tracy\Debugger;
Debugger::fireLog('Hello World'); // send string into FireLogger console
Debugger::fireLog($_SERVER); // or even arrays and objects
Debugger::fireLog(new Exception('Test Exception')); // or exceptions
```
The result looks like this:
![FireLogger](https://nette.github.io/tracy/images/tracy-firelogger.png)
Custom Logger
-------------
We can create a custom logger to log errors, uncatched exceptions, and also be called by `Tracy\Debugger::log()`. Logger implements the interface Tracy\ILogger.
```php
use Tracy\ILogger;
class SlackLogger implements ILogger
{
public function log($value, $priority = ILogger::INFO)
{
// sends a request to Slack
}
}
```
And then we activate it:
```php
Tracy\Debugger::setLogger(new SlackLogger);
```
If we use the full Nette Framework, we can set it in the NEON configuration file:
```neon
services:
tracy.logger: SlackLogger
```
nginx
-----
If Tracy does not work on nginx, it is probably misconfigured. If there is something like
```nginx
try_files $uri $uri/ /index.php;
```
change it to
```nginx
try_files $uri $uri/ /index.php$is_args$args;
```
Integrations
------------
This is a list of unofficial integrations to other frameworks and CMS:
- [Drupal 7](http://drupal.org/project/traced)
- Laravel framework: [recca0120/laravel-tracy](https://github.com/recca0120/laravel-tracy), [whipsterCZ/laravel-tracy](https://github.com/whipsterCZ/laravel-tracy)
- [OpenCart](https://github.com/BurdaPraha/oc_tracy)
- [ProcessWire CMS/CMF](https://github.com/adrianbj/TracyDebugger)
- [Slim Framework](https://github.com/runcmf/runtracy)
- Symfony framework: [kutny/tracy-bundle](https://github.com/kutny/tracy-bundle), [VasekPurchart/Tracy-Blue-Screen-Bundle](https://github.com/VasekPurchart/Tracy-Blue-Screen-Bundle)
- [Wordpress](https://github.com/ktstudio/WP-Tracy)
... feel free to be famous, create an integration for your favourite platform!

View File

@ -0,0 +1,437 @@
<?php
/**
* This file is part of the Tracy (https://tracy.nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare (strict_types=1);
namespace RectorPrefix202302\Tracy;
/**
* Red BlueScreen.
*/
class BlueScreen
{
private const MaxMessageLength = 2000;
/** @var string[] */
public $info = [];
/** @var string[] paths to be collapsed in stack trace (e.g. core libraries) */
public $collapsePaths = [];
/** @var int */
public $maxDepth = 5;
/** @var int */
public $maxLength = 150;
/** @var int */
public $maxItems = 100;
/** @var callable|null a callable returning true for sensitive data; fn(string $key, mixed $val): bool */
public $scrubber;
/** @var string[] */
public $keysToHide = ['password', 'passwd', 'pass', 'pwd', 'creditcard', 'credit card', 'cc', 'pin', self::class . '::$snapshot'];
/** @var bool */
public $showEnvironment = \true;
/** @var callable[] */
private $panels = [];
/** @var callable[] functions that returns action for exceptions */
private $actions = [];
/** @var callable[] */
private $fileGenerators = [];
/** @var array */
private $snapshot;
/** @var \WeakMap<\Fiber|\Generator> */
private $fibers;
public function __construct()
{
$this->collapsePaths = \preg_match('#(.+/vendor)/tracy/tracy/src/Tracy/BlueScreen$#', \strtr(__DIR__, '\\', '/'), $m) ? [$m[1] . '/tracy', $m[1] . '/nette', $m[1] . '/latte'] : [\dirname(__DIR__)];
$this->fileGenerators[] = [self::class, 'generateNewPhpFileContents'];
$this->fibers = \PHP_VERSION_ID < 80000 ? new \SplObjectStorage() : new \WeakMap();
}
/**
* Add custom panel as function (?\Throwable $e): ?array
* @return static
*/
public function addPanel(callable $panel) : self
{
if (!\in_array($panel, $this->panels, \true)) {
$this->panels[] = $panel;
}
return $this;
}
/**
* Add action.
* @return static
*/
public function addAction(callable $action) : self
{
$this->actions[] = $action;
return $this;
}
/**
* Add new file generator.
* @param callable(string): ?string $generator
* @return static
*/
public function addFileGenerator(callable $generator) : self
{
$this->fileGenerators[] = $generator;
return $this;
}
/**
* @param \Fiber|\Generator $fiber
* @return static
*/
public function addFiber($fiber) : self
{
$this->fibers[$fiber] = \true;
return $this;
}
/**
* Renders blue screen.
*/
public function render(\Throwable $exception) : void
{
if (!\headers_sent()) {
\header('Content-Type: text/html; charset=UTF-8');
}
$this->renderTemplate($exception, __DIR__ . '/assets/page.phtml');
}
/** @internal */
public function renderToAjax(\Throwable $exception, DeferredContent $defer) : void
{
$defer->addSetup('Tracy.BlueScreen.loadAjax', Helpers::capture(function () use($exception) {
$this->renderTemplate($exception, __DIR__ . '/assets/content.phtml');
}));
}
/**
* Renders blue screen to file (if file exists, it will not be overwritten).
*/
public function renderToFile(\Throwable $exception, string $file) : bool
{
if ($handle = @\fopen($file, 'x')) {
\ob_start();
// double buffer prevents sending HTTP headers in some PHP
\ob_start(function ($buffer) use($handle) : void {
\fwrite($handle, $buffer);
}, 4096);
$this->renderTemplate($exception, __DIR__ . '/assets/page.phtml', \false);
\ob_end_flush();
\ob_end_clean();
\fclose($handle);
return \true;
}
return \false;
}
private function renderTemplate(\Throwable $exception, string $template, $toScreen = \true) : void
{
[$generators, $fibers] = $this->findGeneratorsAndFibers($exception);
$headersSent = \headers_sent($headersFile, $headersLine);
$obStatus = Debugger::$obStatus;
$showEnvironment = $this->showEnvironment && \strpos($exception->getMessage(), 'Allowed memory size') === \false;
$info = \array_filter($this->info);
$source = Helpers::getSource();
$title = $exception instanceof \ErrorException ? Helpers::errorTypeToString($exception->getSeverity()) : Helpers::getClass($exception);
$lastError = $exception instanceof \ErrorException || $exception instanceof \Error ? null : \error_get_last();
if (\function_exists('apache_request_headers')) {
$httpHeaders = \apache_request_headers();
} else {
$httpHeaders = \array_filter($_SERVER, function ($k) {
return \strncmp($k, 'HTTP_', 5) === 0;
}, \ARRAY_FILTER_USE_KEY);
$httpHeaders = \array_combine(\array_map(function ($k) {
return \strtolower(\strtr(\substr($k, 5), '_', '-'));
}, \array_keys($httpHeaders)), $httpHeaders);
}
$snapshot =& $this->snapshot;
$snapshot = [];
$dump = $this->getDumper();
$css = \array_map('file_get_contents', \array_merge([__DIR__ . '/../assets/reset.css', __DIR__ . '/assets/bluescreen.css', __DIR__ . '/../assets/toggle.css', __DIR__ . '/../assets/table-sort.css', __DIR__ . '/../assets/tabs.css', __DIR__ . '/../Dumper/assets/dumper-light.css'], Debugger::$customCssFiles));
$css = Helpers::minifyCss(\implode('', $css));
$nonce = $toScreen ? Helpers::getNonce() : null;
$actions = $toScreen ? $this->renderActions($exception) : [];
require $template;
}
/**
* @return \stdClass[]
*/
private function renderPanels(?\Throwable $ex) : array
{
$obLevel = \ob_get_level();
$res = [];
foreach ($this->panels as $callback) {
try {
$panel = $callback($ex);
if (empty($panel['tab']) || empty($panel['panel'])) {
continue;
}
$res[] = (object) $panel;
continue;
} catch (\Throwable $e) {
}
while (\ob_get_level() > $obLevel) {
// restore ob-level if broken
\ob_end_clean();
}
\is_callable($callback, \true, $name);
$res[] = (object) ['tab' => "Error in panel {$name}", 'panel' => \nl2br(Helpers::escapeHtml($e))];
}
return $res;
}
/**
* @return array[]
*/
private function renderActions(\Throwable $ex) : array
{
$actions = [];
foreach ($this->actions as $callback) {
$action = $callback($ex);
if (!empty($action['link']) && !empty($action['label'])) {
$actions[] = $action;
}
}
if (\property_exists($ex, 'tracyAction') && !empty($ex->tracyAction['link']) && !empty($ex->tracyAction['label'])) {
$actions[] = $ex->tracyAction;
}
if (\preg_match('# ([\'"])(\\w{3,}(?:\\\\\\w{3,})+)\\1#i', $ex->getMessage(), $m)) {
$class = $m[2];
if (!\class_exists($class, \false) && !\interface_exists($class, \false) && !\trait_exists($class, \false) && ($file = Helpers::guessClassFile($class)) && !\is_file($file)) {
[$content, $line] = $this->generateNewFileContents($file, $class);
$actions[] = ['link' => Helpers::editorUri($file, $line, 'create', '', $content), 'label' => 'create class'];
}
}
if (\preg_match('# ([\'"])((?:/|[a-z]:[/\\\\])\\w[^\'"]+\\.\\w{2,5})\\1#i', $ex->getMessage(), $m)) {
$file = $m[2];
if (\is_file($file)) {
$label = 'open';
$content = '';
$line = 1;
} else {
$label = 'create';
[$content, $line] = $this->generateNewFileContents($file);
}
$actions[] = ['link' => Helpers::editorUri($file, $line, $label, '', $content), 'label' => $label . ' file'];
}
$query = ($ex instanceof \ErrorException ? '' : Helpers::getClass($ex) . ' ') . \preg_replace('#\'.*\'|".*"#Us', '', $ex->getMessage());
$actions[] = ['link' => 'https://www.google.com/search?sourceid=tracy&q=' . \urlencode($query), 'label' => 'search', 'external' => \true];
if ($ex instanceof \ErrorException && !empty($ex->skippable) && \preg_match('#^https?://#', $source = Helpers::getSource())) {
$actions[] = ['link' => $source . (\strpos($source, '?') ? '&' : '?') . '_tracy_skip_error', 'label' => 'skip error'];
}
return $actions;
}
/**
* Returns syntax highlighted source code.
*/
public static function highlightFile(string $file, int $line, int $lines = 15, bool $php = \true, int $column = 0) : ?string
{
$source = @\file_get_contents($file);
// @ file may not exist
if ($source === \false) {
return null;
}
$source = $php ? static::highlightPhp($source, $line, $lines, $column) : '<pre class=tracy-code><div>' . static::highlightLine(\htmlspecialchars($source, \ENT_IGNORE, 'UTF-8'), $line, $lines, $column) . '</div></pre>';
if ($editor = Helpers::editorUri($file, $line)) {
$source = \substr_replace($source, ' title="Ctrl-Click to open in editor" data-tracy-href="' . Helpers::escapeHtml($editor) . '"', 4, 0);
}
return $source;
}
/**
* Returns syntax highlighted source code.
*/
public static function highlightPhp(string $source, int $line, int $lines = 15, int $column = 0) : string
{
if (\function_exists('ini_set')) {
\ini_set('highlight.comment', '#998; font-style: italic');
\ini_set('highlight.default', '#000');
\ini_set('highlight.html', '#06B');
\ini_set('highlight.keyword', '#D24; font-weight: bold');
\ini_set('highlight.string', '#080');
}
$source = \preg_replace('#(__halt_compiler\\s*\\(\\)\\s*;).*#is', '$1', $source);
$source = \str_replace(["\r\n", "\r"], "\n", $source);
$source = \explode("\n", \highlight_string($source, \true));
$out = $source[0];
// <code><span color=highlight.html>
$source = \str_replace('<br />', "\n", $source[1]);
$out .= static::highlightLine($source, $line, $lines, $column);
$out = \str_replace('&nbsp;', ' ', $out);
return "<pre class='tracy-code'><div>{$out}</div></pre>";
}
/**
* Returns highlighted line in HTML code.
*/
public static function highlightLine(string $html, int $line, int $lines = 15, int $column = 0) : string
{
$source = \explode("\n", "\n" . \str_replace("\r\n", "\n", $html));
$out = '';
$spans = 1;
$start = $i = \max(1, \min($line, \count($source) - 1) - (int) \floor($lines * 2 / 3));
while (--$i >= 1) {
// find last highlighted block
if (\preg_match('#.*(</?span[^>]*>)#', $source[$i], $m)) {
if ($m[1] !== '</span>') {
$spans++;
$out .= $m[1];
}
break;
}
}
$source = \array_slice($source, $start, $lines, \true);
\end($source);
$numWidth = \strlen((string) \key($source));
foreach ($source as $n => $s) {
$spans += \substr_count($s, '<span') - \substr_count($s, '</span');
$s = \str_replace(["\r", "\n"], ['', ''], $s);
\preg_match_all('#<[^>]+>#', $s, $tags);
if ($n == $line) {
$s = \strip_tags($s);
if ($column) {
$s = \preg_replace('#((?:&.*?;|[^&]){' . ($column - 1) . '})(&.*?;|.)#u', '\\1<span class="tracy-column-highlight">\\2</span>', $s . ' ', 1);
}
$out .= \sprintf("<span class='tracy-line-highlight'>%{$numWidth}s: %s\n</span>%s", $n, $s, \implode('', $tags[0]));
} else {
$out .= \sprintf("<span class='tracy-line'>%{$numWidth}s:</span> %s\n", $n, $s);
}
}
$out .= \str_repeat('</span>', $spans) . '</code>';
return $out;
}
/**
* Returns syntax highlighted source code to Terminal.
*/
public static function highlightPhpCli(string $file, int $line, int $lines = 15, int $column = 0) : ?string
{
$source = @\file_get_contents($file);
// @ file may not exist
if ($source === \false) {
return null;
}
$s = self::highlightPhp($source, $line, $lines);
$colors = ['color: ' . \ini_get('highlight.comment') => '1;30', 'color: ' . \ini_get('highlight.default') => '1;36', 'color: ' . \ini_get('highlight.html') => '1;35', 'color: ' . \ini_get('highlight.keyword') => '1;37', 'color: ' . \ini_get('highlight.string') => '1;32', 'tracy-line' => '1;30', 'tracy-line-highlight' => "1;37m\x1b[41"];
$stack = ['0'];
$s = \preg_replace_callback('#<\\w+(?: (class|style)=["\'](.*?)["\'])?[^>]*>|</\\w+>#', function ($m) use($colors, &$stack) : string {
if ($m[0][1] === '/') {
\array_pop($stack);
} else {
$stack[] = isset($m[2], $colors[$m[2]]) ? $colors[$m[2]] : '0';
}
return "\x1b[0m\x1b[" . \end($stack) . 'm';
}, $s);
$s = \htmlspecialchars_decode(\strip_tags($s), \ENT_QUOTES | \ENT_HTML5);
return $s;
}
/**
* Should a file be collapsed in stack trace?
* @internal
*/
public function isCollapsed(string $file) : bool
{
$file = \strtr($file, '\\', '/') . '/';
foreach ($this->collapsePaths as $path) {
$path = \strtr($path, '\\', '/') . '/';
if (\strncmp($file, $path, \strlen($path)) === 0) {
return \true;
}
}
return \false;
}
/** @internal */
public function getDumper() : \Closure
{
return function ($v, $k = null) : string {
return Dumper::toHtml($v, [Dumper::DEPTH => $this->maxDepth, Dumper::TRUNCATE => $this->maxLength, Dumper::ITEMS => $this->maxItems, Dumper::SNAPSHOT => &$this->snapshot, Dumper::LOCATION => Dumper::LOCATION_CLASS, Dumper::SCRUBBER => $this->scrubber, Dumper::KEYS_TO_HIDE => $this->keysToHide], $k);
};
}
public function formatMessage(\Throwable $exception) : string
{
$msg = Helpers::encodeString(\trim((string) $exception->getMessage()), self::MaxMessageLength, \false);
// highlight 'string'
$msg = \preg_replace('#\'\\S(?:[^\']|\\\\\')*\\S\'|"\\S(?:[^"]|\\\\")*\\S"#', '<i>$0</i>', $msg);
// clickable class & methods
$msg = \preg_replace_callback('#(\\w+\\\\[\\w\\\\]+\\w)(?:::(\\w+))?#', function ($m) {
if (isset($m[2]) && \method_exists($m[1], $m[2])) {
$r = new \ReflectionMethod($m[1], $m[2]);
} elseif (\class_exists($m[1], \false) || \interface_exists($m[1], \false)) {
$r = new \ReflectionClass($m[1]);
}
if (empty($r) || !$r->getFileName()) {
return $m[0];
}
return '<a href="' . Helpers::escapeHtml(Helpers::editorUri($r->getFileName(), $r->getStartLine())) . '" class="tracy-editor">' . $m[0] . '</a>';
}, $msg);
// clickable file name
$msg = \preg_replace_callback('#([\\w\\\\/.:-]+\\.(?:php|phpt|phtml|latte|neon))(?|:(\\d+)| on line (\\d+))?#', function ($m) {
return @\is_file($m[1]) ? '<a href="' . Helpers::escapeHtml(Helpers::editorUri($m[1], isset($m[2]) ? (int) $m[2] : null)) . '" class="tracy-editor">' . $m[0] . '</a>' : $m[0];
}, $msg);
return $msg;
}
private function renderPhpInfo() : void
{
\ob_start();
@\phpinfo(\INFO_LICENSE);
// @ phpinfo may be disabled
$license = \ob_get_clean();
\ob_start();
@\phpinfo(\INFO_CONFIGURATION | \INFO_MODULES);
// @ phpinfo may be disabled
$info = \ob_get_clean();
if (\strpos($license, '<body') === \false) {
echo '<pre class="tracy-dump tracy-light">', Helpers::escapeHtml($info), '</pre>';
} else {
$info = \str_replace('<table', '<table class="tracy-sortable"', $info);
echo \preg_replace('#^.+<body>|</body>.+\\z|<hr />|<h1>Configuration</h1>#s', '', $info);
}
}
/** @internal */
private function generateNewFileContents(string $file, ?string $class = null) : array
{
foreach (\array_reverse($this->fileGenerators) as $generator) {
$content = $generator($file, $class);
if ($content !== null) {
$line = 1;
$pos = \strpos($content, '$END$');
if ($pos !== \false) {
$content = \substr_replace($content, '', $pos, 5);
$line = \substr_count($content, "\n", 0, $pos) + 1;
}
return [$content, $line];
}
}
return ['', 1];
}
/** @internal */
public static function generateNewPhpFileContents(string $file, ?string $class = null) : ?string
{
if (\substr($file, -4) !== '.php') {
return null;
}
$res = "<?php\n\ndeclare(strict_types=1);\n\n";
if (!$class) {
return $res . '$END$';
}
if ($pos = \strrpos($class, '\\')) {
$res .= 'namespace ' . \substr($class, 0, $pos) . ";\n\n";
$class = \substr($class, $pos + 1);
}
return $res . "class {$class}\n{\n\$END\$\n}\n";
}
private function findGeneratorsAndFibers(object $object) : array
{
$generators = $fibers = [];
$add = function ($obj) use(&$generators, &$fibers) {
if ($obj instanceof \Generator) {
try {
new \ReflectionGenerator($obj);
$generators[\spl_object_id($obj)] = $obj;
} catch (\ReflectionException $e) {
}
} elseif ($obj instanceof \Fiber && $obj->isStarted() && !$obj->isTerminated()) {
$fibers[\spl_object_id($obj)] = $obj;
}
};
foreach ($this->fibers as $k => $v) {
$add($this->fibers instanceof \WeakMap ? $k : $v);
}
if (\PHP_VERSION_ID >= 80000) {
Helpers::traverseValue($object, $add);
}
return [$generators, $fibers];
}
}

View File

@ -0,0 +1,418 @@
/**
* This file is part of the Tracy (https://tracy.nette.org)
*/
:root {
--tracy-space: 16px;
}
@media (max-width: 600px) {
:root {
--tracy-space: 8px;
}
}
html.tracy-bs-visible,
html.tracy-bs-visible body {
display: block;
overflow: auto;
}
#tracy-bs {
font: 9pt/1.5 Verdana, sans-serif;
background: white;
color: #333;
position: absolute;
z-index: 20000;
left: 0;
top: 0;
width: 100%;
text-align: left;
}
#tracy-bs a {
text-decoration: none;
color: #328ADC;
padding: 0 4px;
margin: 0 -4px;
}
#tracy-bs a + a {
margin-left: 0;
}
#tracy-bs a:hover,
#tracy-bs a:focus {
color: #085AA3;
}
#tracy-bs-toggle {
position: absolute;
right: .5em;
top: .5em;
text-decoration: none;
background: #CD1818;
color: white !important;
padding: 3px;
}
#tracy-bs-toggle.tracy-collapsed {
position: fixed;
}
.tracy-bs-main {
display: flex;
flex-direction: column;
padding-bottom: 80vh;
}
.tracy-bs-main.tracy-collapsed {
display: none;
}
#tracy-bs p,
#tracy-bs table,
#tracy-bs pre,
#tracy-bs h1,
#tracy-bs h2,
#tracy-bs h3 {
margin: 0 0 var(--tracy-space);
}
#tracy-bs h1 {
font-size: 15pt;
font-weight: normal;
text-shadow: 1px 1px 2px rgba(0, 0, 0, .3);
}
#tracy-bs h1 span {
white-space: pre-wrap;
}
#tracy-bs h2 {
font-size: 14pt;
font-weight: normal;
margin-top: var(--tracy-space);
}
#tracy-bs h3 {
font-size: 10pt;
font-weight: bold;
}
#tracy-bs pre,
#tracy-bs code,
#tracy-bs table {
font: 9pt/1.5 Consolas, monospace !important;
}
#tracy-bs pre,
#tracy-bs table {
background: #FDF5CE;
padding: .4em .7em;
border: 2px solid #ffffffa6;
box-shadow: 1px 2px 6px #00000005;
overflow: auto;
}
#tracy-bs table pre {
padding: 0;
margin: 0;
border: none;
box-shadow: none;
}
#tracy-bs table {
border-collapse: collapse;
width: 100%;
}
#tracy-bs td,
#tracy-bs th {
vertical-align: top;
text-align: left;
padding: 2px 6px;
border: 1px solid #e6dfbf;
}
#tracy-bs th {
font-weight: bold;
}
#tracy-bs tr > :first-child {
width: 20%;
}
#tracy-bs tr:nth-child(2n),
#tracy-bs tr:nth-child(2n) pre {
background-color: #F7F0CB;
}
#tracy-bs .tracy-footer--sticky {
position: fixed;
width: 100%;
bottom: 0;
}
#tracy-bs footer ul {
font-size: 7pt;
padding: var(--tracy-space);
margin: var(--tracy-space) 0 0;
color: #777;
background: #F6F5F3;
border-top: 1px solid #DDD;
list-style: none;
}
#tracy-bs .tracy-footer-logo {
position: relative;
}
#tracy-bs .tracy-footer-logo a {
position: absolute;
bottom: 0;
right: 0;
width: 100px;
height: 50px;
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFoAAAAUBAMAAAD/1DctAAAAMFBMVEWupZzj39rEvbTy8O3X0sz9/PvGwLu8tavQysHq6OS0rKP5+Pbd2dT29fPMxbzPx8DKErMJAAAACXBIWXMAAAsTAAALEwEAmpwYAAACGUlEQVQoFX3TQWgTQRQA0MWLIJJDYehBTykhG5ERTx56K1u8eEhCYtomE7x5L4iLh0ViF7egewuFFqSIYE6hIHsIYQ6CQSg9CDKn4QsNCRlB59C74J/ZNHW1+An5+bOPyf6/s46oz2P+A0yIeZZ2ieEHi6TOnLKTxvWq+b52mxlVO3xnM1s7xLX1504XQH65OnW2dBqn7cCkYsFsfYsWpyY/2salmFTpEyzeR8zosYqMdiPDXdyU52K1wgEa/SjGpdEwUAxqvRfckQCDOyFearsEHe2grvkh/cFAHKvdtI3lcVceKQIOFpv+FOZaNPQBwJZLPp+hfrvT5JZXaUFsR8zqQc9qSgAharkfS5M/5F6nGJJAtXq/eLr3ucZpHccSxOOIPaQhtHohpCH2Xu6rLmQ0djnr4/+J3C6v+AW8/XWYxwYNdlhWj/P5fPSTQwVr0T9lGxdaBCqErNZaqYnEwbkjEB3NasGF3lPdrHa1nnxNOMgj0+neePUPjd2v/qVvUv29ifvc19huQ48qwXShy/9o8o3OSk0cs37mOFd0Ydgvsf/oZEnPVtggfd66lORn9mDyyzXU13SRtH2L6aR5T/snGAcZPfAXz5J1YlJWBEuxdMYqQecpBrlM49xAbmqyHA+xlA1FxBtqT2xmJoNXZlIt74ZBLeJ9ZGDqByNI7p543idzJ23vXEv7IgnsxiS+eNtwNbFdLq7+Bi4wQ0I4SVb9AAAAAElFTkSuQmCC') no-repeat;
opacity: .6;
padding: 0;
margin: 0;
}
#tracy-bs .tracy-footer-logo a:hover,
#tracy-bs .tracy-footer-logo a:focus {
opacity: 1;
transition: opacity 0.1s;
}
#tracy-bs .tracy-section {
padding-left: calc(1.5 * var(--tracy-space));
padding-right: calc(1.5 * var(--tracy-space));
}
#tracy-bs .tracy-section-panel {
background: #F4F3F1;
padding: var(--tracy-space) var(--tracy-space) 0;
margin: 0 0 var(--tracy-space);
border-radius: 8px;
box-shadow: inset 1px 1px 0px 0 #00000005;
overflow: hidden;
}
#tracy-bs .outer, /* deprecated */
#tracy-bs .tracy-pane {
overflow: auto;
}
#tracy-bs.tracy-mac .tracy-pane {
padding-bottom: 12px;
}
/* header */
#tracy-bs .tracy-section--error {
background: #CD1818;
color: white;
font-size: 13pt;
padding-top: var(--tracy-space);
}
#tracy-bs .tracy-section--error h1 {
color: white;
}
#tracy-bs .tracy-section--error::selection,
#tracy-bs .tracy-section--error ::selection {
color: black !important;
background: #FDF5CE !important;
}
#tracy-bs .tracy-section--error a {
color: #ffefa1 !important;
}
#tracy-bs .tracy-section--error span span {
font-size: 80%;
color: rgba(255, 255, 255, 0.5);
text-shadow: none;
}
#tracy-bs .tracy-section--error a.tracy-action {
color: white !important;
opacity: 0;
font-size: .7em;
border-bottom: none !important;
}
#tracy-bs .tracy-section--error:hover a.tracy-action {
opacity: .6;
}
#tracy-bs .tracy-section--error a.tracy-action:hover {
opacity: 1;
}
#tracy-bs .tracy-section--error i {
color: #ffefa1;
font-style: normal;
}
/* source code */
#tracy-bs pre.tracy-code > div {
min-width: 100%;
float: left;
white-space: pre;
}
#tracy-bs .tracy-line-highlight {
background: #CD1818;
color: white;
font-weight: bold;
font-style: normal;
display: block;
padding: 0 1ch;
margin: 0 -1ch;
}
#tracy-bs .tracy-column-highlight {
display: inline-block;
backdrop-filter: grayscale(1);
margin: 0 -1px;
padding: 0 1px;
}
#tracy-bs .tracy-line {
color: #9F9C7F;
font-weight: normal;
font-style: normal;
}
#tracy-bs a.tracy-editor {
color: inherit;
border-bottom: 1px dotted rgba(0, 0, 0, .3);
border-radius: 3px;
}
#tracy-bs a.tracy-editor:hover {
background: #0001;
}
#tracy-bs span[data-tracy-href] {
border-bottom: 1px dotted rgba(0, 0, 0, .3);
}
#tracy-bs .tracy-dump-whitespace {
color: #0003;
}
#tracy-bs .tracy-caused {
float: right;
padding: .3em calc(1.5 * var(--tracy-space));
background: #df8075;
border-radius: 0 0 0 8px;
white-space: nowrap;
}
#tracy-bs .tracy-caused a {
color: white;
}
#tracy-bs .tracy-callstack {
display: grid;
grid-template-columns: max-content 1fr;
margin-bottom: calc(.5 * var(--tracy-space));
}
#tracy-bs .tracy-callstack-file {
text-align: right;
padding-right: var(--tracy-space);
white-space: nowrap;
height: calc(1.5 * var(--tracy-space));
}
#tracy-bs .tracy-callstack-callee {
white-space: nowrap;
height: calc(1.5 * var(--tracy-space));
}
#tracy-bs .tracy-callstack-additional {
grid-column-start: 1;
grid-column-end: 3;
}
#tracy-bs .tracy-callstack-args tr:first-child > * {
position: relative;
}
#tracy-bs .tracy-callstack-args tr:first-child td:before {
position: absolute;
right: .3em;
content: 'may not be true';
opacity: .4;
}
#tracy-bs .tracy-panel-fadein {
animation: tracy-panel-fadein .12s ease;
}
@keyframes tracy-panel-fadein {
0% {
opacity: 0;
}
}
#tracy-bs .tracy-section--causedby {
flex-direction: column;
padding: 0;
}
#tracy-bs .tracy-section--causedby:not(.tracy-collapsed) {
display: flex;
}
#tracy-bs .tracy-section--causedby .tracy-section--error {
background: #cd1818a6;
}
#tracy-bs .tracy-section--error + .tracy-section--stack {
margin-top: calc(1.5 * var(--tracy-space));
}
/* tabs */
#tracy-bs .tracy-tab-bar {
display: flex;
list-style: none;
padding-left: 0;
margin: 0;
width: 100%;
font-size: 110%;
}
#tracy-bs .tracy-tab-bar > *:not(:first-child) {
margin-left: var(--tracy-space);
}
#tracy-bs .tracy-tab-bar a {
display: block;
padding: calc(.5 * var(--tracy-space)) var(--tracy-space);
margin: 0;
height: 100%;
box-sizing: border-box;
border-radius: 5px 5px 0 0;
text-decoration: none;
transition: all 0.1s;
}
#tracy-bs .tracy-tab-bar > .tracy-active a {
background: white;
}
#tracy-bs .tracy-tab-panel {
border-top: 2px solid white;
padding-top: var(--tracy-space);
overflow: auto;
}

View File

@ -0,0 +1,77 @@
/**
* This file is part of the Tracy (https://tracy.nette.org)
*/
class BlueScreen
{
static init(ajax) {
BlueScreen.globalInit();
let blueScreen = document.getElementById('tracy-bs');
document.documentElement.classList.add('tracy-bs-visible');
if (navigator.platform.indexOf('Mac') > -1) {
blueScreen.classList.add('tracy-mac');
}
blueScreen.addEventListener('tracy-toggle', (e) => {
if (e.target.matches('#tracy-bs-toggle')) { // blue screen toggle
document.documentElement.classList.toggle('tracy-bs-visible', !e.detail.collapsed);
} else if (!e.target.matches('.tracy-dump *') && e.detail.originalEvent) { // panel toggle
e.detail.relatedTarget.classList.toggle('tracy-panel-fadein', !e.detail.collapsed);
}
});
if (!ajax) {
document.body.appendChild(blueScreen);
let id = location.href + document.querySelector('.tracy-section--error').textContent;
Tracy.Toggle.persist(blueScreen, sessionStorage.getItem('tracy-toggles-bskey') === id);
sessionStorage.setItem('tracy-toggles-bskey', id);
}
(new ResizeObserver(stickyFooter)).observe(blueScreen);
if (document.documentElement.classList.contains('tracy-bs-visible')) {
window.scrollTo(0, 0);
}
}
static globalInit() {
// enables toggling via ESC
document.addEventListener('keyup', (e) => {
if (e.keyCode === 27 && !e.shiftKey && !e.altKey && !e.ctrlKey && !e.metaKey) { // ESC
Tracy.Toggle.toggle(document.getElementById('tracy-bs-toggle'));
}
});
Tracy.TableSort.init();
Tracy.Tabs.init();
window.addEventListener('scroll', stickyFooter);
BlueScreen.globalInit = function() {};
}
static loadAjax(content) {
let ajaxBs = document.getElementById('tracy-bs');
if (ajaxBs) {
ajaxBs.remove();
}
document.body.insertAdjacentHTML('beforeend', content);
ajaxBs = document.getElementById('tracy-bs');
Tracy.Dumper.init(ajaxBs);
BlueScreen.init(true);
}
}
function stickyFooter() {
let footer = document.querySelector('#tracy-bs footer');
footer.classList.toggle('tracy-footer--sticky', false); // to measure footer.offsetTop
footer.classList.toggle('tracy-footer--sticky', footer.offsetHeight + footer.offsetTop - window.innerHeight - document.documentElement.scrollTop < 0);
}
let Tracy = window.Tracy = window.Tracy || {};
Tracy.BlueScreen = Tracy.BlueScreen || BlueScreen;

View File

@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace Tracy;
/**
* @var \Throwable $exception
* @var array[] $actions
* @var string[] $info
* @var string $source
* @var ?array $lastError
* @var string[] $httpHeaders
* @var callable $dump
* @var array $snapshot
* @var bool $showEnvironment
* @var BlueScreen $this
* @var bool $headersSent
* @var ?string $headersFile
* @var ?int $headersLine
* @var ?array $obStatus
* @var \Generator[] $generators
* @var \Fiber[] $fibers
*/
?>
<tracy-div id="tracy-bs" itemscope>
<a id="tracy-bs-toggle" href="#" class="tracy-toggle">&#xfeff;</a>
<div class="tracy-bs-main">
<?php $ex = $exception; $exceptions = []; ?>
<?php require __DIR__ . '/section-exception.phtml' ?>
<?php require __DIR__ . '/section-lastMutedError.phtml' ?>
<?php $bottomPanels = [] ?>
<?php foreach ($this->renderPanels(null) as $panel): ?>
<?php if (!empty($panel->bottom)) { $bottomPanels[] = $panel; continue; } ?>
<?php $collapsedClass = !isset($panel->collapsed) || $panel->collapsed ? ' tracy-collapsed' : ''; ?>
<section class="tracy-section">
<h2 class="tracy-section-label"><a href="#" data-tracy-ref="^+" class="tracy-toggle<?= $collapsedClass ?>"><?= Helpers::escapeHtml($panel->tab) ?></a></h2>
<div class="tracy-section-panel<?= $collapsedClass ?>">
<?= $panel->panel ?>
</div>
</section>
<?php endforeach ?>
<?php require __DIR__ . '/section-environment.phtml' ?>
<?php require __DIR__ . '/section-cli.phtml' ?>
<?php require __DIR__ . '/section-http.phtml' ?>
<?php foreach ($bottomPanels as $panel): ?>
<section class="tracy-section">
<h2 class="tracy-section-label"><a href="#" data-tracy-ref="^+" class="tracy-toggle"><?= Helpers::escapeHtml($panel->tab) ?></a></h2>
<div class="tracy-section-panel">
<?= $panel->panel ?>
</div>
</section>
<?php endforeach ?>
<footer>
<ul>
<li><b><a href="https://github.com/sponsors/dg" target="_blank" rel="noreferrer noopener">Please support Tracy via a donation 💙️</a></b></li>
<li>Report generated at <?= date('Y/m/d H:i:s') ?></li>
<?php foreach ($info as $item): ?><li><?= Helpers::escapeHtml($item) ?></li><?php endforeach ?>
</ul>
<div class="tracy-footer-logo"><a href="https://tracy.nette.org" rel="noreferrer"></a></div>
</footer>
</div>
<meta itemprop=tracy-snapshot content=<?= Dumper::formatSnapshotAttribute($snapshot) ?>>
</tracy-div>

View File

@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
namespace Tracy;
/**
* @var \Throwable $exception
* @var string $title
* @var ?string $nonce
* @var string $css
*/
$code = $exception->getCode() ? ' #' . $exception->getCode() : '';
$nonceAttr = $nonce ? ' nonce="' . Helpers::escapeHtml($nonce) . '"' : '';
$chain = Helpers::getExceptionChain($exception);
?><!DOCTYPE html><!-- "' --></textarea></script></style></pre></xmp></a></iframe></noembed></noframes></noscript></option></select></template></title></table></p></code>
<html>
<head>
<meta charset="utf-8">
<meta name="robots" content="noindex">
<meta name="generator" content="Tracy by Nette Framework">
<title><?= Helpers::escapeHtml($title . ': ' . $exception->getMessage() . $code) ?></title>
<!-- in <?= str_replace('--', '- ', Helpers::escapeHtml($exception->getFile() . ':' . $exception->getLine())) ?> -->
<?php if (count($chain) > 1): ?>
<!--<?php foreach (array_slice($chain, 1) as $ex) {
echo str_replace('--', '- ', Helpers::escapeHtml("\n\tcaused by " . Helpers::getClass($ex) . ': ' . $ex->getMessage() . ($ex->getCode() ? ' #' . $ex->getCode() : '')));
} ?> -->
<?php endif ?>
<style class="tracy-debug">
<?= str_replace('</', '<\/', $css) ?>
</style>
</head>
<body>
<?php require __DIR__ . '/content.phtml' ?>
<script<?= $nonceAttr ?>>
'use strict';
<?php
array_map(function ($file) { echo '(function(){', str_replace(['<!--', '</s'], ['<\!--', '<\/s'], Helpers::minifyJs(file_get_contents($file))), '})();'; }, [
__DIR__ . '/../../assets/toggle.js',
__DIR__ . '/../../assets/table-sort.js',
__DIR__ . '/../../assets/tabs.js',
__DIR__ . '/../../Dumper/assets/dumper.js',
__DIR__ . '/bluescreen.js',
]);
?>
Tracy.BlueScreen.init();
</script>
</body>
</html>

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Tracy;
/**
* @var string $source
* @var callable $dump
*/
if (!Helpers::isCli()) {
return;
}
?>
<section class="tracy-section">
<h2 class="tracy-section-label"><a href="#" data-tracy-ref="^+" class="tracy-toggle tracy-collapsed">CLI request</a></h2>
<div class="tracy-section-panel tracy-collapsed">
<h3>Process ID <?= Helpers::escapeHtml(getmypid()) ?></h3>
<pre>php<?= Helpers::escapeHtml(explode('):', $source, 2)[1]) ?></pre>
<h3>Arguments</h3>
<div class="tracy-pane">
<table>
<?php foreach ($_SERVER['argv'] as $k => $v): ?>
<tr><th><?= Helpers::escapeHtml($k) ?></th><td><?= $dump($v, $k) ?></td></tr>
<?php endforeach ?>
</table>
</div>
</div>
</section>

View File

@ -0,0 +1,103 @@
<?php
declare(strict_types=1);
namespace Tracy;
/**
* @var callable $dump
* @var bool $showEnvironment
* @var array $obStatus
* @var BlueScreen $this
*/
if (!$showEnvironment) {
return;
}
$constants = get_defined_constants(true)['user'] ?? [];
?>
<section class="tracy-section">
<h2 class="tracy-section-label"><a href="#" data-tracy-ref="^+" class="tracy-toggle tracy-collapsed">Environment</a></h2>
<div class="tracy-section-panel tracy-collapsed">
<div class="tracy-tabs">
<ul class="tracy-tab-bar">
<li class="tracy-tab-label tracy-active"><a href="#">$_SERVER</a></li>
<?php if ($_SESSION ?? null): ?>
<li class="tracy-tab-label"><a href="#">$_SESSION</a></li>
<?php endif ?>
<?php if ($constants): ?>
<li class="tracy-tab-label"><a href="#">Constants</a></li>
<?php endif ?>
<li class="tracy-tab-label"><a href="#">Configuration</a></li>
<?php if ($obStatus): ?>
<li class="tracy-tab-label"><a href="#">Output buffers</a></li>
<?php endif ?>
</ul>
<div>
<div class="tracy-tab-panel tracy-pane tracy-active">
<table class="tracy-sortable">
<?php foreach ($_SERVER as $k => $v): ?>
<tr><th><?= Helpers::escapeHtml($k) ?></th><td><?= $dump($v, $k) ?></td></tr>
<?php endforeach ?>
</table>
</div>
<?php if ($_SESSION ?? null): ?>
<div class="tracy-tab-panel">
<div class="tracy-pane">
<table class="tracy-sortable">
<?php foreach ($_SESSION as $k => $v): ?>
<tr><th><?= Helpers::escapeHtml($k) ?></th><td><?= $k === '__NF' ? '<i>Nette Session</i>' : $dump($v, $k) ?></td></tr>
<?php endforeach ?>
</table>
</div>
<?php if (!empty($_SESSION['__NF']['DATA'])):?>
<h3>Nette Session</h3>
<div class="tracy-pane">
<table class="tracy-sortable">
<?php foreach ($_SESSION['__NF']['DATA'] as $k => $v): ?>
<tr><th><?= Helpers::escapeHtml($k) ?></th><td><?= $dump($v, $k) ?></td></tr>
<?php endforeach ?>
</table>
</div>
<?php endif ?>
</div>
<?php endif ?>
<?php if ($constants): ?>
<div class="tracy-tab-panel tracy-pane">
<table class="tracy-sortable">
<?php foreach ($constants as $k => $v): ?>
<tr><th><?= Helpers::escapeHtml($k) ?></th><td><?= $dump($v, $k) ?></td></tr>
<?php endforeach ?>
</table>
</div>
<?php endif ?>
<div class="tracy-tab-panel">
<?php $this->renderPhpInfo() ?>
</div>
<?php if ($obStatus): ?>
<div class="tracy-tab-panel tracy-pane">
<?= Dumper::toHtml($obStatus, [Dumper::COLLAPSE_COUNT => 10]) ?>
</div>
<?php endif ?>
</div>
</div>
</div>
</section>

View File

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Tracy;
/**
* @var \Throwable $ex
* @var \Throwable[] $exceptions
* @var BlueScreen $this
* @var array[] $actions
* @var callable $dump
*/
$ex = $ex->getPrevious();
if (!$ex || in_array($ex, $exceptions, true)) {
return;
}
$exceptions[] = $ex;
?>
<section class="tracy-section" id="tracyCaused<?= count($exceptions) ?>">
<h2 class="tracy-section-label"><a href="#" data-tracy-ref="^+" class="tracy-toggle<?= ($collapsed = count($exceptions) > 1) ? ' tracy-collapsed' : '' ?>">Caused by</a></h2>
<div class="tracy-section-panel tracy-section--causedby<?= $collapsed ? ' tracy-collapsed' : '' ?>">
<?php require __DIR__ . '/section-exception.phtml' ?>
</div>
</section>

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Tracy;
/**
* @var \Throwable $ex
* @var callable $dump
*/
if (count((array) $ex) <= count((array) new \Exception)) {
return;
}
?>
<section class="tracy-section">
<h2 class="tracy-section-label"><a href="#" data-tracy-ref="^+" class="tracy-toggle tracy-collapsed">Exception</a></h2>
<div class="tracy-section-panel tracy-collapsed">
<?= $dump($ex) ?>
</div>
</section>

View File

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace Tracy;
/**
* @var \Throwable $ex
* @var callable $dump
*/
if (!$ex instanceof \ErrorException || empty($ex->context) || !is_array($ex->context)) {
return;
}
?>
<section class="tracy-section">
<h2 class="tracy-section-label"><a href="#" data-tracy-ref="^+" class="tracy-toggle tracy-collapsed">Variables</a></h2>
<div class="tracy-section-panel tracy-collapsed">
<div class="tracy-pane">
<table class="tracy-sortable">
<?php foreach ($ex->context as $k => $v): ?>
<tr><th><?= Helpers::escapeHtml($k) ?></th><td><?= $dump($v, $k) ?></td></tr>
<?php endforeach ?>
</table>
</div>
</div>
</section>

View File

@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
namespace Tracy;
/**
* @var \Throwable $ex
* @var \Throwable[] $exceptions
* @var array[] $actions
* @var callable $dump
* @var BlueScreen $this
* @var \Generator[] $generators
* @var \Fiber[] $fibers
*/
?>
<?php require __DIR__ . '/section-header.phtml' ?>
<?php foreach ($this->renderPanels($ex) as $panel): ?>
<section class="tracy-section">
<h2 class="tracy-section-label"><a href="#" data-tracy-ref="^+" class="tracy-toggle"><?= Helpers::escapeHtml($panel->tab) ?></a></h2>
<div class="tracy-section-panel">
<?= $panel->panel ?>
</div>
</section>
<?php endforeach ?>
<?php if (!$exceptions && ($generators || $fibers)): ?>
<section class="tracy-section tracy-section--stack">
<div class="tracy-section-panel">
<div class="tracy-tabs">
<ul class="tracy-tab-bar">
<li class="tracy-tab-label tracy-active"><a href="#">Main thread</a></li>
<?php foreach ($generators as $id => $generator): ?>
<li class="tracy-tab-label"><a href="#">Generator #<?= $id ?></a></li>
<?php endforeach ?>
<?php foreach ($fibers as $id => $fiber): ?>
<li class="tracy-tab-label"><a href="#">Fiber #<?= $id ?></a></li>
<?php endforeach ?>
</ul>
<div>
<div class="tracy-tab-panel tracy-active">
<?php require __DIR__ . '/section-stack-exception.phtml' ?>
</div>
<?php foreach ($generators as $generator): ?>
<div class="tracy-tab-panel">
<?php require __DIR__ . '/section-stack-generator.phtml' ?>
</div>
<?php endforeach ?>
<?php foreach ($fibers as $fiber): ?>
<div class="tracy-tab-panel">
<?php require __DIR__ . '/section-stack-fiber.phtml' ?>
</div>
<?php endforeach ?>
</div>
</div>
</div>
</section>
<?php else: ?>
<?php require __DIR__ . '/section-stack-exception.phtml' ?>
<?php endif ?>
<?php require __DIR__ . '/section-exception-variables.phtml' ?>
<?php require __DIR__ . '/section-exception-exception.phtml' ?>
<?php require __DIR__ . '/section-exception-causedBy.phtml' ?>

View File

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace Tracy;
/**
* @var \Throwable $ex
* @var \Throwable[] $exceptions
* @var array[] $actions
* @var BlueScreen $this
*/
$title = $ex instanceof \ErrorException
? Helpers::errorTypeToString($ex->getSeverity())
: Helpers::getClass($ex);
$code = $ex->getCode() ? ' #' . $ex->getCode() : '';
?>
<section class="tracy-section tracy-section--error">
<?php if ($ex->getMessage()): ?><p><?= Helpers::escapeHtml($title . $code) ?></p><?php endif ?>
<h1><span><?= $this->formatMessage($ex) ?: Helpers::escapeHtml($title . $code) ?></span>
<?php foreach ($actions as $item): ?>
<a href="<?= Helpers::escapeHtml($item['link']) ?>" class="tracy-action"<?= empty($item['external']) ? '' : ' target="_blank" rel="noreferrer noopener"'?>><?= Helpers::escapeHtml($item['label']) ?>&#x25ba;</a>
<?php endforeach ?>
</h1>
</section>
<?php if ($ex->getPrevious()): ?>
<div class="tracy-caused">
<a href="#tracyCaused<?= count($exceptions) + 1 ?>">Caused by <?= Helpers::escapeHtml(Helpers::getClass($ex->getPrevious())) ?></a>
</div>
<?php endif ?>

View File

@ -0,0 +1,91 @@
<?php
declare(strict_types=1);
namespace Tracy;
/**
* @var string $source
* @var string[] $httpHeaders
* @var callable $dump
* @var bool $headersSent
* @var ?string $headersFile
* @var ?int $headersLine
*/
if (Helpers::isCli()) {
return;
}
?>
<section class="tracy-section">
<h2 class="tracy-section-label"><a href="#" data-tracy-ref="^+" class="tracy-toggle tracy-collapsed">HTTP</a></h2>
<div class="tracy-section-panel tracy-collapsed">
<div class="tracy-tabs">
<ul class="tracy-tab-bar">
<li class="tracy-tab-label tracy-active"><a href="#">Request</a></li>
<li class="tracy-tab-label"><a href="#">Response</a></li>
</ul>
<div>
<div class="tracy-tab-panel tracy-active">
<h3><?= Helpers::escapeHtml($_SERVER['REQUEST_METHOD'] ?? 'URL') ?> <a href="<?= Helpers::escapeHtml($source) ?>" target="_blank" rel="noreferrer noopener" style="font-weight: normal"><?= Helpers::escapeHtml($source) ?></a></h3>
<?php if ($httpHeaders): ?>
<div class="tracy-pane">
<table class="tracy-sortable">
<?php foreach ($httpHeaders as $k => $v): ?>
<tr><th><?= Helpers::escapeHtml($k) ?></th><td><?= $dump($v, $k) ?></td></tr>
<?php endforeach ?>
</table>
</div>
<?php endif ?>
<?php foreach (['_GET', '_POST', '_COOKIE'] as $name): ?>
<h3>$<?= Helpers::escapeHtml($name) ?></h3>
<?php if (empty($GLOBALS[$name])):?>
<p><i>empty</i></p>
<?php else: ?>
<div class="tracy-pane">
<table class="tracy-sortable">
<?php foreach ($GLOBALS[$name] as $k => $v): ?>
<tr><th><?= Helpers::escapeHtml($k) ?></th><td><?= $dump($v, $k) ?></td></tr>
<?php endforeach ?>
</table>
</div>
<?php endif ?>
<?php endforeach ?>
</div>
<div class="tracy-tab-panel">
<h3>Code: <?= Helpers::escapeHtml(http_response_code()) ?></h3>
<?php if (headers_list()): ?>
<div class="tracy-pane">
<table class="tracy-sortable">
<?php foreach (headers_list() as $s): $s = explode(':', $s, 2); ?>
<tr><th><?= Helpers::escapeHtml($s[0]) ?></th><td><?= $dump(trim($s[1]), $s[0]) ?></td></tr>
<?php endforeach ?>
</table>
</div>
<?php else: ?>
<p><i>no headers</i></p>
<?php endif ?>
<?php if ($headersSent && $headersFile && @is_file($headersFile)): ?>
<p>Headers have been sent, output started at <?= Helpers::editorLink($headersFile, $headersLine) ?> <a href="#" data-tracy-ref="^p + div" class="tracy-toggle tracy-collapsed">source</a></p>
<div class="tracy-collapsed"><?= BlueScreen::highlightFile($headersFile, $headersLine) ?></div>
<?php elseif ($headersSent): ?>
<p>Headers have been sent</p>
<?php else: ?>
<p>Headers were not sent at the time the exception was thrown</p>
<?php endif ?>
</div>
</div>
</div>
</div>
</section>

View File

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Tracy;
/**
* @var ?array $lastError
*/
if (!$lastError) {
return;
}
?>
<section class="tracy-section">
<h2 class="tracy-section-label"><a href="#" data-tracy-ref="^+" class="tracy-toggle tracy-collapsed">Last muted error</a></h2>
<div class="tracy-section-panel tracy-collapsed">
<h3><?= Helpers::errorTypeToString($lastError['type']) ?>: <?= Helpers::escapeHtml($lastError['message']) ?></h3>
<p><i>Note: the last muted error may have nothing to do with the thrown exception.</i></p>
<?php if (isset($lastError['file']) && is_file($lastError['file'])): ?>
<p><?= Helpers::editorLink($lastError['file'], $lastError['line']) ?></p>
<div><?= BlueScreen::highlightFile($lastError['file'], $lastError['line']) ?></div>
<?php else: ?>
<p><i>inner-code</i><?php if (isset($lastError['line'])) echo ':', $lastError['line'] ?></p>
<?php endif ?>
</div>
</section>

View File

@ -0,0 +1,92 @@
<?php
declare(strict_types=1);
namespace Tracy;
/**
* @var callable $dump
* @var int $expanded
* @var array $stack
*/
if (!$stack) {
return;
}
?>
<section class="tracy-section">
<h2 class="tracy-section-label"><a href="#" data-tracy-ref="^+" class="tracy-toggle">Call stack</a></h2>
<div class="tracy-section-panel">
<div class="tracy-callstack">
<?php foreach ($stack as $key => $row): ?>
<?php $clickable = !empty($row['args']) || (isset($row['file']) && is_file($row['file'])) ?>
<div class="tracy-callstack-file">
<?php if (isset($row['file']) && is_file($row['file'])): ?>
<?= Helpers::editorLink($row['file'], $row['line']) ?>
<?php else: ?>
<i>inner-code</i><?php if (isset($row['line'])) echo ':', $row['line'] ?>
<?php endif ?>
</div>
<div class="tracy-callstack-callee">
<?php if ($clickable): ?>
<a href="#" data-tracy-ref="^div + div" class="tracy-toggle<?php if ($expanded !== $key) echo ' tracy-collapsed' ?>"><?php endif ?>
<?php if (isset($row['class'])) echo Helpers::escapeHtml($row['class']), '::' ?><b><?= Helpers::escapeHtml($row['function']) ?></b> <?= empty($row['args']) ? '()' : '(...)' ?>
<?php if ($clickable): ?></a><?php endif ?>
</div>
<?php if ($clickable): ?>
<div class="tracy-callstack-additional<?php if ($expanded !== $key) echo ' tracy-collapsed' ?>">
<?php $sourceOriginal = isset($row['file']) && is_file($row['file']) ? [$row['file'], $row['line']] : null ?>
<?php $sourceMapped = $sourceOriginal ? Debugger::mapSource(...$sourceOriginal) : null ?>
<?php if ($sourceOriginal && $sourceMapped): ?>
<div class="tracy-tabs">
<ul class="tracy-tab-bar">
<li class="tracy-tab-label<?= $sourceMapped['active'] ? '' : ' tracy-active' ?>"><a href="#">PHP</a></li>
<li class="tracy-tab-label<?= $sourceMapped['active'] ? ' tracy-active' : '' ?>"><a href="#"><?= Helpers::escapeHtml($sourceMapped['label']) ?></a></li>
</ul>
<div>
<div class="tracy-tab-panel<?= $sourceMapped['active'] ? '' : ' tracy-active' ?>">
<?= BlueScreen::highlightFile(...$sourceOriginal) ?>
</div>
<div class="tracy-tab-panel<?= $sourceMapped['active'] ? ' tracy-active' : '' ?>">
<?= BlueScreen::highlightFile($sourceMapped['file'], $sourceMapped['line'], 15, false) ?>
</div>
</div>
</div>
<?php elseif ($sourceOriginal): ?>
<?= BlueScreen::highlightFile(...$sourceOriginal) ?>
<?php endif ?>
<?php if (!empty($row['args'])): ?>
<table class="tracy-callstack-args">
<?php
try {
$r = isset($row['class']) ? new \ReflectionMethod($row['class'], $row['function']) : new \ReflectionFunction($row['function']);
$params = $r->getParameters();
} catch (\Exception $e) {
$params = [];
}
foreach ($row['args'] as $k => $v) {
$argName = isset($params[$k]) && !$params[$k]->isVariadic() ? $params[$k]->name : $k;
echo '<tr><th>', Helpers::escapeHtml((is_string($argName) ? '$' : '#') . $argName), '</th><td>';
echo $dump($v, (string) $argName);
echo "</td></tr>\n";
}
?>
</table>
<?php endif ?>
</div>
<?php endif ?>
<?php endforeach ?>
</div>
</div>
</section>

View File

@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Tracy;
/**
* @var \Throwable $ex
* @var callable $dump
* @var BlueScreen $this
*/
$stack = $ex->getTrace();
$expanded = null;
if (
(!$ex instanceof \ErrorException
|| in_array($ex->getSeverity(), [E_USER_NOTICE, E_USER_WARNING, E_USER_DEPRECATED], true))
&& $this->isCollapsed($ex->getFile())
) {
foreach ($stack as $key => $row) {
if (isset($row['file']) && !$this->isCollapsed($row['file'])) {
$expanded = $key;
break;
}
}
}
if (in_array($stack[0]['class'] ?? null, [DevelopmentStrategy::class, ProductionStrategy::class], true)) {
array_shift($stack);
}
if (($stack[0]['class'] ?? null) === Debugger::class && in_array($stack[0]['function'], ['shutdownHandler', 'errorHandler'], true)) {
array_shift($stack);
}
$file = $ex->getFile();
$line = $ex->getLine();
require __DIR__ . '/section-stack-sourceFile.phtml';
require __DIR__ . '/section-stack-callStack.phtml';

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Tracy;
/**
* @var \Fiber $fiber
* @var callable $dump
*/
$ref = new \ReflectionFiber($fiber);
$stack = $ref->getTrace();
$expanded = 0;
require __DIR__ . '/section-stack-callStack.phtml';

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Tracy;
/**
* @var \Generator $generator
* @var callable $dump
*/
$ref = new \ReflectionGenerator($generator);
$stack = $ref->getTrace();
$expanded = null;
$execGenerator = $ref->getExecutingGenerator();
$refExec = new \ReflectionGenerator($execGenerator);
$file = $refExec->getExecutingFile();
$line = $refExec->getExecutingLine();
require __DIR__ . '/section-stack-sourceFile.phtml';
require __DIR__ . '/section-stack-callStack.phtml';

View File

@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace Tracy;
/**
* @var string $file
* @var int $line
* @var int $expanded
*/
$sourceOriginal = $file && is_file($file) ? [$file, $line] : null;
$sourceMapped = $sourceOriginal ? Debugger::mapSource($file, $line) : null;
?>
<section class="tracy-section">
<h2 class="tracy-section-label"><a href="#" data-tracy-ref="^+" class="tracy-toggle<?= ($collapsed = $expanded !== null) ? ' tracy-collapsed' : '' ?>">Source file</a></h2>
<div class="tracy-section-panel<?= $collapsed ? ' tracy-collapsed' : '' ?>">
<?php if ($sourceOriginal && $sourceMapped): ?>
<div class="tracy-tabs">
<ul class="tracy-tab-bar">
<li class="tracy-tab-label<?= $sourceMapped['active'] ? '' : ' tracy-active' ?>"><a href="#">PHP</a></li>
<li class="tracy-tab-label<?= $sourceMapped['active'] ? ' tracy-active' : '' ?>"><a href="#"><?= Helpers::escapeHtml($sourceMapped['label']) ?></a></li>
</ul>
<div>
<div class="tracy-tab-panel<?= $sourceMapped['active'] ? '' : ' tracy-active' ?>">
<p><b>File:</b> <?= Helpers::editorLink(...$sourceOriginal) ?></p>
<?= BlueScreen::highlightFile(...$sourceOriginal) ?>
</div>
<div class="tracy-tab-panel<?= $sourceMapped['active'] ? ' tracy-active' : '' ?>">
<p><b>File:</b> <?= Helpers::editorLink($sourceMapped['file'], $sourceMapped['line']) ?></p>
<?= BlueScreen::highlightFile($sourceMapped['file'], $sourceMapped['line'], 15, false) ?>
</div>
</div>
</div>
<?php else: ?>
<p><b>File:</b> <?= Helpers::editorLink($file, $line) ?></p>
<?php if ($sourceOriginal) echo BlueScreen::highlightFile(...$sourceOriginal) ?>
<?php endif ?>
</div>
</section>

View File

@ -0,0 +1,448 @@
<?php
/**
* This file is part of the Tracy (https://tracy.nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare (strict_types=1);
namespace RectorPrefix202302\Tracy;
use ErrorException;
/**
* Debugger: displays and logs errors.
*/
class Debugger
{
public const VERSION = '2.9.6';
/** server modes for Debugger::enable() */
public const Development = \false, Production = \true, Detect = null;
public const DEVELOPMENT = self::Development, PRODUCTION = self::Production, DETECT = self::Detect;
public const CookieSecret = 'tracy-debug';
public const COOKIE_SECRET = self::CookieSecret;
/** @var bool in production mode is suppressed any debugging output */
public static $productionMode = self::Detect;
/** @var bool whether to display debug bar in development mode */
public static $showBar = \true;
/** @var bool whether to send data to FireLogger in development mode */
public static $showFireLogger = \true;
/** @var int size of reserved memory */
public static $reservedMemorySize = 500000;
/** @var bool */
private static $enabled = \false;
/** @var string|null reserved memory; also prevents double rendering */
private static $reserved;
/** @var int initial output buffer level */
private static $obLevel;
/** @var ?array output buffer status @internal */
public static $obStatus;
/********************* errors and exceptions reporting ****************d*g**/
/** @var bool|int determines whether any error will cause immediate death in development mode; if integer that it's matched against error severity */
public static $strictMode = \false;
/** @var bool|int disables the @ (shut-up) operator so that notices and warnings are no longer hidden; if integer than it's matched against error severity */
public static $scream = \false;
/** @var callable[] functions that are automatically called after fatal error */
public static $onFatalError = [];
/********************* Debugger::dump() ****************d*g**/
/** @var int how many nested levels of array/object properties display by dump() */
public static $maxDepth = 15;
/** @var int how long strings display by dump() */
public static $maxLength = 150;
/** @var int how many items in array/object display by dump() */
public static $maxItems = 100;
/** @var bool display location by dump()? */
public static $showLocation;
/** @var string[] sensitive keys not displayed by dump() */
public static $keysToHide = [];
/** @var string theme for dump() */
public static $dumpTheme = 'light';
/** @deprecated */
public static $maxLen;
/********************* logging ****************d*g**/
/** @var string|null name of the directory where errors should be logged */
public static $logDirectory;
/** @var int log bluescreen in production mode for this error severity */
public static $logSeverity = 0;
/** @var string|array email(s) to which send error notifications */
public static $email;
/** for Debugger::log() and Debugger::fireLog() */
public const DEBUG = ILogger::DEBUG, INFO = ILogger::INFO, WARNING = ILogger::WARNING, ERROR = ILogger::ERROR, EXCEPTION = ILogger::EXCEPTION, CRITICAL = ILogger::CRITICAL;
/********************* misc ****************d*g**/
/** @var float timestamp with microseconds of the start of the request */
public static $time;
/** @var string URI pattern mask to open editor */
public static $editor = 'editor://%action/?file=%file&line=%line&search=%search&replace=%replace';
/** @var array replacements in path */
public static $editorMapping = [];
/** @var string command to open browser (use 'start ""' in Windows) */
public static $browser;
/** @var string custom static error template */
public static $errorTemplate;
/** @var string[] */
public static $customCssFiles = [];
/** @var string[] */
public static $customJsFiles = [];
/** @var callable[] */
private static $sourceMappers = [];
/** @var array|null */
private static $cpuUsage;
/********************* services ****************d*g**/
/** @var BlueScreen */
private static $blueScreen;
/** @var Bar */
private static $bar;
/** @var ILogger */
private static $logger;
/** @var ILogger */
private static $fireLogger;
/** @var array{DevelopmentStrategy, ProductionStrategy} */
private static $strategy;
/** @var SessionStorage */
private static $sessionStorage;
/**
* Static class - cannot be instantiated.
*/
public final function __construct()
{
throw new \LogicException();
}
/**
* Enables displaying or logging errors and exceptions.
* @param bool|string|string[] $mode use constant Debugger::Production, Development, Detect (autodetection) or IP address(es) whitelist.
* @param string $logDirectory error log directory
* @param string|array $email administrator email; enables email sending in production mode
*/
public static function enable($mode = null, ?string $logDirectory = null, $email = null) : void
{
if ($mode !== null || self::$productionMode === null) {
self::$productionMode = \is_bool($mode) ? $mode : !self::detectDebugMode($mode);
}
self::$reserved = \str_repeat('t', self::$reservedMemorySize);
self::$time = $_SERVER['REQUEST_TIME_FLOAT'] ?? \microtime(\true);
self::$obLevel = \ob_get_level();
self::$cpuUsage = !self::$productionMode && \function_exists('getrusage') ? \getrusage() : null;
// logging configuration
if ($email !== null) {
self::$email = $email;
}
if ($logDirectory !== null) {
self::$logDirectory = $logDirectory;
}
if (self::$logDirectory) {
if (!\preg_match('#([a-z]+:)?[/\\\\]#Ai', self::$logDirectory)) {
self::exceptionHandler(new \RuntimeException('Logging directory must be absolute path.'));
exit(255);
} elseif (!\is_dir(self::$logDirectory)) {
self::exceptionHandler(new \RuntimeException("Logging directory '" . self::$logDirectory . "' is not found."));
exit(255);
}
}
// php configuration
if (\function_exists('ini_set')) {
\ini_set('display_errors', '0');
// or 'stderr'
\ini_set('html_errors', '0');
\ini_set('log_errors', '0');
\ini_set('zend.exception_ignore_args', '0');
}
\error_reporting(\E_ALL);
$strategy = self::getStrategy();
$strategy->initialize();
self::dispatch();
if (self::$enabled) {
return;
}
\register_shutdown_function([self::class, 'shutdownHandler']);
\set_exception_handler(function (\Throwable $e) {
self::exceptionHandler($e);
exit(255);
});
\set_error_handler([self::class, 'errorHandler']);
foreach (['Bar/Bar', 'Bar/DefaultBarPanel', 'BlueScreen/BlueScreen', 'Dumper/Describer', 'Dumper/Dumper', 'Dumper/Exposer', 'Dumper/Renderer', 'Dumper/Value', 'Logger/FireLogger', 'Logger/Logger', 'Session/SessionStorage', 'Session/FileSession', 'Session/NativeSession', 'Helpers'] as $path) {
require_once \dirname(__DIR__) . "/{$path}.php";
}
self::$enabled = \true;
}
public static function dispatch() : void
{
if (!Helpers::isCli() && self::getStrategy()->sendAssets()) {
self::$showBar = \false;
exit;
}
}
/**
* Renders loading <script>
*/
public static function renderLoader() : void
{
self::getStrategy()->renderLoader();
}
public static function isEnabled() : bool
{
return self::$enabled;
}
/**
* Shutdown handler to catch fatal errors and execute of the planned activities.
* @internal
*/
public static function shutdownHandler() : void
{
$error = \error_get_last();
if (\in_array($error['type'] ?? null, [\E_ERROR, \E_CORE_ERROR, \E_COMPILE_ERROR, \E_PARSE, \E_RECOVERABLE_ERROR, \E_USER_ERROR], \true)) {
self::exceptionHandler(Helpers::fixStack(new ErrorException($error['message'], 0, $error['type'], $error['file'], $error['line'])));
} elseif (($error['type'] ?? null) === \E_COMPILE_WARNING) {
\error_clear_last();
self::errorHandler($error['type'], $error['message'], $error['file'], $error['line']);
}
self::$reserved = null;
if (self::$showBar && !Helpers::isCli()) {
try {
self::getStrategy()->renderBar();
} catch (\Throwable $e) {
self::exceptionHandler($e);
}
}
}
/**
* Handler to catch uncaught exception.
* @internal
*/
public static function exceptionHandler(\Throwable $exception) : void
{
$firstTime = (bool) self::$reserved;
self::$reserved = null;
self::$obStatus = \ob_get_status(\true);
if (!\headers_sent()) {
\http_response_code(isset($_SERVER['HTTP_USER_AGENT']) && \strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE ') !== \false ? 503 : 500);
}
Helpers::improveException($exception);
self::removeOutputBuffers(\true);
self::getStrategy()->handleException($exception, $firstTime);
try {
foreach ($firstTime ? self::$onFatalError : [] as $handler) {
$handler($exception);
}
} catch (\Throwable $e) {
try {
self::log($e, self::EXCEPTION);
} catch (\Throwable $e) {
}
}
}
/**
* Handler to catch warnings and notices.
* @return bool|null false to call normal error handler, null otherwise
* @throws ErrorException
* @internal
*/
public static function errorHandler(int $severity, string $message, string $file, int $line, ?array $context = null) : bool
{
$error = \error_get_last();
if (($error['type'] ?? null) === \E_COMPILE_WARNING) {
\error_clear_last();
self::errorHandler($error['type'], $error['message'], $error['file'], $error['line']);
}
if ($context) {
$context = (array) (object) $context;
// workaround for PHP bug #80234
}
if ($severity === \E_RECOVERABLE_ERROR || $severity === \E_USER_ERROR) {
if (Helpers::findTrace(\debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS), '*::__toString')) {
// workaround for PHP < 7.4
$previous = isset($context['e']) && $context['e'] instanceof \Throwable ? $context['e'] : null;
$e = new ErrorException($message, 0, $severity, $file, $line, $previous);
@($e->context = $context);
// dynamic properties are deprecated since PHP 8.2
self::exceptionHandler($e);
exit(255);
}
$e = new ErrorException($message, 0, $severity, $file, $line);
@($e->context = $context);
// dynamic properties are deprecated since PHP 8.2
throw $e;
} elseif ($severity & \error_reporting() || (\is_int(self::$scream) ? $severity & self::$scream : self::$scream)) {
self::getStrategy()->handleError($severity, $message, $file, $line, $context);
}
return \false;
// calls normal error handler to fill-in error_get_last()
}
/** @internal */
public static function removeOutputBuffers(bool $errorOccurred) : void
{
while (\ob_get_level() > self::$obLevel) {
$status = \ob_get_status();
if (\in_array($status['name'], ['ob_gzhandler', 'zlib output compression'], \true)) {
break;
}
$fnc = $status['chunk_size'] || !$errorOccurred ? 'ob_end_flush' : 'ob_end_clean';
if (!@$fnc()) {
// @ may be not removable
break;
}
}
}
/********************* services ****************d*g**/
public static function getBlueScreen() : BlueScreen
{
if (!self::$blueScreen) {
self::$blueScreen = new BlueScreen();
self::$blueScreen->info = ['PHP ' . \PHP_VERSION, $_SERVER['SERVER_SOFTWARE'] ?? null, 'Tracy ' . self::VERSION];
}
return self::$blueScreen;
}
public static function getBar() : Bar
{
if (!self::$bar) {
self::$bar = new Bar();
self::$bar->addPanel($info = new DefaultBarPanel('info'), 'Tracy:info');
$info->cpuUsage = self::$cpuUsage;
self::$bar->addPanel(new DefaultBarPanel('errors'), 'Tracy:errors');
// filled by errorHandler()
}
return self::$bar;
}
public static function setLogger(ILogger $logger) : void
{
self::$logger = $logger;
}
public static function getLogger() : ILogger
{
if (!self::$logger) {
self::$logger = new Logger(self::$logDirectory, self::$email, self::getBlueScreen());
self::$logger->directory =& self::$logDirectory;
// back compatiblity
self::$logger->email =& self::$email;
}
return self::$logger;
}
public static function getFireLogger() : ILogger
{
if (!self::$fireLogger) {
self::$fireLogger = new FireLogger();
}
return self::$fireLogger;
}
/** @return ProductionStrategy|DevelopmentStrategy @internal */
public static function getStrategy()
{
if (empty(self::$strategy[self::$productionMode])) {
self::$strategy[self::$productionMode] = self::$productionMode ? new ProductionStrategy() : new DevelopmentStrategy(self::getBar(), self::getBlueScreen(), new DeferredContent(self::getSessionStorage()));
}
return self::$strategy[self::$productionMode];
}
public static function setSessionStorage(SessionStorage $storage) : void
{
if (self::$sessionStorage) {
throw new \Exception('Storage is already set.');
}
self::$sessionStorage = $storage;
}
/** @internal */
public static function getSessionStorage() : SessionStorage
{
if (!self::$sessionStorage) {
self::$sessionStorage = @\is_dir($dir = \session_save_path()) || @\is_dir($dir = \ini_get('upload_tmp_dir')) || @\is_dir($dir = \sys_get_temp_dir()) || ($dir = self::$logDirectory) ? new FileSession($dir) : new NativeSession();
}
return self::$sessionStorage;
}
/********************* useful tools ****************d*g**/
/**
* Dumps information about a variable in readable format.
* @tracySkipLocation
* @param mixed $var variable to dump
* @param bool $return return output instead of printing it? (bypasses $productionMode)
* @return mixed variable itself or dump
*/
public static function dump($var, bool $return = \false)
{
if ($return) {
$options = [Dumper::DEPTH => self::$maxDepth, Dumper::TRUNCATE => self::$maxLength, Dumper::ITEMS => self::$maxItems];
return Helpers::isCli() ? Dumper::toText($var) : Helpers::capture(function () use($var, $options) {
Dumper::dump($var, $options);
});
} elseif (!self::$productionMode) {
$html = Helpers::isHtmlMode();
echo $html ? '<tracy-div>' : '';
Dumper::dump($var, [Dumper::DEPTH => self::$maxDepth, Dumper::TRUNCATE => self::$maxLength, Dumper::ITEMS => self::$maxItems, Dumper::LOCATION => self::$showLocation, Dumper::THEME => self::$dumpTheme, Dumper::KEYS_TO_HIDE => self::$keysToHide]);
echo $html ? '</tracy-div>' : '';
}
return $var;
}
/**
* Starts/stops stopwatch.
* @return float elapsed seconds
*/
public static function timer(?string $name = null) : float
{
static $time = [];
$now = \microtime(\true);
$delta = isset($time[$name]) ? $now - $time[$name] : 0;
$time[$name] = $now;
return $delta;
}
/**
* Dumps information about a variable in Tracy Debug Bar.
* @tracySkipLocation
* @param mixed $var
* @return mixed variable itself
*/
public static function barDump($var, ?string $title = null, array $options = [])
{
if (!self::$productionMode) {
static $panel;
if (!$panel) {
self::getBar()->addPanel($panel = new DefaultBarPanel('dumps'), 'Tracy:dumps');
}
$panel->data[] = ['title' => $title, 'dump' => Dumper::toHtml($var, $options + [Dumper::DEPTH => self::$maxDepth, Dumper::TRUNCATE => self::$maxLength, Dumper::LOCATION => self::$showLocation ?: Dumper::LOCATION_CLASS | Dumper::LOCATION_SOURCE, Dumper::LAZY => \true])];
}
return $var;
}
/**
* Logs message or exception.
* @param mixed $message
* @return mixed
*/
public static function log($message, string $level = ILogger::INFO)
{
return self::getLogger()->log($message, $level);
}
/**
* Sends message to FireLogger console.
* @param mixed $message
*/
public static function fireLog($message) : bool
{
return !self::$productionMode && self::$showFireLogger ? self::getFireLogger()->log($message) : \false;
}
/** @internal */
public static function addSourceMapper(callable $mapper) : void
{
self::$sourceMappers[] = $mapper;
}
/** @return array{file: string, line: int, label: string, active: bool} */
public static function mapSource(string $file, int $line) : ?array
{
foreach (self::$sourceMappers as $mapper) {
if ($res = $mapper($file, $line)) {
return $res;
}
}
return null;
}
/**
* Detects debug mode by IP address.
* @param string|array $list IP addresses or computer names whitelist detection
*/
public static function detectDebugMode($list = null) : bool
{
$addr = $_SERVER['REMOTE_ADDR'] ?? \php_uname('n');
$secret = isset($_COOKIE[self::CookieSecret]) && \is_string($_COOKIE[self::CookieSecret]) ? $_COOKIE[self::CookieSecret] : null;
$list = \is_string($list) ? \preg_split('#[,\\s]+#', $list) : (array) $list;
if (!isset($_SERVER['HTTP_X_FORWARDED_FOR']) && !isset($_SERVER['HTTP_FORWARDED'])) {
$list[] = '127.0.0.1';
$list[] = '::1';
$list[] = '[::1]';
// workaround for PHP < 7.3.4
}
return \in_array($addr, $list, \true) || \in_array("{$secret}@{$addr}", $list, \true);
}
}

View File

@ -0,0 +1,108 @@
<?php
/**
* This file is part of the Tracy (https://tracy.nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare (strict_types=1);
namespace RectorPrefix202302\Tracy;
/**
* @internal
*/
final class DeferredContent
{
/** @var SessionStorage */
private $sessionStorage;
/** @var string */
private $requestId;
/** @var bool */
private $useSession = \false;
public function __construct(SessionStorage $sessionStorage)
{
$this->sessionStorage = $sessionStorage;
$this->requestId = $_SERVER['HTTP_X_TRACY_AJAX'] ?? Helpers::createId();
}
public function isAvailable() : bool
{
return $this->useSession && $this->sessionStorage->isAvailable();
}
public function getRequestId() : string
{
return $this->requestId;
}
public function &getItems(string $key) : array
{
$items =& $this->sessionStorage->getData()[$key];
$items = (array) $items;
return $items;
}
public function addSetup(string $method, $argument) : void
{
$argument = \json_encode($argument, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE | \JSON_INVALID_UTF8_SUBSTITUTE);
$item =& $this->getItems('setup')[$this->requestId];
$item['code'] = ($item['code'] ?? '') . "{$method}({$argument});\n";
$item['time'] = \time();
}
public function sendAssets() : bool
{
if (\headers_sent($file, $line) || \ob_get_length()) {
throw new \LogicException(__METHOD__ . '() called after some output has been sent. ' . ($file ? "Output started at {$file}:{$line}." : 'Try Tracy\\OutputDebugger to find where output started.'));
}
$asset = $_GET['_tracy_bar'] ?? null;
if ($asset === 'js') {
\header('Content-Type: application/javascript; charset=UTF-8');
\header('Cache-Control: max-age=864000');
\header_remove('Pragma');
\header_remove('Set-Cookie');
$str = $this->buildJsCss();
\header('Content-Length: ' . \strlen($str));
echo $str;
\flush();
return \true;
}
$this->useSession = $this->sessionStorage->isAvailable();
if (!$this->useSession) {
return \false;
}
$this->clean();
if (\is_string($asset) && \preg_match('#^content(-ajax)?\\.(\\w+)$#', $asset, $m)) {
[, $ajax, $requestId] = $m;
\header('Content-Type: application/javascript; charset=UTF-8');
\header('Cache-Control: max-age=60');
\header_remove('Set-Cookie');
$str = $ajax ? '' : $this->buildJsCss();
$data =& $this->getItems('setup');
$str .= $data[$requestId]['code'] ?? '';
unset($data[$requestId]);
\header('Content-Length: ' . \strlen($str));
echo $str;
\flush();
return \true;
}
if (Helpers::isAjax()) {
\header('X-Tracy-Ajax: 1');
// session must be already locked
}
return \false;
}
private function buildJsCss() : string
{
$css = \array_map('file_get_contents', \array_merge([__DIR__ . '/../assets/reset.css', __DIR__ . '/../Bar/assets/bar.css', __DIR__ . '/../assets/toggle.css', __DIR__ . '/../assets/table-sort.css', __DIR__ . '/../assets/tabs.css', __DIR__ . '/../Dumper/assets/dumper-light.css', __DIR__ . '/../Dumper/assets/dumper-dark.css', __DIR__ . '/../BlueScreen/assets/bluescreen.css'], Debugger::$customCssFiles));
$js1 = \array_map(function ($file) {
return '(function() {' . \file_get_contents($file) . '})();';
}, [__DIR__ . '/../Bar/assets/bar.js', __DIR__ . '/../assets/toggle.js', __DIR__ . '/../assets/table-sort.js', __DIR__ . '/../assets/tabs.js', __DIR__ . '/../Dumper/assets/dumper.js', __DIR__ . '/../BlueScreen/assets/bluescreen.js']);
$js2 = \array_map('file_get_contents', Debugger::$customJsFiles);
$str = "'use strict';\n(function(){\n\tvar el = document.createElement('style');\n\tel.setAttribute('nonce', document.currentScript.getAttribute('nonce') || document.currentScript.nonce);\n\tel.className='tracy-debug';\n\tel.textContent=" . \json_encode(Helpers::minifyCss(\implode('', $css))) . ";\n\tdocument.head.appendChild(el);})\n();\n" . \implode('', $js1) . \implode('', $js2);
return $str;
}
public function clean() : void
{
foreach ($this->sessionStorage->getData() as &$items) {
$items = \array_slice((array) $items, -10, null, \true);
$items = \array_filter($items, function ($item) {
return isset($item['time']) && $item['time'] > \time() - 60;
});
}
}
}

View File

@ -0,0 +1,102 @@
<?php
/**
* This file is part of the Tracy (https://tracy.nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare (strict_types=1);
namespace RectorPrefix202302\Tracy;
use ErrorException;
/**
* @internal
*/
final class DevelopmentStrategy
{
/** @var Bar */
private $bar;
/** @var BlueScreen */
private $blueScreen;
/** @var DeferredContent */
private $defer;
public function __construct(Bar $bar, BlueScreen $blueScreen, DeferredContent $defer)
{
$this->bar = $bar;
$this->blueScreen = $blueScreen;
$this->defer = $defer;
}
public function initialize() : void
{
}
public function handleException(\Throwable $exception, bool $firstTime) : void
{
if (Helpers::isAjax() && $this->defer->isAvailable()) {
$this->blueScreen->renderToAjax($exception, $this->defer);
} elseif ($firstTime && Helpers::isHtmlMode()) {
$this->blueScreen->render($exception);
} else {
Debugger::fireLog($exception);
$this->renderExceptionCli($exception);
}
}
private function renderExceptionCli(\Throwable $exception) : void
{
try {
$logFile = Debugger::log($exception, Debugger::EXCEPTION);
} catch (\Throwable $e) {
echo "{$exception}\nTracy is unable to log error: {$e->getMessage()}\n";
return;
}
if ($logFile && !\headers_sent()) {
\header("X-Tracy-Error-Log: {$logFile}", \false);
}
if (Helpers::detectColors()) {
echo "\n\n" . $this->blueScreen->highlightPhpCli($exception->getFile(), $exception->getLine()) . "\n";
}
echo "{$exception}\n" . ($logFile ? "\n(stored in {$logFile})\n" : '');
if ($logFile && Debugger::$browser) {
\exec(Debugger::$browser . ' ' . \escapeshellarg(\strtr($logFile, Debugger::$editorMapping)));
}
}
public function handleError(int $severity, string $message, string $file, int $line, array $context = null) : void
{
if (\function_exists('ini_set')) {
$oldDisplay = \ini_set('display_errors', '1');
}
if ((\is_bool(Debugger::$strictMode) ? Debugger::$strictMode : Debugger::$strictMode & $severity) && !isset($_GET['_tracy_skip_error'])) {
$e = new ErrorException($message, 0, $severity, $file, $line);
@($e->context = $context);
// dynamic properties are deprecated since PHP 8.2
@($e->skippable = \true);
Debugger::exceptionHandler($e);
exit(255);
}
$message = 'PHP ' . Helpers::errorTypeToString($severity) . ': ' . Helpers::improveError($message, (array) $context);
$count =& $this->bar->getPanel('Tracy:errors')->data["{$file}|{$line}|{$message}"];
if (!$count++) {
// not repeated error
Debugger::fireLog(new ErrorException($message, 0, $severity, $file, $line));
if (!Helpers::isHtmlMode() && !Helpers::isAjax()) {
echo "\n{$message} in {$file} on line {$line}\n";
}
}
if (\function_exists('ini_set')) {
\ini_set('display_errors', $oldDisplay);
}
}
public function sendAssets() : bool
{
return $this->defer->sendAssets();
}
public function renderLoader() : void
{
$this->bar->renderLoader($this->defer);
}
public function renderBar() : void
{
if (\function_exists('ini_set')) {
\ini_set('display_errors', '1');
}
$this->bar->render($this->defer);
}
}

View File

@ -0,0 +1,67 @@
<?php
/**
* This file is part of the Tracy (https://tracy.nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare (strict_types=1);
namespace RectorPrefix202302\Tracy;
use ErrorException;
/**
* @internal
*/
final class ProductionStrategy
{
public function initialize() : void
{
if (!\function_exists('ini_set') && (\ini_get('display_errors') && \ini_get('display_errors') !== 'stderr')) {
Debugger::exceptionHandler(new \RuntimeException("Unable to set 'display_errors' because function ini_set() is disabled."));
}
}
public function handleException(\Throwable $exception, bool $firstTime) : void
{
try {
Debugger::log($exception, Debugger::EXCEPTION);
} catch (\Throwable $e) {
}
if (!$firstTime) {
// nothing
} elseif (Helpers::isHtmlMode()) {
if (!\headers_sent()) {
\header('Content-Type: text/html; charset=UTF-8');
}
(function ($logged) use($exception) {
require Debugger::$errorTemplate ?: __DIR__ . '/assets/error.500.phtml';
})(empty($e));
} elseif (Helpers::isCli()) {
// @ triggers E_NOTICE when strerr is closed since PHP 7.4
@\fwrite(\STDERR, "ERROR: {$exception->getMessage()}\n" . (isset($e) ? 'Unable to log error. You may try enable debug mode to inspect the problem.' : 'Check log to see more info.') . "\n");
}
}
public function handleError(int $severity, string $message, string $file, int $line, array $context = null) : void
{
if ($severity & Debugger::$logSeverity) {
$err = new ErrorException($message, 0, $severity, $file, $line);
@($err->context = $context);
// dynamic properties are deprecated since PHP 8.2
Helpers::improveException($err);
} else {
$err = 'PHP ' . Helpers::errorTypeToString($severity) . ': ' . Helpers::improveError($message, (array) $context) . " in {$file}:{$line}";
}
try {
Debugger::log($err, Debugger::ERROR);
} catch (\Throwable $e) {
}
}
public function sendAssets() : bool
{
return \false;
}
public function renderLoader() : void
{
}
public function renderBar() : void
{
}
}

View File

@ -0,0 +1,43 @@
<?php
/**
* Default error page.
*/
declare(strict_types=1);
namespace Tracy;
/**
* @var bool $logged
*/
?>
<!DOCTYPE html><!-- "' --></textarea></script></style></pre></xmp></a></audio></button></canvas></datalist></details></dialog></iframe></listing></meter></noembed></noframes></noscript></optgroup></option></progress></rp></select></table></template></title></video>
<meta charset="utf-8">
<meta name=robots content=noindex>
<meta name=generator content="Tracy">
<title>Server Error</title>
<style>
#tracy-error { all: initial; position: absolute; top: 0; left: 0; right: 0; height: 70vh; min-height: 400px; display: flex; align-items: center; justify-content: center; z-index: 1000 }
#tracy-error div { all: initial; max-width: 550px; background: white; color: #333; display: block }
#tracy-error h1 { all: initial; font: bold 50px/1.1 sans-serif; display: block; margin: 40px }
#tracy-error p { all: initial; font: 20px/1.4 sans-serif; margin: 40px; display: block }
#tracy-error small { color: gray }
#tracy-error small span { color: silver }
</style>
<div id=tracy-error>
<div>
<h1>Server Error</h1>
<p>We're sorry! The server encountered an internal error and
was unable to complete your request. Please try again later.</p>
<p><small>error 500 <span> | <?php echo date('j. n. Y H:i') ?></span><?php if (!$logged): ?><br>Tracy is unable to log error.<?php endif ?></small></p>
</div>
</div>
<script>
document.body.insertBefore(document.getElementById('tracy-error'), document.body.firstChild);
</script>

View File

@ -0,0 +1,281 @@
<?php
/**
* This file is part of the Tracy (https://tracy.nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare (strict_types=1);
namespace RectorPrefix202302\Tracy\Dumper;
use RectorPrefix202302\Tracy;
use RectorPrefix202302\Tracy\Helpers;
/**
* Converts PHP values to internal representation.
* @internal
*/
final class Describer
{
public const HiddenValue = '*****';
// Number.MAX_SAFE_INTEGER
private const JsSafeInteger = 1 << 53 - 1;
/** @var int */
public $maxDepth = 7;
/** @var int */
public $maxLength = 150;
/** @var int */
public $maxItems = 100;
/** @var Value[] */
public $snapshot = [];
/** @var bool */
public $debugInfo = \false;
/** @var array */
public $keysToHide = [];
/** @var callable|null fn(string $key, mixed $val): bool */
public $scrubber;
/** @var bool */
public $location = \false;
/** @var callable[] */
public $resourceExposers;
/** @var array<string,callable> */
public $objectExposers;
/** @var (int|\stdClass)[] */
public $references = [];
public function describe($var) : \stdClass
{
\uksort($this->objectExposers, function ($a, $b) : int {
return $b === '' || \class_exists($a, \false) && \is_subclass_of($a, $b) ? -1 : 1;
});
try {
return (object) ['value' => $this->describeVar($var), 'snapshot' => $this->snapshot, 'location' => $this->location ? self::findLocation() : null];
} finally {
$free = [[], []];
$this->snapshot =& $free[0];
$this->references =& $free[1];
}
}
/**
* @return mixed
*/
private function describeVar($var, int $depth = 0, ?int $refId = null)
{
if ($var === null || \is_bool($var)) {
return $var;
}
$m = 'describe' . \explode(' ', \gettype($var))[0];
return $this->{$m}($var, $depth, $refId);
}
/**
* @return Value|int
*/
private function describeInteger(int $num)
{
return $num <= self::JsSafeInteger && $num >= -self::JsSafeInteger ? $num : new Value(Value::TypeNumber, "{$num}");
}
/**
* @return Value|float
*/
private function describeDouble(float $num)
{
if (!\is_finite($num)) {
return new Value(Value::TypeNumber, (string) $num);
}
$js = \json_encode($num);
return \strpos($js, '.') ? $num : new Value(Value::TypeNumber, "{$js}.0");
// to distinct int and float in JS
}
/**
* @return Value|string
*/
private function describeString(string $s, int $depth = 0)
{
$encoded = Helpers::encodeString($s, $depth ? $this->maxLength : null);
if ($encoded === $s) {
return $encoded;
} elseif (Helpers::isUtf8($s)) {
return new Value(Value::TypeStringHtml, $encoded, Helpers::utf8Length($s));
} else {
return new Value(Value::TypeBinaryHtml, $encoded, \strlen($s));
}
}
/**
* @return Value|array
*/
private function describeArray(array $arr, int $depth = 0, ?int $refId = null)
{
if ($refId) {
$res = new Value(Value::TypeRef, 'p' . $refId);
$value =& $this->snapshot[$res->value];
if ($value && $value->depth <= $depth) {
return $res;
}
$value = new Value(Value::TypeArray);
$value->id = $res->value;
$value->depth = $depth;
if ($this->maxDepth && $depth >= $this->maxDepth) {
$value->length = \count($arr);
return $res;
} elseif ($depth && $this->maxItems && \count($arr) > $this->maxItems) {
$value->length = \count($arr);
$arr = \array_slice($arr, 0, $this->maxItems, \true);
}
$items =& $value->items;
} elseif ($arr && $this->maxDepth && $depth >= $this->maxDepth) {
return new Value(Value::TypeArray, null, \count($arr));
} elseif ($depth && $this->maxItems && \count($arr) > $this->maxItems) {
$res = new Value(Value::TypeArray, null, \count($arr));
$res->depth = $depth;
$items =& $res->items;
$arr = \array_slice($arr, 0, $this->maxItems, \true);
}
$items = [];
foreach ($arr as $k => $v) {
$refId = $this->getReferenceId($arr, $k);
$items[] = [$this->describeVar($k, $depth + 1), $this->isSensitive((string) $k, $v) ? new Value(Value::TypeText, self::hideValue($v)) : $this->describeVar($v, $depth + 1, $refId)] + ($refId ? [2 => $refId] : []);
}
return $res ?? $items;
}
private function describeObject(object $obj, int $depth = 0) : Value
{
$id = \spl_object_id($obj);
$value =& $this->snapshot[$id];
if ($value && $value->depth <= $depth) {
return new Value(Value::TypeRef, $id);
}
$value = new Value(Value::TypeObject, Helpers::getClass($obj));
$value->id = $id;
$value->depth = $depth;
$value->holder = $obj;
// to be not released by garbage collector in collecting mode
if ($this->location) {
$rc = $obj instanceof \Closure ? new \ReflectionFunction($obj) : new \ReflectionClass($obj);
if ($rc->getFileName() && ($editor = Helpers::editorUri($rc->getFileName(), $rc->getStartLine()))) {
$value->editor = (object) ['file' => $rc->getFileName(), 'line' => $rc->getStartLine(), 'url' => $editor];
}
}
if ($this->maxDepth && $depth < $this->maxDepth) {
$value->items = [];
$props = $this->exposeObject($obj, $value);
foreach ($props ?? [] as $k => $v) {
$this->addPropertyTo($value, (string) $k, $v, Value::PropertyVirtual, $this->getReferenceId($props, $k));
}
}
return new Value(Value::TypeRef, $id);
}
/**
* @param resource $resource
*/
private function describeResource($resource, int $depth = 0) : Value
{
$id = 'r' . (int) $resource;
$value =& $this->snapshot[$id];
if (!$value) {
$type = \is_resource($resource) ? \get_resource_type($resource) : 'closed';
$value = new Value(Value::TypeResource, $type . ' resource');
$value->id = $id;
$value->depth = $depth;
$value->items = [];
if (isset($this->resourceExposers[$type])) {
foreach ($this->resourceExposers[$type]($resource) as $k => $v) {
$value->items[] = [\htmlspecialchars($k), $this->describeVar($v, $depth + 1)];
}
}
}
return new Value(Value::TypeRef, $id);
}
/**
* @return Value|string
*/
public function describeKey(string $key)
{
if (\preg_match('#^[\\w!\\#$%&*+./;<>?@^{|}~-]{1,50}$#D', $key) && !\preg_match('#^(true|false|null)$#iD', $key)) {
return $key;
}
$value = $this->describeString($key);
return \is_string($value) ? new Value(Value::TypeStringHtml, $key, Helpers::utf8Length($key)) : $value;
}
public function addPropertyTo(Value $value, string $k, $v, $type = Value::PropertyVirtual, ?int $refId = null, ?string $class = null)
{
if ($value->depth && $this->maxItems && \count($value->items ?? []) >= $this->maxItems) {
$value->length = ($value->length ?? \count($value->items)) + 1;
return;
}
$class = $class ?? $value->value;
$value->items[] = [$this->describeKey($k), $type !== Value::PropertyVirtual && $this->isSensitive($k, $v, $class) ? new Value(Value::TypeText, self::hideValue($v)) : $this->describeVar($v, $value->depth + 1, $refId), $type === Value::PropertyPrivate ? $class : $type] + ($refId ? [3 => $refId] : []);
}
private function exposeObject(object $obj, Value $value) : ?array
{
foreach ($this->objectExposers as $type => $dumper) {
if (!$type || $obj instanceof $type) {
return $dumper($obj, $value, $this);
}
}
if ($this->debugInfo && \method_exists($obj, '__debugInfo')) {
return $obj->__debugInfo();
}
Exposer::exposeObject($obj, $value, $this);
return null;
}
private function isSensitive(string $key, $val, ?string $class = null) : bool
{
return $this->scrubber !== null && ($this->scrubber)($key, $val, $class) || isset($this->keysToHide[\strtolower($key)]) || isset($this->keysToHide[\strtolower($class . '::$' . $key)]);
}
private static function hideValue($var) : string
{
return self::HiddenValue . ' (' . (\is_object($var) ? Helpers::getClass($var) : \gettype($var)) . ')';
}
public function getReferenceId($arr, $key) : ?int
{
if (\PHP_VERSION_ID >= 70400) {
if (!($rr = \ReflectionReference::fromArrayElement($arr, $key))) {
return null;
}
$tmp =& $this->references[$rr->getId()];
if ($tmp === null) {
return $tmp = \count($this->references);
}
return $tmp;
}
$uniq = new \stdClass();
$copy = $arr;
$orig = $copy[$key];
$copy[$key] = $uniq;
if ($arr[$key] !== $uniq) {
return null;
}
$res = \array_search($uniq, $this->references, \true);
$copy[$key] = $orig;
if ($res === \false) {
$this->references[] =& $arr[$key];
return \count($this->references);
}
return $res + 1;
}
/**
* Finds the location where dump was called. Returns [file, line, code]
*/
private static function findLocation() : ?array
{
foreach (\debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS) as $item) {
if (isset($item['class']) && ($item['class'] === self::class || $item['class'] === Tracy\Dumper::class)) {
$location = $item;
continue;
} elseif (isset($item['function'])) {
try {
$reflection = isset($item['class']) ? new \ReflectionMethod($item['class'], $item['function']) : new \ReflectionFunction($item['function']);
if ($reflection->isInternal() || \preg_match('#\\s@tracySkipLocation\\s#', (string) $reflection->getDocComment())) {
$location = $item;
continue;
}
} catch (\ReflectionException $e) {
}
}
break;
}
if (isset($location['file'], $location['line']) && \is_file($location['file'])) {
$lines = \file($location['file']);
$line = $lines[$location['line'] - 1];
return [$location['file'], $location['line'], \trim(\preg_match('#\\w*dump(er::\\w+)?\\(.*\\)#i', $line, $m) ? $m[0] : $line)];
}
return null;
}
}

View File

@ -0,0 +1,157 @@
<?php
/**
* This file is part of the Tracy (https://tracy.nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare (strict_types=1);
namespace RectorPrefix202302\Tracy;
use RectorPrefix202302\Ds;
use RectorPrefix202302\Tracy\Dumper\Describer;
use RectorPrefix202302\Tracy\Dumper\Exposer;
use RectorPrefix202302\Tracy\Dumper\Renderer;
/**
* Dumps a variable.
*/
class Dumper
{
public const DEPTH = 'depth', TRUNCATE = 'truncate', ITEMS = 'items', COLLAPSE = 'collapse', COLLAPSE_COUNT = 'collapsecount', LOCATION = 'location', OBJECT_EXPORTERS = 'exporters', LAZY = 'lazy', LIVE = 'live', SNAPSHOT = 'snapshot', DEBUGINFO = 'debuginfo', KEYS_TO_HIDE = 'keystohide', SCRUBBER = 'scrubber', THEME = 'theme', HASH = 'hash';
// show object and reference hashes (defaults to true)
public const LOCATION_CLASS = 0b1, LOCATION_SOURCE = 0b11, LOCATION_LINK = self::LOCATION_SOURCE;
// deprecated
public const HIDDEN_VALUE = Describer::HiddenValue;
/** @var Dumper\Value[] */
public static $liveSnapshot = [];
/** @var array */
public static $terminalColors = ['bool' => '1;33', 'null' => '1;33', 'number' => '1;32', 'string' => '1;36', 'array' => '1;31', 'public' => '1;37', 'protected' => '1;37', 'private' => '1;37', 'dynamic' => '1;37', 'virtual' => '1;37', 'object' => '1;31', 'resource' => '1;37', 'indent' => '1;30'];
/** @var array */
public static $resources = ['stream' => 'stream_get_meta_data', 'stream-context' => 'stream_context_get_options', 'curl' => 'curl_getinfo'];
/** @var array */
public static $objectExporters = [\Closure::class => [Exposer::class, 'exposeClosure'], \UnitEnum::class => [Exposer::class, 'exposeEnum'], \ArrayObject::class => [Exposer::class, 'exposeArrayObject'], \SplFileInfo::class => [Exposer::class, 'exposeSplFileInfo'], \SplObjectStorage::class => [Exposer::class, 'exposeSplObjectStorage'], \__PHP_Incomplete_Class::class => [Exposer::class, 'exposePhpIncompleteClass'], \Generator::class => [Exposer::class, 'exposeGenerator'], \Fiber::class => [Exposer::class, 'exposeFiber'], \DOMNode::class => [Exposer::class, 'exposeDOMNode'], \DOMNodeList::class => [Exposer::class, 'exposeDOMNodeList'], \DOMNamedNodeMap::class => [Exposer::class, 'exposeDOMNodeList'], Ds\Collection::class => [Exposer::class, 'exposeDsCollection'], Ds\Map::class => [Exposer::class, 'exposeDsMap']];
/** @var Describer */
private $describer;
/** @var Renderer */
private $renderer;
/**
* Dumps variable to the output.
* @return mixed variable
*/
public static function dump($var, array $options = [])
{
if (Helpers::isCli()) {
$useColors = self::$terminalColors && Helpers::detectColors();
$dumper = new self($options);
\fwrite(\STDOUT, $dumper->asTerminal($var, $useColors ? self::$terminalColors : []));
} elseif (Helpers::isHtmlMode()) {
$options[self::LOCATION] = $options[self::LOCATION] ?? \true;
self::renderAssets();
echo self::toHtml($var, $options);
} else {
echo self::toText($var, $options);
}
return $var;
}
/**
* Dumps variable to HTML.
*/
public static function toHtml($var, array $options = [], $key = null) : string
{
return (new self($options))->asHtml($var, $key);
}
/**
* Dumps variable to plain text.
*/
public static function toText($var, array $options = []) : string
{
return (new self($options))->asTerminal($var);
}
/**
* Dumps variable to x-terminal.
*/
public static function toTerminal($var, array $options = []) : string
{
return (new self($options))->asTerminal($var, self::$terminalColors);
}
/**
* Renders <script> & <style>
*/
public static function renderAssets() : void
{
static $sent;
if (Debugger::$productionMode === \true || $sent) {
return;
}
$sent = \true;
$nonce = Helpers::getNonce();
$nonceAttr = $nonce ? ' nonce="' . Helpers::escapeHtml($nonce) . '"' : '';
$s = \file_get_contents(__DIR__ . '/../assets/toggle.css') . \file_get_contents(__DIR__ . '/assets/dumper-light.css') . \file_get_contents(__DIR__ . '/assets/dumper-dark.css');
echo "<style{$nonceAttr}>", \str_replace('</', '<\\/', Helpers::minifyCss($s)), "</style>\n";
if (!Debugger::isEnabled()) {
$s = '(function(){' . \file_get_contents(__DIR__ . '/../assets/toggle.js') . '})();' . '(function(){' . \file_get_contents(__DIR__ . '/../Dumper/assets/dumper.js') . '})();';
echo "<script{$nonceAttr}>", \str_replace(['<!--', '</s'], ['<\\!--', '<\\/s'], Helpers::minifyJs($s)), "</script>\n";
}
}
private function __construct(array $options = [])
{
$location = $options[self::LOCATION] ?? 0;
$location = $location === \true ? ~0 : (int) $location;
$describer = $this->describer = new Describer();
$describer->maxDepth = (int) ($options[self::DEPTH] ?? $describer->maxDepth);
$describer->maxLength = (int) ($options[self::TRUNCATE] ?? $describer->maxLength);
$describer->maxItems = (int) ($options[self::ITEMS] ?? $describer->maxItems);
$describer->debugInfo = (bool) ($options[self::DEBUGINFO] ?? $describer->debugInfo);
$describer->scrubber = $options[self::SCRUBBER] ?? $describer->scrubber;
$describer->keysToHide = \array_flip(\array_map('strtolower', $options[self::KEYS_TO_HIDE] ?? []));
$describer->resourceExposers = ($options['resourceExporters'] ?? []) + self::$resources;
$describer->objectExposers = ($options[self::OBJECT_EXPORTERS] ?? []) + self::$objectExporters;
$describer->location = (bool) $location;
if ($options[self::LIVE] ?? \false) {
$tmp =& self::$liveSnapshot;
} elseif (isset($options[self::SNAPSHOT])) {
$tmp =& $options[self::SNAPSHOT];
}
if (isset($tmp)) {
$tmp[0] = $tmp[0] ?? [];
$tmp[1] = $tmp[1] ?? [];
$describer->snapshot =& $tmp[0];
$describer->references =& $tmp[1];
}
$renderer = $this->renderer = new Renderer();
$renderer->collapseTop = $options[self::COLLAPSE] ?? $renderer->collapseTop;
$renderer->collapseSub = $options[self::COLLAPSE_COUNT] ?? $renderer->collapseSub;
$renderer->collectingMode = isset($options[self::SNAPSHOT]) || !empty($options[self::LIVE]);
$renderer->lazy = $renderer->collectingMode ? \true : $options[self::LAZY] ?? $renderer->lazy;
$renderer->sourceLocation = !(~$location & self::LOCATION_SOURCE);
$renderer->classLocation = !(~$location & self::LOCATION_CLASS);
$renderer->theme = $options[self::THEME] ?? $renderer->theme;
$renderer->hash = $options[self::HASH] ?? \true;
}
/**
* Dumps variable to HTML.
*/
private function asHtml($var, $key = null) : string
{
if ($key === null) {
$model = $this->describer->describe($var);
} else {
$model = $this->describer->describe([$key => $var]);
$model->value = $model->value[0][1];
}
return $this->renderer->renderAsHtml($model);
}
/**
* Dumps variable to x-terminal.
*/
private function asTerminal($var, array $colors = []) : string
{
$model = $this->describer->describe($var);
return $this->renderer->renderAsText($model, $colors);
}
public static function formatSnapshotAttribute(array &$snapshot) : string
{
$res = "'" . Renderer::jsonEncode($snapshot[0] ?? []) . "'";
$snapshot = [];
return $res;
}
}

View File

@ -0,0 +1,182 @@
<?php
/**
* This file is part of the Tracy (https://tracy.nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare (strict_types=1);
namespace RectorPrefix202302\Tracy\Dumper;
use RectorPrefix202302\Ds;
/**
* Exposes internal PHP objects.
* @internal
*/
final class Exposer
{
public static function exposeObject(object $obj, Value $value, Describer $describer) : void
{
$tmp = (array) $obj;
$values = $tmp;
// bug #79477, PHP < 7.4.6
$props = self::getProperties(\get_class($obj));
foreach (\array_diff_key($values, $props) as $k => $v) {
$describer->addPropertyTo($value, (string) $k, $v, Value::PropertyDynamic, $describer->getReferenceId($values, $k));
}
foreach ($props as $k => [$name, $class, $type]) {
if (\array_key_exists($k, $values)) {
$describer->addPropertyTo($value, $name, $values[$k], $type, $describer->getReferenceId($values, $k), $class);
} else {
$value->items[] = [$name, new Value(Value::TypeText, 'unset'), $type === Value::PropertyPrivate ? $class : $type];
}
}
}
private static function getProperties($class) : array
{
static $cache;
if (isset($cache[$class])) {
return $cache[$class];
}
$rc = new \ReflectionClass($class);
$parentProps = $rc->getParentClass() ? self::getProperties($rc->getParentClass()->getName()) : [];
$props = [];
foreach ($rc->getProperties() as $prop) {
$name = $prop->getName();
if ($prop->isStatic() || $prop->getDeclaringClass()->getName() !== $class) {
// nothing
} elseif ($prop->isPrivate()) {
$props["\x00" . $class . "\x00" . $name] = [$name, $class, Value::PropertyPrivate];
} elseif ($prop->isProtected()) {
$props["\x00*\x00" . $name] = [$name, $class, Value::PropertyProtected];
} else {
$props[$name] = [$name, $class, Value::PropertyPublic];
unset($parentProps["\x00*\x00" . $name]);
}
}
return $cache[$class] = $props + $parentProps;
}
public static function exposeClosure(\Closure $obj, Value $value, Describer $describer) : void
{
$rc = new \ReflectionFunction($obj);
if ($describer->location) {
$describer->addPropertyTo($value, 'file', $rc->getFileName() . ':' . $rc->getStartLine());
}
$params = [];
foreach ($rc->getParameters() as $param) {
$params[] = '$' . $param->getName();
}
$value->value .= '(' . \implode(', ', $params) . ')';
$uses = [];
$useValue = new Value(Value::TypeObject);
$useValue->depth = $value->depth + 1;
foreach ($rc->getStaticVariables() as $name => $v) {
$uses[] = '$' . $name;
$describer->addPropertyTo($useValue, '$' . $name, $v);
}
if ($uses) {
$useValue->value = \implode(', ', $uses);
$useValue->collapsed = \true;
$value->items[] = ['use', $useValue];
}
}
public static function exposeEnum(\UnitEnum $enum, Value $value, Describer $describer) : void
{
$value->value = \get_class($enum) . '::' . $enum->name;
if ($enum instanceof \BackedEnum) {
$describer->addPropertyTo($value, 'value', $enum->value);
$value->collapsed = \true;
}
}
public static function exposeArrayObject(\ArrayObject $obj, Value $value, Describer $describer) : void
{
$flags = $obj->getFlags();
$obj->setFlags(\ArrayObject::STD_PROP_LIST);
self::exposeObject($obj, $value, $describer);
$obj->setFlags($flags);
$describer->addPropertyTo($value, 'storage', $obj->getArrayCopy(), Value::PropertyPrivate, null, \ArrayObject::class);
}
public static function exposeDOMNode(\DOMNode $obj, Value $value, Describer $describer) : void
{
$props = \preg_match_all('#^\\s*\\[([^\\]]+)\\] =>#m', \print_r($obj, \true), $tmp) ? $tmp[1] : [];
\sort($props);
foreach ($props as $p) {
$describer->addPropertyTo($value, $p, $obj->{$p}, Value::PropertyPublic);
}
}
/**
* @param \DOMNodeList|\DOMNamedNodeMap $obj
*/
public static function exposeDOMNodeList($obj, Value $value, Describer $describer) : void
{
$describer->addPropertyTo($value, 'length', $obj->length, Value::PropertyPublic);
$describer->addPropertyTo($value, 'items', \iterator_to_array($obj));
}
public static function exposeGenerator(\Generator $gen, Value $value, Describer $describer) : void
{
try {
$r = new \ReflectionGenerator($gen);
$describer->addPropertyTo($value, 'file', $r->getExecutingFile() . ':' . $r->getExecutingLine());
$describer->addPropertyTo($value, 'this', $r->getThis());
} catch (\ReflectionException $e) {
$value->value = \get_class($gen) . ' (terminated)';
}
}
public static function exposeFiber(\Fiber $fiber, Value $value, Describer $describer) : void
{
if ($fiber->isTerminated()) {
$value->value = \get_class($fiber) . ' (terminated)';
} elseif (!$fiber->isStarted()) {
$value->value = \get_class($fiber) . ' (not started)';
} else {
$r = new \ReflectionFiber($fiber);
$describer->addPropertyTo($value, 'file', $r->getExecutingFile() . ':' . $r->getExecutingLine());
$describer->addPropertyTo($value, 'callable', $r->getCallable());
}
}
public static function exposeSplFileInfo(\SplFileInfo $obj) : array
{
return ['path' => $obj->getPathname()];
}
public static function exposeSplObjectStorage(\SplObjectStorage $obj) : array
{
$res = [];
foreach (clone $obj as $item) {
$res[] = ['object' => $item, 'data' => $obj[$item]];
}
return $res;
}
public static function exposePhpIncompleteClass(\__PHP_Incomplete_Class $obj, Value $value, Describer $describer) : void
{
$values = (array) $obj;
$class = $values['__PHP_Incomplete_Class_Name'];
unset($values['__PHP_Incomplete_Class_Name']);
foreach ($values as $k => $v) {
$refId = $describer->getReferenceId($values, $k);
if (isset($k[0]) && $k[0] === "\x00") {
$info = \explode("\x00", $k);
$k = \end($info);
$type = $info[1] === '*' ? Value::PropertyProtected : Value::PropertyPrivate;
$decl = $type === Value::PropertyPrivate ? $info[1] : null;
} else {
$type = Value::PropertyPublic;
$k = (string) $k;
$decl = null;
}
$describer->addPropertyTo($value, $k, $v, $type, $refId, $decl);
}
$value->value = $class . ' (Incomplete Class)';
}
public static function exposeDsCollection(Ds\Collection $obj, Value $value, Describer $describer) : void
{
foreach ($obj as $k => $v) {
$describer->addPropertyTo($value, (string) $k, $v, Value::PropertyVirtual);
}
}
public static function exposeDsMap(Ds\Map $obj, Value $value, Describer $describer) : void
{
$i = 0;
foreach ($obj as $k => $v) {
$describer->addPropertyTo($value, (string) $i++, new Ds\Pair($k, $v), Value::PropertyVirtual);
}
}
}

View File

@ -0,0 +1,327 @@
<?php
/**
* This file is part of the Tracy (https://tracy.nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare (strict_types=1);
namespace RectorPrefix202302\Tracy\Dumper;
use RectorPrefix202302\Tracy\Helpers;
/**
* Visualisation of internal representation.
* @internal
*/
final class Renderer
{
private const TypeArrayKey = 'array';
/** @var int|bool */
public $collapseTop = 14;
/** @var int */
public $collapseSub = 7;
/** @var bool */
public $classLocation = \false;
/** @var bool */
public $sourceLocation = \false;
/** @var bool|null lazy-loading via JavaScript? true=full, false=none, null=collapsed parts */
public $lazy;
/** @var bool */
public $hash = \true;
/** @var string */
public $theme = 'light';
/** @var bool */
public $collectingMode = \false;
/** @var Value[] */
private $snapshot = [];
/** @var Value[]|null */
private $snapshotSelection;
/** @var array */
private $parents = [];
/** @var array */
private $above = [];
public function renderAsHtml(\stdClass $model) : string
{
try {
$value = $model->value;
$this->snapshot = $model->snapshot;
if ($this->lazy === \false) {
// no lazy-loading
$html = $this->renderVar($value);
$json = $snapshot = null;
} elseif ($this->lazy && (\is_array($value) && $value || \is_object($value))) {
// full lazy-loading
$html = '';
$snapshot = $this->collectingMode ? null : $this->snapshot;
$json = $value;
} else {
// lazy-loading of collapsed parts
$html = $this->renderVar($value);
$snapshot = $this->snapshotSelection;
$json = null;
}
} finally {
$this->parents = $this->snapshot = $this->above = [];
$this->snapshotSelection = null;
}
$location = null;
if ($model->location && $this->sourceLocation) {
[$file, $line, $code] = $model->location;
$uri = Helpers::editorUri($file, $line);
$location = Helpers::formatHtml('<a href="%" class="tracy-dump-location" title="in file % on line %%">', $uri ?? '#', $file, $line, $uri ? "\nClick to open in editor" : '') . Helpers::encodeString($code, 50) . " 📍</a\n>";
}
return '<pre class="tracy-dump' . ($this->theme ? ' tracy-' . \htmlspecialchars($this->theme) : '') . ($json && $this->collapseTop === \true ? ' tracy-collapsed' : '') . '"' . ($snapshot !== null ? " data-tracy-snapshot='" . self::jsonEncode($snapshot) . "'" : '') . ($json ? " data-tracy-dump='" . self::jsonEncode($json) . "'" : '') . ($location || \strlen($html) > 100 ? "\n" : '') . '>' . $location . $html . "</pre>\n";
}
public function renderAsText(\stdClass $model, array $colors = []) : string
{
try {
$this->snapshot = $model->snapshot;
$this->lazy = \false;
$s = $this->renderVar($model->value);
} finally {
$this->parents = $this->snapshot = $this->above = [];
}
$s = $colors ? self::htmlToAnsi($s, $colors) : $s;
$s = \htmlspecialchars_decode(\strip_tags($s), \ENT_QUOTES | \ENT_HTML5);
$s = \str_replace('…', '...', $s);
$s .= \substr($s, -1) === "\n" ? '' : "\n";
if ($this->sourceLocation && ([$file, $line] = $model->location)) {
$s .= "in {$file}:{$line}\n";
}
return $s;
}
/**
* @param mixed $value
* @param string|int|null $keyType
*/
private function renderVar($value, int $depth = 0, $keyType = null) : string
{
switch (\true) {
case $value === null:
return '<span class="tracy-dump-null">null</span>';
case \is_bool($value):
return '<span class="tracy-dump-bool">' . ($value ? 'true' : 'false') . '</span>';
case \is_int($value):
return '<span class="tracy-dump-number">' . $value . '</span>';
case \is_float($value):
return '<span class="tracy-dump-number">' . self::jsonEncode($value) . '</span>';
case \is_string($value):
return $this->renderString($value, $depth, $keyType);
case \is_array($value):
case $value->type === Value::TypeArray:
return $this->renderArray($value, $depth);
case $value->type === Value::TypeRef:
return $this->renderVar($this->snapshot[$value->value], $depth, $keyType);
case $value->type === Value::TypeObject:
return $this->renderObject($value, $depth);
case $value->type === Value::TypeNumber:
return '<span class="tracy-dump-number">' . Helpers::escapeHtml($value->value) . '</span>';
case $value->type === Value::TypeText:
return '<span class="tracy-dump-virtual">' . Helpers::escapeHtml($value->value) . '</span>';
case $value->type === Value::TypeStringHtml:
case $value->type === Value::TypeBinaryHtml:
return $this->renderString($value, $depth, $keyType);
case $value->type === Value::TypeResource:
return $this->renderResource($value, $depth);
default:
throw new \Exception('Unknown type');
}
}
/**
* @param string|Value $str
* @param string|int|null $keyType
*/
private function renderString($str, int $depth, $keyType) : string
{
if ($keyType === self::TypeArrayKey) {
$indent = '<span class="tracy-dump-indent"> ' . \str_repeat('| ', $depth - 1) . ' </span>';
return '<span class="tracy-dump-string">' . "<span class='tracy-dump-lq'>'</span>" . (\is_string($str) ? Helpers::escapeHtml($str) : \str_replace("\n", "\n" . $indent, $str->value)) . "<span>'</span>" . '</span>';
} elseif ($keyType !== null) {
$classes = [Value::PropertyPublic => 'tracy-dump-public', Value::PropertyProtected => 'tracy-dump-protected', Value::PropertyDynamic => 'tracy-dump-dynamic', Value::PropertyVirtual => 'tracy-dump-virtual'];
$indent = '<span class="tracy-dump-indent"> ' . \str_repeat('| ', $depth - 1) . ' </span>';
$title = \is_string($keyType) ? ' title="declared in ' . Helpers::escapeHtml($keyType) . '"' : null;
return '<span class="' . ($title ? 'tracy-dump-private' : $classes[$keyType]) . '"' . $title . '>' . (\is_string($str) ? Helpers::escapeHtml($str) : "<span class='tracy-dump-lq'>'</span>" . \str_replace("\n", "\n" . $indent, $str->value) . "<span>'</span>") . '</span>';
} elseif (\is_string($str)) {
$len = Helpers::utf8Length($str);
return '<span class="tracy-dump-string"' . ($len > 1 ? ' title="' . $len . ' characters"' : '') . '>' . "<span>'</span>" . Helpers::escapeHtml($str) . "<span>'</span>" . '</span>';
} else {
$unit = $str->type === Value::TypeStringHtml ? 'characters' : 'bytes';
$count = \substr_count($str->value, "\n");
if ($count) {
$collapsed = $indent1 = $toggle = null;
$indent = '<span class="tracy-dump-indent"> </span>';
if ($depth) {
$collapsed = $count >= $this->collapseSub;
$indent1 = '<span class="tracy-dump-indent"> ' . \str_repeat('| ', $depth) . '</span>';
$indent = '<span class="tracy-dump-indent"> ' . \str_repeat('| ', $depth) . ' </span>';
$toggle = '<span class="tracy-toggle' . ($collapsed ? ' tracy-collapsed' : '') . '">string</span>' . "\n";
}
return $toggle . '<div class="tracy-dump-string' . ($collapsed ? ' tracy-collapsed' : '') . '" title="' . $str->length . ' ' . $unit . '">' . $indent1 . '<span' . ($count ? ' class="tracy-dump-lq"' : '') . ">'</span>" . \str_replace("\n", "\n" . $indent, $str->value) . "<span>'</span>" . ($depth ? "\n" : '') . '</div>';
}
return '<span class="tracy-dump-string"' . ($str->length > 1 ? " title=\"{$str->length} {$unit}\"" : '') . '>' . "<span>'</span>" . $str->value . "<span>'</span>" . '</span>';
}
}
/**
* @param array|Value $array
*/
private function renderArray($array, int $depth) : string
{
$out = '<span class="tracy-dump-array">array</span> (';
if (\is_array($array)) {
$items = $array;
$count = \count($items);
$out .= $count . ')';
} elseif ($array->items === null) {
return $out . $array->length . ') …';
} else {
$items = $array->items;
$count = $array->length ?? \count($items);
$out .= $count . ')';
if ($array->id && isset($this->parents[$array->id])) {
return $out . ' <i>RECURSION</i>';
} elseif ($array->id && ($array->depth < $depth || isset($this->above[$array->id]))) {
if ($this->lazy !== \false) {
$ref = new Value(Value::TypeRef, $array->id);
$this->copySnapshot($ref);
return '<span class="tracy-toggle tracy-collapsed" data-tracy-dump=\'' . \json_encode($ref) . "'>" . $out . '</span>';
} elseif ($this->hash) {
return $out . (isset($this->above[$array->id]) ? ' <i>see above</i>' : ' <i>see below</i>');
}
}
}
if (!$count) {
return $out;
}
$collapsed = $depth ? $this->lazy === \false || $depth === 1 ? $count >= $this->collapseSub : \true : (\is_int($this->collapseTop) ? $count >= $this->collapseTop : $this->collapseTop);
$span = '<span class="tracy-toggle' . ($collapsed ? ' tracy-collapsed' : '') . '"';
if ($collapsed && $this->lazy !== \false) {
$array = isset($array->id) ? new Value(Value::TypeRef, $array->id) : $array;
$this->copySnapshot($array);
return $span . " data-tracy-dump='" . self::jsonEncode($array) . "'>" . $out . '</span>';
}
$out = $span . '>' . $out . "</span>\n" . '<div' . ($collapsed ? ' class="tracy-collapsed"' : '') . '>';
$indent = '<span class="tracy-dump-indent"> ' . \str_repeat('| ', $depth) . '</span>';
$this->parents[$array->id ?? null] = $this->above[$array->id ?? null] = \true;
foreach ($items as $info) {
[$k, $v, $ref] = $info + [2 => null];
$out .= $indent . $this->renderVar($k, $depth + 1, self::TypeArrayKey) . ' => ' . ($ref && $this->hash ? '<span class="tracy-dump-hash">&' . $ref . '</span> ' : '') . ($tmp = $this->renderVar($v, $depth + 1)) . (\substr($tmp, -6) === '</div>' ? '' : "\n");
}
if ($count > \count($items)) {
$out .= $indent . "\n";
}
unset($this->parents[$array->id ?? null]);
return $out . '</div>';
}
private function renderObject(Value $object, int $depth) : string
{
$editorAttributes = '';
if ($this->classLocation && $object->editor) {
$editorAttributes = Helpers::formatHtml(' title="Declared in file % on line %%%" data-tracy-href="%"', $object->editor->file, $object->editor->line, $object->editor->url ? "\nCtrl-Click to open in editor" : '', "\nAlt-Click to expand/collapse all child nodes", $object->editor->url);
}
$pos = \strrpos($object->value, '\\');
$out = '<span class="tracy-dump-object"' . $editorAttributes . '>' . ($pos ? Helpers::escapeHtml(\substr($object->value, 0, $pos + 1)) . '<b>' . Helpers::escapeHtml(\substr($object->value, $pos + 1)) . '</b>' : Helpers::escapeHtml($object->value)) . '</span>' . ($object->id && $this->hash ? ' <span class="tracy-dump-hash">#' . $object->id . '</span>' : '');
if ($object->items === null) {
return $out . ' …';
} elseif (!$object->items) {
return $out;
} elseif ($object->id && isset($this->parents[$object->id])) {
return $out . ' <i>RECURSION</i>';
} elseif ($object->id && ($object->depth < $depth || isset($this->above[$object->id]))) {
if ($this->lazy !== \false) {
$ref = new Value(Value::TypeRef, $object->id);
$this->copySnapshot($ref);
return '<span class="tracy-toggle tracy-collapsed" data-tracy-dump=\'' . \json_encode($ref) . "'>" . $out . '</span>';
} elseif ($this->hash) {
return $out . (isset($this->above[$object->id]) ? ' <i>see above</i>' : ' <i>see below</i>');
}
}
$collapsed = $object->collapsed ?? ($depth ? $this->lazy === \false || $depth === 1 ? \count($object->items) >= $this->collapseSub : \true : (\is_int($this->collapseTop) ? \count($object->items) >= $this->collapseTop : $this->collapseTop));
$span = '<span class="tracy-toggle' . ($collapsed ? ' tracy-collapsed' : '') . '"';
if ($collapsed && $this->lazy !== \false) {
$value = $object->id ? new Value(Value::TypeRef, $object->id) : $object;
$this->copySnapshot($value);
return $span . " data-tracy-dump='" . self::jsonEncode($value) . "'>" . $out . '</span>';
}
$out = $span . '>' . $out . "</span>\n" . '<div' . ($collapsed ? ' class="tracy-collapsed"' : '') . '>';
$indent = '<span class="tracy-dump-indent"> ' . \str_repeat('| ', $depth) . '</span>';
$this->parents[$object->id] = $this->above[$object->id] = \true;
foreach ($object->items as $info) {
[$k, $v, $type, $ref] = $info + [2 => Value::PropertyVirtual, null];
$out .= $indent . $this->renderVar($k, $depth + 1, $type) . ': ' . ($ref && $this->hash ? '<span class="tracy-dump-hash">&' . $ref . '</span> ' : '') . ($tmp = $this->renderVar($v, $depth + 1)) . (\substr($tmp, -6) === '</div>' ? '' : "\n");
}
if ($object->length > \count($object->items)) {
$out .= $indent . "\n";
}
unset($this->parents[$object->id]);
return $out . '</div>';
}
private function renderResource(Value $resource, int $depth) : string
{
$out = '<span class="tracy-dump-resource">' . Helpers::escapeHtml($resource->value) . '</span> ' . ($this->hash ? '<span class="tracy-dump-hash">@' . \substr($resource->id, 1) . '</span>' : '');
if (!$resource->items) {
return $out;
} elseif (isset($this->above[$resource->id])) {
if ($this->lazy !== \false) {
$ref = new Value(Value::TypeRef, $resource->id);
$this->copySnapshot($ref);
return '<span class="tracy-toggle tracy-collapsed" data-tracy-dump=\'' . \json_encode($ref) . "'>" . $out . '</span>';
}
return $out . ' <i>see above</i>';
} else {
$this->above[$resource->id] = \true;
$out = "<span class=\"tracy-toggle tracy-collapsed\">{$out}</span>\n<div class=\"tracy-collapsed\">";
foreach ($resource->items as [$k, $v]) {
$out .= '<span class="tracy-dump-indent"> ' . \str_repeat('| ', $depth) . '</span>' . $this->renderVar($k, $depth + 1, Value::PropertyVirtual) . ': ' . ($tmp = $this->renderVar($v, $depth + 1)) . (\substr($tmp, -6) === '</div>' ? '' : "\n");
}
return $out . '</div>';
}
}
private function copySnapshot($value) : void
{
if ($this->collectingMode) {
return;
}
if ($this->snapshotSelection === null) {
$this->snapshotSelection = [];
}
if (\is_array($value)) {
foreach ($value as [, $v]) {
$this->copySnapshot($v);
}
} elseif ($value instanceof Value && $value->type === Value::TypeRef) {
if (!isset($this->snapshotSelection[$value->value])) {
$ref = $this->snapshotSelection[$value->value] = $this->snapshot[$value->value];
$this->copySnapshot($ref);
}
} elseif ($value instanceof Value && $value->items) {
foreach ($value->items as [, $v]) {
$this->copySnapshot($v);
}
}
}
public static function jsonEncode($snapshot) : string
{
$old = @\ini_set('serialize_precision', '-1');
// @ may be disabled
try {
return \json_encode($snapshot, \JSON_HEX_APOS | \JSON_HEX_AMP | \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES);
} finally {
if ($old !== \false) {
\ini_set('serialize_precision', $old);
}
}
}
private static function htmlToAnsi(string $s, array $colors) : string
{
$stack = ['0'];
$s = \preg_replace_callback('#<\\w+(?: class="tracy-dump-(\\w+)")?[^>]*>|</\\w+>#', function ($m) use($colors, &$stack) : string {
if ($m[0][1] === '/') {
\array_pop($stack);
} else {
$stack[] = isset($m[1], $colors[$m[1]]) ? $colors[$m[1]] : '0';
}
return "\x1b[" . \end($stack) . 'm';
}, $s);
$s = \preg_replace('/\\e\\[0m(\\n*)(?=\\e)/', '$1', $s);
return $s;
}
}

View File

@ -0,0 +1,51 @@
<?php
/**
* This file is part of the Tracy (https://tracy.nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare (strict_types=1);
namespace RectorPrefix202302\Tracy\Dumper;
/**
* @internal
*/
final class Value implements \JsonSerializable
{
public const TypeArray = 'array', TypeBinaryHtml = 'bin', TypeNumber = 'number', TypeObject = 'object', TypeRef = 'ref', TypeResource = 'resource', TypeStringHtml = 'string', TypeText = 'text';
public const PropertyPublic = 0, PropertyProtected = 1, PropertyPrivate = 2, PropertyDynamic = 3, PropertyVirtual = 4;
/** @var string */
public $type;
/** @var string|int */
public $value;
/** @var ?int */
public $length;
/** @var ?int */
public $depth;
/** @var int|string */
public $id;
/** @var object */
public $holder;
/** @var ?array */
public $items;
/** @var ?\stdClass */
public $editor;
/** @var ?bool */
public $collapsed;
public function __construct(string $type, $value = null, ?int $length = null)
{
$this->type = $type;
$this->value = $value;
$this->length = $length;
}
public function jsonSerialize() : array
{
$res = [$this->type => $this->value];
foreach (['length', 'editor', 'items', 'collapsed'] as $k) {
if ($this->{$k} !== null) {
$res[$k] = $this->{$k};
}
}
return $res;
}
}

View File

@ -0,0 +1,145 @@
/**
* This file is part of the Tracy (https://tracy.nette.org)
*/
.tracy-dump.tracy-dark {
text-align: left;
color: #f8f8f2;
background: #29292e;
border-radius: 4px;
padding: 1em;
margin: 1em 0;
word-break: break-all;
white-space: pre-wrap;
}
.tracy-dump.tracy-dark div {
padding-left: 2.5ex;
}
.tracy-dump.tracy-dark div div {
border-left: 1px solid rgba(255, 255, 255, .1);
margin-left: .5ex;
}
.tracy-dump.tracy-dark div div:hover {
border-left-color: rgba(255, 255, 255, .25);
}
.tracy-dark .tracy-dump-location {
color: silver;
font-size: 80%;
text-decoration: none;
background: none;
opacity: .5;
float: right;
cursor: pointer;
}
.tracy-dark .tracy-dump-location:hover,
.tracy-dark .tracy-dump-location:focus {
opacity: 1;
}
.tracy-dark .tracy-dump-array,
.tracy-dark .tracy-dump-object {
color: #f69c2e;
user-select: text;
}
.tracy-dark .tracy-dump-string {
color: #3cdfef;
white-space: break-spaces;
}
.tracy-dark div.tracy-dump-string {
position: relative;
padding-left: 3.5ex;
}
.tracy-dark .tracy-dump-lq {
margin-left: calc(-1ex - 1px);
}
.tracy-dark div.tracy-dump-string:before {
content: '';
position: absolute;
left: calc(3ex - 1px);
top: 1.5em;
bottom: 0;
border-left: 1px solid rgba(255, 255, 255, .1);
}
.tracy-dark .tracy-dump-virtual span,
.tracy-dark .tracy-dump-dynamic span,
.tracy-dark .tracy-dump-string span {
color: rgba(255, 255, 255, 0.5);
}
.tracy-dark .tracy-dump-virtual i,
.tracy-dark .tracy-dump-dynamic i,
.tracy-dark .tracy-dump-string i {
font-size: 80%;
font-style: normal;
color: rgba(255, 255, 255, 0.5);
user-select: none;
}
.tracy-dark .tracy-dump-number {
color: #77d285;
}
.tracy-dark .tracy-dump-null,
.tracy-dark .tracy-dump-bool {
color: #f3cb44;
}
.tracy-dark .tracy-dump-virtual {
font-style: italic;
}
.tracy-dark .tracy-dump-public::after {
content: ' pub';
}
.tracy-dark .tracy-dump-protected::after {
content: ' pro';
}
.tracy-dark .tracy-dump-private::after {
content: ' pri';
}
.tracy-dark .tracy-dump-public::after,
.tracy-dark .tracy-dump-protected::after,
.tracy-dark .tracy-dump-private::after,
.tracy-dark .tracy-dump-hash {
font-size: 85%;
color: rgba(255, 255, 255, 0.35);
}
.tracy-dark .tracy-dump-indent {
display: none;
}
.tracy-dark .tracy-dump-highlight {
background: #C22;
color: white;
border-radius: 2px;
padding: 0 2px;
margin: 0 -2px;
}
span[data-tracy-href] {
border-bottom: 1px dotted rgba(255, 255, 255, .2);
}
.tracy-dark .tracy-dump-flash {
animation: tracy-dump-flash .2s ease;
}
@keyframes tracy-dump-flash {
0% {
background: #c0c0c033;
}
}

View File

@ -0,0 +1,145 @@
/**
* This file is part of the Tracy (https://tracy.nette.org)
*/
.tracy-dump.tracy-light {
text-align: left;
color: #444;
background: #fdf9e2;
border-radius: 4px;
padding: 1em;
margin: 1em 0;
word-break: break-all;
white-space: pre-wrap;
}
.tracy-dump.tracy-light div {
padding-left: 2.5ex;
}
.tracy-dump.tracy-light div div {
border-left: 1px solid rgba(0, 0, 0, .1);
margin-left: .5ex;
}
.tracy-dump.tracy-light div div:hover {
border-left-color: rgba(0, 0, 0, .25);
}
.tracy-light .tracy-dump-location {
color: gray;
font-size: 80%;
text-decoration: none;
background: none;
opacity: .5;
float: right;
cursor: pointer;
}
.tracy-light .tracy-dump-location:hover,
.tracy-light .tracy-dump-location:focus {
opacity: 1;
}
.tracy-light .tracy-dump-array,
.tracy-light .tracy-dump-object {
color: #C22;
user-select: text;
}
.tracy-light .tracy-dump-string {
color: #35D;
white-space: break-spaces;
}
.tracy-light div.tracy-dump-string {
position: relative;
padding-left: 3.5ex;
}
.tracy-light .tracy-dump-lq {
margin-left: calc(-1ex - 1px);
}
.tracy-light div.tracy-dump-string:before {
content: '';
position: absolute;
left: calc(3ex - 1px);
top: 1.5em;
bottom: 0;
border-left: 1px solid rgba(0, 0, 0, .1);
}
.tracy-light .tracy-dump-virtual span,
.tracy-light .tracy-dump-dynamic span,
.tracy-light .tracy-dump-string span {
color: rgba(0, 0, 0, 0.5);
}
.tracy-light .tracy-dump-virtual i,
.tracy-light .tracy-dump-dynamic i,
.tracy-light .tracy-dump-string i {
font-size: 80%;
font-style: normal;
color: rgba(0, 0, 0, 0.5);
user-select: none;
}
.tracy-light .tracy-dump-number {
color: #090;
}
.tracy-light .tracy-dump-null,
.tracy-light .tracy-dump-bool {
color: #850;
}
.tracy-light .tracy-dump-virtual {
font-style: italic;
}
.tracy-light .tracy-dump-public::after {
content: ' pub';
}
.tracy-light .tracy-dump-protected::after {
content: ' pro';
}
.tracy-light .tracy-dump-private::after {
content: ' pri';
}
.tracy-light .tracy-dump-public::after,
.tracy-light .tracy-dump-protected::after,
.tracy-light .tracy-dump-private::after,
.tracy-light .tracy-dump-hash {
font-size: 85%;
color: rgba(0, 0, 0, 0.35);
}
.tracy-light .tracy-dump-indent {
display: none;
}
.tracy-light .tracy-dump-highlight {
background: #C22;
color: white;
border-radius: 2px;
padding: 0 2px;
margin: 0 -2px;
}
span[data-tracy-href] {
border-bottom: 1px dotted rgba(0, 0, 0, .2);
}
.tracy-light .tracy-dump-flash {
animation: tracy-dump-flash .2s ease;
}
@keyframes tracy-dump-flash {
0% {
background: #c0c0c033;
}
}

View File

@ -0,0 +1,393 @@
/**
* This file is part of the Tracy (https://tracy.nette.org)
*/
const
COLLAPSE_COUNT = 7,
COLLAPSE_COUNT_TOP = 14,
TYPE_ARRAY = 'a',
TYPE_OBJECT = 'o',
TYPE_RESOURCE = 'r',
PROP_VIRTUAL = 4,
PROP_PRIVATE = 2;
const
HINT_CTRL = 'Ctrl-Click to open in editor',
HINT_ALT = 'Alt-Click to expand/collapse all child nodes';
class Dumper
{
static init(context) {
// full lazy
(context || document).querySelectorAll('[data-tracy-snapshot][data-tracy-dump]').forEach((pre) => { // <pre>
let snapshot = JSON.parse(pre.getAttribute('data-tracy-snapshot'));
pre.removeAttribute('data-tracy-snapshot');
pre.appendChild(build(JSON.parse(pre.getAttribute('data-tracy-dump')), snapshot, pre.classList.contains('tracy-collapsed')));
pre.removeAttribute('data-tracy-dump');
pre.classList.remove('tracy-collapsed');
});
// snapshots
(context || document).querySelectorAll('meta[itemprop=tracy-snapshot]').forEach((meta) => {
let snapshot = JSON.parse(meta.getAttribute('content'));
meta.parentElement.querySelectorAll('[data-tracy-dump]').forEach((pre) => { // <pre>
if (pre.closest('[data-tracy-snapshot]')) { // ignore unrelated <span data-tracy-dump>
return;
}
pre.appendChild(build(JSON.parse(pre.getAttribute('data-tracy-dump')), snapshot, pre.classList.contains('tracy-collapsed')));
pre.removeAttribute('data-tracy-dump');
pre.classList.remove('tracy-collapsed');
});
// <meta> must be left for debug bar panel content
});
if (Dumper.inited) {
return;
}
Dumper.inited = true;
document.documentElement.addEventListener('click', (e) => {
let el;
// enables <span data-tracy-href=""> & ctrl key
if (e.ctrlKey && (el = e.target.closest('[data-tracy-href]'))) {
location.href = el.getAttribute('data-tracy-href');
return false;
}
});
document.documentElement.addEventListener('tracy-beforetoggle', (e) => {
let el;
// initializes lazy <span data-tracy-dump> inside <pre data-tracy-snapshot>
if ((el = e.target.closest('[data-tracy-snapshot]'))) {
let snapshot = JSON.parse(el.getAttribute('data-tracy-snapshot'));
el.removeAttribute('data-tracy-snapshot');
el.querySelectorAll('[data-tracy-dump]').forEach((toggler) => {
if (!toggler.nextSibling) {
toggler.after(document.createTextNode('\n')); // enforce \n after toggler
}
toggler.nextSibling.after(buildStruct(JSON.parse(toggler.getAttribute('data-tracy-dump')), snapshot, toggler, true, []));
toggler.removeAttribute('data-tracy-dump');
});
}
});
document.documentElement.addEventListener('tracy-toggle', (e) => {
if (!e.target.matches('.tracy-dump *')) {
return;
}
let cont = e.detail.relatedTarget;
let origE = e.detail.originalEvent;
if (origE && origE.usedIds) { // triggered by expandChild()
toggleChildren(cont, origE.usedIds);
return;
} else if (origE && origE.altKey && cont.querySelector('.tracy-toggle')) { // triggered by alt key
if (e.detail.collapsed) { // reopen
e.target.classList.toggle('tracy-collapsed', false);
cont.classList.toggle('tracy-collapsed', false);
e.detail.collapsed = false;
}
let expand = e.target.tracyAltExpand = !e.target.tracyAltExpand;
toggleChildren(cont, expand ? {} : false);
}
cont.classList.toggle('tracy-dump-flash', !e.detail.collapsed);
});
document.documentElement.addEventListener('animationend', (e) => {
if (e.animationName === 'tracy-dump-flash') {
e.target.classList.toggle('tracy-dump-flash', false);
}
});
document.addEventListener('mouseover', (e) => {
if (!e.target.matches('.tracy-dump *')) {
return;
}
let el;
if (e.target.matches('.tracy-dump-hash') && (el = e.target.closest('.tracy-dump'))) {
el.querySelectorAll('.tracy-dump-hash').forEach((el) => {
if (el.textContent === e.target.textContent) {
el.classList.add('tracy-dump-highlight');
}
});
return;
}
if ((el = e.target.closest('.tracy-toggle')) && !el.title) {
el.title = HINT_ALT;
}
});
document.addEventListener('mouseout', (e) => {
if (e.target.matches('.tracy-dump-hash')) {
document.querySelectorAll('.tracy-dump-hash.tracy-dump-highlight').forEach((el) => {
el.classList.remove('tracy-dump-highlight');
});
}
});
Tracy.Toggle.init();
}
}
function build(data, repository, collapsed, parentIds, keyType) {
let id, type = data === null ? 'null' : typeof data,
collapseCount = collapsed === null ? COLLAPSE_COUNT : COLLAPSE_COUNT_TOP;
if (type === 'null' || type === 'number' || type === 'boolean') {
return createEl(null, null, [
createEl(
'span',
{'class': 'tracy-dump-' + type.replace('ean', '')},
[data + '']
)
]);
} else if (type === 'string') {
data = {
string: data.replace(/&/g, '&amp;').replace(/</g, '&lt;'),
length: [...data].length
};
} else if (Array.isArray(data)) {
data = {array: null, items: data};
} else if (data.ref) {
id = data.ref;
data = repository[id];
if (!data) {
throw new UnknownEntityException;
}
}
if (data.string !== undefined || data.bin !== undefined) {
let s = data.string === undefined ? data.bin : data.string;
if (keyType === TYPE_ARRAY) {
return createEl(null, null, [
createEl(
'span',
{'class': 'tracy-dump-string'},
{html: '<span class="tracy-dump-lq">\'</span>' + s + '<span>\'</span>'}
),
]);
} else if (keyType !== undefined) {
if (type !== 'string') {
s = '<span class="tracy-dump-lq">\'</span>' + s + '<span>\'</span>';
}
const classes = [
'tracy-dump-public',
'tracy-dump-protected',
'tracy-dump-private',
'tracy-dump-dynamic',
'tracy-dump-virtual',
];
return createEl(null, null, [
createEl(
'span',
{
'class': classes[typeof keyType === 'string' ? PROP_PRIVATE : keyType],
'title': typeof keyType === 'string' ? 'declared in ' + keyType : null,
},
{html: s}
),
]);
}
let count = (s.match(/\n/g) || []).length;
if (count) {
let collapsed = count >= COLLAPSE_COUNT;
return createEl(null, null, [
createEl('span', {'class': collapsed ? 'tracy-toggle tracy-collapsed' : 'tracy-toggle'}, ['string']),
'\n',
createEl(
'div',
{
'class': 'tracy-dump-string' + (collapsed ? ' tracy-collapsed' : ''),
'title': data.length + (data.bin ? ' bytes' : ' characters'),
},
{html: '<span class="tracy-dump-lq">\'</span>' + s + '<span>\'</span>'}
),
]);
}
return createEl(null, null, [
createEl(
'span',
{
'class': 'tracy-dump-string',
'title': data.length + (data.bin ? ' bytes' : ' characters'),
},
{html: '<span>\'</span>' + s + '<span>\'</span>'}
),
]);
} else if (data.number) {
return createEl(null, null, [
createEl('span', {'class': 'tracy-dump-number'}, [data.number])
]);
} else if (data.text !== undefined) {
return createEl(null, null, [
createEl('span', {class: 'tracy-dump-virtual'}, [data.text])
]);
} else { // object || resource || array
let pos, nameEl;
nameEl = data.object && (pos = data.object.lastIndexOf('\\')) > 0
? [data.object.substr(0, pos + 1), createEl('b', null, [data.object.substr(pos + 1)])]
: [data.object || data.resource];
let span = data.array !== undefined
? [
createEl('span', {'class': 'tracy-dump-array'}, ['array']),
' (' + (data.length || data.items.length) + ')'
]
: [
createEl('span', {
'class': data.object ? 'tracy-dump-object' : 'tracy-dump-resource',
title: data.editor ? 'Declared in file ' + data.editor.file + ' on line ' + data.editor.line + (data.editor.url ? '\n' + HINT_CTRL : '') + '\n' + HINT_ALT : null,
'data-tracy-href': data.editor ? data.editor.url : null
}, nameEl),
...(id ? [' ', createEl('span', {'class': 'tracy-dump-hash'}, [data.resource ? '@' + id.substr(1) : '#' + id])] : [])
];
parentIds = parentIds ? parentIds.slice() : [];
let recursive = id && parentIds.indexOf(id) > -1;
parentIds.push(id);
if (recursive || !data.items || !data.items.length) {
span.push(recursive ? ' RECURSION' : (!data.items || data.items.length ? ' …' : ''));
return createEl(null, null, span);
}
collapsed = collapsed === true || data.collapsed || (data.items && data.items.length >= collapseCount);
let toggle = createEl('span', {'class': collapsed ? 'tracy-toggle tracy-collapsed' : 'tracy-toggle'}, span);
return createEl(null, null, [
toggle,
'\n',
buildStruct(data, repository, toggle, collapsed, parentIds),
]);
}
}
function buildStruct(data, repository, toggle, collapsed, parentIds) {
if (Array.isArray(data)) {
data = {items: data};
} else if (data.ref) {
parentIds = parentIds.slice();
parentIds.push(data.ref);
data = repository[data.ref];
}
let cut = data.items && data.length > data.items.length;
let type = data.object ? TYPE_OBJECT : data.resource ? TYPE_RESOURCE : TYPE_ARRAY;
let div = createEl('div', {'class': collapsed ? 'tracy-collapsed' : null});
if (collapsed) {
let handler;
toggle.addEventListener('tracy-toggle', handler = function() {
toggle.removeEventListener('tracy-toggle', handler);
createItems(div, data.items, type, repository, parentIds, null);
if (cut) {
createEl(div, null, ['…\n']);
}
});
} else {
createItems(div, data.items, type, repository, parentIds, true);
if (cut) {
createEl(div, null, ['…\n']);
}
}
return div;
}
function createEl(el, attrs, content) {
if (!(el instanceof Node)) {
el = el ? document.createElement(el) : document.createDocumentFragment();
}
for (let id in attrs || {}) {
if (attrs[id] !== null) {
el.setAttribute(id, attrs[id]);
}
}
if (content && content.html !== undefined) {
el.innerHTML = content.html;
return el;
}
content = content || [];
el.append(...content.filter((child) => (child !== null)));
return el;
}
function createItems(el, items, type, repository, parentIds, collapsed) {
let key, val, vis, ref, i, tmp;
for (i = 0; i < items.length; i++) {
if (type === TYPE_ARRAY) {
[key, val, ref] = items[i];
} else {
[key, val, vis = PROP_VIRTUAL, ref] = items[i];
}
createEl(el, null, [
build(key, null, null, null, type === TYPE_ARRAY ? TYPE_ARRAY : vis),
type === TYPE_ARRAY ? ' => ' : ': ',
...(ref ? [createEl('span', {'class': 'tracy-dump-hash'}, ['&' + ref]), ' '] : []),
tmp = build(val, repository, collapsed, parentIds),
tmp.lastElementChild.tagName === 'DIV' ? '' : '\n',
]);
}
}
function toggleChildren(cont, usedIds) {
let hashEl, id;
cont.querySelectorAll(':scope > .tracy-toggle').forEach((el) => {
hashEl = (el.querySelector('.tracy-dump-hash') || el.previousElementSibling);
id = hashEl && hashEl.matches('.tracy-dump-hash') ? hashEl.textContent : null;
if (!usedIds || (id && usedIds[id])) {
Tracy.Toggle.toggle(el, false);
} else {
usedIds[id] = true;
Tracy.Toggle.toggle(el, true, {usedIds: usedIds});
}
});
}
function UnknownEntityException() {}
let Tracy = window.Tracy = window.Tracy || {};
Tracy.Dumper = Tracy.Dumper || Dumper;
function init() {
Tracy.Dumper.init();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}

430
vendor/tracy/tracy/src/Tracy/Helpers.php vendored Normal file
View File

@ -0,0 +1,430 @@
<?php
/**
* This file is part of the Tracy (https://tracy.nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare (strict_types=1);
namespace RectorPrefix202302\Tracy;
use RectorPrefix202302\Nette;
/**
* Rendering helpers for Debugger.
*/
class Helpers
{
/**
* Returns HTML link to editor.
*/
public static function editorLink(string $file, ?int $line = null) : string
{
$file = \strtr($origFile = $file, Debugger::$editorMapping);
if ($editor = self::editorUri($origFile, $line)) {
$parts = \explode('/', \strtr($file, '\\', '/'));
$file = \array_pop($parts);
while ($parts && \strlen($file) < 50) {
$file = \array_pop($parts) . '/' . $file;
}
$file = ($parts ? '.../' : '') . $file;
$file = \strtr($file, '/', \DIRECTORY_SEPARATOR);
return self::formatHtml('<a href="%" title="%" class="tracy-editor">%<b>%</b>%</a>', $editor, $origFile . ($line ? ":{$line}" : ''), \rtrim(\dirname($file), \DIRECTORY_SEPARATOR) . \DIRECTORY_SEPARATOR, \basename($file), $line ? ":{$line}" : '');
} else {
return self::formatHtml('<span>%</span>', $file . ($line ? ":{$line}" : ''));
}
}
/**
* Returns link to editor.
*/
public static function editorUri(string $file, ?int $line = null, string $action = 'open', string $search = '', string $replace = '') : ?string
{
if (Debugger::$editor && $file && ($action === 'create' || \is_file($file))) {
$file = \strtr($file, '/', \DIRECTORY_SEPARATOR);
$file = \strtr($file, Debugger::$editorMapping);
$search = \str_replace("\n", \PHP_EOL, $search);
$replace = \str_replace("\n", \PHP_EOL, $replace);
return \strtr(Debugger::$editor, ['%action' => $action, '%file' => \rawurlencode($file), '%line' => $line ?: 1, '%search' => \rawurlencode($search), '%replace' => \rawurlencode($replace)]);
}
return null;
}
public static function formatHtml(string $mask) : string
{
$args = \func_get_args();
return \preg_replace_callback('#%#', function () use(&$args, &$count) : string {
return \str_replace("\n", '&#10;', self::escapeHtml($args[++$count]));
}, $mask);
}
public static function escapeHtml($s) : string
{
return \htmlspecialchars((string) $s, \ENT_QUOTES | \ENT_SUBSTITUTE | \ENT_HTML5, 'UTF-8');
}
public static function findTrace(array $trace, $method, ?int &$index = null) : ?array
{
$m = \is_array($method) ? $method : \explode('::', $method);
foreach ($trace as $i => $item) {
if (isset($item['function']) && $item['function'] === \end($m) && isset($item['class']) === isset($m[1]) && (!isset($item['class']) || $m[0] === '*' || \is_a($item['class'], $m[0], \true))) {
$index = $i;
return $item;
}
}
return null;
}
public static function getClass($obj) : string
{
return \explode("\x00", \get_class($obj))[0];
}
/** @internal */
public static function fixStack(\Throwable $exception) : \Throwable
{
if (\function_exists('xdebug_get_function_stack')) {
$stack = [];
$trace = @\xdebug_get_function_stack();
// @ xdebug compatibility warning
$trace = \array_slice(\array_reverse($trace), 2, -1);
foreach ($trace as $row) {
$frame = ['file' => $row['file'], 'line' => $row['line'], 'function' => $row['function'] ?? '*unknown*', 'args' => []];
if (!empty($row['class'])) {
$frame['type'] = isset($row['type']) && $row['type'] === 'dynamic' ? '->' : '::';
$frame['class'] = $row['class'];
}
$stack[] = $frame;
}
$ref = new \ReflectionProperty('Exception', 'trace');
$ref->setAccessible(\true);
$ref->setValue($exception, $stack);
}
return $exception;
}
/** @internal */
public static function errorTypeToString(int $type) : string
{
$types = [\E_ERROR => 'Fatal Error', \E_USER_ERROR => 'User Error', \E_RECOVERABLE_ERROR => 'Recoverable Error', \E_CORE_ERROR => 'Core Error', \E_COMPILE_ERROR => 'Compile Error', \E_PARSE => 'Parse Error', \E_WARNING => 'Warning', \E_CORE_WARNING => 'Core Warning', \E_COMPILE_WARNING => 'Compile Warning', \E_USER_WARNING => 'User Warning', \E_NOTICE => 'Notice', \E_USER_NOTICE => 'User Notice', \E_STRICT => 'Strict standards', \E_DEPRECATED => 'Deprecated', \E_USER_DEPRECATED => 'User Deprecated'];
return $types[$type] ?? 'Unknown error';
}
/** @internal */
public static function getSource() : string
{
if (self::isCli()) {
return 'CLI (PID: ' . \getmypid() . ')' . (isset($_SERVER['argv']) ? ': ' . \implode(' ', \array_map([self::class, 'escapeArg'], $_SERVER['argv'])) : '');
} elseif (isset($_SERVER['REQUEST_URI'])) {
return (!empty($_SERVER['HTTPS']) && \strcasecmp($_SERVER['HTTPS'], 'off') ? 'https://' : 'http://') . ($_SERVER['HTTP_HOST'] ?? '') . $_SERVER['REQUEST_URI'];
} else {
return \PHP_SAPI;
}
}
/** @internal */
public static function improveException(\Throwable $e) : void
{
$message = $e->getMessage();
if (!$e instanceof \Error && !$e instanceof \ErrorException || $e instanceof Nette\MemberAccessException || \strpos($e->getMessage(), 'did you mean')) {
// do nothing
} elseif (\preg_match('#^Call to undefined function (\\S+\\\\)?(\\w+)\\(#', $message, $m)) {
$funcs = \array_merge(\get_defined_functions()['internal'], \get_defined_functions()['user']);
$hint = self::getSuggestion($funcs, $m[1] . $m[2]) ?: self::getSuggestion($funcs, $m[2]);
$message = "Call to undefined function {$m[2]}(), did you mean {$hint}()?";
$replace = ["{$m[2]}(", "{$hint}("];
} elseif (\preg_match('#^Call to undefined method ([\\w\\\\]+)::(\\w+)#', $message, $m)) {
$hint = self::getSuggestion(\get_class_methods($m[1]) ?: [], $m[2]);
$message .= ", did you mean {$hint}()?";
$replace = ["{$m[2]}(", "{$hint}("];
} elseif (\preg_match('#^Undefined variable:? \\$?(\\w+)#', $message, $m) && !empty($e->context)) {
$hint = self::getSuggestion(\array_keys($e->context), $m[1]);
$message = "Undefined variable \${$m[1]}, did you mean \${$hint}?";
$replace = ["\${$m[1]}", "\${$hint}"];
} elseif (\preg_match('#^Undefined property: ([\\w\\\\]+)::\\$(\\w+)#', $message, $m)) {
$rc = new \ReflectionClass($m[1]);
$items = \array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), function ($prop) {
return !$prop->isStatic();
});
$hint = self::getSuggestion($items, $m[2]);
$message .= ", did you mean \${$hint}?";
$replace = ["->{$m[2]}", "->{$hint}"];
} elseif (\preg_match('#^Access to undeclared static property:? ([\\w\\\\]+)::\\$(\\w+)#', $message, $m)) {
$rc = new \ReflectionClass($m[1]);
$items = \array_filter($rc->getProperties(\ReflectionProperty::IS_STATIC), function ($prop) {
return $prop->isPublic();
});
$hint = self::getSuggestion($items, $m[2]);
$message .= ", did you mean \${$hint}?";
$replace = ["::\${$m[2]}", "::\${$hint}"];
}
if (isset($hint)) {
$loc = Debugger::mapSource($e->getFile(), $e->getLine()) ?? ['file' => $e->getFile(), 'line' => $e->getLine()];
$ref = new \ReflectionProperty($e, 'message');
$ref->setAccessible(\true);
$ref->setValue($e, $message);
@($e->tracyAction = [
// dynamic properties are deprecated since PHP 8.2
'link' => self::editorUri($loc['file'], $loc['line'], 'fix', $replace[0], $replace[1]),
'label' => 'fix it',
]);
}
}
/** @internal */
public static function improveError(string $message, array $context = []) : string
{
if (\preg_match('#^Undefined variable:? \\$?(\\w+)#', $message, $m) && $context) {
$hint = self::getSuggestion(\array_keys($context), $m[1]);
return $hint ? "Undefined variable \${$m[1]}, did you mean \${$hint}?" : $message;
} elseif (\preg_match('#^Undefined property: ([\\w\\\\]+)::\\$(\\w+)#', $message, $m)) {
$rc = new \ReflectionClass($m[1]);
$items = \array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), function ($prop) {
return !$prop->isStatic();
});
$hint = self::getSuggestion($items, $m[2]);
return $hint ? $message . ", did you mean \${$hint}?" : $message;
}
return $message;
}
/** @internal */
public static function guessClassFile(string $class) : ?string
{
$segments = \explode('\\', $class);
$res = null;
$max = 0;
foreach (\get_declared_classes() as $class) {
$parts = \explode('\\', $class);
foreach ($parts as $i => $part) {
if ($part !== ($segments[$i] ?? null)) {
break;
}
}
if ($i > $max && $i < \count($segments) && ($file = (new \ReflectionClass($class))->getFileName())) {
$max = $i;
$res = \array_merge(\array_slice(\explode(\DIRECTORY_SEPARATOR, $file), 0, $i - \count($parts)), \array_slice($segments, $i));
$res = \implode(\DIRECTORY_SEPARATOR, $res) . '.php';
}
}
return $res;
}
/**
* Finds the best suggestion.
* @internal
*/
public static function getSuggestion(array $items, string $value) : ?string
{
$best = null;
$min = (\strlen($value) / 4 + 1) * 10 + 0.1;
$items = \array_map(function ($item) {
return $item instanceof \Reflector ? $item->getName() : (string) $item;
}, $items);
foreach (\array_unique($items) as $item) {
if (($len = \levenshtein($item, $value, 10, 11, 10)) > 0 && $len < $min) {
$min = $len;
$best = $item;
}
}
return $best;
}
/** @internal */
public static function isHtmlMode() : bool
{
return empty($_SERVER['HTTP_X_REQUESTED_WITH']) && empty($_SERVER['HTTP_X_TRACY_AJAX']) && isset($_SERVER['HTTP_HOST']) && !self::isCli() && !\preg_match('#^Content-Type: *+(?!text/html)#im', \implode("\n", \headers_list()));
}
/** @internal */
public static function isAjax() : bool
{
return isset($_SERVER['HTTP_X_TRACY_AJAX']) && \preg_match('#^\\w{10,15}$#D', $_SERVER['HTTP_X_TRACY_AJAX']);
}
/** @internal */
public static function isRedirect() : bool
{
return (bool) \preg_match('#^Location:#im', \implode("\n", \headers_list()));
}
/** @internal */
public static function createId() : string
{
return \bin2hex(\random_bytes(5));
}
/** @internal */
public static function isCli() : bool
{
return \PHP_SAPI === 'cli' || \PHP_SAPI === 'phpdbg';
}
/** @internal */
public static function getNonce() : ?string
{
return \preg_match('#^Content-Security-Policy(?:-Report-Only)?:.*\\sscript-src\\s+(?:[^;]+\\s)?\'nonce-([\\w+/]+=*)\'#mi', \implode("\n", \headers_list()), $m) ? $m[1] : null;
}
/**
* Escape a string to be used as a shell argument.
*/
private static function escapeArg(string $s) : string
{
if (\preg_match('#^[a-z0-9._=/:-]+$#Di', $s)) {
return $s;
}
return \defined('PHP_WINDOWS_VERSION_BUILD') ? '"' . \str_replace('"', '""', $s) . '"' : \escapeshellarg($s);
}
/**
* Captures PHP output into a string.
*/
public static function capture(callable $func) : string
{
\ob_start(function () {
});
try {
$func();
return \ob_get_clean();
} catch (\Throwable $e) {
\ob_end_clean();
throw $e;
}
}
/** @internal */
public static function encodeString(string $s, ?int $maxLength = null, bool $showWhitespaces = \true) : string
{
$utf8 = self::isUtf8($s);
$len = $utf8 ? self::utf8Length($s) : \strlen($s);
return $maxLength && $len > $maxLength + 20 ? self::doEncodeString(self::truncateString($s, $maxLength, $utf8), $utf8, $showWhitespaces) . ' <span>…</span> ' . self::doEncodeString(self::truncateString($s, -10, $utf8), $utf8, $showWhitespaces) : self::doEncodeString($s, $utf8, $showWhitespaces);
}
private static function doEncodeString(string $s, bool $utf8, bool $showWhitespaces) : string
{
$specials = [\true => ["\r" => '<i>\\r</i>', "\n" => "<i>\\n</i>\n", "\t" => '<i>\\t</i> ', "\x1b" => '<i>\\e</i>', '<' => '&lt;', '&' => '&amp;'], \false => ["\r" => "\r", "\n" => "\n", "\t" => "\t", "\x1b" => '<i>\\e</i>', '<' => '&lt;', '&' => '&amp;']];
$special = $specials[$showWhitespaces];
$s = \preg_replace_callback($utf8 ? '#[\\p{C}<&]#u' : '#[\\x00-\\x1F\\x7F-\\xFF<&]#', function ($m) use($special) {
return $special[$m[0]] ?? (\strlen($m[0]) === 1 ? '<i>\\x' . \str_pad(\strtoupper(\dechex(\ord($m[0]))), 2, '0', \STR_PAD_LEFT) . '</i>' : '<i>\\u{' . \strtoupper(\ltrim(\dechex(self::utf8Ord($m[0])), '0')) . '}</i>');
}, $s);
$s = \str_replace('</i><i>', '', $s);
$s = \preg_replace('~\\n$~D', '', $s);
return $s;
}
private static function utf8Ord(string $c) : int
{
$ord0 = \ord($c[0]);
if ($ord0 < 0x80) {
return $ord0;
} elseif ($ord0 < 0xe0) {
return ($ord0 << 6) + \ord($c[1]) - 0x3080;
} elseif ($ord0 < 0xf0) {
return ($ord0 << 12) + (\ord($c[1]) << 6) + \ord($c[2]) - 0xe2080;
} else {
return ($ord0 << 18) + (\ord($c[1]) << 12) + (\ord($c[2]) << 6) + \ord($c[3]) - 0x3c82080;
}
}
/** @internal */
public static function utf8Length(string $s) : int
{
return \function_exists('mb_strlen') ? \mb_strlen($s, 'UTF-8') : \strlen(\utf8_decode($s));
}
/** @internal */
public static function isUtf8(string $s) : bool
{
return (bool) \preg_match('##u', $s);
}
/** @internal */
public static function truncateString(string $s, int $len, bool $utf) : string
{
if (!$utf) {
return $len < 0 ? \substr($s, $len) : \substr($s, 0, $len);
} elseif (\function_exists('mb_substr')) {
return $len < 0 ? \mb_substr($s, $len, -$len, 'UTF-8') : \mb_substr($s, 0, $len, 'UTF-8');
} else {
$len < 0 ? \preg_match('#.{0,' . -$len . '}\\z#us', $s, $m) : \preg_match("#^.{0,{$len}}#us", $s, $m);
return $m[0];
}
}
/** @internal */
public static function minifyJs(string $s) : string
{
// author: Jakub Vrana https://php.vrana.cz/minifikace-javascriptu.php
$last = '';
return \preg_replace_callback(<<<'XX'
(
(?:
(^|[-+\([{}=,:;!%^&*|?~]|/(?![/*])|return|throw) # context before regexp
(?:\s|//[^\n]*+\n|/\*(?:[^*]|\*(?!/))*+\*/)* # optional space
(/(?![/*])(?:\\[^\n]|[^[\n/\\]|\[(?:\\[^\n]|[^]])++)+/) # regexp
|(^
|'(?:\\.|[^\n'\\])*'
|"(?:\\.|[^\n"\\])*"
|([0-9A-Za-z_$]+)
|([-+]+)
|.
)
)(?:\s|//[^\n]*+\n|/\*(?:[^*]|\*(?!/))*+\*/)* # optional space
())sx
XX
, function ($match) use(&$last) {
[, $context, $regexp, $result, $word, $operator] = $match;
if ($word !== '') {
$result = ($last === 'word' ? ' ' : ($last === 'return' ? ' ' : '')) . $result;
$last = $word === 'return' || $word === 'throw' || $word === 'break' ? 'return' : 'word';
} elseif ($operator) {
$result = ($last === $operator[0] ? ' ' : '') . $result;
$last = $operator[0];
} else {
if ($regexp) {
$result = $context . ($context === '/' ? ' ' : '') . $regexp;
}
$last = '';
}
return $result;
}, $s . "\n");
}
/** @internal */
public static function minifyCss(string $s) : string
{
$last = '';
return \preg_replace_callback(<<<'XX'
(
(^
|'(?:\\.|[^\n'\\])*'
|"(?:\\.|[^\n"\\])*"
|([0-9A-Za-z_*#.%:()[\]-]+)
|.
)(?:\s|/\*(?:[^*]|\*(?!/))*+\*/)* # optional space
())sx
XX
, function ($match) use(&$last) {
[, $result, $word] = $match;
if ($last === ';') {
$result = $result === '}' ? '}' : ';' . $result;
$last = '';
}
if ($word !== '') {
$result = ($last === 'word' ? ' ' : '') . $result;
$last = 'word';
} elseif ($result === ';') {
$last = ';';
$result = '';
} else {
$last = '';
}
return $result;
}, $s . "\n");
}
public static function detectColors() : bool
{
return self::isCli() && \getenv('NO_COLOR') === \false && (\getenv('FORCE_COLOR') || (\function_exists('sapi_windows_vt100_support') ? \sapi_windows_vt100_support(\STDOUT) : @\stream_isatty(\STDOUT)));
}
public static function getExceptionChain(\Throwable $ex) : array
{
$res = [$ex];
while (($ex = $ex->getPrevious()) && !\in_array($ex, $res, \true)) {
$res[] = $ex;
}
return $res;
}
public static function traverseValue($val, callable $callback, array &$skip = [], ?string $refId = null) : void
{
if (\is_object($val)) {
$id = \spl_object_id($val);
if (!isset($skip[$id])) {
$skip[$id] = \true;
$callback($val);
self::traverseValue((array) $val, $callback, $skip);
}
} elseif (\is_array($val)) {
if ($refId) {
if (isset($skip[$refId])) {
return;
}
$skip[$refId] = \true;
}
foreach ($val as $k => $v) {
$refId = ($r = \ReflectionReference::fromArrayElement($val, $k)) ? $r->getId() : null;
self::traverseValue($v, $callback, $skip, $refId);
}
}
}
}

View File

@ -0,0 +1,135 @@
<?php
/**
* This file is part of the Tracy (https://tracy.nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare (strict_types=1);
namespace RectorPrefix202302\Tracy;
/**
* FireLogger console logger.
*
* @see http://firelogger.binaryage.com
* @see https://chrome.google.com/webstore/detail/firelogger-for-chrome/hmagilfopmdjkeomnjpchokglfdfjfeh
*/
class FireLogger implements ILogger
{
/** @var int */
public $maxDepth = 3;
/** @var int */
public $maxLength = 150;
/** @var array */
private $payload = ['logs' => []];
/**
* Sends message to FireLogger console.
* @param mixed $message
*/
public function log($message, $level = self::DEBUG) : bool
{
if (!isset($_SERVER['HTTP_X_FIRELOGGER']) || \headers_sent()) {
return \false;
}
$item = ['name' => 'PHP', 'level' => $level, 'order' => \count($this->payload['logs']), 'time' => \str_pad(\number_format((\microtime(\true) - Debugger::$time) * 1000, 1, '.', ' '), 8, '0', \STR_PAD_LEFT) . ' ms', 'template' => '', 'message' => '', 'style' => 'background:#767ab6'];
$args = \func_get_args();
if (isset($args[0]) && \is_string($args[0])) {
$item['template'] = \array_shift($args);
}
if (isset($args[0]) && $args[0] instanceof \Throwable) {
$e = \array_shift($args);
$trace = $e->getTrace();
if (isset($trace[0]['class']) && $trace[0]['class'] === Debugger::class && ($trace[0]['function'] === 'shutdownHandler' || $trace[0]['function'] === 'errorHandler')) {
unset($trace[0]);
}
$file = \str_replace(\dirname($e->getFile(), 3), "", $e->getFile());
$item['template'] = ($e instanceof \ErrorException ? '' : Helpers::getClass($e) . ': ') . $e->getMessage() . ($e->getCode() ? ' #' . $e->getCode() : '') . ' in ' . $file . ':' . $e->getLine();
$item['pathname'] = $e->getFile();
$item['lineno'] = $e->getLine();
} else {
$trace = \debug_backtrace();
if (isset($trace[1]['class']) && $trace[1]['class'] === Debugger::class && $trace[1]['function'] === 'fireLog') {
unset($trace[0]);
}
foreach ($trace as $frame) {
if (isset($frame['file']) && \is_file($frame['file'])) {
$item['pathname'] = $frame['file'];
$item['lineno'] = $frame['line'];
break;
}
}
}
$item['exc_info'] = ['', '', []];
$item['exc_frames'] = [];
foreach ($trace as $frame) {
$frame += ['file' => null, 'line' => null, 'class' => null, 'type' => null, 'function' => null, 'object' => null, 'args' => null];
$item['exc_info'][2][] = [$frame['file'], $frame['line'], "{$frame['class']}{$frame['type']}{$frame['function']}", $frame['object']];
$item['exc_frames'][] = $frame['args'];
}
if (isset($args[0]) && \in_array($args[0], [self::DEBUG, self::INFO, self::WARNING, self::ERROR, self::CRITICAL], \true)) {
$item['level'] = \array_shift($args);
}
$item['args'] = $args;
$this->payload['logs'][] = $this->jsonDump($item, -1);
foreach (\str_split(\base64_encode(\json_encode($this->payload, \JSON_INVALID_UTF8_SUBSTITUTE)), 4990) as $k => $v) {
\header("FireLogger-de11e-{$k}: {$v}");
}
return \true;
}
/**
* Dump implementation for JSON.
* @param mixed $var
* @return array|int|float|bool|string|null
*/
private function jsonDump(&$var, int $level = 0)
{
if (\is_bool($var) || $var === null || \is_int($var) || \is_float($var)) {
return $var;
} elseif (\is_string($var)) {
$var = Helpers::encodeString($var, $this->maxLength);
return \htmlspecialchars_decode(\strip_tags($var));
} elseif (\is_array($var)) {
static $marker;
if ($marker === null) {
$marker = \uniqid("\x00", \true);
}
if (isset($var[$marker])) {
return "…RECURSION…";
} elseif ($level < $this->maxDepth || !$this->maxDepth) {
$var[$marker] = \true;
$res = [];
foreach ($var as $k => &$v) {
if ($k !== $marker) {
$res[$this->jsonDump($k)] = $this->jsonDump($v, $level + 1);
}
}
unset($var[$marker]);
return $res;
} else {
return "";
}
} elseif (\is_object($var)) {
$arr = (array) $var;
static $list = [];
if (\in_array($var, $list, \true)) {
return "…RECURSION…";
} elseif ($level < $this->maxDepth || !$this->maxDepth) {
$list[] = $var;
$res = ["\x00" => '(object) ' . Helpers::getClass($var)];
foreach ($arr as $k => &$v) {
if (isset($k[0]) && $k[0] === "\x00") {
$k = \substr($k, \strrpos($k, "\x00") + 1);
}
$res[$this->jsonDump($k)] = $this->jsonDump($v, $level + 1);
}
\array_pop($list);
return $res;
} else {
return "";
}
} elseif (\is_resource($var)) {
return 'resource ' . \get_resource_type($var);
} else {
return 'unknown type';
}
}
}

View File

@ -0,0 +1,17 @@
<?php
/**
* This file is part of the Tracy (https://tracy.nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare (strict_types=1);
namespace RectorPrefix202302\Tracy;
/**
* Logger.
*/
interface ILogger
{
public const DEBUG = 'debug', INFO = 'info', WARNING = 'warning', ERROR = 'error', EXCEPTION = 'exception', CRITICAL = 'critical';
function log($value, $level = self::INFO);
}

View File

@ -0,0 +1,136 @@
<?php
/**
* This file is part of the Tracy (https://tracy.nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare (strict_types=1);
namespace RectorPrefix202302\Tracy;
/**
* Logger.
*/
class Logger implements ILogger
{
/** @var string|null name of the directory where errors should be logged */
public $directory;
/** @var string|array|null email or emails to which send error notifications */
public $email;
/** @var string|null sender of email notifications */
public $fromEmail;
/** @var mixed interval for sending email is 2 days */
public $emailSnooze = '2 days';
/** @var callable handler for sending emails */
public $mailer;
/** @var BlueScreen|null */
private $blueScreen;
/**
* @param string|array|null $email
*/
public function __construct(?string $directory, $email = null, ?BlueScreen $blueScreen = null)
{
$this->directory = $directory;
$this->email = $email;
$this->blueScreen = $blueScreen;
$this->mailer = [$this, 'defaultMailer'];
}
/**
* Logs message or exception to file and sends email notification.
* @param mixed $message
* @param string $level one of constant ILogger::INFO, WARNING, ERROR (sends email), EXCEPTION (sends email), CRITICAL (sends email)
* @return string|null logged error filename
*/
public function log($message, $level = self::INFO)
{
if (!$this->directory) {
throw new \LogicException('Logging directory is not specified.');
} elseif (!\is_dir($this->directory)) {
throw new \RuntimeException("Logging directory '{$this->directory}' is not found or is not directory.");
}
$exceptionFile = $message instanceof \Throwable ? $this->getExceptionFile($message, $level) : null;
$line = static::formatLogLine($message, $exceptionFile);
$file = $this->directory . '/' . \strtolower($level ?: self::INFO) . '.log';
if (!@\file_put_contents($file, $line . \PHP_EOL, \FILE_APPEND | \LOCK_EX)) {
// @ is escalated to exception
throw new \RuntimeException("Unable to write to log file '{$file}'. Is directory writable?");
}
if ($exceptionFile) {
$this->logException($message, $exceptionFile);
}
if (\in_array($level, [self::ERROR, self::EXCEPTION, self::CRITICAL], \true)) {
$this->sendEmail($message);
}
return $exceptionFile;
}
/**
* @param mixed $message
*/
public static function formatMessage($message) : string
{
if ($message instanceof \Throwable) {
foreach (Helpers::getExceptionChain($message) as $exception) {
$tmp[] = ($exception instanceof \ErrorException ? Helpers::errorTypeToString($exception->getSeverity()) . ': ' . $exception->getMessage() : Helpers::getClass($exception) . ': ' . $exception->getMessage() . ($exception->getCode() ? ' #' . $exception->getCode() : '')) . ' in ' . $exception->getFile() . ':' . $exception->getLine();
}
$message = \implode("\ncaused by ", $tmp);
} elseif (!\is_string($message)) {
$message = Dumper::toText($message);
}
return \trim($message);
}
/**
* @param mixed $message
*/
public static function formatLogLine($message, ?string $exceptionFile = null) : string
{
return \implode(' ', [\date('[Y-m-d H-i-s]'), \preg_replace('#\\s*\\r?\\n\\s*#', ' ', static::formatMessage($message)), ' @ ' . Helpers::getSource(), $exceptionFile ? ' @@ ' . \basename($exceptionFile) : null]);
}
public function getExceptionFile(\Throwable $exception, string $level = self::EXCEPTION) : string
{
foreach (Helpers::getExceptionChain($exception) as $exception) {
$data[] = [\get_class($exception), $exception->getMessage(), $exception->getCode(), $exception->getFile(), $exception->getLine(), \array_map(function (array $item) : array {
unset($item['args']);
return $item;
}, $exception->getTrace())];
}
$hash = \substr(\md5(\serialize($data)), 0, 10);
$dir = \strtr($this->directory . '/', '\\/', \DIRECTORY_SEPARATOR . \DIRECTORY_SEPARATOR);
foreach (new \DirectoryIterator($this->directory) as $file) {
if (\strpos($file->getBasename(), $hash)) {
return $dir . $file;
}
}
return $dir . $level . '--' . \date('Y-m-d--H-i') . "--{$hash}.html";
}
/**
* Logs exception to the file if file doesn't exist.
* @return string logged error filename
*/
protected function logException(\Throwable $exception, ?string $file = null) : string
{
$file = $file ?: $this->getExceptionFile($exception);
$bs = $this->blueScreen ?: new BlueScreen();
$bs->renderToFile($exception, $file);
return $file;
}
/**
* @param mixed $message
*/
protected function sendEmail($message) : void
{
$snooze = \is_numeric($this->emailSnooze) ? $this->emailSnooze : \strtotime($this->emailSnooze) - \time();
if ($this->email && $this->mailer && @\filemtime($this->directory . '/email-sent') + $snooze < \time() && @\file_put_contents($this->directory . '/email-sent', 'sent')) {
($this->mailer)($message, \implode(', ', (array) $this->email));
}
}
/**
* Default mailer.
* @param mixed $message
* @internal
*/
public function defaultMailer($message, string $email) : void
{
$host = \preg_replace('#[^\\w.-]+#', '', $_SERVER['SERVER_NAME'] ?? \php_uname('n'));
$parts = \str_replace(["\r\n", "\n"], ["\n", \PHP_EOL], ['headers' => \implode("\n", ['From: ' . ($this->fromEmail ?: "noreply@{$host}"), 'X-Mailer: Tracy', 'Content-Type: text/plain; charset=UTF-8', 'Content-Transfer-Encoding: 8bit']) . "\n", 'subject' => "PHP: An error occurred on the server {$host}", 'body' => static::formatMessage($message) . "\n\nsource: " . Helpers::getSource()]);
\mail($email, $parts['subject'], $parts['body'], $parts['headers']);
}
}

View File

@ -0,0 +1,61 @@
<?php
/**
* This file is part of the Tracy (https://tracy.nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare (strict_types=1);
namespace RectorPrefix202302\Tracy;
/**
* Debugger for outputs.
*/
final class OutputDebugger
{
private const BOM = "";
/** @var array of [file, line, output, stack] */
private $list = [];
public static function enable() : void
{
$me = new static();
$me->start();
}
public function start() : void
{
foreach (\get_included_files() as $file) {
if (\fread(\fopen($file, 'r'), 3) === self::BOM) {
$this->list[] = [$file, 1, self::BOM];
}
}
\ob_start([$this, 'handler'], 1);
}
/** @internal */
public function handler(string $s, int $phase) : ?string
{
$trace = \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS);
if (isset($trace[0]['file'], $trace[0]['line'])) {
$stack = $trace;
unset($stack[0]['line'], $stack[0]['args']);
$i = \count($this->list);
if ($i && $this->list[$i - 1][3] === $stack) {
$this->list[$i - 1][2] .= $s;
} else {
$this->list[] = [$trace[0]['file'], $trace[0]['line'], $s, $stack];
}
}
return $phase === \PHP_OUTPUT_HANDLER_FINAL ? $this->renderHtml() : null;
}
private function renderHtml() : string
{
$res = '<style>code, pre {white-space:nowrap} a {text-decoration:none} pre {color:gray;display:inline} big {color:red}</style><code>';
foreach ($this->list as $item) {
$stack = [];
foreach (\array_slice($item[3], 1) as $t) {
$t += ['class' => '', 'type' => '', 'function' => ''];
$stack[] = "{$t['class']}{$t['type']}{$t['function']}()" . (isset($t['file'], $t['line']) ? ' in ' . \basename($t['file']) . ":{$t['line']}" : '');
}
$res .= '<span title="' . Helpers::escapeHtml(\implode("\n", $stack)) . '">' . Helpers::editorLink($item[0], $item[1]) . ' ' . \str_replace(self::BOM, '<big>BOM</big>', Dumper::toHtml($item[2])) . "</span><br>\n";
}
return $res . '</code>';
}
}

View File

@ -0,0 +1,376 @@
/**
* This file is part of the Tracy (https://tracy.nette.org)
*/
tracy-div:not(a b),
tracy-div:not(a b) * {
font: inherit;
line-height: inherit;
color: inherit;
background: transparent;
margin: 0;
padding: 0;
border: none;
text-align: inherit;
list-style: inherit;
opacity: 1;
border-radius: 0;
box-shadow: none;
text-shadow: none;
box-sizing: border-box;
text-decoration: none;
text-transform: inherit;
white-space: inherit;
float: none;
clear: none;
max-width: initial;
min-width: initial;
max-height: initial;
min-height: initial;
}
tracy-div:not(a b) *:hover {
color: inherit;
background: transparent;
}
tracy-div:not(a b) *:not(svg):not(img):not(table) {
width: initial;
height: initial;
}
tracy-div:not(a b):before,
tracy-div:not(a b):after,
tracy-div:not(a b) *:before,
tracy-div:not(a b) *:after {
all: unset;
}
tracy-div:not(a b) b,
tracy-div:not(a b) strong {
font-weight: bold;
}
tracy-div:not(a b) small {
font-size: smaller;
}
tracy-div:not(a b) i,
tracy-div:not(a b) em {
font-style: italic;
}
tracy-div:not(a b) big {
font-size: larger;
}
tracy-div:not(a b) small,
tracy-div:not(a b) sub,
tracy-div:not(a b) sup {
font-size: smaller;
}
tracy-div:not(a b) ins {
text-decoration: underline;
}
tracy-div:not(a b) del {
text-decoration: line-through;
}
tracy-div:not(a b) table {
border-collapse: collapse;
}
tracy-div:not(a b) pre {
font-family: monospace;
white-space: pre;
}
tracy-div:not(a b) code,
tracy-div:not(a b) kbd,
tracy-div:not(a b) samp {
font-family: monospace;
}
tracy-div:not(a b) input {
background-color: white;
padding: 1px;
border: 1px solid;
}
tracy-div:not(a b) textarea {
background-color: white;
border: 1px solid;
padding: 2px;
white-space: pre-wrap;
}
tracy-div:not(a b) select {
border: 1px solid;
white-space: pre;
}
tracy-div:not(a b) article,
tracy-div:not(a b) aside,
tracy-div:not(a b) details,
tracy-div:not(a b) div,
tracy-div:not(a b) figcaption,
tracy-div:not(a b) footer,
tracy-div:not(a b) form,
tracy-div:not(a b) header,
tracy-div:not(a b) hgroup,
tracy-div:not(a b) main,
tracy-div:not(a b) nav,
tracy-div:not(a b) section,
tracy-div:not(a b) summary,
tracy-div:not(a b) pre,
tracy-div:not(a b) p,
tracy-div:not(a b) dl,
tracy-div:not(a b) dd,
tracy-div:not(a b) dt,
tracy-div:not(a b) blockquote,
tracy-div:not(a b) figure,
tracy-div:not(a b) address,
tracy-div:not(a b) h1,
tracy-div:not(a b) h2,
tracy-div:not(a b) h3,
tracy-div:not(a b) h4,
tracy-div:not(a b) h5,
tracy-div:not(a b) h6,
tracy-div:not(a b) ul,
tracy-div:not(a b) ol,
tracy-div:not(a b) li,
tracy-div:not(a b) hr {
display: block;
}
tracy-div:not(a b) a,
tracy-div:not(a b) b,
tracy-div:not(a b) big,
tracy-div:not(a b) code,
tracy-div:not(a b) em,
tracy-div:not(a b) i,
tracy-div:not(a b) small,
tracy-div:not(a b) span,
tracy-div:not(a b) strong {
display: inline;
}
tracy-div:not(a b) table {
display: table;
}
tracy-div:not(a b) tr {
display: table-row;
}
tracy-div:not(a b) col {
display: table-column;
}
tracy-div:not(a b) colgroup {
display: table-column-group;
}
tracy-div:not(a b) tbody {
display: table-row-group;
}
tracy-div:not(a b) thead {
display: table-header-group;
}
tracy-div:not(a b) tfoot {
display: table-footer-group;
}
tracy-div:not(a b) td {
display: table-cell;
}
tracy-div:not(a b) th {
display: table-cell;
}
/* TableSort */
tracy-div:not(a b) .tracy-sortable > :first-child > tr:first-child > * {
position: relative;
}
tracy-div:not(a b) .tracy-sortable > :first-child > tr:first-child > *:hover:before {
position: absolute;
right: .3em;
content: "\21C5";
opacity: .4;
font-weight: normal;
}
/* dump */
tracy-div:not(a b) .tracy-dump div {
padding-left: 3ex;
}
tracy-div:not(a b) .tracy-dump div div {
border-left: 1px solid rgba(0, 0, 0, .1);
margin-left: .5ex;
}
tracy-div:not(a b) .tracy-dump div div:hover {
border-left-color: rgba(0, 0, 0, .25);
}
tracy-div:not(a b) .tracy-dump {
background: #FDF5CE;
padding: .4em .7em;
border: 1px dotted silver;
overflow: auto;
}
tracy-div:not(a b) table .tracy-dump.tracy-dump { /* overwrite .tracy-dump.tracy-light etc. */
padding: 0;
margin: 0;
border: none;
}
tracy-div:not(a b) .tracy-dump-location {
color: gray;
font-size: 80%;
text-decoration: none;
background: none;
opacity: .5;
float: right;
cursor: pointer;
}
tracy-div:not(a b) .tracy-dump-location:hover,
tracy-div:not(a b) .tracy-dump-location:focus {
color: gray;
background: none;
opacity: 1;
}
tracy-div:not(a b) .tracy-dump-array,
tracy-div:not(a b) .tracy-dump-object {
color: #C22;
}
tracy-div:not(a b) .tracy-dump-string {
color: #35D;
white-space: break-spaces;
}
tracy-div:not(a b) div.tracy-dump-string {
position: relative;
padding-left: 3.5ex;
}
tracy-div:not(a b) .tracy-dump-lq {
margin-left: calc(-1ex - 1px);
}
tracy-div:not(a b) div.tracy-dump-string:before {
content: '';
position: absolute;
left: calc(3ex - 1px);
top: 1.5em;
bottom: 0;
border-left: 1px solid rgba(0, 0, 0, .1);
}
tracy-div:not(a b) .tracy-dump-virtual span,
tracy-div:not(a b) .tracy-dump-dynamic span,
tracy-div:not(a b) .tracy-dump-string span {
color: rgba(0, 0, 0, 0.5);
}
tracy-div:not(a b) .tracy-dump-virtual i,
tracy-div:not(a b) .tracy-dump-dynamic i,
tracy-div:not(a b) .tracy-dump-string i {
font-size: 80%;
font-style: normal;
color: rgba(0, 0, 0, 0.5);
user-select: none;
}
tracy-div:not(a b) .tracy-dump-number {
color: #090;
}
tracy-div:not(a b) .tracy-dump-null,
tracy-div:not(a b) .tracy-dump-bool {
color: #850;
}
tracy-div:not(a b) .tracy-dump-virtual {
font-style: italic;
}
tracy-div:not(a b) .tracy-dump-public::after {
content: ' pub';
}
tracy-div:not(a b) .tracy-dump-protected::after {
content: ' pro';
}
tracy-div:not(a b) .tracy-dump-private::after {
content: ' pri';
}
tracy-div:not(a b) .tracy-dump-public::after,
tracy-div:not(a b) .tracy-dump-protected::after,
tracy-div:not(a b) .tracy-dump-private::after,
tracy-div:not(a b) .tracy-dump-hash {
font-size: 85%;
color: rgba(0, 0, 0, 0.5);
}
tracy-div:not(a b) .tracy-dump-indent {
display: none;
}
tracy-div:not(a b) .tracy-dump-highlight {
background: #C22;
color: white;
border-radius: 2px;
padding: 0 2px;
margin: 0 -2px;
}
tracy-div:not(a b) span[data-tracy-href] {
border-bottom: 1px dotted rgba(0, 0, 0, .2);
}
/* toggle */
tracy-div:not(a b) .tracy-toggle:after {
content: '';
display: inline-block;
vertical-align: middle;
line-height: 0;
border-top: .6ex solid;
border-right: .6ex solid transparent;
border-left: .6ex solid transparent;
transform: scale(1, 1.5);
margin: 0 .2ex 0 .7ex;
transition: .1s transform;
opacity: .5;
}
tracy-div:not(a b) .tracy-toggle.tracy-collapsed:after {
transform: rotate(-90deg) scale(1, 1.5) translate(.1ex, 0);
}
/* tabs */
tracy-div:not(a b) .tracy-tab-label {
user-select: none;
}
tracy-div:not(a b) .tracy-tab-panel:not(.tracy-active) {
display: none;
}

View File

@ -0,0 +1,15 @@
/**
* This file is part of the Tracy (https://tracy.nette.org)
*/
.tracy-sortable > :first-child > tr:first-child > * {
position: relative;
}
.tracy-sortable > :first-child > tr:first-child > *:hover:before {
position: absolute;
right: .3em;
content: "\21C5";
opacity: .4;
font-weight: normal;
}

View File

@ -0,0 +1,40 @@
/**
* This file is part of the Tracy (https://tracy.nette.org)
*/
// enables <table class="tracy-sortable">
class TableSort
{
static init() {
document.documentElement.addEventListener('click', (e) => {
if (e.target.matches('.tracy-sortable > :first-child > tr:first-child *')) {
TableSort.sort(e.target.closest('td,th'));
}
});
TableSort.init = function() {};
}
static sort(tcell) {
let tbody = tcell.closest('table').tBodies[0];
let preserveFirst = !tcell.closest('thead') && !tcell.parentNode.querySelectorAll('td').length;
let asc = !(tbody.tracyAsc === tcell.cellIndex);
tbody.tracyAsc = asc ? tcell.cellIndex : null;
let getText = (cell) => { return cell ? (cell.getAttribute('data-order') || cell.innerText) : ''; };
Array.from(tbody.children)
.slice(preserveFirst ? 1 : 0)
.sort((a, b) => {
return function(v1, v2) {
return v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2)
? v1 - v2
: v1.toString().localeCompare(v2, undefined, {numeric: true, sensitivity: 'base'});
}(getText((asc ? a : b).children[tcell.cellIndex]), getText((asc ? b : a).children[tcell.cellIndex]));
})
.forEach((tr) => { tbody.appendChild(tr); });
}
}
let Tracy = window.Tracy = window.Tracy || {};
Tracy.TableSort = Tracy.TableSort || TableSort;

View File

@ -0,0 +1,11 @@
/**
* This file is part of the Tracy (https://tracy.nette.org)
*/
.tracy-tab-label {
user-select: none;
}
.tracy-tab-panel:not(.tracy-active) {
display: none;
}

View File

@ -0,0 +1,41 @@
/**
* This file is part of the Tracy (https://tracy.nette.org)
*/
// enables .tracy-tabs, .tracy-tab-label, .tracy-tab-panel, .tracy-active
class Tabs
{
static init() {
document.documentElement.addEventListener('click', (e) => {
let label, context;
if (
!e.shiftKey && !e.ctrlKey && !e.metaKey
&& (label = e.target.closest('.tracy-tab-label'))
&& (context = e.target.closest('.tracy-tabs'))
) {
Tabs.toggle(context, label);
e.preventDefault();
e.stopImmediatePropagation();
}
});
Tabs.init = function() {};
}
static toggle(context, label) {
let labels = context.querySelector('.tracy-tab-label').parentNode.querySelectorAll('.tracy-tab-label'),
panels = context.querySelector('.tracy-tab-panel').parentNode.querySelectorAll(':scope > .tracy-tab-panel');
for (let i = 0; i < labels.length; i++) {
labels[i].classList.toggle('tracy-active', labels[i] === label);
}
for (let i = 0; i < panels.length; i++) {
panels[i].classList.toggle('tracy-active', labels[i] === label);
}
}
}
let Tracy = window.Tracy = window.Tracy || {};
Tracy.Tabs = Tracy.Tabs || Tabs;

View File

@ -0,0 +1,35 @@
/**
* This file is part of the Tracy (https://tracy.nette.org)
*/
.tracy-collapsed {
display: none;
}
.tracy-toggle.tracy-collapsed {
display: inline;
}
.tracy-toggle {
cursor: pointer;
user-select: none;
white-space: nowrap;
}
.tracy-toggle:after {
content: '';
display: inline-block;
vertical-align: middle;
line-height: 0;
border-top: .6ex solid;
border-right: .6ex solid transparent;
border-left: .6ex solid transparent;
transform: scale(1, 1.5);
margin: 0 .2ex 0 .7ex;
transition: .1s transform;
opacity: .5;
}
.tracy-toggle.tracy-collapsed:after {
transform: rotate(-90deg) scale(1, 1.5) translate(.1ex, 0);
}

View File

@ -0,0 +1,117 @@
/**
* This file is part of the Tracy (https://tracy.nette.org)
*/
const MOVE_THRESHOLD = 100;
// enables <a class="tracy-toggle" href="#"> or <span data-tracy-ref="#"> toggling
class Toggle
{
static init() {
let start;
document.documentElement.addEventListener('mousedown', (e) => {
start = [e.clientX, e.clientY];
});
document.documentElement.addEventListener('click', (e) => {
let el;
if (
!e.shiftKey && !e.ctrlKey && !e.metaKey
&& (el = e.target.closest('.tracy-toggle'))
&& Math.pow(start[0] - e.clientX, 2) + Math.pow(start[1] - e.clientY, 2) < MOVE_THRESHOLD
) {
Toggle.toggle(el, undefined, e);
e.preventDefault();
e.stopImmediatePropagation();
}
});
Toggle.init = function() {};
}
// changes element visibility
static toggle(el, expand, e) {
let collapsed = el.classList.contains('tracy-collapsed'),
ref = el.getAttribute('data-tracy-ref') || el.getAttribute('href', 2),
dest = el;
if (typeof expand === 'undefined') {
expand = collapsed;
}
el.dispatchEvent(new CustomEvent('tracy-beforetoggle', {
bubbles: true,
detail: {collapsed: !expand, originalEvent: e}
}));
if (!ref || ref === '#') {
ref = '+';
} else if (ref.substr(0, 1) === '#') {
dest = document;
}
ref = ref.match(/(\^\s*([^+\s]*)\s*)?(\+\s*(\S*)\s*)?(.*)/);
dest = ref[1] ? dest.parentNode : dest;
dest = ref[2] ? dest.closest(ref[2]) : dest;
dest = ref[3] ? Toggle.nextElement(dest.nextElementSibling, ref[4]) : dest;
dest = ref[5] ? dest.querySelector(ref[5]) : dest;
el.classList.toggle('tracy-collapsed', !expand);
dest.classList.toggle('tracy-collapsed', !expand);
el.dispatchEvent(new CustomEvent('tracy-toggle', {
bubbles: true,
detail: {relatedTarget: dest, collapsed: !expand, originalEvent: e}
}));
}
// save & restore toggles
static persist(baseEl, restore) {
let saved = [];
baseEl.addEventListener('tracy-toggle', (e) => {
if (saved.indexOf(e.target) < 0) {
saved.push(e.target);
}
});
let toggles = JSON.parse(sessionStorage.getItem('tracy-toggles-' + baseEl.id));
if (toggles && restore !== false) {
toggles.forEach((item) => {
let el = baseEl;
for (let i in item.path) {
if (!(el = el.children[item.path[i]])) {
return;
}
}
if (el.textContent === item.text) {
Toggle.toggle(el, item.expand);
}
});
}
window.addEventListener('unload', () => {
toggles = saved.map((el) => {
let item = {path: [], text: el.textContent, expand: !el.classList.contains('tracy-collapsed')};
do {
item.path.unshift(Array.from(el.parentNode.children).indexOf(el));
el = el.parentNode;
} while (el && el !== baseEl);
return item;
});
sessionStorage.setItem('tracy-toggles-' + baseEl.id, JSON.stringify(toggles));
});
}
// finds next matching element
static nextElement(el, selector) {
while (el && selector && !el.matches(selector)) {
el = el.nextElementSibling;
}
return el;
}
}
let Tracy = window.Tracy = window.Tracy || {};
Tracy.Toggle = Tracy.Toggle || Toggle;

View File

@ -0,0 +1,44 @@
<?php
/**
* This file is part of the Tracy (https://tracy.nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare (strict_types=1);
namespace RectorPrefix202302;
if (!\function_exists('RectorPrefix202302\\dump')) {
/**
* Tracy\Debugger::dump() shortcut.
* @tracySkipLocation
*/
function dump($var)
{
\array_map([Tracy\Debugger::class, 'dump'], \func_get_args());
return $var;
}
}
if (!\function_exists('RectorPrefix202302\\dumpe')) {
/**
* Tracy\Debugger::dump() & exit shortcut.
* @tracySkipLocation
*/
function dumpe($var) : void
{
\array_map([Tracy\Debugger::class, 'dump'], \func_get_args());
if (!Tracy\Debugger::$productionMode) {
exit;
}
}
}
if (!\function_exists('RectorPrefix202302\\bdump')) {
/**
* Tracy\Debugger::barDump() shortcut.
* @tracySkipLocation
*/
function bdump($var)
{
Tracy\Debugger::barDump(...\func_get_args());
return $var;
}
}

32
vendor/tracy/tracy/src/tracy.php vendored Normal file
View File

@ -0,0 +1,32 @@
<?php
/**
* Tracy (https://tracy.nette.org)
*
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare (strict_types=1);
namespace RectorPrefix202302;
require __DIR__ . '/Tracy/Bar/IBarPanel.php';
require __DIR__ . '/Tracy/Bar/Bar.php';
require __DIR__ . '/Tracy/Bar/DefaultBarPanel.php';
require __DIR__ . '/Tracy/BlueScreen/BlueScreen.php';
require __DIR__ . '/Tracy/Dumper/Value.php';
require __DIR__ . '/Tracy/Dumper/Describer.php';
require __DIR__ . '/Tracy/Dumper/Dumper.php';
require __DIR__ . '/Tracy/Dumper/Exposer.php';
require __DIR__ . '/Tracy/Dumper/Renderer.php';
require __DIR__ . '/Tracy/Logger/ILogger.php';
require __DIR__ . '/Tracy/Logger/FireLogger.php';
require __DIR__ . '/Tracy/Logger/Logger.php';
require __DIR__ . '/Tracy/Debugger/Debugger.php';
require __DIR__ . '/Tracy/Debugger/DeferredContent.php';
require __DIR__ . '/Tracy/Debugger/DevelopmentStrategy.php';
require __DIR__ . '/Tracy/Debugger/ProductionStrategy.php';
require __DIR__ . '/Tracy/OutputDebugger/OutputDebugger.php';
require __DIR__ . '/Tracy/Session/SessionStorage.php';
require __DIR__ . '/Tracy/Session/NativeSession.php';
require __DIR__ . '/Tracy/Session/FileSession.php';
require __DIR__ . '/Tracy/Helpers.php';
require __DIR__ . '/Tracy/functions.php';

View File

@ -0,0 +1,69 @@
<?php
declare (strict_types=1);
namespace RectorPrefix202302;
// creates tracy.phar
if (!\class_exists('Phar') || \ini_get('phar.readonly')) {
echo "Enable Phar extension and set directive 'phar.readonly=off'.\n";
die(1);
}
function compressJs(string $s) : string
{
if (\function_exists('curl_init')) {
$curl = \curl_init('https://closure-compiler.appspot.com/compile');
\curl_setopt($curl, \CURLOPT_RETURNTRANSFER, 1);
\curl_setopt($curl, \CURLOPT_POST, 1);
\curl_setopt($curl, \CURLOPT_POSTFIELDS, 'output_info=compiled_code&js_code=' . \urlencode($s));
$s = \curl_exec($curl) ?: $s;
\curl_close($curl);
}
return $s;
}
function compressCss(string $s) : string
{
$s = \preg_replace('#/\\*.*?\\*/#s', '', $s);
// remove comments
$s = \preg_replace('#[ \\t\\r\\n]+#', ' ', $s);
// compress space, ignore hard space
$s = \preg_replace('# ([^0-9a-z.\\#*-])#i', '$1', $s);
$s = \preg_replace('#([^0-9a-z%)]) #i', '$1', $s);
$s = \str_replace(';}', '}', $s);
// remove leading semicolon
return \trim($s);
}
@\unlink('tracy.phar');
// @ - file may not exist
$phar = new \Phar('tracy.phar');
$phar->setStub("<?php\nrequire 'phar://' . __FILE__ . '/tracy.php';\n__HALT_COMPILER();\n");
$phar->startBuffering();
foreach ($iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator(__DIR__ . '/../../src', \RecursiveDirectoryIterator::SKIP_DOTS)) as $file) {
echo "adding: {$iterator->getSubPathname()}\n";
$s = \file_get_contents($file->getPathname());
if (\strpos($s, '@tracySkipLocation') === \false) {
$s = \php_strip_whitespace($file->getPathname());
}
if ($file->getExtension() === 'js') {
$s = compressJs($s);
} elseif ($file->getExtension() === 'css') {
$s = compressCss($s);
} elseif ($file->getExtension() === 'phtml') {
$s = \preg_replace_callback('#(<(script|style).*(?<![?=])>)(.*)(</)#Uis', function ($m) : string {
[, $begin, $type, $s, $end] = $m;
if ($s === '' || \strpos($s, '<?') !== \false) {
return $m[0];
} elseif ($type === 'script') {
$s = compressJs($s);
} elseif ($type === 'style') {
$s = compressCss($s);
}
return $begin . $s . $end;
}, $s);
} elseif ($file->getExtension() !== 'php') {
continue;
}
$phar[$iterator->getSubPathname()] = $s;
}
$phar->stopBuffering();
$phar->compressFiles(\Phar::GZ);
echo "OK\n";

View File

@ -0,0 +1,38 @@
#!/bin/bash
# This shell script sets open-editor.sh as handler for editor:// protocol
matches=0
while read -r line
do
if [ "editor=" == "${line:0:7}" ]; then
matches=1
break
fi
done < "open-editor.sh"
if [ "$matches" == "0" ]; then
echo -e "\e[31;1mError: it seems like you have not set command to run your editor."
echo -e "Before install, set variable \`\$editor\` in file \`open-editor.sh\`.\e[0m"
exit 1
fi
# --------------------------------------------------------------
echo "[Desktop Entry]
Name=Tracy Open Editor
Exec=tracy-openeditor.sh %u
Terminal=false
NoDisplay=true
Type=Application
MimeType=x-scheme-handler/editor;" > tracy-openeditor.desktop
chmod +x open-editor.sh
chmod +x tracy-openeditor.desktop
sudo cp open-editor.sh /usr/bin/tracy-openeditor.sh
sudo xdg-desktop-menu install tracy-openeditor.desktop
sudo update-desktop-database
rm tracy-openeditor.desktop
echo -e "\e[32;1mDone.\e[0m"

View File

@ -0,0 +1,109 @@
#!/bin/bash
declare -A mapping
#
# Configure your editor by setting the $editor variable:
#
# Visual Studio Code
#editor='code --goto "$FILE":"$LINE"'
# Emacs
#editor='emacs +$LINE "$FILE"'
# gVim
#editor='gvim +$LINE "$FILE"'
# gEdit
#editor='gedit +$LINE "$FILE"'
# Pluma
#editor='pluma +$LINE "$FILE"'
# PHPStorm
# To enable PHPStorm command-line interface, folow this guide: https://www.jetbrains.com/help/phpstorm/working-with-the-ide-features-from-command-line.html
#editor='phpstorm --line $LINE "$FILE"'
# VS Codium
#editor='codium --goto "$FILE":"$LINE"'
# Visual Studio Code
#editor='code --goto "$FILE":"$LINE"'
#
# Optionally configure custom mapping here:
#
#mapping["/remotepath"]="/localpath"
#mapping["/mnt/d/"]="d:/"
#
# Please, do not modify the code below.
#
# Find and return URI parameter value. Or nothing, if the param is missing.
# Arguments: 1) URI, 2) Parameter name.
function get_param {
echo "$1" | sed -n -r "s/.*$2=([^&]*).*/\1/ip"
}
if [[ -z "$editor" ]]; then
echo "You need to set the \$editor variable in file '`realpath $0`'"
exit
fi
url=$1
if [ "${url:0:9}" != "editor://" ]; then
exit
fi
# Parse action and essential data from the URI.
regex='editor\:\/\/(open|create|fix)\/\?(.*)'
action=`echo $url | sed -r "s/$regex/\1/i"`
uri_params=`echo $url | sed -r "s/$regex/\2/i"`
file=`get_param $uri_params "file"`
line=`get_param $uri_params "line"`
search=`get_param $uri_params "search"`
replace=`get_param $uri_params "replace"`
# Debug?
#echo "action '$action'"
#echo "file '$file'"
#echo "line '$line'"
#echo "search '$search'"
#echo "replace '$replace'"
# Convert URI encoded codes to normal characters (e.g. '%2F' => '/').
printf -v file "${file//%/\\x}"
# And escape double-quotes.
file=${file//\"/\\\"}
# Apply custom mapping conversion.
for path in "${!mapping[@]}"; do
file="${file//$path/${mapping[$path]}}"
done
# Action: Create a file (only if it does not already exist).
if [ "$action" == "create" ] && [[ ! -f "$file" ]]; then
mkdir -p $(dirname "$file")
touch "$file"
echo $replace > "$file"
fi
# Action: Fix the file (if the file exists and while creating backup beforehand).
if [ "$action" == "fix" ]; then
if [[ ! -f "$file" ]]; then
echo "Cannot fix non-existing file '$file'"
exit
fi
# Backup the original file.
cp $file "$file.bak"
# Search and replace in place - only on the specified line.
sed -i "${line}s/${search}/${replace}/" $file
fi
# Format the command according to the selected editor.
command="${editor//\$FILE/$file}"
command="${command//\$LINE/$line}"
# Debug?
#echo $command
eval $command

View File

@ -0,0 +1,9 @@
@echo off
:: This Windows batch file sets open-editor.js as handler for editor:// protocol
if defined PROCESSOR_ARCHITEW6432 (set reg="%systemroot%\sysnative\reg.exe") else (set reg=reg)
%reg% ADD HKCR\editor /ve /d "URL:editor Protocol" /f
%reg% ADD HKCR\editor /v "URL Protocol" /d "" /f
%reg% ADD HKCR\editor\shell\open\command /ve /d "wscript \"%~dp0open-editor.js\" \"%%1\"" /f
%reg% ADD HKLM\SOFTWARE\Policies\Google\Chrome\URLWhitelist /v "123" /d "editor://*" /f

View File

@ -0,0 +1,84 @@
var settings = {
// PhpStorm
// editor: '"C:\\Program Files\\JetBrains\\PhpStorm 2018.1.2\\bin\\phpstorm64.exe" --line %line% "%file%"',
// title: 'PhpStorm',
// NetBeans
// editor: '"C:\\Program Files\\NetBeans 8.1\\bin\\netbeans.exe" "%file%:%line%" --console suppress',
// Nusphere PHPEd
// editor: '"C:\\Program Files\\NuSphere\\PhpED\\phped.exe" "%file%" --line=%line%',
// SciTE
// editor: '"C:\\Program Files\\SciTE\\scite.exe" "-open:%file%" -goto:%line%',
// EmEditor
// editor: '"C:\\Program Files\\EmEditor\\EmEditor.exe" "%file%" /l %line%',
// PSPad Editor
// editor: '"C:\\Program Files\\PSPad editor\\PSPad.exe" -%line% "%file%"',
// gVim
// editor: '"C:\\Program Files\\Vim\\vim73\\gvim.exe" "%file%" +%line%',
// Sublime Text 2
// editor: '"C:\\Program Files\\Sublime Text 2\\sublime_text.exe" "%file%:%line%"',
// Visual Studio Code / VSCodium
// editor: '"C:\\Program Files\\Microsoft VS Code\\Code.exe" --goto "%file%:%line%"',
mappings: {
// '/remotepath': '/localpath'
}
};
if (!settings.editor) {
WScript.Echo('Create variable "settings.editor" in ' + WScript.ScriptFullName);
WScript.Quit();
}
var url = WScript.Arguments(0);
var match = /^editor:\/\/(open|create|fix)\/\?file=([^&]+)&line=(\d+)(?:&search=([^&]*)&replace=([^&]*))?/.exec(url);
if (!match) {
WScript.Echo('Unexpected URI ' + url);
WScript.Quit();
}
for (var i in match) {
match[i] = decodeURIComponent(match[i]).replace(/\+/g, ' ');
}
var action = match[1];
var file = match[2];
var line = match[3];
var search = match[4];
var replace = match[5];
var shell = new ActiveXObject('WScript.Shell');
var fileSystem = new ActiveXObject('Scripting.FileSystemObject');
for (var id in settings.mappings) {
if (file.indexOf(id) === 0) {
file = settings.mappings[id] + file.substr(id.length);
break;
}
}
if (action === 'create' && !fileSystem.FileExists(file)) {
shell.Run('cmd /c mkdir "' + fileSystem.GetParentFolderName(file) + '"', 0, 1);
fileSystem.CreateTextFile(file).Write(replace);
} else if (action === 'fix') {
var lines = fileSystem.OpenTextFile(file).ReadAll().split('\n');
lines[line-1] = lines[line-1].replace(search, replace);
fileSystem.OpenTextFile(file, 2).Write(lines.join('\n'));
}
var command = settings.editor.replace(/%line%/, line).replace(/%file%/, file);
shell.Exec(command);
if (settings.title) {
shell.AppActivate(settings.title);
}