Commit f0058098 authored by tufengqi's avatar tufengqi

fix

parent 17455f1f
......@@ -8,6 +8,7 @@ $baseDir = dirname($vendorDir);
return array(
'yii\\swiftmailer\\' => array($vendorDir . '/yiisoft/yii2-swiftmailer'),
'yii\\redis\\' => array($vendorDir . '/yiisoft/yii2-redis/src'),
'yii\\debug\\' => array($vendorDir . '/yiisoft/yii2-debug'),
'yii\\composer\\' => array($vendorDir . '/yiisoft/yii2-composer'),
'yii\\bootstrap\\' => array($vendorDir . '/yiisoft/yii2-bootstrap/src'),
'yii\\' => array($vendorDir . '/yiisoft/yii2'),
......
......@@ -16,6 +16,7 @@ class ComposerStaticInitbdb6249a02d7626d1497a483c8a891d8
array (
'yii\\swiftmailer\\' => 16,
'yii\\redis\\' => 10,
'yii\\debug\\' => 10,
'yii\\composer\\' => 13,
'yii\\bootstrap\\' => 14,
'yii\\' => 4,
......@@ -35,6 +36,10 @@ class ComposerStaticInitbdb6249a02d7626d1497a483c8a891d8
array (
0 => __DIR__ . '/..' . '/yiisoft/yii2-redis/src',
),
'yii\\debug\\' =>
array (
0 => __DIR__ . '/..' . '/yiisoft/yii2-debug',
),
'yii\\composer\\' =>
array (
0 => __DIR__ . '/..' . '/yiisoft/yii2-composer',
......
......@@ -498,6 +498,55 @@
]
},
{
"name": "yiisoft/yii2-debug",
"version": "2.0.13",
"version_normalized": "2.0.13.0",
"source": {
"type": "git",
"url": "https://github.com/yiisoft/yii2-debug.git",
"reference": "b37f414959c2fafefb332020b42037cd17c1cb7f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/yiisoft/yii2-debug/zipball/b37f414959c2fafefb332020b42037cd17c1cb7f",
"reference": "b37f414959c2fafefb332020b42037cd17c1cb7f",
"shasum": ""
},
"require": {
"yiisoft/yii2": "~2.0.13",
"yiisoft/yii2-bootstrap": "~2.0.0"
},
"time": "2017-12-05T07:36:23+00:00",
"type": "yii2-extension",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"yii\\debug\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Qiang Xue",
"email": "qiang.xue@gmail.com"
}
],
"description": "The debugger extension for the Yii framework",
"keywords": [
"debug",
"debugger",
"yii2"
]
},
{
"name": "yiisoft/yii2-mns",
"version": "dev-master",
"version_normalized": "9999999-dev",
......
......@@ -3,31 +3,40 @@
$vendorDir = dirname(__DIR__);
return array (
'yiisoft/yii2-swiftmailer' =>
'yiisoft/yii2-swiftmailer' =>
array (
'name' => 'yiisoft/yii2-swiftmailer',
'version' => '2.0.7.0',
'alias' =>
'alias' =>
array (
'@yii/swiftmailer' => $vendorDir . '/yiisoft/yii2-swiftmailer',
),
),
'yiisoft/yii2-redis' =>
'yiisoft/yii2-redis' =>
array (
'name' => 'yiisoft/yii2-redis',
'version' => '2.0.8.0',
'alias' =>
'alias' =>
array (
'@yii/redis' => $vendorDir . '/yiisoft/yii2-redis/src',
),
),
'yiisoft/yii2-bootstrap' =>
'yiisoft/yii2-bootstrap' =>
array (
'name' => 'yiisoft/yii2-bootstrap',
'version' => '2.0.8.0',
'alias' =>
'alias' =>
array (
'@yii/bootstrap' => $vendorDir . '/yiisoft/yii2-bootstrap/src',
),
),
'yiisoft/yii2-debug' =>
array (
'name' => 'yiisoft/yii2-debug',
'version' => '2.0.13.0',
'alias' =>
array (
'@yii/debug' => $vendorDir . '/yiisoft/yii2-debug',
),
),
);
Yii Framework 2 debug extension Change Log
==========================================
2.0.13 December 5, 2017
-----------------------
- Bug #284: Fixed "TypeError: input.substr is not a function" (leopold537)
- Bug #290: Fixed "fetch request profile link" (leopold537)
- Enh #274: Made user component configurable for `UserSwitch` and `UserPanel` (samdark)
- Enh #283: Send debug headers in AJAX requests in order to be able to link to debug panel from single page apps (glendemon)
- Enh #283: Duplicated queries count on DB panel (pistej)
- Enh #294: Added a "General Info" table to the Request panel (brandonkelly)
- Chg #292: Added PHP 7.2 compatibility (brandonkelly)
- Chg: Changed `default/view` not to depend on `db` panel (silverfire)
2.0.12 October 09, 2017
-----------------------
- Bug #271: Fixed regression in 2.0.11 causing debug fail with some custom classes implementing IdentityInterface (zertex)
- Bug #279: Fixed incomplete initialization of path aliases while using non-web application (samdark)
2.0.11 September 06, 2017
------------------------
- Bug #262: Fixed issue when identity ID is stored in a field different from `id` (samdark)
- Bug #265: Fixed calling `isMainUser()` on null regression in 2.0.10 (samdark)
2.0.10 September 04, 2017
-------------------------
- Bug #221: Fixed the decimal point issue in Timeline when using various locales (bashkarev)
- Bug #223: Limit the height during the opening animation (nkovacs)
- Bug #226: Fixed issue in user panel when you use custom RBAC module that does not implement `\yii\rbac\ManagerInterface` (pana1990)
- Bug #236: Fixed rendering AJAX errors to use `innerText` instead of `innerHTML` (samdark)
- Bug #239: Fixed an issue in the user panel when using console application with debug module enabled (pana1990)
- Bug #241: Fixed double query to the user table (LAV45)
- Bug #242: Fixed silent crash by omitting AssetsPanel creation when yii/web/AssetManager not being used like in REST apps (tunecino)
- Bug #244: Fixed copying SQL via triple-click in Firefox (arzzen)
- Bug #249: Fixed toolbar not displayed because of misconfigured authManager (samdark)
- Bug #251: User panel was displaying current user info instead of user info at the moment of request (samdark)
- Bug #252, #234, #220, #242: Reworked error handling to be error-resistent and display errors in the panel itself (bashkarev)
- Bug #257: Fixed user panel to properly display object attributes (samdark)
- Enh #188: Added `RequestPanel::$displayVars` that lists allowed variables in request panel (samdark)
- Enh #204: Switch users from the panel (sam002)
- Enh #208: All identity models get converted to arrays when saving User panel data now, not just ActiveRecord models (brandonkelly)
- Enh #208: Identity model packaging for User panels is now done in an `identityData()` method, making it easier for subclasses to customize (brandonkelly)
- Enh #218: Hide the debug toolbar when an HTML page is printed (githubjeka)
- Enh #225: Added classes to use bootstrap styles for filter inputs in Timeline panel (johonunu)
- Enh #256: Catch fetch AJAX requests (leopold537)
2.0.9 February 21, 2017
-----------------------
- Bug #195: Fixed failure when user model has timestamp behavior attached (sam002)
- Bug #199: Do not use user panel in case component isn't properly defined in the application (samdark)
- Bug #200: Fixed error in user panel when RBAC role or permission contains non-string data (samdark)
2.0.8 February 19, 2017
-----------------------
- Bug #82: Fixed debug crashing when there's a closure in log message (samdark)
- Bug #176: Use module's real ID instead of hardcoded "debug" (samdark)
- Enh #34: Added memory graph to timeline panel (bashkarev)
- Enh #174: Added routing panel (bashkarev, samdark)
- Enh #179: Increased request time logging accuracy and precision (samdark)
- Enh #181: Added user panel (pana1990)
- Enh #185: Added meta tag to prevent indexing of debug by search engines in case it's exposed (aminkt, samdark)
- Enh #196: Added language information to config panel (cebe)
2.0.7 November 24, 2016
-----------------------
- Bug #61: Fixed toolbar not to be cached by using renderDynamic (dynasource)
- Bug #93: Fixed `AssetPanel` error when bundle `$js` or `$css` contained `jsOptions` overrides (Razzwan, samdark)
- Bug #99: Avoid serializing php7 errors (zuozp8)
- Bug #111: Fixed `LogTarget` to work properly when tests are ran via Codeception (samdark, nlmedina)
- Bug #120: Fixed toolbar height changing when opened/closed and when using bootstrap (nkovacs)
- Bug #148: Don't animate iframe needlessly when window is resized. (nkovacs)
- Bug #150: Fixed "Cannot read property 'replaceChild' of null" error (BetsuNo)
- Bug #152: Fixed log search to work with non-scalar values (samdark)
- Bug #160: Remove height as it prevents the background from stretching, causing unreadable overlapping texts over background (dynasource)
- Bug #168: Fixed wrong toggle button direction (fps01)
- Enh #8: Added ability to configure default sorting and filtering for Database panel (laszlovl)
- Enh #27: Adjusted sorting defaults, removed row numbers from database, log and profiling panels (samdark)
- Enh #58: Added timeline panel (bashkarev)
- Enh #97: Added AJAX requests handling (bashkarev)
- Enh #105: Enhanced `ConfigPanel` to detect and report memcached extension presence (samdark)
- Enh #115: Make the default panel configurable and set it to `log` (mikehaertl)
- Enh #117: Added ability to customize the logo with `Module::setYiiLogo()` (brandonkelly)
- Enh #143: Added application version display at `ConfigPanel` (klimov-paul)
- Enh #145: The error and warning labels of the log section on the summary bar now link directly to the log page filtered by log level type (rhertogh)
- Enh #162: Added ability to config the trace file and line number (thiagotalma)
- Enh: Mouse wheel click, or Ctrl+Click opens debugger in new tab (silverfire)
- Enh: `yii\debug\Module::defaultVersion()` implemented to pick up 'yiisoft/yii2-debug' extension version (klimov-paul)
2.0.6 March 17, 2016
--------------------
- Bug #41: Debug toolbar was unable to work without asset manager, removed `ToolbarAsset` class (samdark)
- Bug #51: Explain wasn't displayig all data available (lichunqiang)
- Bug #66: Fixed debug panel not working inside applications with response format different from HTML (creocoder, cebe)
- Bug #70: Exception was throwed when `UrlManager::ruleConfig` class was setted with `yii\rest\UrlRule` (lichunqiang)
- Bug: Fixed error when `Yii::$app->db` is not an instance of `yii\db\Connection` (cebe, jafaripur)
- Bug: Fixed exception when no data was recorded for db and profiling panel (cebe, jafaripur)
- Enh #44: Improved display of memory usage to use 3 decimals (dynasource)
- Enh #47: LogTarget storage directory is now created recursively if it does not exist (thiagotalma)
- Enh #63: Enhanced reliablity of request panel in case session is misconfigured (arisk)
- Enh #67: Ability to change permissions for debugger data files and directories (mg-code)
- Enh #83: Debug toolbar now works at the page in async manner (JiLiZART)
2.0.5 August 06, 2015
---------------------
- Bug #33: Fixed `LogTarget::collect()` to call `export()` in a proper way (cornernote)
- Bug #7305: Logging of Exception objects resulted in failure of the logger and no debug data was present (cebe)
- Bug #9112: Fixed initial state of debug toolbar placeholder to prevent "blink" on loading (samdark)
- Bug #9169: Fixed incorrect toolbar image mime causing XML5605 errors in IE console (samdark)
- Enh #16: Added ability to EXPLAIN queries in Database panel for MySQL, SQLite, PostgreSQL and Cubrid (laszlovl, samdark)
- Enh #19: Mark selected log item in dropdown list with bold font and an arrow (idMolotov)
- Enh #25: Make use of full screen width in debug toolbar backend (dynasource, cebe)
- Enh #36: Added check for EXPLAIN support in DbPanel (webdevsega)
- Enh: More compact toolbar (samdark)
- Enh: Display colorful status at index page (samdark)
- Enh: More readable format for date and time at index page (samdark)
- Enh: Toolbar script and styles are now properly registered instead of just echoed (samdark)
- Enh: Toolbar data URL is now HTML-escaped producing valid HTML (samdark)
2.0.4 May 10, 2015
------------------
- Bug #7222: Improved debug toolbar display in rtl pages (mohammadhosain, cebe, samdark)
- Enh #7655: Added ability to filter access by hostname (thiagotalma)
- Enh #7746: Background color of request selector is now choosen based on the current requests status (githubjeka, cebe)
2.0.3 March 01, 2015
--------------------
- Bug #6903: Fixed display issue with phpinfo() table (kalayda, cebe)
- Bug #7222: Debug toolbar wasn't displayed properly in rtl pages (mohammadhosain, johonunu, samdark)
- Enh #6890: Added ability to filter by query type (pana1990)
2.0.2 January 11, 2015
----------------------
- Bug #4820: Fixed reading incomplete debug index data in case of high request concurrency (martingeorg, samdark)
- Chg #6572: Allow panels to stay even if they do not receive any debug data (qiangxue)
2.0.1 December 07, 2014
-----------------------
- Bug #5402: Debugger was not loading when there were closures in asset classes (samdark)
- Bug #5745: Gii and debug modules may cause 404 exception when the route contains dashes (qiangxue)
- Enh #5600: Allow configuring debug panels in `yii\debug\Module::panels` as panel class name strings (qiangxue)
- Enh #6113: Improved configuration and request UI (schmunk42)
- Enh: Made `DefaultController::getManifest()` more robust against corrupt files (cebe)
2.0.0 October 12, 2014
----------------------
- no changes in this release.
2.0.0-rc September 27, 2014
---------------------------
- Bug #1263: Fixed the issue that Gii and Debug modules might be affected by incompatible asset manager configuration (qiangxue)
- Bug #3956: Debug toolbar was affecting flash message removal (samdark)
- Bug #4812: Fixed search filter (samdark)
- Bug #5126: Fixed text body and charset not being set for multipart mail (nkovacs)
- Enh #2299: Date and time in request list is now never wrapped (samdark)
- Enh #3088: The debug module will manage their own URL rules now (qiangxue)
- Enh #3103: debugger panel is now not displayed when printing a page (githubjeka)
- Enh #3108: Added `yii\debug\Module::enableDebugLogs` to disable logging debug logs by default (qiangxue)
- Enh #3810: Added "Latest" button on panels page (thiagotalma)
- Enh #4031: Http status codes were hardcoded in filter (sdkiller)
- Enh #5089: Added asset debugger panel (arturf, qiangxue)
2.0.0-beta April 13, 2014
-------------------------
- Bug #1783: Using VarDumper::dumpAsString() instead var_export(), because var_export() does not handle circular references. (djagya)
- Bug #1504: Debug toolbar isn't loaded successfully in some environments when xdebug is enabled (qiangxue)
- Bug #1747: Fixed problems with displaying toolbar on small screens (cebe)
- Bug #1827: Debugger toolbar is loaded twice if an action is calling `run()` to execute another action (qiangxue)
- Enh #1667: Added mail panel (Ragazzo, 6pblcb)
- Enh #2006: Added total queries count monitoring (o-rey, Ragazzo)
2.0.0-alpha, December 1, 2013
-----------------------------
- Initial release.
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug;
use yii\web\AssetBundle;
/**
* Debugger asset bundle
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class DebugAsset extends AssetBundle
{
public $sourcePath = '@yii/debug/assets';
public $css = [
'main.css',
'toolbar.css',
];
public $depends = [
'yii\web\YiiAsset',
'yii\bootstrap\BootstrapAsset',
];
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug;
/**
* FlattenException wraps a PHP Exception to be able to serialize it.
* Implements the Throwable interface
* Basically, this class removes all objects from the trace.
* Ported from Symfony components @link https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Debug/Exception/FlattenException.php
*
* @author Dmitry Bashkarev <dmitry@bashkarev.com>
* @since 2.0.10
*/
class FlattenException
{
/**
* @var string
*/
protected $message;
/**
* @var mixed|int
*/
protected $code;
/**
* @var string
*/
protected $file;
/**
* @var int
*/
protected $line;
/**
* @var FlattenException|null
*/
private $_previous;
/**
* @var array
*/
private $_trace;
/**
* @var string
*/
private $_toString;
/**
* @var string
*/
private $_class;
/**
* FlattenException constructor.
* @param \Exception $exception
*/
public function __construct(\Exception $exception)
{
$this->setMessage($exception->getMessage());
$this->setCode($exception->getCode());
$this->setFile($exception->getFile());
$this->setLine($exception->getLine());
$this->setTrace($exception->getTrace());
$this->setToString($exception->__toString());
$this->setClass(get_class($exception));
$previous = $exception->getPrevious();
if ($previous instanceof \Exception) {
$this->setPrevious(new self($previous));
}
}
/**
* Gets the Exception message
* @return string the Exception message as a string.
*/
public function getMessage()
{
return $this->message;
}
/**
* Gets the Exception code
* @return mixed|int the exception code as integer.
*/
public function getCode()
{
return $this->code;
}
/**
* Gets the file in which the exception occurred
* @return string the filename in which the exception was created.
*/
public function getFile()
{
return $this->file;
}
/**
* Gets the line in which the exception occurred
* @return int the line number where the exception was created.
*/
public function getLine()
{
return $this->line;
}
/**
* Gets the stack trace
* @return array the Exception stack trace as an array.
*/
public function getTrace()
{
return $this->_trace;
}
/**
* Returns previous Exception
* @return FlattenException the previous `FlattenException` if available or null otherwise.
*/
public function getPrevious()
{
return $this->_previous;
}
/**
* Gets the stack trace as a string
* @return string the Exception stack trace as a string.
*/
public function getTraceAsString()
{
$remove = "Stack trace:\n";
$len = strpos($this->_toString, $remove);
if ($len === false) {
return '';
}
return substr($this->_toString, $len + strlen($remove));
}
/**
* String representation of the exception
* @return string the string representation of the exception.
*/
public function __toString()
{
return $this->_toString;
}
/**
* @return string the name of the class in which the exception was created.
*/
public function getClass()
{
return $this->_class;
}
/**
* @param string $message the Exception message as a string.
*/
protected function setMessage($message)
{
$this->message = $message;
}
/**
* @param mixed|int $code the exception code as integer.
*/
protected function setCode($code)
{
$this->code = $code;
}
/**
* @param string $file the filename in which the exception was created.
*/
protected function setFile($file)
{
$this->file = $file;
}
/**
* @param int $line the line number where the exception was created.
*/
protected function setLine($line)
{
$this->line = $line;
}
/**
* @param array $trace the Exception stack trace as an array.
*/
protected function setTrace($trace)
{
$this->_trace = [];
foreach ($trace as $entry) {
$class = '';
$namespace = '';
if (isset($entry['class'])) {
$parts = explode('\\', $entry['class']);
$class = array_pop($parts);
$namespace = implode('\\', $parts);
}
$this->_trace[] = [
'namespace' => $namespace,
'short_class' => $class,
'class' => isset($entry['class']) ? $entry['class'] : '',
'type' => isset($entry['type']) ? $entry['type'] : '',
'function' => isset($entry['function']) ? $entry['function'] : null,
'file' => isset($entry['file']) ? $entry['file'] : null,
'line' => isset($entry['line']) ? $entry['line'] : null,
'args' => isset($entry['args']) ? $this->flattenArgs($entry['args']) : [],
];
}
}
/**
* @param string $string the string representation of the thrown object.
*/
protected function setToString($string)
{
$this->_toString = $string;
}
/**
* @param FlattenException $previous previous Exception.
*/
protected function setPrevious(FlattenException $previous)
{
$this->_previous = $previous;
}
/**
* @param string $class the name of the class in which the exception was created.
*/
protected function setClass($class)
{
$this->_class = $class;
}
/**
* Allows you to sterilize the Exception trace arguments
* @param array $args
* @param int $level recursion level
* @param int $count number of records counter
* @return array arguments tracing.
*/
private function flattenArgs($args, $level = 0, &$count = 0)
{
$result = [];
foreach ($args as $key => $value) {
if (++$count > 10000) {
return ['array', '*SKIPPED over 10000 entries*'];
}
if ($value instanceof \__PHP_Incomplete_Class) {
// is_object() returns false on PHP<=7.1
$result[$key] = ['incomplete-object', $this->getClassNameFromIncomplete($value)];
} elseif (is_object($value)) {
$result[$key] = ['object', get_class($value)];
} elseif (is_array($value)) {
if ($level > 10) {
$result[$key] = ['array', '*DEEP NESTED ARRAY*'];
} else {
$result[$key] = ['array', $this->flattenArgs($value, $level + 1, $count)];
}
} elseif (null === $value) {
$result[$key] = ['null', null];
} elseif (is_bool($value)) {
$result[$key] = ['boolean', $value];
} elseif (is_int($value)) {
$result[$key] = ['integer', $value];
} elseif (is_float($value)) {
$result[$key] = ['float', $value];
} elseif (is_resource($value)) {
$result[$key] = ['resource', get_resource_type($value)];
} else {
$result[$key] = ['string', (string)$value];
}
}
return $result;
}
/**
* @param \__PHP_Incomplete_Class $value
* @return string the real class name of an incomplete class
*/
private function getClassNameFromIncomplete(\__PHP_Incomplete_Class $value)
{
$array = new \ArrayObject($value);
return $array['__PHP_Incomplete_Class_Name'];
}
}
\ No newline at end of file
The Yii framework is free software. It is released under the terms of
the following BSD License.
Copyright © 2008 by Yii Software LLC (http://www.yiisoft.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 Yii Software LLC 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.
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug;
use Yii;
use yii\base\InvalidConfigException;
use yii\helpers\FileHelper;
use yii\log\Target;
/**
* The debug LogTarget is used to store logs for later use in the debugger tool
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class LogTarget extends Target
{
/**
* @var Module
*/
public $module;
public $tag;
/**
* @param \yii\debug\Module $module
* @param array $config
*/
public function __construct($module, $config = [])
{
parent::__construct($config);
$this->module = $module;
$this->tag = uniqid();
}
/**
* Exports log messages to a specific destination.
* Child classes must implement this method.
*/
public function export()
{
$path = $this->module->dataPath;
FileHelper::createDirectory($path, $this->module->dirMode);
$summary = $this->collectSummary();
$dataFile = "$path/{$this->tag}.data";
$data = [];
$exceptions = [];
foreach ($this->module->panels as $id => $panel) {
try {
$data[$id] = serialize($panel->save());
} catch (\Exception $exception) {
$exceptions[$id] = new FlattenException($exception);
}
}
$data['summary'] = $summary;
$data['exceptions'] = $exceptions;
file_put_contents($dataFile, serialize($data));
if ($this->module->fileMode !== null) {
@chmod($dataFile, $this->module->fileMode);
}
$indexFile = "$path/index.data";
$this->updateIndexFile($indexFile, $summary);
}
/**
* Updates index file with summary log data
*
* @param string $indexFile path to index file
* @param array $summary summary log data
* @throws \yii\base\InvalidConfigException
*/
private function updateIndexFile($indexFile, $summary)
{
touch($indexFile);
if (($fp = @fopen($indexFile, 'r+')) === false) {
throw new InvalidConfigException("Unable to open debug data index file: $indexFile");
}
@flock($fp, LOCK_EX);
$manifest = '';
while (($buffer = fgets($fp)) !== false) {
$manifest .= $buffer;
}
if (!feof($fp) || empty($manifest)) {
// error while reading index data, ignore and create new
$manifest = [];
} else {
$manifest = unserialize($manifest);
}
$manifest[$this->tag] = $summary;
$this->gc($manifest);
ftruncate($fp, 0);
rewind($fp);
fwrite($fp, serialize($manifest));
@flock($fp, LOCK_UN);
@fclose($fp);
if ($this->module->fileMode !== null) {
@chmod($indexFile, $this->module->fileMode);
}
}
/**
* Processes the given log messages.
* This method will filter the given messages with [[levels]] and [[categories]].
* And if requested, it will also export the filtering result to specific medium (e.g. email).
* @param array $messages log messages to be processed. See [[\yii\log\Logger::messages]] for the structure
* of each message.
* @param bool $final whether this method is called at the end of the current application
*/
public function collect($messages, $final)
{
$this->messages = array_merge($this->messages, $messages);
if ($final) {
$this->export();
}
}
/**
* Removes obsolete data files
* @param array $manifest
*/
protected function gc(&$manifest)
{
if (count($manifest) > $this->module->historySize + 10) {
$n = count($manifest) - $this->module->historySize;
foreach (array_keys($manifest) as $tag) {
$file = $this->module->dataPath . "/$tag.data";
@unlink($file);
unset($manifest[$tag]);
if (--$n <= 0) {
break;
}
}
}
}
/**
* Collects summary data of current request.
* @return array
*/
protected function collectSummary()
{
if (Yii::$app === null) {
return '';
}
$request = Yii::$app->getRequest();
$response = Yii::$app->getResponse();
$summary = [
'tag' => $this->tag,
'url' => $request->getAbsoluteUrl(),
'ajax' => (int) $request->getIsAjax(),
'method' => $request->getMethod(),
'ip' => $request->getUserIP(),
'time' => $_SERVER['REQUEST_TIME_FLOAT'],
'statusCode' => $response->statusCode,
'sqlCount' => $this->getSqlTotalCount(),
];
if (isset($this->module->panels['mail'])) {
$summary['mailCount'] = count($this->module->panels['mail']->getMessages());
}
return $summary;
}
/**
* Returns total sql count executed in current request. If database panel is not configured
* returns 0.
* @return int
*/
protected function getSqlTotalCount()
{
if (!isset($this->module->panels['db'])) {
return 0;
}
$profileLogs = $this->module->panels['db']->getProfileLogs();
# / 2 because messages are in couple (begin/end)
return count($profileLogs) / 2;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug;
use Yii;
use yii\base\Application;
use yii\base\BootstrapInterface;
use yii\helpers\Json;
use yii\web\Response;
use yii\helpers\Html;
use yii\helpers\Url;
use yii\web\View;
use yii\web\ForbiddenHttpException;
/**
* The Yii Debug Module provides the debug toolbar and debugger
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Module extends \yii\base\Module implements BootstrapInterface
{
const DEFAULT_IDE_TRACELINE = '<a href="ide://open?url=file://{file}&line={line}">{text}</a>';
/**
* @var array the list of IPs that are allowed to access this module.
* Each array element represents a single IP filter which can be either an IP address
* or an address with wildcard (e.g. 192.168.0.*) to represent a network segment.
* The default value is `['127.0.0.1', '::1']`, which means the module can only be accessed
* by localhost.
*/
public $allowedIPs = ['127.0.0.1', '::1'];
/**
* @var array the list of hosts that are allowed to access this module.
* Each array element is a hostname that will be resolved to an IP address that is compared
* with the IP address of the user. A use case is to use a dynamic DNS (DDNS) to allow access.
* The default value is `[]`.
*/
public $allowedHosts = [];
/**
* @inheritdoc
*/
public $controllerNamespace = 'yii\debug\controllers';
/**
* @var LogTarget
*/
public $logTarget;
/**
* @var array|Panel[] list of debug panels. The array keys are the panel IDs, and values are the corresponding
* panel class names or configuration arrays. This will be merged with [[corePanels()]].
* You may reconfigure a core panel via this property by using the same panel ID.
* You may also disable a core panel by setting it to be false in this property.
*/
public $panels = [];
/**
* @var string the name of the panel that should be visible when opening the debug panel.
* The default value is 'log'.
* @since 2.0.7
*/
public $defaultPanel = 'log';
/**
* @var string the directory storing the debugger data files. This can be specified using a path alias.
*/
public $dataPath = '@runtime/debug';
/**
* @var integer the permission to be set for newly created debugger data files.
* This value will be used by PHP [[chmod()]] function. No umask will be applied.
* If not set, the permission will be determined by the current environment.
* @since 2.0.6
*/
public $fileMode;
/**
* @var integer the permission to be set for newly created directories.
* This value will be used by PHP [[chmod()]] function. No umask will be applied.
* Defaults to 0775, meaning the directory is read-writable by owner and group,
* but read-only for other users.
* @since 2.0.6
*/
public $dirMode = 0775;
/**
* @var integer the maximum number of debug data files to keep. If there are more files generated,
* the oldest ones will be removed.
*/
public $historySize = 50;
/**
* @var boolean whether to enable message logging for the requests about debug module actions.
* You normally do not want to keep these logs because they may distract you from the logs about your applications.
* You may want to enable the debug logs if you want to investigate how the debug module itself works.
*/
public $enableDebugLogs = false;
/**
* @var mixed the string with placeholders to be be substituted or an anonymous function that returns the trace line string.
* The placeholders are {file}, {line} and {text} and the string should be as follows:
*
* `File: {file} - Line: {line} - Text: {text}`
*
* The signature of the anonymous function should be as follows:
*
* ```php
* function($trace, $panel) {
* // compute line string
* return $line;
* }
* ```
* @since 2.0.7
*/
public $traceLine = self::DEFAULT_IDE_TRACELINE;
/**
* @var string Yii logo URL
*/
private static $_yiiLogo = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAMAAAANIilAAAAC7lBMVEUAAACl034Cb7HlcjGRyT/H34fyy5PxqlSfzjwQeb5PmtX71HAMdrWOxkDzmU3qcDSPx0HzhUGNxT+/2lX2olDmUy/Q1l+TyD7rgjq21k3ZRzDQ4GGFw0Ghzz6MwOkKdrTA2lTzzMVjo9mhzkCIxUPk1MLynU7qWS33vmbP1rm011Fwqsj123/r44tUltTyq1aCxEOo0EL1tFuCw0Npp9v7xGVHkM8Ddrza0pvC3FboczHmXSvE21h+wkRkpNHvjkS92FPW3avpeDT2t1zX5GefzUD6wGQReLtMltPN417oczPZ0L+62FF+tuJgqtXZUzNzrN3s4Y7n65y72FLwmk7xjESr0kYof8MQe8DY5Gc6jMnN32DoaDLbTiLulUo1hsni45vuwnIigMXC21dqq8vKzaaBt+XU4mUMd7wDdr7xlUrU4a7A2VTD0LbVx5vvpFP/0m9godp/tuTD0LVyrsfZVDUuhMjkPChsrMt3suK92VDd52oEc7un0EKjzj7D21e01EuSyD2fzDvH3Fqu0kcDdL641k+x00rmXy0EdLiayzzynU2XyTzxmUur0ETshD7lZDDvkUbtiUDrgTvqfjrkWS292FPujEKAuObQ4GH3vWH1slr0r1j0pVLulEiPxj7oeDRnptn4zWrM31/1t13A2lb1rFb1qVS72FKHw0CLxD/qdTfnazL4wGPJ3VzwpFLpcjKFveljo9dfn9ZbntUYfcEIdr35w2XyoFH0ok/pfDZ9tONUmNRPltJIj89Ais388IL85Hn82nL80W33uV72tFy611DxlUnujkSCwkGlz0DqeTnocDJ3r99yrN1Xm9RFjc42hsorgsYhgMQPer/81XD5yGbT4mTriD/lbS3laCvjTiluqN5NktAxhMf853v84He/2VTgVCnmVSg8h8sHcrf6633+3nb8zGr2xmR/wEGcyzt3r+T/6n7tm01tqNnfSCnfPyO4zLmFwkDVRDGOweLP1aX55nrZTTOaxdjuY9uiAAAAfHRSTlMABv7+9hAJ/vMyGP2CbV5DOA+NbyYeG/DV0sC/ubaonYN5blZRQT41MSUk/v797+zj49PR0MXEw8PDu6imppqYlpOGhYN+bldWVFJROjAM+fPy8fDw8O7t6+vp5+Lh4N7e3Nvb2NPQ0MW8urm2rqiimJKFg3t5amZTT0k1ewExHwAABPVJREFUSMed1Xc81HEYB/DvhaOUEe29995777333ntv2sopUTQ4F104hRBSl8ohldCwOqfuuEiKaPdfz/P7/u6Syuu+ff727vM8z+8bhDHNB3TrXI38V6p1fvSosLBwgICd1qx/5cqVT8jrl9c1Wlm2qmFdgbWq5X316lXKq5dxu+ouyNWePevo6JjVd6il9T/soUPe3t48tyI0LeqWlpbk5oJ1dXVVKpNCH/e1/NO2rXXy5CEI5Y+6EZomn0tLSlS50OuaFZQUGuojl7vXtii/VQMnp5MQPW/+C6tUXDFnfeTubm4utVv+fud3EPTIUdfXYZVKpQULxTp75sz5h4PK7C4wO8zFCT1XbkxHG/cdZuaLqXV5Afb0xYW2etxsPxfg73htbEUPBhgXDgoKCg30kbu58Pai8/SW+o3t7e0TExPBYzuObkyXFk7SAnYFnBQYyPeePn3R2fnEiZsWPO5y6pQ9JpHXgPlHWlcLxWiTAh/LqX3wAOlNiYTXRzGn8F9I5LUx/052aLWOWVnwgQMfu7u7UQu9t26FhISYcpObHMdwHstxcR2uAc1ZSlgYsJsL7kutRCKT+XeyxWMfxHAeykE7OQGm6ecIOInaF3grmPkEWn8vL3FXIfxEnWMY8FTD5GYjeNwK3pbSCDEsTC30ysCK79/3HQY/MTggICABOZRTbYYHo9WuSiMjvhi/EWf90frGe3q2JmR8Ts65cwEJCVAOGgc3a6bD1vOVRj5wLVwY7U2dvR/vGRy1BB7TsgMH/HKAQzfVZlZEF0sjwHgtLC7GbySjvWCjojYS0vjIEcpBH8WTmwmIPmON4GEChksXF8MnotYX7NuMDGkb0vbaEeQ50E11A1R67SOnUzsjlsjgzvHx8cFRQKUFvQmpd/kaaD+sPoiYrqyfvDY39QPYOMTU1F8shn09g98WSOPi4szbEBuPy8BRY7V9l3L/34VDy2AvsdgXLfTGmZun9yY1PTw8Ll+DwenWI0j52A6awWGJzNQLj0VtenpsbHshWZXpQasTYO6ZJuTPCC3WQjFeix5LKpWap8dqNJohZHgmaA5DtQ35e6wtNnXS4wwojn2jUSimkH2ZtBpxnYp+67ce1pX7xBkF1KrV+S3IHIrxYuNJxbEd2SM4qoDDim/5+THrSD09bmzIn5eRPTiMNmYqLM2PDUMblNabzaE5PwbSZowHPdi0tsTQmKxor1EXFcXEDKnJf6q9xOBMCPvyVQG6aDGZhw80x8ZwK1h5ISzsRwe1Wt2B1MPHPZgYnqa3b1+4gOUKhUl/sP0Z7ITJycmowz5q3oxrfMBvvYBh6O7ZKcnvqY7dZuPXR8hQvOXSJdQc/7hhTB8TBjs6Ivz6pezsbKobmggYbJWOT1ADT8HFGxKW9LwTjRp4CujbTHj007t37kRHhGP5h5Tk5K0MduLce0/vvoyOjoiIuH4ddMoeBrzz2WvUMDrMDvpDFQa89Pkr4KCBo+7OYEdFpqLGcqqbMuDVaZGpqc/1OjycYerKohtpkZFl9ECG4qoihxvA9aN3ZDlXL5GDXR7Vr56BZtlYcAOwnQMdHXRPlmdd2U5kh5gffRHL0GSUXR5gKBeJ0tIiZ1UmLKlqlydygHD1s8EyYYe8PBFMjulVhbClEdy6kohLVTaJGEYW4eBr6MhsY1fi0ggoe7a3a7d84O6J5L8iNOiX3U+uoa/p8UPtoQAAAABJRU5ErkJggg==';
/**
* Returns the logo URL to be used in `<img src="`
*
* @return string the logo URL
*/
public static function getYiiLogo()
{
return self::$_yiiLogo;
}
/**
* Sets the logo URL to be used in `<img src="`
*
* @param string $logo the logo URL
*/
public static function setYiiLogo($logo)
{
self::$_yiiLogo = $logo;
}
/**
* @inheritdoc
*/
public function init()
{
parent::init();
$this->dataPath = Yii::getAlias($this->dataPath);
if (Yii::$app instanceof \yii\web\Application) {
$this->initPanels();
}
}
/**
* Initializes panels.
*/
protected function initPanels()
{
// merge custom panels and core panels so that they are ordered mainly by custom panels
if (empty($this->panels)) {
$this->panels = $this->corePanels();
} else {
$corePanels = $this->corePanels();
foreach ($corePanels as $id => $config) {
if (isset($this->panels[$id])) {
unset($corePanels[$id]);
}
}
$this->panels = array_filter(array_merge($corePanels, $this->panels));
}
foreach ($this->panels as $id => $config) {
if (is_string($config)) {
$config = ['class' => $config];
}
$config['module'] = $this;
$config['id'] = $id;
$this->panels[$id] = Yii::createObject($config);
if ($this->panels[$id] instanceof Panel && !$this->panels[$id]->isEnabled()) {
unset($this->panels[$id]);
}
}
}
/**
* @inheritdoc
*/
public function bootstrap($app)
{
$this->logTarget = $app->getLog()->targets['debug'] = new LogTarget($this);
// delay attaching event handler to the view component after it is fully configured
$app->on(Application::EVENT_BEFORE_REQUEST, function () use ($app) {
$app->getView()->on(View::EVENT_END_BODY, [$this, 'renderToolbar']);
$app->getResponse()->on(Response::EVENT_AFTER_PREPARE, [$this, 'setDebugHeaders']);
});
$app->getUrlManager()->addRules([
[
'class' => 'yii\web\UrlRule',
'route' => $this->id,
'pattern' => $this->id,
],
[
'class' => 'yii\web\UrlRule',
'route' => $this->id . '/<controller>/<action>',
'pattern' => $this->id . '/<controller:[\w\-]+>/<action:[\w\-]+>',
]
], false);
}
/**
* @inheritdoc
*/
public function beforeAction($action)
{
if (!$this->enableDebugLogs) {
foreach (Yii::$app->getLog()->targets as $target) {
$target->enabled = false;
}
}
if (!parent::beforeAction($action)) {
return false;
}
// do not display debug toolbar when in debug view mode
Yii::$app->getView()->off(View::EVENT_END_BODY, [$this, 'renderToolbar']);
Yii::$app->getResponse()->off(Response::EVENT_AFTER_PREPARE, [$this, 'setDebugHeaders']);
if ($this->checkAccess()) {
$this->resetGlobalSettings();
return true;
}
if ($action->id === 'toolbar') {
// Accessing toolbar remotely is normal. Do not throw exception.
return false;
}
throw new ForbiddenHttpException('You are not allowed to access this page.');
}
/**
* Setting headers to transfer debug data in AJAX requests
* without interfering with the request itself.
*
* @param \yii\base\Event $event
* @since 2.0.7
*/
public function setDebugHeaders($event)
{
if (!$this->checkAccess()) {
return;
}
$url = Url::toRoute(['/' . $this->id . '/default/view',
'tag' => $this->logTarget->tag,
]);
$event->sender->getHeaders()
->set('X-Debug-Tag', $this->logTarget->tag)
->set('X-Debug-Duration', number_format((microtime(true) - YII_BEGIN_TIME) * 1000 + 1))
->set('X-Debug-Link', $url);
}
/**
* Resets potentially incompatible global settings done in app config.
*/
protected function resetGlobalSettings()
{
Yii::$app->assetManager->bundles = [];
}
/**
* Gets toolbar HTML
* @since 2.0.7
*/
public function getToolbarHtml()
{
$url = Url::toRoute(['/' . $this->id . '/default/toolbar',
'tag' => $this->logTarget->tag,
]);
return '<div id="yii-debug-toolbar" data-url="' . Html::encode($url) . '" style="display:none" class="yii-debug-toolbar-bottom"></div>';
}
/**
* Renders mini-toolbar at the end of page body.
*
* @param \yii\base\Event $event
*/
public function renderToolbar($event)
{
if (!$this->checkAccess() || Yii::$app->getRequest()->getIsAjax()) {
return;
}
/* @var $view View */
$view = $event->sender;
echo $view->renderDynamic('return Yii::$app->getModule("' . $this->id . '")->getToolbarHtml();');
// echo is used in order to support cases where asset manager is not available
echo '<style>' . $view->renderPhpFile(__DIR__ . '/assets/toolbar.css') . '</style>';
echo '<script>' . $view->renderPhpFile(__DIR__ . '/assets/toolbar.js') . '</script>';
}
/**
* Checks if current user is allowed to access the module
* @return bool if access is granted
*/
protected function checkAccess()
{
$ip = Yii::$app->getRequest()->getUserIP();
foreach ($this->allowedIPs as $filter) {
if ($filter === '*' || $filter === $ip || (($pos = strpos($filter, '*')) !== false && !strncmp($ip, $filter, $pos))) {
return true;
}
}
foreach ($this->allowedHosts as $hostname) {
$filter = gethostbyname($hostname);
if ($filter === $ip) {
return true;
}
}
Yii::warning('Access to debugger is denied due to IP address restriction. The requesting IP address is ' . $ip, __METHOD__);
return false;
}
/**
* @return array default set of panels
*/
protected function corePanels()
{
return [
'config' => ['class' => 'yii\debug\panels\ConfigPanel'],
'request' => ['class' => 'yii\debug\panels\RequestPanel'],
'log' => ['class' => 'yii\debug\panels\LogPanel'],
'profiling' => ['class' => 'yii\debug\panels\ProfilingPanel'],
'db' => ['class' => 'yii\debug\panels\DbPanel'],
'assets' => ['class' => 'yii\debug\panels\AssetPanel'],
'mail' => ['class' => 'yii\debug\panels\MailPanel'],
'timeline' => ['class' => 'yii\debug\panels\TimelinePanel'],
'user' => ['class' => 'yii\debug\panels\UserPanel'],
'router' => ['class' => 'yii\debug\panels\RouterPanel']
];
}
/**
* @inheritdoc
* @since 2.0.7
*/
protected function defaultVersion()
{
$packageInfo = Json::decode(file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'composer.json'));
$extensionName = $packageInfo['name'];
if (isset(Yii::$app->extensions[$extensionName])) {
return Yii::$app->extensions[$extensionName]['version'];
}
return parent::defaultVersion();
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug;
use Yii;
use yii\base\Component;
use yii\helpers\ArrayHelper;
use yii\helpers\Url;
/**
* Panel is a base class for debugger panel classes. It defines how data should be collected,
* what should be displayed at debug toolbar and on debugger details view.
*
* @property string $detail Content that is displayed in debugger detail view. This property is read-only.
* @property string $name Name of the panel. This property is read-only.
* @property string $summary Content that is displayed at debug toolbar. This property is read-only.
* @property string $url URL pointing to panel detail view. This property is read-only.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Panel extends Component
{
/**
* @var string panel unique identifier.
* It is set automatically by the container module.
*/
public $id;
/**
* @var string request data set identifier.
*/
public $tag;
/**
* @var Module
*/
public $module;
/**
* @var mixed data associated with panel
*/
public $data;
/**
* @var array array of actions to add to the debug modules default controller.
* This array will be merged with all other panels actions property.
* See [[\yii\base\Controller::actions()]] for the format.
*/
public $actions = [];
/**
* @var FlattenException|null Error while saving the panel
* @since 2.0.10
*/
protected $error;
/**
* @return string name of the panel
*/
public function getName()
{
return '';
}
/**
* @return string content that is displayed at debug toolbar
*/
public function getSummary()
{
return '';
}
/**
* @return string content that is displayed in debugger detail view
*/
public function getDetail()
{
return '';
}
/**
* Saves data to be later used in debugger detail view.
* This method is called on every page where debugger is enabled.
*
* @return mixed data to be saved
*/
public function save()
{
return null;
}
/**
* Loads data into the panel
*
* @param mixed $data
*/
public function load($data)
{
$this->data = $data;
}
/**
* @param null|array $additionalParams Optional additional parameters to add to the route
* @return string URL pointing to panel detail view
*/
public function getUrl($additionalParams = null)
{
$route = [
'/' . $this->module->id . '/default/view',
'panel' => $this->id,
'tag' => $this->tag,
];
if (is_array($additionalParams)){
$route = ArrayHelper::merge($route, $additionalParams);
}
return Url::toRoute($route);
}
/**
* Returns a trace line
* @param array $options The array with trace
* @return string the trace line
* @since 2.0.7
*/
public function getTraceLine($options)
{
if (!isset($options['text'])) {
$options['text'] = "{$options['file']}:{$options['line']}";
}
$traceLine = $this->module->traceLine;
if ($traceLine === false) {
return $options['text'];
}
$options['file'] = str_replace('\\', '/', $options['file']);
$rawLink = $traceLine instanceof \Closure ? $traceLine($options, $this) : $traceLine;
return strtr($rawLink, ['{file}' => $options['file'], '{line}' => $options['line'], '{text}' => $options['text']]);
}
/**
* @param FlattenException $error
* @since 2.0.10
*/
public function setError(FlattenException $error)
{
$this->error = $error;
}
/**
* @return FlattenException|null
* @since 2.0.10
*/
public function getError()
{
return $this->error;
}
/**
* @return bool
* @since 2.0.10
*/
public function hasError()
{
return $this->error !== null;
}
/**
* Is the panel enabled?
* @return bool
* @since 2.0.10
*/
public function isEnabled()
{
return true;
}
}
<p align="center">
<a href="https://github.com/yiisoft" target="_blank">
<img src="https://avatars0.githubusercontent.com/u/993323" height="100px">
</a>
<h1 align="center">Debug Extension for Yii 2</h1>
<br>
</p>
This extension provides a debugger for [Yii framework 2.0](http://www.yiiframework.com) applications. When this extension is used,
a debugger toolbar will appear at the bottom of every page. The extension also provides
a set of standalone pages to display more detailed debug information.
For license information check the [LICENSE](LICENSE.md)-file.
Documentation is at [docs/guide/README.md](docs/guide/README.md).
[![Latest Stable Version](https://poser.pugx.org/yiisoft/yii2-debug/v/stable.png)](https://packagist.org/packages/yiisoft/yii2-debug)
[![Total Downloads](https://poser.pugx.org/yiisoft/yii2-debug/downloads.png)](https://packagist.org/packages/yiisoft/yii2-debug)
[![Build Status](https://travis-ci.org/yiisoft/yii2-debug.svg?branch=master)](https://travis-ci.org/yiisoft/yii2-debug)
Installation
------------
The preferred way to install this extension is through [composer](http://getcomposer.org/download/).
Either run
```
php composer.phar require --prefer-dist yiisoft/yii2-debug
```
or add
```
"yiisoft/yii2-debug": "~2.0.0"
```
to the require section of your `composer.json` file.
Usage
-----
Once the extension is installed, simply modify your application configuration as follows:
```php
return [
'bootstrap' => ['debug'],
'modules' => [
'debug' => [
'class' => 'yii\debug\Module',
// uncomment and adjust the following to add your IP if you are not connecting from localhost.
//'allowedIPs' => ['127.0.0.1', '::1'],
],
// ...
],
...
];
```
You will see a debugger toolbar showing at the bottom of every page of your application.
You can click on the toolbar to see more detailed debug information.
Open Files in IDE
-----
You can create a link to open files in your favorite IDE with this configuration:
```php
return [
'bootstrap' => ['debug'],
'modules' => [
'debug' => [
'class' => 'yii\debug\Module',
'traceLine' => '<a href="phpstorm://open?url={file}&line={line}">{file}:{line}</a>',
// uncomment and adjust the following to add your IP if you are not connecting from localhost.
//'allowedIPs' => ['127.0.0.1', '::1'],
],
// ...
],
...
];
```
You must make some changes to your OS. See these examples:
- PHPStorm: https://github.com/aik099/PhpStormProtocol
- Sublime Text 3 on Windows or Linux: https://packagecontrol.io/packages/subl%20protocol
- Sublime Text 3 on Mac: https://github.com/inopinatus/sublime_url
#### Virtualized or dockerized
If your application is run under a virtualized or dockerized environment, it is often the case that the application's base path is different inside of the virtual machine or container than on your host machine. For the links work in those situations, you can configure `traceLine` like this (change the path to your app):
```php
'traceLine' => function($options, $panel) {
$filePath = str_replace(Yii::$app->basePath, '~/path/to/your/app', $options['file']);
return strtr('<a href="ide://open?url=file://{file}&line={line}">{text}</a>', ['{file}' => $filePath]);
},
```
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug;
use yii\web\AssetBundle;
/**
* Timeline asset bundle
*
* @author Dmitriy Bashkarev <dmitriy@bashkarev.com>
* @since 2.0.7
*/
class TimelineAsset extends AssetBundle
{
public $sourcePath = '@yii/debug/assets';
public $css = [
'timeline.css',
];
public $js = [
'timeline.js',
];
}
\ No newline at end of file
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug;
use yii\web\AssetBundle;
/**
* Userswitch asset bundle
*
* @author Semen Dubina <yii2debug@sam002.net>
* @since 2.0.10
*/
class UserswitchAsset extends AssetBundle
{
public $sourcePath = '@yii/debug/assets';
public $js = [
'userswitch.js',
];
}
\ No newline at end of file
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\actions\db;
use yii\base\Action;
use yii\debug\panels\DbPanel;
use yii\web\HttpException;
/**
* ExplainAction provides EXPLAIN information for SQL queries
*
* @author Laszlo <github@lvlconsultancy.nl>
* @since 2.0.6
*/
class ExplainAction extends Action
{
/**
* @var DbPanel
*/
public $panel;
public function run($seq, $tag)
{
$this->controller->loadData($tag);
$timings = $this->panel->calculateTimings();
if (!isset($timings[$seq])) {
throw new HttpException(404, 'Log message not found.');
}
$query = $timings[$seq]['info'];
$results = $this->panel->getDb()->createCommand('EXPLAIN ' . $query)->queryAll();
$output[] = '<table class="table"><thead><tr>' . implode(array_map(function($key) {
return '<th>' . $key . '</th>';
}, array_keys($results[0]))) . '</tr></thead><tbody>';
foreach ($results as $result) {
$output[] = '<tr>' . implode(array_map(function($value) {
return '<td>' . (empty($value) ? 'NULL' : htmlspecialchars($value)) . '</td>';
}, $result)) . '</tr>';
}
$output[] = '</tbody></table>';
return implode($output);
}
}
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 50 50"><path fill="#444" d="M15.563 40.836a.997.997 0 0 0 1.414 0l15-15a1 1 0 0 0 0-1.414l-15-15a1 1 0 0 0-1.414 1.414L29.856 25.13 15.563 39.42a1 1 0 0 0 0 1.414z"/></svg>
.main-container {
width: auto;
}
span.indent {
color: #ccc;
}
ul.trace {
font-size: 12px;
color: #999;
margin: 2px 0 0 0;
padding: 0;
list-style: none;
white-space: normal;
}
#db-panel-detailed-grid table tbody tr td {
position: relative;
}
.db-explain {
position: absolute;
bottom: 4px;
right: 4px;
font-size: 10px;
}
.db-explain-text {
display: none;
margin: 10px 0 0px 0;
font-size: 13px;
width: 100%;
word-break: break-all;
}
#db-explain-all {
position: absolute;
bottom: 0;
right: 0;
font-size: 12px;
margin-right: 15px;
}
ul.assets {
margin: 2px 0 0 0;
padding: 0;
list-style: none;
white-space: normal;
}
.callout {
margin: 0 0 10px 0;
padding: 5px;
border: solid 1px #eee;
border-radius: 3px;
}
.callout-important {
background-color: rgba(185, 74, 72, 0.2);
border-color: rgba(185, 74, 72, 0.4);
}
.callout-success {
background-color: rgba(70, 136, 71, 0.2);
border-color: rgba(70, 136, 71, 0.4);
}
.callout-info {
background-color: rgba(58, 135, 173, 0.2);
border-color: rgba(58, 135, 173, 0.4);
}
.list-group .glyphicon {
float: right;
}
td, th {
white-space: pre-wrap;
word-wrap: break-word;
}
.request-table td {
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
word-break: break-all;
}
.request-table tr > th:first-child {
width: 25%;
}
.config-php-info-table td.v {
word-break: break-all;
}
.not-set {
color: #c55;
font-style: italic;
}
.detail-grid-view th {
white-space: nowrap;
}
/* add sorting icons to gridview sort links */
a.asc:after, a.desc:after {
position: relative;
top: 1px;
display: inline-block;
font-family: 'Glyphicons Halflings';
font-style: normal;
font-weight: normal;
line-height: 1;
padding-left: 5px;
}
a.asc:after {
content: /*"\e113"*/ "\e151";
}
a.desc:after {
content: /*"\e114"*/ "\e152";
}
.sort-numerical a.asc:after {
content: "\e153";
}
.sort-numerical a.desc:after {
content: "\e154";
}
.sort-ordinal a.asc:after {
content: "\e155";
}
.sort-ordinal a.desc:after {
content: "\e156";
}
.mail-sorter {
margin-top: 7px;
}
.mail-sorter li {
list-style: none;
float: left;
width: 12%;
font-weight: bold;
}
.nowrap {
white-space: nowrap;
}
.table-pointer tbody tr {
cursor: pointer;
}
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 50 50"><path fill="#444" d="M39.642 9.722a1.01 1.01 0 0 0-.382-.077H28.103a1 1 0 0 0 0 2h8.743L21.7 26.79a1 1 0 0 0 1.414 1.415L38.26 13.06v8.743a1 1 0 0 0 2 0V10.646a1.005 1.005 0 0 0-.618-.924z"/><path d="M39.26 27.985a1 1 0 0 0-1 1v10.66h-28v-28h10.683a1 1 0 0 0 0-2H9.26a1 1 0 0 0-1 1v30a1 1 0 0 0 1 1h30a1 1 0 0 0 1-1v-11.66a1 1 0 0 0-1-1z"/></svg>
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 50 50" version="1.1"><path d="m41.1 23c-0.6 0-1 0.4-1 1v10.7l-25.6-0.1c0 0 0-2 0-2.8 0-0.8-0.7-1-1-0.6l-3.5 3.5c-0.6 0.6-0.6 1.3 0 2l3.4 3.4c0.4 0.4 1.1 0.2 1-0.6l0-2.9c0 0 20.8 0.1 26.6 0 0.6 0 1-0.4 1-1v-11.7c0-0.6-0.4-1-1-1zM9 26.9 9 26.9 9 26.9 9 26.9"/><path d="m9 26.9c0.6 0 1-0.4 1-1v-10.7l25.6 0.1c0 0 0 2 0 2.8 0 0.8 0.7 1 1 0.6l3.5-3.5c0.6-0.6 0.6-1.3 0-2l-3.4-3.4c-0.4-0.4-1.1-0.2-1 0.6l0 2.9c0 0-20.8-0.1-26.6 0-0.6 0-1 0.4-1 1v11.7c0 0.6 0.4 1 1 1z"/></svg>
\ No newline at end of file
.debug-timeline-panel {
border: 1px solid #ddd;
position: relative;
margin-bottom: 20px;
}
.debug-timeline-panel.inline .debug-timeline-panel__item {
height: 20px;
margin-top: -20px;
border-bottom: 0;
}
.debug-timeline-panel.inline .debug-timeline-panel__item:first-child {
margin: 0;
}
.debug-timeline-panel.inline .debug-timeline-panel__item:not(.empty):hover {
background-color: transparent;
}
.debug-timeline-panel.inline .debug-timeline-panel__items .time {
box-shadow: inset 0px 0 3px -1px rgba(255, 255, 255, 0.7);
}
.debug-timeline-panel.inline .debug-timeline-panel__items .category {
display: none;
}
.debug-timeline-panel.inline .ruler.ruler-start,
.debug-timeline-panel.inline .ruler.ruler-end{
display: none;
}
.debug-timeline-panel:not(.inline) .debug-timeline-panel__item a:focus{
outline: none;
}
.debug-timeline-panel.affix .ruler b {
z-index: 2;
position: fixed;
top: 0;
}
.debug-timeline-panel .category {
opacity: 1;
font-size: 10px;
position: absolute;
line-height: 20px;
padding: 0 10px;
color: #222;
white-space: nowrap;
cursor: pointer;
}
.debug-timeline-panel .category span {
color: #7d7d7d;
}
.debug-timeline-panel .category span.memory[title] {
cursor: help;
border-bottom: 1px dotted #777;
}
.debug-timeline-panel .right > .category {
right: 100%;
}
.debug-timeline-panel .left > .category {
left: 100%;
}
.debug-timeline-panel .ruler {
position: absolute;
content: '';
font-size: 10px;
padding-left: 2px;
top: 0;
height: 100%;
border-left: 1px solid #ddd;
}
.debug-timeline-panel__header .ruler:first-child{
border-left: none;
}
.debug-timeline-panel .ruler.ruler-start {
top: auto;
margin-top: 20px;
}
.debug-timeline-panel .ruler.ruler-end {
left: -1px;
top: auto;
}
.debug-timeline-panel .ruler b {
position: absolute;
z-index: 2;
color: black;
font-weight: bold;
white-space: nowrap;
background-color: rgba(255,255,255,.4);
min-width: 40px;
line-height: 19px;
display: block;
text-align: center;
}
.debug-timeline-panel .time {
position: relative;
min-height: 20px;
display: block;
min-width: 1px;
padding: 0;
background-color: #989898;
}
.debug-timeline-panel .time + .tooltip .tooltip-inner{
max-width: 300px;
max-height: 180px;
overflow: auto;
word-wrap: break-word;
overflow-wrap: break-word;
}
.debug-timeline-panel__header {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
}
.debug-timeline-panel__header,
.debug-timeline-panel__item {
min-height: 20px;
border-bottom: 1px solid #ddd;
overflow: hidden;
}
.debug-timeline-panel__header .control {
position: absolute;
margin-left: -20px;
top:0;
}
.debug-timeline-panel__header .control button {
display: none;
padding: 0;
}
.debug-timeline-panel__header .control button:focus{
outline: none;
}
.debug-timeline-panel__header .control button:hover{
fill: #337ab7;
}
.debug-timeline-panel:not(.inline) .debug-timeline-panel__header .control button.inline,
.debug-timeline-panel.inline .debug-timeline-panel__header .control button.open{
display: block;
}
.debug-timeline-panel.affix .debug-timeline-panel__header .control{
position: fixed;
}
.debug-timeline-panel__item:last-child {
border-bottom: 0;
}
.debug-timeline-panel__item:nth-child(2n) {
background-color: #f9f9f9;
}
.debug-timeline-panel__item:hover {
background-color: rgba(51, 122, 183, 0.16);
}
.debug-timeline-panel__item.empty {
background-color: #f9f9f9;
line-height: 20px;
padding-left: 10px;
}
.debug-timeline-panel__item.empty span {
position: absolute;
background-color: inherit;
}
.debug-timeline-panel__search {
background-color: #f9f9f9;
padding: 10px 10px 0px 10px;
margin-bottom: 10px;
font-size: 16px;
}
.debug-timeline-panel__search > div {
display: inline-block;
margin-bottom: 10px;
}
.debug-timeline-panel__search .duration {
margin-right: 20px;
}
.debug-timeline-panel__search label {
width: 80px;
}
.debug-timeline-panel__search input {
font-size: 16px;
padding: 4px;
}
.debug-timeline-panel__search input#timeline-duration {
width: 55px;
text-align: right;
}
.debug-timeline-panel__search input#timeline-category {
min-width: 185px;
}
.debug-timeline-panel__memory {
position: relative;
margin-top: 18px;
box-sizing: content-box;
border-bottom: 1px solid #ddd;
}
.debug-timeline-panel__memory svg{
width: 100%;
}
.debug-timeline-panel__memory .scale {
font-size: 12px;
line-height: 16px;
position: absolute;
border-bottom: 1px dashed #000;
width: 100%;
padding-left: 6px;
transition: bottom 0.2s ease;
}
@media (max-width:767px) {
.debug-timeline-panel .ruler:nth-child(2n) b{
display: none;
}
}
@media (max-width: 991px) {
.debug-timeline-panel__header .control{
margin-left: -17px;
}
}
(function () {
'use strict';
var Timeline = function (options) {
this.options = options;
var self = this;
this.init = function () {
if (this.options.$focus) {
this.options.$focus.focus();
delete this.options.$focus;
}
self.options.$timeline.find('.debug-timeline-panel__item a')
.on('show.bs.tooltip', function () {
var data = $(this).data('memory');
if (data) {
self.options.$memory.text(data[0]).css({'bottom': data[1]+'%'});
}
})
.tooltip();
return self;
};
this.setFocus = function ($elem) {
this.options.$focus = $elem;
return $elem;
};
this.affixTop = function (refresh) {
if (!this.options.affixTop || refresh) {
this.options.affixTop = self.options.$header.offset().top;
}
return this.options.affixTop;
};
$(document).on('pjax:success', function () {
self.init()
});
$(window).on('resize', function () {
self.affixTop(true);
});
self.options.$header
.on('dblclick', function () {
self.options.$timeline.toggleClass('inline');
})
.on('click', 'button', function () {
self.options.$timeline.toggleClass('inline');
});
self.options.$search.on('change', function () {
self.setFocus($(this)).submit();
});
self.options.$timeline.affix({
offset: {
top: function () {
return self.affixTop()
}
}
});
this.init();
};
(new Timeline({
'$timeline': $('.debug-timeline-panel'),
'$header': $('.debug-timeline-panel__header'),
'$search': $('.debug-timeline-panel__search input'),
'$memory': $('.debug-timeline-panel__memory .scale')
}));
})();
\ No newline at end of file
#yii-debug-toolbar-logo {
position: fixed;
right: 31px;
bottom: 4px;
}
@media print {
.yii-debug-toolbar {
display: none !important;
}
}
.yii-debug-toolbar {
font: 11px Verdana, Arial, sans-serif;
text-align: left;
width: 96px;
transition: width .3s ease;
z-index: 1000000;
}
.yii-debug-toolbar_active {
width: 100%;
}
.yii-debug-toolbar_position_top {
margin: 0 0 20px 0;
width: 100%;
}
.yii-debug-toolbar_position_bottom {
position: fixed;
right: 0;
bottom: 0;
margin: 0;
}
.yii-debug-toolbar__bar {
position: relative;
padding: 0;
font: 11px Verdana, Arial, sans-serif;
text-align: left;
overflow: hidden;
box-sizing: content-box;
background: rgb(255, 255, 255);
background: -moz-linear-gradient(top, rgb(255, 255, 255) 0%, rgb(247, 247, 247) 100%); /* FF3.6-15 */
background: -webkit-linear-gradient(top, rgb(255, 255, 255) 0%, rgb(247, 247, 247) 100%); /* Chrome10-25,Safari5.1-6 */
background: linear-gradient(to bottom, rgb(255, 255, 255) 0%, rgb(247, 247, 247) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f7f7f7', GradientType=0); /* IE6-9 */
border: 1px solid rgba(0, 0, 0, 0.11);
/* ensure debug toolbar text is displayed ltr even on rtl pages */
direction: ltr;
}
.yii-debug-toolbar.yii-debug-toolbar_active:not(.yii-debug-toolbar_animating) .yii-debug-toolbar__bar {
overflow: visible;
}
.yii-debug-toolbar:not(.yii-debug-toolbar_active) .yii-debug-toolbar__bar,
.yii-debug-toolbar.yii-debug-toolbar_animating .yii-debug-toolbar__bar {
height:40px;
}
.yii-debug-toolbar__bar:after {
content: '';
display: table;
clear: both;
}
.yii-debug-toolbar__view {
height: 0;
overflow: hidden;
background: white;
}
.yii-debug-toolbar__view iframe {
margin: 0;
padding: 0;
padding-top: 10px;
height: 100%;
width: 100%;
border: 0;
}
.yii-debug-toolbar_iframe_active .yii-debug-toolbar__view {
height: 100%;
}
.yii-debug-toolbar_iframe_animating .yii-debug-toolbar__view {
transition: height .3s ease;
}
.yii-debug-toolbar__block {
float: left;
margin: 0;
border-right: 1px solid rgba(0, 0, 0, 0.11);
padding: 4px 8px;
line-height: 32px;
white-space: nowrap;
}
.yii-debug-toolbar__block_active,
.yii-debug-toolbar__ajax:hover {
background: rgb(247, 247, 247); /* Old browsers */
background: -moz-linear-gradient(top, rgb(247, 247, 247) 0%, rgb(224, 224, 224) 100%); /* FF3.6-15 */
background: -webkit-linear-gradient(top, rgb(247, 247, 247) 0%, rgb(224, 224, 224) 100%); /* Chrome10-25,Safari5.1-6 */
background: linear-gradient(to bottom, rgb(247, 247, 247) 0%, rgb(224, 224, 224) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f7f7f7', endColorstr='#e0e0e0', GradientType=0); /* IE6-9 */
}
.yii-debug-toolbar__block a {
display: inline-block;
text-decoration: none;
color: black;
}
.yii-debug-toolbar__block img {
vertical-align: middle;
}
.yii-debug-toolbar__label {
display: inline-block;
padding: 2px 4px;
font-size: 12px;
font-weight: normal;
line-height: 14px;
white-space: nowrap;
vertical-align: baseline;
color: #ffffff;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
background-color: #999999;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
}
.yii-debug-toolbar__label:empty {
display: none;
}
a.yii-debug-toolbar__label:hover,
a.yii-debug-toolbar__label:focus {
color: #ffffff;
text-decoration: none;
cursor: pointer;
}
.yii-debug-toolbar__label_important,
.yii-debug-toolbar__label_error {
background-color: #b94a48;
}
.yii-debug-toolbar__label_important[href] {
background-color: #953b39;
}
.yii-debug-toolbar__label_warning,
.yii-debug-toolbar__badge_warning {
background-color: #f89406;
}
.yii-debug-toolbar__label_warning[href] {
background-color: #c67605;
}
.yii-debug-toolbar__label_success {
background-color: #468847;
}
.yii-debug-toolbar__label_success[href] {
background-color: #356635;
}
.yii-debug-toolbar__label_info {
background-color: #3a87ad;
}
.yii-debug-toolbar__label_info[href] {
background-color: #2d6987;
}
.yii-debug-toolbar__label_inverse,
.yii-debug-toolbar__badge_inverse {
background-color: #333333;
}
.yii-debug-toolbar__label_inverse[href],
.yii-debug-toolbar__badge_inverse[href] {
background-color: #1a1a1a;
}
.yii-debug-toolbar__title {
background: rgb(247, 247, 247); /* Old browsers */
background: -moz-linear-gradient(top, rgb(247, 247, 247) 0%, rgb(224, 224, 224) 100%); /* FF3.6-15 */
background: -webkit-linear-gradient(top, rgb(247, 247, 247) 0%, rgb(224, 224, 224) 100%); /* Chrome10-25,Safari5.1-6 */
background: linear-gradient(to bottom, rgb(247, 247, 247) 0%, rgb(224, 224, 224) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f7f7f7', endColorstr='#e0e0e0', GradientType=0); /* IE6-9 */
}
.yii-debug-toolbar__block_last{ /* creates space for .yii-debug-toolbar__toggle, .yii-debug-toolbar__external */
width: 80px;
height: 40px;
float: left;
}
.yii-debug-toolbar__toggle,
.yii-debug-toolbar__external {
cursor: pointer;
position: absolute;
width: 30px;
height: 30px;
font-size: 25px;
font-weight: 100;
line-height: 28px;
color: #ffffff;
text-align: center;
opacity: 0.5;
filter: alpha(opacity=50);
transition: opacity .3s ease;
}
.yii-debug-toolbar__toggle:hover,
.yii-debug-toolbar__toggle:focus,
.yii-debug-toolbar__external:hover,
.yii-debug-toolbar__external:focus {
color: #ffffff;
text-decoration: none;
opacity: 0.9;
filter: alpha(opacity=90);
}
.yii-debug-toolbar__toggle-icon,
.yii-debug-toolbar__external-icon {
display: inline-block;
background-position: 50% 50%;
background-repeat: no-repeat;
}
.yii-debug-toolbar__toggle {
right: 10px;
bottom: 4px;
}
.yii-debug-toolbar__toggle-icon {
padding: 7px 0;
width: 10px;
height: 16px;
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNSIgaGVpZ2h0PSIxNSIgdmlld0JveD0iMCAwIDUwIDUwIj48cGF0aCBmaWxsPSIjNDQ0IiBkPSJNMTUuNTYzIDQwLjgzNmEuOTk3Ljk5NyAwIDAgMCAxLjQxNCAwbDE1LTE1YTEgMSAwIDAgMCAwLTEuNDE0bC0xNS0xNWExIDEgMCAwIDAtMS40MTQgMS40MTRMMjkuODU2IDI1LjEzIDE1LjU2MyAzOS40MmExIDEgMCAwIDAgMCAxLjQxNHoiLz48L3N2Zz4=');
transition: -webkit-transform .3s ease-out;
transition: transform .3s ease-out;
-webkit-transform: rotate(180deg);
transform: rotate(180deg);
}
.yii-debug-toolbar_active .yii-debug-toolbar__toggle-icon {
-webkit-transform: rotate(0);
transform: rotate(0);
}
.yii-debug-toolbar_iframe_active .yii-debug-toolbar__toggle-icon {
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
}
.yii-debug-toolbar__external {
display: none;
right: 50px;
bottom: 4px;
}
.yii-debug-toolbar_iframe_active .yii-debug-toolbar__external {
display: block;
}
.yii-debug-toolbar__external-icon {
padding: 8px 0;
width: 14px;
height: 14px;
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNSIgaGVpZ2h0PSIxNSIgdmlld0JveD0iMCAwIDUwIDUwIj48cGF0aCBmaWxsPSIjNDQ0IiBkPSJNMzkuNjQyIDkuNzIyYTEuMDEgMS4wMSAwIDAgMC0uMzgyLS4wNzdIMjguMTAzYTEgMSAwIDAgMCAwIDJoOC43NDNMMjEuNyAyNi43OWExIDEgMCAwIDAgMS40MTQgMS40MTVMMzguMjYgMTMuMDZ2OC43NDNhMSAxIDAgMCAwIDIgMFYxMC42NDZhMS4wMDUgMS4wMDUgMCAwIDAtLjYxOC0uOTI0eiIvPjxwYXRoIGQ9Ik0zOS4yNiAyNy45ODVhMSAxIDAgMCAwLTEgMXYxMC42NmgtMjh2LTI4aDEwLjY4M2ExIDEgMCAwIDAgMC0ySDkuMjZhMSAxIDAgMCAwLTEgMXYzMGExIDEgMCAwIDAgMSAxaDMwYTEgMSAwIDAgMCAxLTF2LTExLjY2YTEgMSAwIDAgMC0xLTF6Ii8+PC9zdmc+');
}
.yii-debug-toolbar__switch-icon {
margin-left: 10px;
padding: 5px 10px;
width: 18px;
height: 18px;
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgdmlld0JveD0iMCAwIDUwIDUwIiB2ZXJzaW9uPSIxLjEiPjxwYXRoIGQ9Im00MS4xIDIzYy0wLjYgMC0xIDAuNC0xIDF2MTAuN2wtMjUuNi0wLjFjMCAwIDAtMiAwLTIuOCAwLTAuOC0wLjctMS0xLTAuNmwtMy41IDMuNWMtMC42IDAuNi0wLjYgMS4zIDAgMmwzLjQgMy40YzAuNCAwLjQgMS4xIDAuMiAxLTAuNmwwLTIuOWMwIDAgMjAuOCAwLjEgMjYuNiAwIDAuNiAwIDEtMC40IDEtMXYtMTEuN2MwLTAuNi0wLjQtMS0xLTF6TTkgMjYuOSA5IDI2LjkgOSAyNi45IDkgMjYuOSIvPjxwYXRoIGQ9Im05IDI2LjljMC42IDAgMS0wLjQgMS0xdi0xMC43bDI1LjYgMC4xYzAgMCAwIDIgMCAyLjggMCAwLjggMC43IDEgMSAwLjZsMy41LTMuNWMwLjYtMC42IDAuNi0xLjMgMC0ybC0zLjQtMy40Yy0wLjQtMC40LTEuMS0wLjItMSAwLjZsMCAyLjljMCAwLTIwLjgtMC4xLTI2LjYgMC0wLjYgMC0xIDAuNC0xIDF2MTEuN2MwIDAuNiAwLjQgMSAxIDF6Ii8+PC9zdmc+');
}
.yii-debug-toolbar__ajax {
position: relative;
}
.yii-debug-toolbar__ajax:hover .yii-debug-toolbar__ajax_info,
.yii-debug-toolbar__ajax:focus .yii-debug-toolbar__ajax_info {
visibility: visible;
}
.yii-debug-toolbar__ajax_info {
visibility: hidden;
transition: visibility .2s linear;
background-color: white;
box-shadow: inset 0 -10px 10px -10px #e1e1e1;
position: absolute;
bottom: 40px;
left: -1px;
padding: 10px;
max-width: 480px;
max-height: 480px;
word-wrap: break-word;
overflow: hidden;
overflow-y: auto;
box-sizing: border-box;
border: 1px solid rgba(0, 0, 0, 0.11);
z-index: 1000001;
}
.yii-debug-toolbar__ajax a {
color: #337ab7;
}
.yii-debug-toolbar__ajax table {
width: 100%;
table-layout: auto;
border-spacing: 0;
border-collapse: collapse;
}
.yii-debug-toolbar__ajax table td {
padding: 4px;
font-size: 12px;
line-height: normal;
vertical-align: top;
border-top: 1px solid #ddd;
}
.yii-debug-toolbar__ajax table th {
padding: 4px;
font-size: 11px;
line-height: normal;
vertical-align: bottom;
border-bottom: 2px solid #ddd;
}
.yii-debug-toolbar__ajax_request_status {
color: white;
padding: 2px 5px;
}
.yii-debug-toolbar__ajax_request_url {
max-width: 170px;
overflow: hidden;
text-overflow: ellipsis;
}
(function () {
'use strict';
var findToolbar = function () {
return document.querySelector('#yii-debug-toolbar');
},
ajax = function (url, settings) {
var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
settings = settings || {};
xhr.open(settings.method || 'GET', url, true);
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.setRequestHeader('Accept', 'text/html');
xhr.onreadystatechange = function (state) {
if (xhr.readyState === 4) {
if (xhr.status === 200 && settings.success) {
settings.success(xhr);
} else if (xhr.status != 200 && settings.error) {
settings.error(xhr);
}
}
};
xhr.send(settings.data || '');
},
url,
div,
toolbarEl = findToolbar(),
toolbarAnimatingClass = 'yii-debug-toolbar_animating',
barSelector = '.yii-debug-toolbar__bar',
viewSelector = '.yii-debug-toolbar__view',
blockSelector = '.yii-debug-toolbar__block',
toggleSelector = '.yii-debug-toolbar__toggle',
externalSelector = '.yii-debug-toolbar__external',
CACHE_KEY = 'yii-debug-toolbar',
ACTIVE_STATE = 'active',
animationTime = 300,
activeClass = 'yii-debug-toolbar_active',
iframeActiveClass = 'yii-debug-toolbar_iframe_active',
iframeAnimatingClass = 'yii-debug-toolbar_iframe_animating',
titleClass = 'yii-debug-toolbar__title',
blockClass = 'yii-debug-toolbar__block',
blockActiveClass = 'yii-debug-toolbar__block_active',
requestStack = [];
if (toolbarEl) {
url = toolbarEl.getAttribute('data-url');
ajax(url, {
success: function (xhr) {
div = document.createElement('div');
div.innerHTML = xhr.responseText;
toolbarEl.parentNode && toolbarEl.parentNode.replaceChild(div, toolbarEl);
showToolbar(findToolbar());
},
error: function (xhr) {
toolbarEl.innerText = xhr.responseText;
}
});
}
function showToolbar(toolbarEl) {
var barEl = toolbarEl.querySelector(barSelector),
viewEl = toolbarEl.querySelector(viewSelector),
toggleEl = toolbarEl.querySelector(toggleSelector),
externalEl = toolbarEl.querySelector(externalSelector),
blockEls = barEl.querySelectorAll(blockSelector),
iframeEl = viewEl.querySelector('iframe'),
iframeHeight = function () {
return (window.innerHeight * 0.7) + 'px';
},
isIframeActive = function () {
return toolbarEl.classList.contains(iframeActiveClass);
},
showIframe = function (href) {
toolbarEl.classList.add(iframeAnimatingClass);
toolbarEl.classList.add(iframeActiveClass);
iframeEl.src = externalEl.href = href;
viewEl.style.height = iframeHeight();
setTimeout(function() {
toolbarEl.classList.remove(iframeAnimatingClass);
}, animationTime);
},
hideIframe = function () {
toolbarEl.classList.add(iframeAnimatingClass);
toolbarEl.classList.remove(iframeActiveClass);
removeActiveBlocksCls();
externalEl.href = '#';
viewEl.style.height = '';
setTimeout(function() {
toolbarEl.classList.remove(iframeAnimatingClass);
}, animationTime);
},
removeActiveBlocksCls = function () {
[].forEach.call(blockEls, function (el) {
el.classList.remove(blockActiveClass);
});
},
toggleToolbarClass = function (className) {
toolbarEl.classList.add(toolbarAnimatingClass);
if (toolbarEl.classList.contains(className)) {
toolbarEl.classList.remove(className);
} else {
toolbarEl.classList.add(className);
}
setTimeout(function () {
toolbarEl.classList.remove(toolbarAnimatingClass);
}, animationTime);
},
toggleStorageState = function (key, value) {
if (window.localStorage) {
var item = localStorage.getItem(key);
if (item) {
localStorage.removeItem(key);
} else {
localStorage.setItem(key, value);
}
}
},
restoreStorageState = function (key) {
if (window.localStorage) {
return localStorage.getItem(key);
}
},
togglePosition = function () {
if (isIframeActive()) {
hideIframe();
} else {
toggleToolbarClass(activeClass);
toggleStorageState(CACHE_KEY, ACTIVE_STATE);
}
};
toolbarEl.style.display = 'block';
if (restoreStorageState(CACHE_KEY) === ACTIVE_STATE) {
toolbarEl.classList.add(activeClass);
}
window.onresize = function () {
if (toolbarEl.classList.contains(iframeActiveClass)) {
viewEl.style.height = iframeHeight();
}
};
barEl.onclick = function (e) {
var target = e.target,
block = findAncestor(target, blockClass);
if (block && !block.classList.contains(titleClass)
&& e.which !== 2 && !e.ctrlKey // not mouse wheel and not ctrl+click
) {
while (target !== this) {
if (target.href) {
removeActiveBlocksCls();
block.classList.add(blockActiveClass);
showIframe(target.href);
}
target = target.parentNode;
}
e.preventDefault();
}
};
toggleEl.onclick = togglePosition;
}
function findAncestor(el, cls) {
while ((el = el.parentElement) && !el.classList.contains(cls));
return el;
}
function renderAjaxRequests() {
var requestCounter = document.getElementsByClassName('yii-debug-toolbar__ajax_counter');
if (!requestCounter.length) {
return;
}
var ajaxToolbarPanel = document.querySelector('.yii-debug-toolbar__ajax');
var tbodies = document.getElementsByClassName('yii-debug-toolbar__ajax_requests');
var state = 'ok';
if (tbodies.length) {
var tbody = tbodies[0];
var rows = document.createDocumentFragment();
if (requestStack.length) {
var firstItem = requestStack.length > 20 ? requestStack.length - 20 : 0;
for (var i = firstItem; i < requestStack.length; i++) {
var request = requestStack[i];
var row = document.createElement('tr');
rows.appendChild(row);
var methodCell = document.createElement('td');
methodCell.innerHTML = request.method;
row.appendChild(methodCell);
var statusCodeCell = document.createElement('td');
var statusCode = document.createElement('span');
if (request.statusCode < 300) {
statusCode.setAttribute('class', 'yii-debug-toolbar__ajax_request_status yii-debug-toolbar__label_success');
} else if (request.statusCode < 400) {
statusCode.setAttribute('class', 'yii-debug-toolbar__ajax_request_status yii-debug-toolbar__label_warning');
} else {
statusCode.setAttribute('class', 'yii-debug-toolbar__ajax_request_status yii-debug-toolbar__label_error');
}
statusCode.textContent = request.statusCode || '-';
statusCodeCell.appendChild(statusCode);
row.appendChild(statusCodeCell);
var pathCell = document.createElement('td');
pathCell.className = 'yii-debug-toolbar__ajax_request_url';
pathCell.innerHTML = request.url;
pathCell.setAttribute('title', request.url);
row.appendChild(pathCell);
var durationCell = document.createElement('td');
durationCell.className = 'yii-debug-toolbar__ajax_request_duration';
if (request.duration) {
durationCell.innerText = request.duration + " ms";
} else {
durationCell.innerText = '-';
}
row.appendChild(durationCell);
row.appendChild(document.createTextNode(' '));
var profilerCell = document.createElement('td');
if (request.profilerUrl) {
var profilerLink = document.createElement('a');
profilerLink.setAttribute('href', request.profilerUrl);
profilerLink.innerText = request.profile;
profilerCell.appendChild(profilerLink);
} else {
profilerCell.innerText = 'n/a';
}
row.appendChild(profilerCell);
if (request.error) {
if (state !== "loading" && i > requestStack.length - 4) {
state = 'error';
}
} else if (request.loading) {
state = 'loading'
}
row.className = 'yii-debug-toolbar__ajax_request';
}
while (tbody.firstChild) {
tbody.removeChild(tbody.firstChild);
}
tbody.appendChild(rows);
}
ajaxToolbarPanel.style.display = 'block';
}
requestCounter[0].innerText = requestStack.length;
var className = 'yii-debug-toolbar__label yii-debug-toolbar__ajax_counter';
if (state === 'ok') {
className += ' yii-debug-toolbar__label_success';
} else if (state === 'error') {
className += ' yii-debug-toolbar__label_error';
}
requestCounter[0].className = className;
};
var proxied = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function (method, url, async, user, pass) {
var self = this;
/* prevent logging AJAX calls to static and inline files, like templates */
if (url.substr(0, 1) === '/' && !url.match(new RegExp("{{ excluded_ajax_paths }}"))) {
var stackElement = {
loading: true,
error: false,
url: url,
method: method,
start: new Date()
};
requestStack.push(stackElement);
this.addEventListener("readystatechange", function () {
if (self.readyState == 4) {
stackElement.duration = self.getResponseHeader("X-Debug-Duration") || new Date() - stackElement.start;
stackElement.loading = false;
stackElement.statusCode = self.status;
stackElement.error = self.status < 200 || self.status >= 400;
stackElement.profile = self.getResponseHeader("X-Debug-Tag");
stackElement.profilerUrl = self.getResponseHeader("X-Debug-Link");
renderAjaxRequests();
}
}, false);
renderAjaxRequests();
}
proxied.apply(this, Array.prototype.slice.call(arguments));
};
// catch fetch AJAX requests
if (window.fetch) {
var originalFetch = window.fetch;
window.fetch = function(input, init) {
var method;
var url;
if (typeof input === "string") {
method = (init && init.method) || 'GET';
url = input;
} else if (window.Request && input instanceof Request) {
method = input.method;
url = input.url;
}
var promise = originalFetch(input, init);
/* prevent logging AJAX calls to static and inline files, like templates */
if (url.substr(0, 1) === '/' && !url.match(new RegExp("{{ excluded_ajax_paths }}"))) {
var stackElement = {
loading: true,
error: false,
url: url,
method: method,
start: new Date()
};
requestStack.push(stackElement);
promise.then(function(response) {
stackElement.duration = response.headers.get("X-Debug-Duration") || new Date() - stackElement.start;
stackElement.loading = false;
stackElement.statusCode = response.status;
stackElement.error = response.status < 200 || response.status >= 400;
stackElement.profile = response.headers.get("X-Debug-Tag");
stackElement.profilerUrl = response.headers.get("X-Debug-Link");
renderAjaxRequests();
return response;
}).catch(function(error) {
stackElement.loading = false;
stackElement.error = true;
renderAjaxRequests();
throw error;
});
renderAjaxRequests();
}
return promise;
};
}
})();
\ No newline at end of file
(function () {
'use strict';
var sendSetIdentity = function(e) {
var form = $(this);
var formData = form.serialize();
$.ajax({
url: form.attr("action"),
type: form.attr("method"),
data: formData,
success: function (data) {
window.top.location.reload();
},
error: function (data) {
form.yiiActiveForm('updateMessages', data.responseJSON, true);
}
});
};
$('#debug-userswitch__set-identity').on('beforeSubmit', sendSetIdentity)
.on('submit', function(e){
e.preventDefault();
});
$('#debug-userswitch__reset-identity').on('beforeSubmit', sendSetIdentity)
.on('submit', function(e){
e.preventDefault();
});
$('#debug-userswitch__filter').on("click", "tbody tr", function(event) {
$('#debug-userswitch__set-identity #user_id').val($(this).data('key'));
$('#debug-userswitch__set-identity').submit();
event.stopPropagation();
});
})();
\ No newline at end of file
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\components\search;
use yii\base\Component;
use yii\debug\components\search\matchers\MatcherInterface;
/**
* Provides array filtering capabilities.
*
* @author Mark Jebri <mark.github@yandex.ru>
* @since 2.0
*/
class Filter extends Component
{
/**
* @var array rules for matching filters in the way: [:fieldName => [rule1, rule2,..]]
*/
protected $rules = [];
/**
* Adds data filtering rule.
*
* @param string $name attribute name
* @param MatcherInterface $rule
*/
public function addMatcher($name, MatcherInterface $rule)
{
if ($rule->hasValue()) {
$this->rules[$name][] = $rule;
}
}
/**
* Applies filter on a given array and returns filtered data.
*
* @param array $data data to filter
* @return array filtered data
*/
public function filter(array $data)
{
$filtered = [];
foreach ($data as $row) {
if ($this->passesFilter($row)) {
$filtered[] = $row;
}
}
return $filtered;
}
/**
* Checks if the given data satisfies filters.
*
* @param array $row data
* @return bool if data passed filtering
*/
private function passesFilter(array $row)
{
foreach ($row as $name => $value) {
if (isset($this->rules[$name])) {
// check all rules for a given attribute
foreach ($this->rules[$name] as $rule) {
/* @var $rule MatcherInterface */
if (!$rule->match($value)) {
return false;
}
}
}
}
return true;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\components\search\matchers;
use yii\base\Component;
/**
* Base class for matchers that are used in a filter.
*
* @author Mark Jebri <mark.github@yandex.ru>
* @since 2.0
*/
abstract class Base extends Component implements MatcherInterface
{
/**
* @var mixed base value to check
*/
protected $baseValue;
/**
* @inheritdoc
*/
public function setValue($value)
{
$this->baseValue = $value;
}
/**
* @inheritdoc
*/
public function hasValue()
{
return !empty($this->baseValue) || ($this->baseValue === '0');
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\components\search\matchers;
/**
* Checks if the given value is greater than the base one.
*
* @author Mark Jebri <mark.github@yandex.ru>
* @since 2.0
*/
class GreaterThan extends Base
{
/**
* @inheritdoc
*/
public function match($value)
{
return ($value > $this->baseValue);
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\components\search\matchers;
/**
* Checks if the given value is greater than or equal the base one.
*
* @author Dmitriy Bashkarev <dmitriy@bashkarev.com>
* @since 2.0.7
*/
class GreaterThanOrEqual extends Base
{
/**
* @inheritdoc
*/
public function match($value)
{
return $value >= $this->baseValue;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\components\search\matchers;
/**
* Checks if the given value is lower than the base one.
*
* @author Mark Jebri <mark.github@yandex.ru>
* @since 2.0
*/
class LowerThan extends Base
{
/**
* @inheritdoc
*/
public function match($value)
{
return ($value < $this->baseValue);
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\components\search\matchers;
/**
* MatcherInterface should be implemented by all matchers that are used in a filter.
*
* @author Mark Jebri <mark.github@yandex.ru>
* @since 2.0
*/
interface MatcherInterface
{
/**
* Checks if the value passed matches base value.
*
* @param mixed $value value to be matched
* @return bool if there is a match
*/
public function match($value);
/**
* Sets base value to match against
*
* @param mixed $value
*/
public function setValue($value);
/**
* Checks if base value is set
*
* @return bool if base value is set
*/
public function hasValue();
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\components\search\matchers;
use yii\helpers\VarDumper;
/**
* Checks if the given value is exactly or partially same as the base one.
*
* @author Mark Jebri <mark.github@yandex.ru>
* @since 2.0
*/
class SameAs extends Base
{
/**
* @var boolean if partial match should be used.
*/
public $partial = false;
/**
* @inheritdoc
*/
public function match($value)
{
if (!is_scalar($value)) {
$value = VarDumper::export($value);
}
if ($this->partial) {
return mb_stripos($value, $this->baseValue, 0, \Yii::$app->charset) !== false;
}
return strcmp(mb_strtoupper($this->baseValue, \Yii::$app->charset), mb_strtoupper($value, \Yii::$app->charset)) === 0;
}
}
{
"name": "yiisoft/yii2-debug",
"description": "The debugger extension for the Yii framework",
"keywords": ["yii2", "debug", "debugger"],
"type": "yii2-extension",
"license": "BSD-3-Clause",
"support": {
"issues": "https://github.com/yiisoft/yii2-debug/issues",
"forum": "http://www.yiiframework.com/forum/",
"wiki": "http://www.yiiframework.com/wiki/",
"irc": "irc://irc.freenode.net/yii",
"source": "https://github.com/yiisoft/yii2-debug"
},
"authors": [
{
"name": "Qiang Xue",
"email": "qiang.xue@gmail.com"
}
],
"minimum-stability": "dev",
"require": {
"yiisoft/yii2": "~2.0.13",
"yiisoft/yii2-bootstrap": "~2.0.0"
},
"autoload": {
"psr-4": {
"yii\\debug\\": ""
}
},
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"repositories": [
{
"type": "composer",
"url": "https://asset-packagist.org"
}
]
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\controllers;
use Yii;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\debug\models\search\Debug;
use yii\web\Response;
/**
* Debugger controller
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class DefaultController extends Controller
{
/**
* @inheritdoc
*/
public $layout = 'main';
/**
* @var \yii\debug\Module
*/
public $module;
/**
* @var array the summary data (e.g. URL, time)
*/
public $summary;
/**
* @inheritdoc
*/
public function actions()
{
$actions = [];
foreach ($this->module->panels as $panel) {
$actions = array_merge($actions, $panel->actions);
}
return $actions;
}
public function beforeAction($action)
{
Yii::$app->response->format = Response::FORMAT_HTML;
return parent::beforeAction($action);
}
public function actionIndex()
{
$searchModel = new Debug();
$dataProvider = $searchModel->search($_GET, $this->getManifest());
// load latest request
$tags = array_keys($this->getManifest());
$tag = reset($tags);
$this->loadData($tag);
return $this->render('index', [
'panels' => $this->module->panels,
'dataProvider' => $dataProvider,
'searchModel' => $searchModel,
'manifest' => $this->getManifest(),
]);
}
public function actionView($tag = null, $panel = null)
{
if ($tag === null) {
$tags = array_keys($this->getManifest());
$tag = reset($tags);
}
$this->loadData($tag);
if (isset($this->module->panels[$panel])) {
$activePanel = $this->module->panels[$panel];
} else {
$activePanel = $this->module->panels[$this->module->defaultPanel];
}
if ($activePanel->hasError()) {
\Yii::$app->errorHandler->handleException($activePanel->getError());
}
return $this->render('view', [
'tag' => $tag,
'summary' => $this->summary,
'manifest' => $this->getManifest(),
'panels' => $this->module->panels,
'activePanel' => $activePanel,
]);
}
public function actionToolbar($tag)
{
$this->loadData($tag, 5);
return $this->renderPartial('toolbar', [
'tag' => $tag,
'panels' => $this->module->panels,
'position' => 'bottom',
]);
}
public function actionDownloadMail($file)
{
$filePath = Yii::getAlias($this->module->panels['mail']->mailPath) . '/' . basename($file);
if ((mb_strpos($file, '\\') !== false || mb_strpos($file, '/') !== false) || !is_file($filePath)) {
throw new NotFoundHttpException('Mail file not found');
}
return Yii::$app->response->sendFile($filePath);
}
private $_manifest;
protected function getManifest($forceReload = false)
{
if ($this->_manifest === null || $forceReload) {
if ($forceReload) {
clearstatcache();
}
$indexFile = $this->module->dataPath . '/index.data';
$content = '';
$fp = @fopen($indexFile, 'r');
if ($fp !== false) {
@flock($fp, LOCK_SH);
$content = fread($fp, filesize($indexFile));
@flock($fp, LOCK_UN);
fclose($fp);
}
if ($content !== '') {
$this->_manifest = array_reverse(unserialize($content), true);
} else {
$this->_manifest = [];
}
}
return $this->_manifest;
}
public function loadData($tag, $maxRetry = 0)
{
// retry loading debug data because the debug data is logged in shutdown function
// which may be delayed in some environment if xdebug is enabled.
// See: https://github.com/yiisoft/yii2/issues/1504
for ($retry = 0; $retry <= $maxRetry; ++$retry) {
$manifest = $this->getManifest($retry > 0);
if (isset($manifest[$tag])) {
$dataFile = $this->module->dataPath . "/$tag.data";
$data = unserialize(file_get_contents($dataFile));
$exceptions = $data['exceptions'];
foreach ($this->module->panels as $id => $panel) {
if (isset($data[$id])) {
$panel->tag = $tag;
$panel->load(unserialize($data[$id]));
}
if (isset($exceptions[$id])) {
$panel->setError($exceptions[$id]);
}
}
$this->summary = $data['summary'];
return;
}
sleep(1);
}
throw new NotFoundHttpException("Unable to find debug data tagged with '$tag'.");
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\controllers;
use Yii;
use yii\debug\models\UserSwitch;
use yii\web\BadRequestHttpException;
use yii\web\Controller;
use yii\web\Response;
/**
* User controller
*
* @author Semen Dubina <yii2debug@sam002.net>
* @since 2.0.10
*/
class UserController extends Controller
{
/**
* @inheritdoc
*/
public function beforeAction($action)
{
Yii::$app->response->format = Response::FORMAT_JSON;
if (!Yii::$app->session->hasSessionId) {
throw new BadRequestHttpException('Need an active session');
}
return parent::beforeAction($action);
}
/**
* Set new identity, switch user
* @return \yii\web\User
*/
public function actionSetIdentity()
{
$user_id = Yii::$app->request->post('user_id');
$userSwitch = new UserSwitch();
$newIdentity = Yii::$app->user->identity->findIdentity($user_id);
$userSwitch->setUserByIdentity($newIdentity);
return Yii::$app->user;
}
/**
* Reset identity, switch to main user
* @return \yii\web\User
*/
public function actionResetIdentity()
{
$userSwitch = new UserSwitch();
$userSwitch->reset();
return Yii::$app->user;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\models;
use yii\base\Model;
use yii\log\Logger;
/**
* Router model
*
* @author Dmitriy Bashkarev <dmitriy@bashkarev.com>
* @since 2.0.8
*/
class Router extends Model
{
/**
* @var array logged messages.
*/
public $messages = [];
/**
* @var string|null info message.
*/
public $message;
/**
* @var array logged rules.
* ```php
* [
* [
* 'rule' => (string),
* 'match' => (bool),
* 'parent'=> parent class (string)
* ]
* ]
* ```
*/
public $logs = [];
/**
* @var int count, before match.
*/
public $count = 0;
/**
* @var bool
*/
public $hasMatch = false;
/**
* @inheritdoc
*/
public function init()
{
parent::init();
$last = null;
foreach ($this->messages as $message) {
if ($message[1] === Logger::LEVEL_TRACE && is_string($message[0])) {
$this->message = $message[0];
} elseif (isset($message[0]['rule'], $message[0]['match'])) {
if (!empty($last['parent']) && $last['parent'] === $message[0]['rule']) {
continue;
}
$this->logs[] = $message[0];
++$this->count;
if ($message[0]['match']) {
$this->hasMatch = true;
}
$last = $message[0];
}
}
}
}
\ No newline at end of file
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\models;
use Yii;
use yii\base\Model;
use yii\web\IdentityInterface;
use yii\web\User;
/**
* UserSwitch is a model used to temporary logging in another user
*
* @property User $mainUser This property is read-only.
* @property null|User $user This property is read-only.
*
* @author Semen Dubina <yii2debug@sam002.net>
* @since 2.0.10
*/
class UserSwitch extends Model
{
/**
* @var User user which we are currently switched to
*/
private $_user;
/**
* @var User the main user who was originally logged in before switching.
*/
private $_mainUser;
/**
* @var string|User ID of the user component or a user object
* @since 2.0.13
*/
public $userComponent = 'user';
/**
* @inheritdoc
*/
public function rules()
{
return [
[['user', 'mainUser'], 'safe']
];
}
/**
* @return array customized attribute labels
*/
public function attributeLabels()
{
return [
'user' => 'Current User',
'mainUser' => 'frontend', 'Main User',
];
}
/**
* Get current user
* @return null|User
*/
public function getUser()
{
if ($this->_user === null) {
/* @var $user User */
$this->_user = is_string($this->userComponent) ? Yii::$app->get($this->userComponent, false) : $this->userComponent;
}
return $this->_user;
}
/**
* Get main user
* @return User
*/
public function getMainUser()
{
$currentUser = $this->getUser();
if ($this->_mainUser === null && $currentUser->getIsGuest() === false) {
$session = Yii::$app->getSession();
if ($session->has('main_user')) {
$mainUserId = $session->get('main_user');
$mainIdentity = call_user_func([$currentUser->identityClass, 'findIdentity'], $mainUserId);
} else {
$mainIdentity = $currentUser->identity;
}
$mainUser = clone $currentUser;
$mainUser->setIdentity($mainIdentity);
$this->_mainUser = $mainUser;
}
return $this->_mainUser;
}
/**
* Switch user
* @param User $user
*/
public function setUser(User $user)
{
// Check if user is currently active one
$isCurrent = ($user->getId() === $this->getMainUser()->getId());
// Switch identity
$this->getUser()->switchIdentity($user->identity);
if (!$isCurrent) {
Yii::$app->getSession()->set('main_user', $this->getMainUser()->getId());
} else {
Yii::$app->getSession()->remove('main_user');
}
}
/**
* Switch to user by identity
* @param IdentityInterface $identity
*/
public function setUserByIdentity(IdentityInterface $identity)
{
$user = clone $this->getUser();
$user->setIdentity($identity);
$this->setUser($user);
}
/**
* Reset to main user
*/
public function reset()
{
$this->setUser($this->getMainUser());
}
/**
* Checks if current user is main or not.
* @return bool
*/
public function isMainUser()
{
$user = $this->getUser();
if ($user->getIsGuest()) {
return true;
}
return ($user->getId() === $this->getMainUser()->getId());
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\models\search;
use yii\base\Model;
use yii\debug\components\search\Filter;
use yii\debug\components\search\matchers;
/**
* Base search model
*
* @author Mark Jebri <mark.github@yandex.ru>
* @since 2.0
*/
class Base extends Model
{
/**
* Adds filtering condition for a given attribute
*
* @param Filter $filter filter instance
* @param string $attribute attribute to filter
* @param bool $partial if partial match should be used
*/
public function addCondition(Filter $filter, $attribute, $partial = false)
{
$value = $this->$attribute;
if (mb_strpos($value, '>') !== false) {
$value = (int) str_replace('>', '', $value);
$filter->addMatcher($attribute, new matchers\GreaterThan(['value' => $value]));
} elseif (mb_strpos($value, '<') !== false) {
$value = (int) str_replace('<', '', $value);
$filter->addMatcher($attribute, new matchers\LowerThan(['value' => $value]));
} else {
$filter->addMatcher($attribute, new matchers\SameAs(['value' => $value, 'partial' => $partial]));
}
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\models\search;
use yii\data\ArrayDataProvider;
use yii\debug\components\search\Filter;
/**
* Search model for current request database queries.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Mark Jebri <mark.github@yandex.ru>
* @since 2.0
*/
class Db extends Base
{
/**
* @var string type of the input search value
*/
public $type;
/**
* @var integer query attribute input search value
*/
public $query;
/**
* @inheritdoc
*/
public function rules()
{
return [
[['type', 'query'], 'safe'],
];
}
/**
* @inheritdoc
*/
public function attributeLabels()
{
return [
'type' => 'Type',
'query' => 'Query',
];
}
/**
* Returns data provider with filled models. Filter applied if needed.
*
* @param array $models data to return provider for
* @return \yii\data\ArrayDataProvider
*/
public function search($models)
{
$dataProvider = new ArrayDataProvider([
'allModels' => $models,
'pagination' => false,
'sort' => [
'attributes' => ['duration', 'seq', 'type', 'query', 'duplicate'],
],
]);
if (!$this->validate()) {
return $dataProvider;
}
$filter = new Filter();
$this->addCondition($filter, 'type', true);
$this->addCondition($filter, 'query', true);
$dataProvider->allModels = $filter->filter($models);
return $dataProvider;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\models\search;
use yii\data\ArrayDataProvider;
use yii\debug\components\search\Filter;
/**
* Search model for requests manifest data.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Mark Jebri <mark.github@yandex.ru>
* @since 2.0
*/
class Debug extends Base
{
/**
* @var string tag attribute input search value
*/
public $tag;
/**
* @var string ip attribute input search value
*/
public $ip;
/**
* @var string method attribute input search value
*/
public $method;
/**
* @var integer ajax attribute input search value
*/
public $ajax;
/**
* @var string url attribute input search value
*/
public $url;
/**
* @var string status code attribute input search value
*/
public $statusCode;
/**
* @var integer sql count attribute input search value
*/
public $sqlCount;
/**
* @var integer total mail count attribute input search value
*/
public $mailCount;
/**
* @var array critical codes, used to determine grid row options.
*/
public $criticalCodes = [400, 404, 500];
/**
* @inheritdoc
*/
public function rules()
{
return [
[['tag', 'ip', 'method', 'ajax', 'url', 'statusCode', 'sqlCount', 'mailCount'], 'safe'],
];
}
/**
* @inheritdoc
*/
public function attributeLabels()
{
return [
'tag' => 'Tag',
'ip' => 'Ip',
'method' => 'Method',
'ajax' => 'Ajax',
'url' => 'url',
'statusCode' => 'Status code',
'sqlCount' => 'Query Count',
'mailCount' => 'Mail Count',
];
}
/**
* Returns data provider with filled models. Filter applied if needed.
* @param array $params an array of parameter values indexed by parameter names
* @param array $models data to return provider for
* @return \yii\data\ArrayDataProvider
*/
public function search($params, $models)
{
$dataProvider = new ArrayDataProvider([
'allModels' => $models,
'sort' => [
'attributes' => ['method', 'ip', 'tag', 'time', 'statusCode', 'sqlCount', 'mailCount'],
],
'pagination' => [
'pageSize' => 50,
],
]);
if (!($this->load($params) && $this->validate())) {
return $dataProvider;
}
$filter = new Filter();
$this->addCondition($filter, 'tag', true);
$this->addCondition($filter, 'ip', true);
$this->addCondition($filter, 'method');
$this->addCondition($filter, 'ajax');
$this->addCondition($filter, 'url', true);
$this->addCondition($filter, 'statusCode');
$this->addCondition($filter, 'sqlCount');
$this->addCondition($filter, 'mailCount');
$dataProvider->allModels = $filter->filter($models);
return $dataProvider;
}
/**
* Checks if code is critical.
*
* @param int $code
* @return bool
*/
public function isCodeCritical($code)
{
return in_array($code, $this->criticalCodes, false);
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\models\search;
use yii\data\ArrayDataProvider;
use yii\debug\components\search\Filter;
/**
* Search model for current request log.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Mark Jebri <mark.github@yandex.ru>
* @since 2.0
*/
class Log extends Base
{
/**
* @var string ip attribute input search value
*/
public $level;
/**
* @var string method attribute input search value
*/
public $category;
/**
* @var integer message attribute input search value
*/
public $message;
/**
* @inheritdoc
*/
public function rules()
{
return [
[['level', 'message', 'category'], 'safe'],
];
}
/**
* @inheritdoc
*/
public function attributeLabels()
{
return [
'level' => 'Level',
'category' => 'Category',
'message' => 'Message',
];
}
/**
* Returns data provider with filled models. Filter applied if needed.
*
* @param array $params an array of parameter values indexed by parameter names
* @param array $models data to return provider for
* @return \yii\data\ArrayDataProvider
*/
public function search($params, $models)
{
$dataProvider = new ArrayDataProvider([
'allModels' => $models,
'pagination' => false,
'sort' => [
'attributes' => ['time', 'level', 'category', 'message'],
'defaultOrder' => [
'time' => SORT_ASC,
],
],
]);
if (!($this->load($params) && $this->validate())) {
return $dataProvider;
}
$filter = new Filter();
$this->addCondition($filter, 'level');
$this->addCondition($filter, 'category', true);
$this->addCondition($filter, 'message', true);
$dataProvider->allModels = $filter->filter($models);
return $dataProvider;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\models\search;
use yii\data\ArrayDataProvider;
use yii\debug\components\search\Filter;
/**
* Mail represents the model behind the search form about current send emails.
*
* @author Mark Jebri <mark.github@yandex.ru>
* @since 2.0
*/
class Mail extends Base
{
/**
* @var string from attribute input search value
*/
public $from;
/**
* @var string to attribute input search value
*/
public $to;
/**
* @var string reply attribute input search value
*/
public $reply;
/**
* @var string cc attribute input search value
*/
public $cc;
/**
* @var string bcc attribute input search value
*/
public $bcc;
/**
* @var string subject attribute input search value
*/
public $subject;
/**
* @var string body attribute input search value
*/
public $body;
/**
* @var string charset attribute input search value
*/
public $charset;
/**
* @var string headers attribute input search value
*/
public $headers;
/**
* @var string file attribute input search value
*/
public $file;
/**
* @inheritdoc
*/
public function rules()
{
return [
[['from', 'to', 'reply', 'cc', 'bcc', 'subject', 'body', 'charset'], 'safe'],
];
}
/**
* @inheritdoc
*/
public function attributeLabels()
{
return [
'from' => 'From',
'to' => 'To',
'reply' => 'Reply',
'cc' => 'Copy receiver',
'bcc' => 'Hidden copy receiver',
'subject' => 'Subject',
'charset' => 'Charset'
];
}
/**
* Returns data provider with filled models. Filter applied if needed.
* @param array $params
* @param array $models
* @return \yii\data\ArrayDataProvider
*/
public function search($params, $models)
{
$dataProvider = new ArrayDataProvider([
'allModels' => $models,
'pagination' => [
'pageSize' => 20,
],
'sort' => [
'attributes' => ['from', 'to', 'reply', 'cc', 'bcc', 'subject', 'body', 'charset'],
],
]);
if (!($this->load($params) && $this->validate())) {
return $dataProvider;
}
$filter = new Filter();
$this->addCondition($filter, 'from', true);
$this->addCondition($filter, 'to', true);
$this->addCondition($filter, 'reply', true);
$this->addCondition($filter, 'cc', true);
$this->addCondition($filter, 'bcc', true);
$this->addCondition($filter, 'subject', true);
$this->addCondition($filter, 'body', true);
$this->addCondition($filter, 'charset', true);
$dataProvider->allModels = $filter->filter($models);
return $dataProvider;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\models\search;
use yii\data\ArrayDataProvider;
use yii\debug\components\search\Filter;
/**
* Search model for current request profiling log.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Mark Jebri <mark.github@yandex.ru>
* @since 2.0
*/
class Profile extends Base
{
/**
* @var string method attribute input search value
*/
public $category;
/**
* @var integer info attribute input search value
*/
public $info;
/**
* @inheritdoc
*/
public function rules()
{
return [
[['category', 'info'], 'safe'],
];
}
/**
* @inheritdoc
*/
public function attributeLabels()
{
return [
'category' => 'Category',
'info' => 'Info',
];
}
/**
* Returns data provider with filled models. Filter applied if needed.
*
* @param array $params an array of parameter values indexed by parameter names
* @param array $models data to return provider for
* @return \yii\data\ArrayDataProvider
*/
public function search($params, $models)
{
$dataProvider = new ArrayDataProvider([
'allModels' => $models,
'pagination' => false,
'sort' => [
'attributes' => ['category', 'seq', 'duration', 'info'],
'defaultOrder' => [
'duration' => SORT_DESC,
],
],
]);
if (!($this->load($params) && $this->validate())) {
return $dataProvider;
}
$filter = new Filter();
$this->addCondition($filter, 'category', true);
$this->addCondition($filter, 'info', true);
$dataProvider->allModels = $filter->filter($models);
return $dataProvider;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\models\search;
use yii\base\Model;
use yii\data\ActiveDataProvider;
use yii\db\ActiveRecord;
/**
* Search model for implementation of IdentityInterface
*
* @author Semen Dubina <yii2debug@sam002.net>
* @since 2.0.10
*/
class User extends Model
{
/**
* @var Model implementation of IdentityInterface
*/
public $identityImplement = null;
/**
* @inheritdoc
*/
public function init()
{
if (\Yii::$app->user && \Yii::$app->user->identityClass) {
$identityImplementation = new \Yii::$app->user->identityClass();
if ($identityImplementation instanceof Model) {
$this->identityImplement = $identityImplementation;
}
}
parent::init();
}
/**
* @inheritdoc
*/
public function __get($name)
{
return $this->identityImplement->__get($name);
}
/**
* @inheritdoc
*/
public function __set($name, $value)
{
return $this->identityImplement->__set($name, $value);
}
/**
* @inheritdoc
*/
public function rules()
{
return [[array_keys($this->identityImplement->getAttributes()), 'safe']];
}
/**
* @inheritdoc
*/
public function attributes()
{
return $this->identityImplement->attributes();
}
/**
* @inheritdoc
*/
public function search($params)
{
if ($this->identityImplement instanceof ActiveRecord) {
return $this->serachActiveDataProvider($params);
}
return null;
}
/**
* Search method for ActiveRecord
* @param array $params the data array to load model.
* @return ActiveDataProvider
*/
private function serachActiveDataProvider($params)
{
/** @var ActiveRecord $model */
$model = $this->identityImplement;
$query = $model::find();
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
if (!($this->load($params) && $this->validate())) {
return $dataProvider;
}
foreach ($model::getTableSchema()->columns as $attribute => $column) {
if ($column->phpType === 'string') {
$query->andFilterWhere(['like', $attribute, $model->getAttribute($attribute)]);
} else {
$query->andFilterWhere([$attribute => $model->getAttribute($attribute)]);
}
}
return $dataProvider;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\models\search;
use yii\data\DataProviderInterface;
use yii\web\IdentityInterface;
/**
* UserSearchInterface is the interface that should be implemented by a class
* providing identity information and search method.
*
* @author Semen Dubina <yii2debug@sam002.net>
* @since 2.0.10
*/
interface UserSearchInterface extends IdentityInterface
{
/**
* Creates data provider instance with search query applied.
* @param array $params the data array to load model.
* @return DataProviderInterface
*/
public function search($params);
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\models\timeline;
use yii\data\ArrayDataProvider;
use yii\debug\panels\TimelinePanel;
/**
* DataProvider implements a data provider based on a data array.
*
* @property array $rulers This property is read-only.
*
* @author Dmitriy Bashkarev <dmitriy@bashkarev.com>
* @since 2.0.8
*/
class DataProvider extends ArrayDataProvider
{
/**
* @var TimelinePanel
*/
protected $panel;
/**
* DataProvider constructor.
* @param TimelinePanel $panel
* @param array $config
*/
public function __construct(TimelinePanel $panel, $config = [])
{
$this->panel = $panel;
parent::__construct($config);
}
/**
* @inheritdoc
*/
protected function prepareModels()
{
if (($models = $this->allModels) === null) {
return [];
}
$child = [];
foreach ($models as $key => &$model) {
$model['timestamp'] *= 1000;
$model['duration'] *= 1000;
$model['child'] = 0;
$model['css']['width'] = $this->getWidth($model);
$model['css']['left'] = $this->getLeft($model);
$model['css']['color'] = $this->getColor($model);
foreach ($child as $id => $timestamp) {
if ($timestamp > $model['timestamp']) {
++$models[$id]['child'];
} else {
unset($child[$id]);
}
}
$child[$key] = $model['timestamp'] + $model['duration'];
}
return $models;
}
/**
* Getting HEX color based on model duration
* @param array $model
* @return string
*/
public function getColor($model)
{
$width = isset($model['css']['width']) ? $model['css']['width'] : $this->getWidth($model);
foreach ($this->panel->colors as $percent => $color) {
if ($width >= $percent) {
return $color;
}
}
return '#d6e685';
}
/**
* Returns the offset left item, percentage of the total width
* @param array $model
* @return float
*/
public function getLeft($model)
{
return $this->getTime($model) / ($this->panel->duration / 100);
}
/**
* Returns item duration, milliseconds
* @param array $model
* @return float
*/
public function getTime($model)
{
return $model['timestamp'] - $this->panel->start;
}
/**
* Returns item width percent of the total width
* @param array $model
* @return float
*/
public function getWidth($model)
{
return $model['duration'] / ($this->panel->duration / 100);
}
/**
* Returns item, css class
* @param array $model
* @return string
*/
public function getCssClass($model)
{
$class = 'time';
$class .= (($model['css']['left'] > 15) && ($model['css']['left'] + $model['css']['width'] > 50)) ? ' right' : ' left';
return $class;
}
/**
* ruler items, key milliseconds, value offset left
* @param int $line number of columns
* @return array
*/
public function getRulers($line = 10)
{
if ($line == 0) {
return [];
}
$data = [0];
$percent = ($this->panel->duration / 100);
$row = $this->panel->duration / $line;
$precision = $row > 100 ? -2 : -1;
for ($i = 1; $i < $line; $i++) {
$ms = round($i * $row, $precision);
$data[$ms] = $ms / $percent;
}
return $data;
}
/**
* ```php
* [
* 0 => string, memory usage (MB)
* 1 => float, Y position (percent)
* ]
* @param array $model
* @return array|null
*/
public function getMemory($model)
{
if (empty($model['memory'])) {
return null;
}
return [
sprintf('%.2f MB', $model['memory'] / 1048576),
$model['memory'] / ($this->panel->memory / 100)
];
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\models\timeline;
use yii\debug\components\search\Filter;
use yii\debug\components\search\matchers\GreaterThanOrEqual;
use yii\debug\models\search\Base;
use yii\debug\panels\TimelinePanel;
/**
* Search model for timeline data.
*
* @author Dmitriy Bashkarev <dmitriy@bashkarev.com>
* @since 2.0.8
*/
class Search extends Base
{
/**
* @var string attribute search
*/
public $category;
/**
* @var integer attribute search
*/
public $duration = 0;
/**
* @inheritdoc
*/
public function rules()
{
return [
[['category', 'duration'], 'safe'],
];
}
/**
* @inheritdoc
*/
public function attributeLabels()
{
return [
'duration' => 'Duration ≥'
];
}
/**
* Returns data provider with filled models. Filter applied if needed.
*
* @param array $params $params an array of parameter values indexed by parameter names
* @param TimeLinePanel $panel
* @return DataProvider
*/
public function search($params, $panel)
{
$models = $panel->models;
$dataProvider = new DataProvider($panel, [
'allModels' => $models,
'sort' => [
'attributes' => ['category', 'timestamp']
],
]);
if (!($this->load($params) && $this->validate())) {
return $dataProvider;
}
$filter = new Filter();
$this->addCondition($filter, 'category', true);
if ($this->duration > 0) {
$filter->addMatcher('duration', new GreaterThanOrEqual(['value' => $this->duration / 1000]));
}
$dataProvider->allModels = $filter->filter($models);
return $dataProvider;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\models\timeline;
use yii\base\BaseObject;
use yii\debug\panels\TimelinePanel;
use yii\helpers\StringHelper;
/**
* Svg is used to draw a graph using SVG
*
* @author Dmitriy Bashkarev <dmitriy@bashkarev.com>
* @since 2.0.8
*/
class Svg extends BaseObject
{
/**
* @var int Max X coordinate
*/
public $x = 1920;
/**
* @var int Max Y coordinate
*/
public $y = 40;
/**
* @var string Stroke color
*/
public $stroke = '#1e6823';
/**
* @var array Listen messages panels
*/
public $listenMessages = ['log', 'profiling'];
/**
* @var array Color indicators svg graph.
*/
public $gradient = [
10 => '#d6e685',
60 => '#8cc665',
90 => '#44a340',
100 => '#1e6823'
];
/**
* @var string Svg template
*/
public $template = '<svg width="{x}" height="{y}" viewBox="0 0 {x} {y}" preserveAspectRatio="none"><defs>{linearGradient}</defs><g><polygon points="{polygon}" fill="url(#gradient)"/><polyline points="{polyline}" fill="none" stroke="{stroke}" stroke-width="1"/></g></svg>';
/**
* ```php
* [
* [x, y]
* ]
* ```
* @var array Each point is define by a X and a Y coordinate.
*/
protected $points = [];
/**
* @var TimelinePanel
*/
protected $panel;
/**
* @inheritdoc
*/
public function __construct(TimelinePanel $panel, $config = [])
{
parent::__construct($config);
$this->panel = $panel;
foreach ($this->listenMessages as $panel) {
if (isset($this->panel->module->panels[$panel]->data['messages'])) {
$this->addPoints($this->panel->module->panels[$panel]->data['messages']);
}
}
}
/**
* @return string
*/
public function __toString()
{
if ($this->points === []) {
return '';
}
return strtr($this->template, [
'{x}' => StringHelper::normalizeNumber($this->x),
'{y}' => StringHelper::normalizeNumber($this->y),
'{stroke}' => $this->stroke,
'{polygon}' => $this->polygon(),
'{polyline}' => $this->polyline(),
'{linearGradient}' => $this->linearGradient()
]);
}
/**
* @return bool Has points
*/
public function hasPoints()
{
return ($this->points !== []);
}
/**
* @param array $messages log messages. See [[Logger::messages]] for the structure
* @return int added points
*/
protected function addPoints($messages)
{
$hasPoints = $this->hasPoints();
$memory = $this->panel->memory / 100; // 1 percent memory
$yOne = $this->y / 100; // 1 percent Y coordinate
$xOne = $this->panel->duration / $this->x; // 1 percent X coordinate
$i = 0;
foreach ($messages as $message) {
if (empty($message[5])) {
break;
}
++$i;
$this->points[] = [
($message[3] * 1000 - $this->panel->start) / $xOne,
$this->y - ($message[5] / $memory * $yOne),
];
}
if ($hasPoints && $i) {
usort($this->points, function ($a, $b) {
return ($a[0] < $b[0]) ? -1 : 1;
});
}
return $i;
}
/**
* @return string Points attribute for polygon path
*/
protected function polygon()
{
$str = "0 $this->y ";
foreach ($this->points as $point) {
list($x, $y) = $point;
$str .= "{$x} {$y} ";
}
$str .= $this->x - 0.001 . " {$y} {$this->x} {$this->y}";
return StringHelper::normalizeNumber($str);
}
/**
* @return string Points attribute for polyline path
*/
protected function polyline()
{
$str = "0 $this->y ";
foreach ($this->points as $point) {
list($x, $y) = $point;
$str .= "{$x} {$y} ";
}
$str .= "$this->x {$y}";
return StringHelper::normalizeNumber($str);
}
/**
* @return string
*/
protected function linearGradient()
{
$gradient = '<linearGradient id="gradient" x1="0" x2="0" y1="1" y2="0">';
foreach ($this->gradient as $percent => $color) {
$gradient .= '<stop offset="' . StringHelper::normalizeNumber($percent) . '%" stop-color="' . $color . '"></stop>';
}
return $gradient . '</linearGradient>';
}
}
\ No newline at end of file
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\panels;
use Yii;
use yii\base\InvalidConfigException;
use yii\helpers\Html;
use yii\debug\Panel;
use yii\web\AssetBundle;
use yii\web\AssetManager;
/**
* Debugger panel that collects and displays asset bundles data.
*
* @author Artur Fursa <arturfursa@gmail.com>
* @since 2.0
*/
class AssetPanel extends Panel
{
/**
* @inheritdoc
*/
public function getName()
{
return 'Asset Bundles';
}
/**
* @inheritdoc
*/
public function getSummary()
{
return Yii::$app->view->render('panels/assets/summary', ['panel' => $this]);
}
/**
* @inheritdoc
*/
public function getDetail()
{
return Yii::$app->view->render('panels/assets/detail', ['panel' => $this]);
}
/**
* @inheritdoc
*/
public function save()
{
$bundles = Yii::$app->view->assetManager->bundles;
if (empty($bundles)) { // bundles can be false
return [];
}
$data = [];
foreach ($bundles as $name => $bundle) {
if ($bundle instanceof AssetBundle) {
$bundleData = (array) $bundle;
if (isset($bundleData['publishOptions']['beforeCopy']) && $bundleData['publishOptions']['beforeCopy'] instanceof \Closure) {
$bundleData['publishOptions']['beforeCopy'] = '\Closure';
}
if (isset($bundleData['publishOptions']['afterCopy']) && $bundleData['publishOptions']['afterCopy'] instanceof \Closure) {
$bundleData['publishOptions']['afterCopy'] = '\Closure';
}
$data[$name] = $bundleData;
}
}
return $data;
}
/**
* @inheritdoc
*/
public function isEnabled()
{
try {
Yii::$app->view->assetManager;
} catch (InvalidConfigException $exception) {
return false;
}
return true;
}
/**
* Additional formatting for view.
*
* @param AssetBundle[] $bundles Array of bundles to formatting.
*
* @return AssetBundle[]
*/
protected function format(array $bundles)
{
foreach ($bundles as $bundle) {
$this->cssCount += count($bundle->css);
$this->jsCount += count($bundle->js);
array_walk($bundle->css, function(&$file, $key, $userdata) {
$file = Html::a($file, $userdata->baseUrl . '/' . $file, ['target' => '_blank']);
}, $bundle);
array_walk($bundle->js, function(&$file, $key, $userdata) {
$file = Html::a($file, $userdata->baseUrl . '/' . $file, ['target' => '_blank']);
}, $bundle);
array_walk($bundle->depends, function(&$depend) {
$depend = Html::a($depend, '#' . $depend);
});
$this->formatOptions($bundle->publishOptions);
$this->formatOptions($bundle->jsOptions);
$this->formatOptions($bundle->cssOptions);
}
return $bundles;
}
/**
* Format associative array of params to simple value.
*
* @param array $params
*
* @return array
*/
protected function formatOptions(array &$params)
{
if (!is_array($params)) {
return $params;
}
foreach ($params as $param => $value) {
$params[$param] = Html::tag('strong', '\'' . $param . '\' => ') . (string) $value;
}
return $params;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\panels;
use Yii;
use yii\debug\Panel;
/**
* Debugger panel that collects and displays application configuration and environment.
*
* @property array $extensions This property is read-only.
* @property array $phpInfo This property is read-only.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class ConfigPanel extends Panel
{
/**
* @inheritdoc
*/
public function getName()
{
return 'Configuration';
}
/**
* @inheritdoc
*/
public function getSummary()
{
return Yii::$app->view->render('panels/config/summary', ['panel' => $this]);
}
/**
* @inheritdoc
*/
public function getDetail()
{
return Yii::$app->view->render('panels/config/detail', ['panel' => $this]);
}
/**
* Returns data about extensions
*
* @return array
*/
public function getExtensions()
{
$data = [];
foreach ($this->data['extensions'] as $extension) {
$data[$extension['name']] = $extension['version'];
}
ksort($data);
return $data;
}
/**
* Returns the BODY contents of the phpinfo() output
*
* @return array
*/
public function getPhpInfo()
{
ob_start();
phpinfo();
$pinfo = ob_get_contents();
ob_end_clean();
$phpinfo = preg_replace('%^.*<body>(.*)</body>.*$%ms', '$1', $pinfo);
$phpinfo = str_replace('<table', '<div class="table-responsive"><table class="table table-condensed table-bordered table-striped table-hover config-php-info-table" ', $phpinfo);
$phpinfo = str_replace('</table>', '</table></div>', $phpinfo);
return $phpinfo;
}
/**
* @inheritdoc
*/
public function save()
{
return [
'phpVersion' => PHP_VERSION,
'yiiVersion' => Yii::getVersion(),
'application' => [
'yii' => Yii::getVersion(),
'name' => Yii::$app->name,
'version' => Yii::$app->version,
'language' => Yii::$app->language,
'sourceLanguage' => Yii::$app->sourceLanguage,
'charset' => Yii::$app->charset,
'env' => YII_ENV,
'debug' => YII_DEBUG,
],
'php' => [
'version' => PHP_VERSION,
'xdebug' => extension_loaded('xdebug'),
'apc' => extension_loaded('apc'),
'memcache' => extension_loaded('memcache'),
'memcached' => extension_loaded('memcached'),
],
'extensions' => Yii::$app->extensions,
];
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\panels;
use Yii;
use yii\base\InvalidConfigException;
use yii\debug\Panel;
use yii\helpers\ArrayHelper;
use yii\log\Logger;
use yii\debug\models\search\Db;
/**
* Debugger panel that collects and displays database queries performed.
*
* @property array $profileLogs This property is read-only.
* @property string $summaryName Short name of the panel, which will be use in summary. This property is
* read-only.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class DbPanel extends Panel
{
/**
* @var integer the threshold for determining whether the request has involved
* critical number of DB queries. If the number of queries exceeds this number,
* the execution is considered taking critical number of DB queries.
*/
public $criticalQueryThreshold;
/**
* @var string the name of the database component to use for executing (explain) queries
*/
public $db = 'db';
/**
* @var array the default ordering of the database queries. In the format of
* [ property => sort direction ], for example: [ 'duration' => SORT_DESC ]
* @since 2.0.7
*/
public $defaultOrder = [
'seq' => SORT_ASC
];
/**
* @var array the default filter to apply to the database queries. In the format
* of [ property => value ], for example: [ 'type' => 'SELECT' ]
* @since 2.0.7
*/
public $defaultFilter = [];
/**
* @var array db queries info extracted to array as models, to use with data provider.
*/
private $_models;
/**
* @var array current database request timings
*/
private $_timings;
/**
* @inheritdoc
*/
public function init()
{
$this->actions['db-explain'] = [
'class' => 'yii\\debug\\actions\\db\\ExplainAction',
'panel' => $this,
];
}
/**
* @inheritdoc
*/
public function getName()
{
return 'Database';
}
/**
* @return string short name of the panel, which will be use in summary.
*/
public function getSummaryName()
{
return 'DB';
}
/**
* @inheritdoc
*/
public function getSummary()
{
$timings = $this->calculateTimings();
$queryCount = count($timings);
$queryTime = number_format($this->getTotalQueryTime($timings) * 1000) . ' ms';
return Yii::$app->view->render('panels/db/summary', [
'timings' => $this->calculateTimings(),
'panel' => $this,
'queryCount' => $queryCount,
'queryTime' => $queryTime,
]);
}
/**
* @inheritdoc
*/
public function getDetail()
{
$searchModel = new Db();
if (!$searchModel->load(Yii::$app->request->getQueryParams())) {
$searchModel->load($this->defaultFilter, '');
}
$models = $this->getModels();
$dataProvider = $searchModel->search($models);
$dataProvider->getSort()->defaultOrder = $this->defaultOrder;
$sumDuplicates = $this->sumDuplicateQueries($models);
return Yii::$app->view->render('panels/db/detail', [
'panel' => $this,
'dataProvider' => $dataProvider,
'searchModel' => $searchModel,
'hasExplain' => $this->hasExplain(),
'sumDuplicates' => $sumDuplicates,
]);
}
/**
* Calculates given request profile timings.
*
* @return array timings [token, category, timestamp, traces, nesting level, elapsed time]
*/
public function calculateTimings()
{
if ($this->_timings === null) {
$this->_timings = Yii::getLogger()->calculateTimings(isset($this->data['messages']) ? $this->data['messages'] : []);
}
return $this->_timings;
}
/**
* @inheritdoc
*/
public function save()
{
return ['messages' => $this->getProfileLogs()];
}
/**
* Returns all profile logs of the current request for this panel. It includes categories such as:
* 'yii\db\Command::query', 'yii\db\Command::execute'.
* @return array
*/
public function getProfileLogs()
{
$target = $this->module->logTarget;
return $target->filterMessages($target->messages, Logger::LEVEL_PROFILE, ['yii\db\Command::query', 'yii\db\Command::execute']);
}
/**
* Returns total query time.
*
* @param array $timings
* @return int total time
*/
protected function getTotalQueryTime($timings)
{
$queryTime = 0;
foreach ($timings as $timing) {
$queryTime += $timing['duration'];
}
return $queryTime;
}
/**
* Returns an array of models that represents logs of the current request.
* Can be used with data providers such as \yii\data\ArrayDataProvider.
* @return array models
*/
protected function getModels()
{
if ($this->_models === null) {
$this->_models = [];
$timings = $this->calculateTimings();
$duplicates = $this->countDuplicateQuery($timings);
foreach ($timings as $seq => $dbTiming) {
$this->_models[] = [
'type' => $this->getQueryType($dbTiming['info']),
'query' => $dbTiming['info'],
'duration' => ($dbTiming['duration'] * 1000), // in milliseconds
'trace' => $dbTiming['trace'],
'timestamp' => ($dbTiming['timestamp'] * 1000), // in milliseconds
'seq' => $seq,
'duplicate' => $duplicates[$dbTiming['info']],
];
}
}
return $this->_models;
}
/**
* Return associative array, where key is query string
* and value is number of occurrences the same query in array.
*
* @param $timings
* @return array
* @since 2.0.13
*/
public function countDuplicateQuery($timings)
{
$query = ArrayHelper::getColumn($timings, 'info');
return array_count_values($query);
}
/**
* Returns sum of all duplicated queries
*
* @param $modelData
* @return int
* @since 2.0.13
*/
public function sumDuplicateQueries($modelData)
{
$numDuplicates = 0;
$duplicates = ArrayHelper::getColumn($modelData, 'duplicate');
foreach ($duplicates as $duplicate) {
if ($duplicate > 1) {
$numDuplicates++;
}
}
return $numDuplicates;
}
/**
* Returns database query type.
*
* @param string $timing timing procedure string
* @return string query type such as select, insert, delete, etc.
*/
protected function getQueryType($timing)
{
$timing = ltrim($timing);
preg_match('/^([a-zA-z]*)/', $timing, $matches);
return count($matches) ? mb_strtoupper($matches[0], 'utf8') : '';
}
/**
* Check if given queries count is critical according settings.
*
* @param int $count queries count
* @return bool
*/
public function isQueryCountCritical($count)
{
return (($this->criticalQueryThreshold !== null) && ($count > $this->criticalQueryThreshold));
}
/**
* Returns array query types
*
* @return array
* @since 2.0.3
*/
public function getTypes()
{
return array_reduce(
$this->_models,
function ($result, $item) {
$result[$item['type']] = $item['type'];
return $result;
},
[]
);
}
/**
* @inheritdoc
*/
public function isEnabled()
{
try {
$this->getDb();
} catch (InvalidConfigException $exception) {
return false;
}
return true;
}
/**
* @return bool Whether the DB component has support for EXPLAIN queries
* @since 2.0.5
*/
protected function hasExplain()
{
$db = $this->getDb();
if (!($db instanceof \yii\db\Connection)) {
return false;
}
switch ($db->getDriverName()) {
case 'mysql':
case 'sqlite':
case 'pgsql':
case 'cubrid':
return true;
default:
return false;
}
}
/**
* Check if given query type can be explained.
*
* @param string $type query type
* @return bool
*
* @since 2.0.5
*/
public static function canBeExplained($type)
{
return $type !== 'SHOW';
}
/**
* Returns a reference to the DB component associated with the panel
*
* @return \yii\db\Connection
* @since 2.0.5
*/
public function getDb()
{
return Yii::$app->get($this->db);
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\panels;
use Yii;
use yii\debug\Panel;
use yii\helpers\VarDumper;
use yii\log\Logger;
use yii\debug\models\search\Log;
/**
* Debugger panel that collects and displays logs.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class LogPanel extends Panel
{
/**
* @var array log messages extracted to array as models, to use with data provider.
*/
private $_models;
/**
* @inheritdoc
*/
public function getName()
{
return 'Logs';
}
/**
* @inheritdoc
*/
public function getSummary()
{
return Yii::$app->view->render('panels/log/summary', ['data' => $this->data, 'panel' => $this]);
}
/**
* @inheritdoc
*/
public function getDetail()
{
$searchModel = new Log();
$dataProvider = $searchModel->search(Yii::$app->request->getQueryParams(), $this->getModels());
return Yii::$app->view->render('panels/log/detail', [
'dataProvider' => $dataProvider,
'panel' => $this,
'searchModel' => $searchModel,
]);
}
/**
* @inheritdoc
*/
public function save()
{
$target = $this->module->logTarget;
$except = [];
if (isset($this->module->panels['router'])) {
$except = $this->module->panels['router']->getCategories();
}
$messages = $target->filterMessages($target->messages, Logger::LEVEL_ERROR | Logger::LEVEL_INFO | Logger::LEVEL_WARNING | Logger::LEVEL_TRACE, [], $except);
foreach ($messages as &$message) {
if (!is_string($message[0])) {
// exceptions may not be serializable if in the call stack somewhere is a Closure
if ($message[0] instanceof \Throwable || $message[0] instanceof \Exception) {
$message[0] = (string) $message[0];
} else {
$message[0] = VarDumper::export($message[0]);
}
}
}
return ['messages' => $messages];
}
/**
* Returns an array of models that represents logs of the current request.
* Can be used with data providers, such as \yii\data\ArrayDataProvider.
*
* @param bool $refresh if need to build models from log messages and refresh them.
* @return array models
*/
protected function getModels($refresh = false)
{
if ($this->_models === null || $refresh) {
$this->_models = [];
foreach ($this->data['messages'] as $message) {
$this->_models[] = [
'message' => $message[0],
'level' => $message[1],
'category' => $message[2],
'time' => $message[3] * 1000, // time in milliseconds
'trace' => $message[4]
];
}
}
return $this->_models;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\panels;
use Yii;
use yii\base\Event;
use yii\debug\models\search\Mail;
use yii\debug\Panel;
use yii\mail\BaseMailer;
use yii\helpers\FileHelper;
use yii\mail\MessageInterface;
/**
* Debugger panel that collects and displays the generated emails.
*
* @property array $messages Messages. This property is read-only.
*
* @author Mark Jebri <mark.github@yandex.ru>
* @since 2.0
*/
class MailPanel extends Panel
{
/**
* @var string path where all emails will be saved. should be an alias.
*/
public $mailPath = '@runtime/debug/mail';
/**
* @var array current request sent messages
*/
private $_messages = [];
/**
* @inheritdoc
*/
public function init()
{
parent::init();
Event::on(BaseMailer::className(), BaseMailer::EVENT_AFTER_SEND, function ($event) {
/* @var $message MessageInterface */
$message = $event->message;
$messageData = [
'isSuccessful' => $event->isSuccessful,
'from' => $this->convertParams($message->getFrom()),
'to' => $this->convertParams($message->getTo()),
'reply' => $this->convertParams($message->getReplyTo()),
'cc' => $this->convertParams($message->getCc()),
'bcc' => $this->convertParams($message->getBcc()),
'subject' => $message->getSubject(),
'charset' => $message->getCharset(),
];
// add more information when message is a SwiftMailer message
if ($message instanceof \yii\swiftmailer\Message) {
/* @var $swiftMessage \Swift_Message */
$swiftMessage = $message->getSwiftMessage();
$body = $swiftMessage->getBody();
if (empty($body)) {
$parts = $swiftMessage->getChildren();
foreach ($parts as $part) {
if (!($part instanceof \Swift_Mime_Attachment)) {
/* @var $part \Swift_Mime_MimePart */
if ($part->getContentType() === 'text/plain') {
$messageData['charset'] = $part->getCharset();
$body = $part->getBody();
break;
}
}
}
}
$messageData['body'] = $body;
$messageData['time'] = $swiftMessage->getDate();
$messageData['headers'] = $swiftMessage->getHeaders();
}
// store message as file
$fileName = $event->sender->generateMessageFileName();
FileHelper::createDirectory(Yii::getAlias($this->mailPath));
file_put_contents(Yii::getAlias($this->mailPath) . '/' . $fileName, $message->toString());
$messageData['file'] = $fileName;
$this->_messages[] = $messageData;
});
}
/**
* @inheritdoc
*/
public function getName()
{
return 'Mail';
}
/**
* @inheritdoc
*/
public function getSummary()
{
return Yii::$app->view->render('panels/mail/summary', ['panel' => $this, 'mailCount' => count($this->data)]);
}
/**
* @inheritdoc
*/
public function getDetail()
{
$searchModel = new Mail();
$dataProvider = $searchModel->search(Yii::$app->request->get(), $this->data);
return Yii::$app->view->render('panels/mail/detail', [
'panel' => $this,
'dataProvider' => $dataProvider,
'searchModel' => $searchModel
]);
}
/**
* @inheritdoc
*/
public function save()
{
return $this->getMessages();
}
/**
* Returns info about messages of current request. Each element is array holding
* message info, such as: time, reply, bc, cc, from, to and other.
* @return array messages
*/
public function getMessages()
{
return $this->_messages;
}
/**
* @param mixed $attr
* @return string
*/
private function convertParams($attr)
{
if (is_array($attr)) {
$attr = implode(', ', array_keys($attr));
}
return $attr;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\panels;
use Yii;
use yii\debug\Panel;
use yii\log\Logger;
use yii\debug\models\search\Profile;
/**
* Debugger panel that collects and displays performance profiling info.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class ProfilingPanel extends Panel
{
/**
* @var array current request profile timings
*/
private $_models;
/**
* @inheritdoc
*/
public function getName()
{
return 'Profiling';
}
/**
* @inheritdoc
*/
public function getSummary()
{
return Yii::$app->view->render('panels/profile/summary', [
'memory' => sprintf('%.3f MB', $this->data['memory'] / 1048576),
'time' => number_format($this->data['time'] * 1000) . ' ms',
'panel' => $this
]);
}
/**
* @inheritdoc
*/
public function getDetail()
{
$searchModel = new Profile();
$dataProvider = $searchModel->search(Yii::$app->request->getQueryParams(), $this->getModels());
return Yii::$app->view->render('panels/profile/detail', [
'panel' => $this,
'dataProvider' => $dataProvider,
'searchModel' => $searchModel,
'memory' => sprintf('%.3f MB', $this->data['memory'] / 1048576),
'time' => number_format($this->data['time'] * 1000) . ' ms',
]);
}
/**
* @inheritdoc
*/
public function save()
{
$target = $this->module->logTarget;
$messages = $target->filterMessages($target->messages, Logger::LEVEL_PROFILE);
return [
'memory' => memory_get_peak_usage(),
'time' => microtime(true) - YII_BEGIN_TIME,
'messages' => $messages,
];
}
/**
* Returns array of profiling models that can be used in a data provider.
* @return array models
*/
protected function getModels()
{
if ($this->_models === null) {
$this->_models = [];
$timings = Yii::getLogger()->calculateTimings(isset($this->data['messages']) ? $this->data['messages'] : []);
foreach ($timings as $seq => $profileTiming) {
$this->_models[] = [
'duration' => $profileTiming['duration'] * 1000, // in milliseconds
'category' => $profileTiming['category'],
'info' => $profileTiming['info'],
'level' => $profileTiming['level'],
'timestamp' => $profileTiming['timestamp'] * 1000, //in milliseconds
'seq' => $seq,
];
}
}
return $this->_models;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\panels;
use Yii;
use yii\base\InlineAction;
use yii\debug\Panel;
/**
* Debugger panel that collects and displays request data.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class RequestPanel extends Panel
{
/**
* @var array list of the PHP predefined variables that are allowed to be displayed in the request panel.
* Note that a variable must be accessible via `$GLOBALS`. Otherwise it won't be displayed.
* @since 2.0.10
*/
public $displayVars = ['_SERVER', '_GET', '_POST', '_COOKIE', '_FILES', '_SESSION'];
/**
* @inheritdoc
*/
public function getName()
{
return 'Request';
}
/**
* @inheritdoc
*/
public function getSummary()
{
return Yii::$app->view->render('panels/request/summary', ['panel' => $this]);
}
/**
* @inheritdoc
*/
public function getDetail()
{
return Yii::$app->view->render('panels/request/detail', ['panel' => $this]);
}
/**
* @inheritdoc
*/
public function save()
{
$headers = Yii::$app->getRequest()->getHeaders();
$requestHeaders = [];
foreach ($headers as $name => $value) {
if (is_array($value) && count($value) == 1) {
$requestHeaders[$name] = current($value);
} else {
$requestHeaders[$name] = $value;
}
}
$responseHeaders = [];
foreach (headers_list() as $header) {
if (($pos = strpos($header, ':')) !== false) {
$name = substr($header, 0, $pos);
$value = trim(substr($header, $pos + 1));
if (isset($responseHeaders[$name])) {
if (!is_array($responseHeaders[$name])) {
$responseHeaders[$name] = [$responseHeaders[$name], $value];
} else {
$responseHeaders[$name][] = $value;
}
} else {
$responseHeaders[$name] = $value;
}
} else {
$responseHeaders[] = $header;
}
}
if (Yii::$app->requestedAction) {
if (Yii::$app->requestedAction instanceof InlineAction) {
$action = get_class(Yii::$app->requestedAction->controller) . '::' . Yii::$app->requestedAction->actionMethod . '()';
} else {
$action = get_class(Yii::$app->requestedAction) . '::run()';
}
} else {
$action = null;
}
$data = [
'flashes' => $this->getFlashes(),
'statusCode' => Yii::$app->getResponse()->getStatusCode(),
'requestHeaders' => $requestHeaders,
'responseHeaders' => $responseHeaders,
'route' => Yii::$app->requestedAction ? Yii::$app->requestedAction->getUniqueId() : Yii::$app->requestedRoute,
'action' => $action,
'actionParams' => Yii::$app->requestedParams,
'general' => [
'method' => Yii::$app->getRequest()->getMethod(),
'isAjax' => Yii::$app->getRequest()->getIsAjax(),
'isPjax' => Yii::$app->getRequest()->getIsPjax(),
'isFlash' => Yii::$app->getRequest()->getIsFlash(),
'isSecureConnection' => Yii::$app->getRequest()->getIsSecureConnection(),
],
'requestBody' => Yii::$app->getRequest()->getRawBody() == '' ? [] : [
'Content Type' => Yii::$app->getRequest()->getContentType(),
'Raw' => Yii::$app->getRequest()->getRawBody(),
'Decoded to Params' => Yii::$app->getRequest()->getBodyParams(),
],
];
foreach ($this->displayVars as $name) {
$data[trim($name, '_')] = empty($GLOBALS[$name]) ? [] : $GLOBALS[$name];
}
return $data;
}
/**
* Getting flash messages without deleting them or touching deletion counters
*
* @return array flash messages (key => message).
*/
protected function getFlashes()
{
/* @var $session \yii\web\Session */
$session = Yii::$app->has('session', true) ? Yii::$app->get('session') : null;
if ($session === null || !$session->getIsActive()) {
return [];
}
$counters = $session->get($session->flashParam, []);
$flashes = [];
foreach (array_keys($counters) as $key) {
if (array_key_exists($key, $_SESSION)) {
$flashes[$key] = $_SESSION[$key];
}
}
return $flashes;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\panels;
use Yii;
use yii\debug\models\Router;
use yii\debug\Panel;
use yii\log\Logger;
/**
* RouterPanel provides a panel which displays information about routing process.
*
* @property array $categories Note that the type of this property differs in getter and setter. See
* [[getCategories()]] and [[setCategories()]] for details.
*
* @author Dmitriy Bashkarev <dmitriy@bashkarev.com>
* @since 2.0.8
*/
class RouterPanel extends Panel
{
/**
* @var array
*/
private $_categories = [
'yii\web\UrlManager::parseRequest',
'yii\web\UrlRule::parseRequest',
'yii\web\CompositeUrlRule::parseRequest',
'yii\rest\UrlRule::parseRequest'
];
/**
* @param string|array $values
*/
public function setCategories($values)
{
if (!is_array($values)) {
$values = [$values];
}
$this->_categories = array_merge($this->_categories, $values);
}
/**
* Listens categories of the messages.
* @return array
*/
public function getCategories()
{
return $this->_categories;
}
/**
* @inheritdoc
*/
public function getName()
{
return 'Router';
}
/**
* @inheritdoc
*/
public function getDetail()
{
return Yii::$app->view->render('panels/router/detail', ['model' => new Router($this->data)]);
}
/**
* @inheritdoc
*/
public function save()
{
$target = $this->module->logTarget;
return [
'messages' => $target::filterMessages($target->messages, Logger::LEVEL_TRACE, $this->_categories)
];
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\panels;
use Yii;
use yii\debug\Panel;
use yii\debug\models\timeline\Search;
use yii\debug\models\timeline\Svg;
use yii\base\InvalidConfigException;
/**
* Debugger panel that collects and displays timeline data.
*
* @property array $colors
* @property float $duration This property is read-only.
* @property float $start This property is read-only.
* @property array $svgOptions
*
* @author Dmitriy Bashkarev <dmitriy@bashkarev.com>
* @since 2.0.7
*/
class TimelinePanel extends Panel
{
/**
* @var array Color indicators item profile.
*
* - keys: percentages of time request
* - values: hex color
*/
private $_colors = [
20 => '#1e6823',
10 => '#44a340',
1 => '#8cc665'
];
/**
* @var array log messages extracted to array as models, to use with data provider.
*/
private $_models;
/**
* @var float Start request, timestamp (obtained by microtime(true))
*/
private $_start;
/**
* @var float End request, timestamp (obtained by microtime(true))
*/
private $_end;
/**
* @var float Request duration, milliseconds
*/
private $_duration;
/**
* @var Svg|null
*/
private $_svg;
/**
* @var array
*/
private $_svgOptions = [
'class' => 'yii\debug\models\timeline\Svg'
];
/**
* @var int Used memory in request
*/
private $_memory;
/**
* @inheritdoc
*/
public function init()
{
if (!isset($this->module->panels['profiling'])) {
throw new InvalidConfigException('Unable to determine the profiling panel');
}
parent::init();
}
/**
* @inheritdoc
*/
public function getName()
{
return 'Timeline';
}
/**
* @inheritdoc
*/
public function getDetail()
{
$searchModel = new Search();
$dataProvider = $searchModel->search(Yii::$app->request->getQueryParams(), $this);
return Yii::$app->view->render('panels/timeline/detail', [
'panel' => $this,
'dataProvider' => $dataProvider,
'searchModel' => $searchModel,
]);
}
/**
* @inheritdoc
*/
public function load($data)
{
if (!isset($data['start']) || empty($data['start'])) {
throw new \RuntimeException('Unable to determine request start time');
}
$this->_start = $data['start'] * 1000;
if (!isset($data['end']) || empty($data['end'])) {
throw new \RuntimeException('Unable to determine request end time');
}
$this->_end = $data['end'] * 1000;
if (isset($this->module->panels['profiling']->data['time'])) {
$this->_duration = $this->module->panels['profiling']->data['time'] * 1000;
} else {
$this->_duration = $this->_end - $this->_start;
}
if ($this->_duration <= 0) {
throw new \RuntimeException('Duration cannot be zero');
}
if (!isset($data['memory']) || empty($data['memory'])) {
throw new \RuntimeException('Unable to determine used memory in request');
}
$this->_memory = $data['memory'];
}
/**
* @inheritdoc
*/
public function save()
{
return [
'start' => YII_BEGIN_TIME,
'end' => microtime(true),
'memory' => memory_get_peak_usage(),
];
}
/**
* Sets color indicators.
* key: percentages of time request, value: hex color
* @param array $colors
*/
public function setColors($colors)
{
krsort($colors);
$this->_colors = $colors;
}
/**
* Color indicators item profile,
* key: percentages of time request, value: hex color
* @return array
*/
public function getColors()
{
return $this->_colors;
}
/**
* @param array $options
*/
public function setSvgOptions($options)
{
if ($this->_svg !== null) {
$this->_svg = null;
}
$this->_svgOptions = array_merge($this->_svgOptions, $options);
}
/**
* @return array
*/
public function getSvgOptions()
{
return $this->_svgOptions;
}
/**
* Start request, timestamp (obtained by microtime(true))
* @return float
*/
public function getStart()
{
return $this->_start;
}
/**
* Request duration, milliseconds
* @return float
*/
public function getDuration()
{
return $this->_duration;
}
/**
* Memory peak in request, bytes. (obtained by memory_get_peak_usage())
* @return int
* @since 2.0.8
*/
public function getMemory()
{
return $this->_memory;
}
/**
* @return Svg
* @since 2.0.8
*/
public function getSvg()
{
if ($this->_svg === null) {
$this->_svg = Yii::createObject($this->_svgOptions,[$this]);
}
return $this->_svg;
}
/**
* Returns an array of models that represents logs of the current request.
* Can be used with data providers, such as \yii\data\ArrayDataProvider.
*
* @param bool $refresh if need to build models from log messages and refresh them.
* @return array models
*/
protected function getModels($refresh = false)
{
if ($this->_models === null || $refresh) {
$this->_models = [];
if (isset($this->module->panels['profiling']->data['messages'])) {
$this->_models = Yii::getLogger()->calculateTimings($this->module->panels['profiling']->data['messages']);
}
}
return $this->_models;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\panels;
use Yii;
use yii\base\Controller;
use yii\base\Model;
use yii\base\InvalidConfigException;
use yii\data\ArrayDataProvider;
use yii\data\DataProviderInterface;
use yii\db\ActiveRecord;
use yii\debug\controllers\UserController;
use yii\debug\models\search\UserSearchInterface;
use yii\debug\models\UserSwitch;
use yii\debug\Panel;
use yii\filters\AccessControl;
use yii\filters\AccessRule;
use yii\helpers\ArrayHelper;
use yii\helpers\VarDumper;
use yii\web\IdentityInterface;
use yii\web\User;
/**
* Debugger panel that collects and displays user data.
*
* @property DataProviderInterface $userDataProvider This property is read-only.
* @property Model|UserSearchInterface $usersFilterModel This property is read-only.
*
* @author Daniel Gomez Pan <pana_1990@hotmail.com>
* @since 2.0.8
*/
class UserPanel extends Panel
{
/**
* @var array the rule which defines who allowed to switch user identity.
* Access Control Filter single rule. Ignore: actions, controllers, verbs.
* Settable: allow, roles, ips, matchCallback, denyCallback.
* By default deny for everyone. Recommendation: can allow for administrator
* or developer (if implement) role: ['allow' => true, 'roles' => ['admin']]
* @see http://www.yiiframework.com/doc-2.0/guide-security-authorization.html
* @since 2.0.10
*/
public $ruleUserSwitch = [
'allow' => false,
];
/**
* @var UserSwitch object of switching users
* @since 2.0.10
*/
public $userSwitch;
/**
* @var Model|UserSearchInterface Implements of User model with search method.
* @since 2.0.10
*/
public $filterModel;
/**
* @var array allowed columns for GridView.
* @see http://www.yiiframework.com/doc-2.0/yii-grid-gridview.html#$columns-detail
* @since 2.0.10
*/
public $filterColumns = [];
/**
* @var string|User ID of the user component or a user object
* @since 2.0.13
*/
public $userComponent = 'user';
/**
* @inheritdoc
*/
public function init()
{
if (
!$this->isEnabled()
|| $this->getUser()->isGuest
) {
return;
}
$this->userSwitch = new UserSwitch(['userComponent' => $this->userComponent]);
$this->addAccesRules();
if (!is_object($this->filterModel)
&& class_exists($this->filterModel)
&& in_array('yii\debug\models\search\UserSearchInterface', class_implements($this->filterModel), true)
) {
$this->filterModel = new $this->filterModel;
} elseif ($this->getUser() && $this->getUser()->identityClass) {
if (is_subclass_of($this->getUser()->identityClass, ActiveRecord::className())) {
$this->filterModel = new \yii\debug\models\search\User();
}
}
}
/**
* @return User|null
* @since 2.0.13
*/
public function getUser()
{
/* @var $user User */
return is_string($this->userComponent) ? Yii::$app->get($this->userComponent, false) : $this->userComponent;
}
/**
* Add ACF rule. AccessControl attach to debug module.
* Access rule for main user.
*/
private function addAccesRules()
{
$this->ruleUserSwitch['controllers'] = [$this->module->id . '/user'];
$this->module->attachBehavior(
'access_debug',
[
'class' => AccessControl::className(),
'only' => [$this->module->id . '/user', $this->module->id . '/default'],
'user' => $this->userSwitch->getMainUser(),
'rules' => [
$this->ruleUserSwitch,
],
]
);
}
/**
* Get model for GridView -> FilterModel
* @return Model|UserSearchInterface
*/
public function getUsersFilterModel()
{
return $this->filterModel;
}
/**
* Get model for GridView -> DataProvider
* @return DataProviderInterface
*/
public function getUserDataProvider()
{
return $this->getUsersFilterModel()->search(Yii::$app->request->queryParams);
}
/**
* Check is available search of users
* @return bool
*/
public function canSearchUsers()
{
return (isset($this->filterModel) &&
$this->filterModel instanceof Model &&
$this->filterModel->hasMethod('search')
);
}
/**
* Check can main user switch identity.
* @return bool
*/
public function canSwitchUser()
{
if ($this->getUser()->isGuest) {
return false;
}
$allowSwitchUser = false;
$rule = new AccessRule($this->ruleUserSwitch);
/** @var Controller $userController */
$userController = null;
$controller = $this->module->createController('user');
if (isset($controller[0]) && $controller[0] instanceof UserController) {
$userController = $controller[0];
}
//check by rule
if ($userController) {
$action = $userController->createAction('set-identity');
$user = $this->userSwitch->getMainUser();
$request = Yii::$app->request;
$allowSwitchUser = $rule->allows($action, $user, $request) ?: false;
}
return $allowSwitchUser;
}
/**
* @inheritdoc
*/
public function getName()
{
return 'User';
}
/**
* @inheritdoc
*/
public function getSummary()
{
return Yii::$app->view->render('panels/user/summary', ['panel' => $this]);
}
/**
* @inheritdoc
*/
public function getDetail()
{
return Yii::$app->view->render('panels/user/detail', ['panel' => $this]);
}
/**
* @inheritdoc
*/
public function save()
{
$identity = Yii::$app->user->identity;
if (!isset($identity)) {
return;
}
$rolesProvider = null;
$permissionsProvider = null;
try {
$authManager = Yii::$app->getAuthManager();
if ($authManager instanceof \yii\rbac\ManagerInterface) {
$roles = ArrayHelper::toArray($authManager->getRolesByUser($this->getUser()->id));
foreach ($roles as &$role) {
$role['data'] = $this->dataToString($role['data']);
}
unset($role);
$rolesProvider = new ArrayDataProvider([
'allModels' => $roles,
]);
$permissions = ArrayHelper::toArray($authManager->getPermissionsByUser($this->getUser()->id));
foreach ($permissions as &$permission) {
$permission['data'] = $this->dataToString($permission['data']);
}
unset($permission);
$permissionsProvider = new ArrayDataProvider([
'allModels' => $permissions,
]);
}
} catch (\Exception $e) {
// ignore auth manager misconfiguration
}
$identityData = $this->identityData($identity);
foreach ($identityData as $key => $value) {
$identityData[$key] = VarDumper::dumpAsString($value);
}
// If the identity is a model, let it specify the attribute labels
if ($identity instanceof Model) {
$attributes = [];
foreach (array_keys($identityData) as $attribute) {
$attributes[] = [
'attribute' => $attribute,
'label' => $identity->getAttributeLabel($attribute),
];
}
} else {
// Let the DetailView widget figure the labels out
$attributes = null;
}
return [
'id' => $identity->getId(),
'identity' => $identityData,
'attributes' => $attributes,
'rolesProvider' => $rolesProvider,
'permissionsProvider' => $permissionsProvider,
];
}
/**
* @inheritdoc
*/
public function isEnabled()
{
try {
$this->getUser();
} catch (InvalidConfigException $exception) {
return false;
}
return true;
}
/**
* Converts mixed data to string
*
* @param mixed $data
* @return string
*/
protected function dataToString($data)
{
if (is_string($data)) {
return $data;
}
return VarDumper::export($data);
}
/**
* Returns the array that should be set on [[\yii\widgets\DetailView::model]]
*
* @param IdentityInterface $identity
* @return array
*/
protected function identityData($identity)
{
if ($identity instanceof Model) {
return $identity->getAttributes();
}
return get_object_vars($identity);
}
}
<?php
/* @var $this \yii\web\View */
/* @var $manifest array */
/* @var $searchModel \yii\debug\models\search\Debug */
/* @var $dataProvider ArrayDataProvider */
/* @var $panels \yii\debug\Panel[] */
use yii\data\ArrayDataProvider;
use yii\grid\GridView;
use yii\helpers\Html;
$this->title = 'Yii Debugger';
?>
<div class="default-index">
<div id="yii-debug-toolbar" class="yii-debug-toolbar yii-debug-toolbar_position_top" style="display: none;">
<div class="yii-debug-toolbar__bar">
<div class="yii-debug-toolbar__block yii-debug-toolbar__title">
<a href="#">
<img width="30" height="30" alt="" src="<?= \yii\debug\Module::getYiiLogo() ?>">
</a>
</div>
<?php foreach ($panels as $panel): ?>
<?= $panel->getSummary() ?>
<?php endforeach; ?>
</div>
</div>
<div class="container">
<div class="row">
<?php
echo ' <h1>Available Debug Data</h1>';
$codes = [];
foreach ($manifest as $tag => $vals) {
if (!empty($vals['statusCode'])) {
$codes[] = $vals['statusCode'];
}
}
$codes = array_unique($codes, SORT_NUMERIC);
$statusCodes = !empty($codes) ? array_combine($codes, $codes) : null;
$hasDbPanel = isset($panels['db']);
echo GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'rowOptions' => function ($model) use ($searchModel, $hasDbPanel) {
if ($searchModel->isCodeCritical($model['statusCode'])) {
return ['class'=>'danger'];
}
if ($hasDbPanel && $this->context->module->panels['db']->isQueryCountCritical($model['sqlCount'])) {
return ['class'=>'danger'];
}
return [];
},
'columns' => array_filter([
['class' => 'yii\grid\SerialColumn'],
[
'attribute' => 'tag',
'value' => function ($data) {
return Html::a($data['tag'], ['view', 'tag' => $data['tag']]);
},
'format' => 'html',
],
[
'attribute' => 'time',
'value' => function ($data) {
return '<span class="nowrap">' . Yii::$app->formatter->asDatetime($data['time'], 'yyyy-MM-dd HH:mm:ss') . '</span>';
},
'format' => 'html',
],
'ip',
$hasDbPanel ? [
'attribute' => 'sqlCount',
'label' => 'Query Count',
'value' => function ($data) {
$dbPanel = $this->context->module->panels['db'];
if ($dbPanel->isQueryCountCritical($data['sqlCount'])) {
$content = Html::tag('b', $data['sqlCount']) . ' ' . Html::tag('span', '', ['class' => 'glyphicon glyphicon-exclamation-sign']);
return Html::a($content, ['view', 'panel' => 'db', 'tag' => $data['tag']], [
'title' => 'Too many queries. Allowed count is ' . $dbPanel->criticalQueryThreshold,
]);
}
return $data['sqlCount'];
},
'format' => 'html',
] : null,
[
'attribute' => 'mailCount',
'visible' => isset($this->context->module->panels['mail']),
],
[
'attribute' => 'method',
'filter' => ['get' => 'GET', 'post' => 'POST', 'delete' => 'DELETE', 'put' => 'PUT', 'head' => 'HEAD']
],
[
'attribute'=>'ajax',
'value' => function ($data) {
return $data['ajax'] ? 'Yes' : 'No';
},
'filter' => ['No', 'Yes'],
],
[
'attribute' => 'url',
'label' => 'URL',
],
[
'attribute' => 'statusCode',
'value' => function ($data) {
$statusCode = $data['statusCode'];
if ($statusCode === null) {
$statusCode = 200;
}
if ($statusCode >= 200 && $statusCode < 300) {
$class = 'label-success';
} elseif ($statusCode >= 300 && $statusCode < 400) {
$class = 'label-info';
} else {
$class = 'label-danger';
}
return "<span class=\"label {$class}\">$statusCode</span>";
},
'format' => 'raw',
'filter' => $statusCodes,
'label' => 'Status code'
],
]),
]);
?>
</div>
</div>
</div>
<script type="text/javascript">
if (!window.frameElement) {
document.querySelector('#yii-debug-toolbar').style.display = 'block';
}
</script>
<?php
/* @var $panel yii\debug\panels\AssetPanel */
use yii\helpers\Html;
use yii\helpers\Inflector;
?>
<h1>Asset Bundles</h1>
<?php if (empty($panel->data)) {
echo '<p>No asset bundle was used.</p>';
return;
} ?>
<div class="table-responsive">
<table class="table table-striped table-bordered">
<caption>
<p>Total <b><?= count($panel->data) ?></b> asset bundles were loaded.</p>
</caption>
<?php
foreach ($panel->data as $name => $bundle) {
?>
<thead>
<tr>
<td colspan="2"><h3 id="<?= Inflector::camel2id($name) ?>"><?= $name ?></h3></td>
</tr>
</thead>
<tbody>
<tr>
<th>sourcePath</th>
<td><?= Html::encode($bundle['sourcePath'] !== null ? $bundle['sourcePath'] : $bundle['basePath']) ?></td>
</tr>
<?php if ($bundle['basePath'] !== null): ?>
<tr>
<th>basePath</th>
<td><?= Html::encode($bundle['basePath']) ?></td>
</tr>
<?php endif; ?>
<?php if ($bundle['baseUrl'] !== null): ?>
<tr>
<th>baseUrl</th>
<td><?= Html::encode($bundle['baseUrl']) ?></td>
</tr>
<?php endif; ?>
<?php if (!empty($bundle['css'])): ?>
<tr>
<th>css</th>
<td>
<?= Html::ul($bundle['css'], [
'class' => 'assets',
'item' => function ($item) {
if (is_array($item)) {
$item = reset($item);
}
return Html::encode($item);
}
]) ?>
</td>
</tr>
<?php endif; ?>
<?php if (!empty($bundle['js'])): ?>
<tr>
<th>js</th>
<td>
<?= Html::ul($bundle['js'], [
'class' => 'assets',
'item' => function ($item) {
if (is_array($item)) {
$item = reset($item);
}
return Html::encode($item);
}
]) ?>
</td>
</tr>
<?php endif; ?>
<?php if (!empty($bundle['depends'])): ?>
<tr>
<th>depends</th>
<td><ul class="assets">
<?php foreach ($bundle['depends'] as $depend): ?>
<li><?= Html::a($depend, '#' . Inflector::camel2id($depend)) ?></li>
<?php endforeach; ?>
</ul></td>
</tr>
<?php endif; ?>
</tbody>
<?php
}
?>
</table>
</div>
<?php
/* @var $panel yii\debug\panels\AssetPanel */
if (!empty($panel->data)):
?>
<div class="yii-debug-toolbar__block">
<a href="<?= $panel->getUrl() ?>" title="Number of asset bundles loaded">Asset Bundles <span class="yii-debug-toolbar__label yii-debug-toolbar__label_info"><?= count($panel->data) ?></span></a>
</div>
<?php endif; ?>
<?php
/* @var $panel yii\debug\panels\ConfigPanel */
$extensions = $panel->getExtensions();
?>
<h1>Configuration</h1>
<?php
$formatLanguage = function($locale) {
if (class_exists('Locale', false)) {
$region = Locale::getDisplayLanguage($locale, 'en');
$language = Locale::getDisplayRegion($locale, 'en');
return ' (' . implode(',', array_filter([$language, $region])) . ')';
}
return '';
};
$app = $panel->data['application'];
echo $this->render('table', [
'caption' => 'Application Configuration',
'values' => [
'Yii Version' => $app['yii'],
'Application Name' => $app['name'],
'Application Version' => $app['version'],
'Current Language' => !empty($app['language']) ? $app['language'] . $formatLanguage($app['language']) : '',
'Source Language' => !empty($app['sourceLanguage']) ? $app['sourceLanguage'] . $formatLanguage($app['sourceLanguage']) : '',
'Charset' => !empty($app['charset']) ? $app['charset'] : '',
'Environment' => $app['env'],
'Debug Mode' => $app['debug'] ? 'Yes' : 'No',
],
]);
if (!empty($extensions)) {
echo $this->render('table', [
'caption' => 'Installed Extensions',
'values' => $extensions,
]);
}
$memcache = 'Disabled';
if ($panel->data['php']['memcache']) {
$memcache = 'Enabled (memcache)';
} elseif ($panel->data['php']['memcached']) {
$memcache = 'Enabled (memcached)';
}
echo $this->render('table', [
'caption' => 'PHP Configuration',
'values' => [
'PHP Version' => $panel->data['php']['version'],
'Xdebug' => $panel->data['php']['xdebug'] ? 'Enabled' : 'Disabled',
'APC' => $panel->data['php']['apc'] ? 'Enabled' : 'Disabled',
'Memcache' => $memcache,
],
]);
echo $panel->getPhpInfo();
<?php
/* @var $panel yii\debug\panels\ConfigPanel */
?>
<div class="yii-debug-toolbar__block">
<a href="<?= $panel->getUrl() ?>">
<span class="yii-debug-toolbar__label"><?= $panel->data['application']['yii'] ?></span>
PHP
<span class="yii-debug-toolbar__label"><?= $panel->data['php']['version'] ?></span>
</a>
</div>
<?php
use yii\helpers\Html;
/* @var $caption string */
/* @var $values array */
?>
<h3><?= $caption ?></h3>
<?php if (empty($values)): ?>
<p>Empty.</p>
<?php else: ?>
<div class="table-responsive">
<table class="table table-condensed table-bordered table-striped table-hover" style="table-layout: fixed;">
<thead>
<tr>
<th style="nowrap">Name</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<?php foreach ($values as $name => $value): ?>
<tr>
<th style="white-space: normal"><?= Html::encode($name) ?></th>
<td style="overflow:auto"><?= Html::encode($value) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
<?php
/* @var $panel yii\debug\panels\DbPanel */
/* @var $searchModel yii\debug\models\search\Db */
/* @var $dataProvider yii\data\ArrayDataProvider */
/* @var $hasExplain bool */
/* @var $sumDuplicates int */
use yii\grid\GridView;
use yii\helpers\Html;
use yii\web\View;
echo Html::tag('h1', $panel->getName() . ' Queries');
if ($sumDuplicates === 1) {
echo "<p><b>$sumDuplicates</b> duplicated query found.</p>";
} elseif ($sumDuplicates > 1) {
echo "<p><b>$sumDuplicates</b> duplicated queries found.</p>";
}
echo GridView::widget([
'dataProvider' => $dataProvider,
'id' => 'db-panel-detailed-grid',
'options' => ['class' => 'detail-grid-view table-responsive'],
'filterModel' => $searchModel,
'filterUrl' => $panel->getUrl(),
'columns' => [
[
'attribute' => 'seq',
'label' => 'Time',
'value' => function ($data) {
$timeInSeconds = $data['timestamp'] / 1000;
$millisecondsDiff = (int) (($timeInSeconds - (int) $timeInSeconds) * 1000);
return date('H:i:s.', $timeInSeconds) . sprintf('%03d', $millisecondsDiff);
},
'headerOptions' => [
'class' => 'sort-numerical'
]
],
[
'attribute' => 'duration',
'value' => function ($data) {
return sprintf('%.1f ms', $data['duration']);
},
'options' => [
'width' => '10%',
],
'headerOptions' => [
'class' => 'sort-numerical'
]
],
[
'attribute' => 'type',
'value' => function ($data) {
return Html::encode($data['type']);
},
'filter' => $panel->getTypes(),
],
[
'attribute' => 'duplicate',
'label' => 'Duplicated',
'options' => [
'width' => '5%',
],
'headerOptions' => [
'class' => 'sort-numerical'
]
],
[
'attribute' => 'query',
'value' => function ($data) use ($hasExplain, $panel) {
$query = Html::tag('div', Html::encode($data['query']));
if (!empty($data['trace'])) {
$query .= Html::ul($data['trace'], [
'class' => 'trace',
'item' => function ($trace) use ($panel) {
return '<li>' . $panel->getTraceLine($trace) . '</li>';
},
]);
}
if ($hasExplain && $panel::canBeExplained($data['type'])) {
$query .= Html::tag('p', '', ['class' => 'db-explain-text']);
$query .= Html::tag(
'div',
Html::a('[+] Explain', ['db-explain', 'seq' => $data['seq'], 'tag' => Yii::$app->controller->summary['tag']]),
['class' => 'db-explain']
);
}
return $query;
},
'format' => 'raw',
'options' => [
'width' => '60%',
],
]
],
]);
if ($hasExplain) {
echo Html::tag(
'div',
Html::a('[+] Explain all', '#'),
['id' => 'db-explain-all']
);
}
$this->registerJs('debug_db_detail();', View::POS_READY);
?>
<script>
function debug_db_detail() {
$('.db-explain a').on('click', function(e) {
e.preventDefault();
var $explain = $('.db-explain-text', $(this).parent().parent());
if ($explain.is(':visible')) {
$explain.hide();
$(this).text('[+] Explain');
} else {
$explain.load($(this).attr('href')).show();
$(this).text('[-] Explain');
}
});
$('#db-explain-all a').on('click', function(e) {
e.preventDefault();
$('.db-explain a').click();
});
}
</script>
<?php
/* @var $panel yii\debug\panels\DbPanel */
/* @var $queryCount integer */
/* @var $queryTime integer */
?>
<?php if ($queryCount): ?>
<div class="yii-debug-toolbar__block">
<a href="<?= $panel->getUrl() ?>" title="Executed <?= $queryCount ?> database queries which took <?= $queryTime ?>.">
<?= $panel->getSummaryName() ?> <span class="yii-debug-toolbar__label yii-debug-toolbar__label_info"><?= $queryCount ?></span> <span class="yii-debug-toolbar__label"><?= $queryTime ?></span>
</a>
</div>
<?php endif; ?>
<?php
/* @var $panel yii\debug\panels\LogPanel */
/* @var $searchModel yii\debug\models\search\Log */
/* @var $dataProvider yii\data\ArrayDataProvider */
use yii\helpers\Html;
use yii\grid\GridView;
use yii\helpers\VarDumper;
use yii\log\Logger;
?>
<h1>Log Messages</h1>
<?php
echo GridView::widget([
'dataProvider' => $dataProvider,
'id' => 'log-panel-detailed-grid',
'options' => ['class' => 'detail-grid-view table-responsive'],
'filterModel' => $searchModel,
'filterUrl' => $panel->getUrl(),
'rowOptions' => function ($model) {
switch ($model['level']) {
case Logger::LEVEL_ERROR : return ['class' => 'danger'];
case Logger::LEVEL_WARNING : return ['class' => 'warning'];
case Logger::LEVEL_INFO : return ['class' => 'success'];
default: return [];
}
},
'columns' => [
[
'attribute' => 'time',
'value' => function ($data) {
$timeInSeconds = $data['time'] / 1000;
$millisecondsDiff = (int) (($timeInSeconds - (int) $timeInSeconds) * 1000);
return date('H:i:s.', $timeInSeconds) . sprintf('%03d', $millisecondsDiff);
},
'headerOptions' => [
'class' => 'sort-numerical'
]
],
[
'attribute' => 'level',
'value' => function ($data) {
return Logger::getLevelName($data['level']);
},
'filter' => [
Logger::LEVEL_TRACE => ' Trace ',
Logger::LEVEL_INFO => ' Info ',
Logger::LEVEL_WARNING => ' Warning ',
Logger::LEVEL_ERROR => ' Error ',
],
],
'category',
[
'attribute' => 'message',
'value' => function ($data) use ($panel) {
$message = Html::encode(is_string($data['message']) ? $data['message'] : VarDumper::export($data['message']));
if (!empty($data['trace'])) {
$message .= Html::ul($data['trace'], [
'class' => 'trace',
'item' => function ($trace) use ($panel) {
return '<li>' . $panel->getTraceLine($trace) . '</li>';
}
]);
}
return $message;
},
'format' => 'raw',
'options' => [
'width' => '50%',
],
],
],
]);
<?php
/* @var $panel yii\debug\panels\LogPanel */
/* @var $data array */
use yii\log\Target;
use yii\log\Logger;
?>
<?php
$titles = ['all' => Yii::$app->i18n->format('Logged {n,plural,=1{1 message} other{# messages}}', ['n' => count($data['messages'])], 'en-US')];
$errorCount = count(Target::filterMessages($data['messages'], Logger::LEVEL_ERROR));
$warningCount = count(Target::filterMessages($data['messages'], Logger::LEVEL_WARNING));
if ($errorCount) {
$titles['errors'] = Yii::$app->i18n->format('{n,plural,=1{1 error} other{# errors}}', ['n' => $errorCount], 'en-US');
}
if ($warningCount) {
$titles['warnings'] = Yii::$app->i18n->format('{n,plural,=1{1 warning} other{# warnings}}', ['n' => $warningCount], 'en-US');
}
?>
<div class="yii-debug-toolbar__block">
<a href="<?= $panel->getUrl() ?>" title="<?= implode(',&nbsp;', $titles) ?>">Log
<span class="yii-debug-toolbar__label"><?= count($data['messages']) ?></span>
</a>
<?php if ($errorCount): ?>
<a href="<?= $panel->getUrl(['Log[level]' => Logger::LEVEL_ERROR])?>" title="<?= $titles['errors'] ?>">
<span class="yii-debug-toolbar__label yii-debug-toolbar__label_important"><?= $errorCount ?></span>
</a>
<?php endif; ?>
<?php if ($warningCount): ?>
<a href="<?= $panel->getUrl(['Log[level]' => Logger::LEVEL_WARNING])?>" title="<?= $titles['warnings'] ?>">
<span class="yii-debug-toolbar__label yii-debug-toolbar__label_warning"><?= $warningCount ?></span>
</a>
<?php endif; ?>
</div>
<?php
/* @var $model array */
use yii\helpers\Html;
use yii\widgets\DetailView;
echo DetailView::widget([
'model' => $model,
'attributes' => [
'headers',
'from',
'to',
'charset',
[
'attribute' => 'time',
'format' => 'datetime',
],
'subject',
[
'attribute' => 'body',
'label' => 'Text body',
],
[
'attribute' => 'isSuccessful',
'label' => 'Successfully sent',
'value' => $model['isSuccessful'] ? 'Yes' : 'No'
],
'reply',
'bcc',
'cc',
[
'attribute' => 'file',
'format' => 'html',
'value' => Html::a('Download eml', ['download-mail', 'file' => $model['file']]),
],
],
]);
<?php
/* @var $panel yii\debug\panels\MailPanel */
/* @var $searchModel yii\debug\models\search\Mail */
/* @var $dataProvider yii\data\ArrayDataProvider */
use \yii\widgets\ListView;
use yii\widgets\ActiveForm;
use yii\helpers\Html;
$listView = new ListView([
'dataProvider' => $dataProvider,
'itemView' => '_item',
'layout' => "{summary}\n{items}\n{pager}\n",
]);
$listView->sorter = ['options' => ['class' => 'mail-sorter']];
?>
<h1>Email messages</h1>
<div class="row">
<div class="col-lg-2">
<?= Html::button('Form filtering', ['class' => 'btn btn-default', 'onclick' => 'jQuery("#email-form").toggle();']) ?>
</div>
<div class="row col-lg-10">
<?= $listView->renderSorter() ?>
</div>
</div>
<div id="email-form" style="display: none;">
<?php $form = ActiveForm::begin([
'method' => 'get',
'action' => ['default/view', 'tag' => Yii::$app->request->get('tag'), 'panel' => 'mail'],
]); ?>
<div class="row">
<?= $form->field($searchModel, 'from', ['options' => ['class' => 'col-lg-6']])->textInput() ?>
<?= $form->field($searchModel, 'to', ['options' => ['class' => 'col-lg-6']])->textInput() ?>
<?= $form->field($searchModel, 'reply', ['options' => ['class' => 'col-lg-6']])->textInput() ?>
<?= $form->field($searchModel, 'cc', ['options' => ['class' => 'col-lg-6']])->textInput() ?>
<?= $form->field($searchModel, 'bcc', ['options' => ['class' => 'col-lg-6']])->textInput() ?>
<?= $form->field($searchModel, 'charset', ['options' => ['class' => 'col-lg-6']])->textInput() ?>
<?= $form->field($searchModel, 'subject', ['options' => ['class' => 'col-lg-6']])->textInput() ?>
<?= $form->field($searchModel, 'body', ['options' => ['class' => 'col-lg-6']])->textInput() ?>
<div class="form-group col-lg-12">
<?= Html::submitButton('Filter', ['class' => 'btn btn-success']) ?>
</div>
</div>
<?php ActiveForm::end(); ?>
</div>
<?= $listView->run() ?>
<?php
/* @var $panel yii\debug\panels\MailPanel */
/* @var $mailCount integer */
if ($mailCount): ?>
<div class="yii-debug-toolbar__block">
<a href="<?= $panel->getUrl() ?>">Mail <span class="yii-debug-toolbar__label"><?= $mailCount ?></span></a>
</div>
<?php endif ?>
<?php
/* @var $panel yii\debug\panels\ProfilingPanel */
/* @var $searchModel yii\debug\models\search\Profile */
/* @var $dataProvider yii\data\ArrayDataProvider */
/* @var $time integer */
/* @var $memory integer */
use yii\grid\GridView;
use yii\helpers\Html;
?>
<h1>Performance Profiling</h1>
<p>
Total processing time: <b><?= $time ?></b>; Peak memory: <b><?= $memory ?></b>.
<?= Html::a('Show Profiling Timeline', ['/' . $panel->module->id . '/default/view',
'panel' => 'timeline',
'tag' => $panel->tag,
]) ?>
</p>
<?php
echo GridView::widget([
'dataProvider' => $dataProvider,
'id' => 'profile-panel-detailed-grid',
'options' => ['class' => 'detail-grid-view table-responsive'],
'filterModel' => $searchModel,
'filterUrl' => $panel->getUrl(),
'columns' => [
[
'attribute' => 'seq',
'label' => 'Time',
'value' => function ($data) {
$timeInSeconds = $data['timestamp'] / 1000;
$millisecondsDiff = (int) (($timeInSeconds - (int) $timeInSeconds) * 1000);
return date('H:i:s.', $timeInSeconds) . sprintf('%03d', $millisecondsDiff);
},
'headerOptions' => [
'class' => 'sort-numerical'
]
],
[
'attribute' => 'duration',
'value' => function ($data) {
return sprintf('%.1f ms', $data['duration']);
},
'options' => [
'width' => '10%',
],
'headerOptions' => [
'class' => 'sort-numerical'
]
],
'category',
[
'attribute' => 'info',
'value' => function ($data) {
return str_repeat('<span class="indent">→</span>', $data['level']) . Html::encode($data['info']);
},
'format' => 'html',
'options' => [
'width' => '60%',
],
],
],
]);
<?php
/* @var $panel yii\debug\panels\ProfilingPanel */
/* @var $time integer */
/* @var $memory integer */
?>
<div class="yii-debug-toolbar__block">
<a href="<?= $panel->getUrl() ?>" title="Total request processing time was <?= $time ?>">Time <span class="yii-debug-toolbar__label yii-debug-toolbar__label_info"><?= $time ?></span></a>
<a href="<?= $panel->getUrl() ?>" title="Peak memory consumption">Memory <span class="yii-debug-toolbar__label yii-debug-toolbar__label_info"><?= $memory ?></span></a>
</div>
<?php
/* @var $panel yii\debug\panels\RequestPanel */
use yii\bootstrap\Tabs;
echo '<h1>Request</h1>';
$items = [];
$parametersContent = '';
if (isset($panel->data['general'])) {
$parametersContent .= $this->render('table', ['caption' => 'General Info', 'values' => $panel->data['general']]);
}
$parametersContent .= $this->render('table', [
'caption' => 'Routing',
'values' => [
'Route' => $panel->data['route'],
'Action' => $panel->data['action'],
'Parameters' => $panel->data['actionParams'],
],
]);
if (isset($panel->data['GET'])) {
$parametersContent .= $this->render('table', ['caption' => '$_GET', 'values' => $panel->data['GET']]);
}
if (isset($panel->data['POST'])) {
$parametersContent .= $this->render('table', ['caption' => '$_POST', 'values' => $panel->data['POST']]);
}
if (isset($panel->data['FILES'])) {
$parametersContent .= $this->render('table', ['caption' => '$_FILES', 'values' => $panel->data['FILES']]);
}
if (isset($panel->data['COOKIE'])) {
$parametersContent .= $this->render('table', ['caption' => '$_COOKIE', 'values' => $panel->data['COOKIE']]);
}
$parametersContent .= $this->render('table', ['caption' => 'Request Body', 'values' => $panel->data['requestBody']]);
$items[] = [
'label' => 'Parameters',
'content' => $parametersContent,
'active' => true,
];
$items[] = [
'label' => 'Headers',
'content' => $this->render('table', ['caption' => 'Request Headers', 'values' => $panel->data['requestHeaders']])
. $this->render('table', ['caption' => 'Response Headers', 'values' => $panel->data['responseHeaders']]),
];
if (isset($panel->data['SESSION'], $panel->data['flashes'])) {
$items[] = [
'label' => 'Session',
'content' => $this->render('table', ['caption' => '$_SESSION', 'values' => $panel->data['SESSION']])
. $this->render('table', ['caption' => 'Flashes', 'values' => $panel->data['flashes']]),
];
}
if (isset($panel->data['SERVER'])) {
$items[] = [
'label' => '$_SERVER',
'content' => $this->render('table', ['caption' => '$_SERVER', 'values' => $panel->data['SERVER']]),
];
}
echo Tabs::widget([
'items' => $items,
]);
<?php
/* @var $panel yii\debug\panels\RequestPanel */
use yii\helpers\Html;
use yii\web\Response;
$statusCode = $panel->data['statusCode'];
if ($statusCode === null) {
$statusCode = 200;
}
if ($statusCode >= 200 && $statusCode < 300) {
$class = 'yii-debug-toolbar__label_success';
} elseif ($statusCode >= 300 && $statusCode < 400) {
$class = 'yii-debug-toolbar__label_info';
} else {
$class = 'yii-debug-toolbar__label_important';
}
$statusText = Html::encode(isset(Response::$httpStatuses[$statusCode]) ? Response::$httpStatuses[$statusCode] : '');
?>
<div class="yii-debug-toolbar__block">
<a href="<?= $panel->getUrl() ?>" title="Status code: <?= $statusCode ?> <?= $statusText ?>">Status <span class="yii-debug-toolbar__label <?= $class ?>"><?= $statusCode ?></span></a>
<a href="<?= $panel->getUrl() ?>" title="Action: <?= $panel->data['action'] ?>">Route <span class="yii-debug-toolbar__label"><?= $panel->data['route'] ?></span></a>
</div>
<?php
/* @var $caption string */
/* @var $values array */
use yii\helpers\Html;
use yii\helpers\VarDumper;
?>
<h3><?= $caption ?></h3>
<?php if (empty($values)): ?>
<p>Empty.</p>
<?php else: ?>
<div class="table-responsive">
<table class="table table-condensed table-bordered table-striped table-hover request-table" style="table-layout: fixed;">
<thead>
<tr>
<th>Name</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<?php foreach ($values as $name => $value): ?>
<tr>
<th><?= Html::encode($name) ?></th>
<td><?= htmlspecialchars(VarDumper::dumpAsString($value), ENT_QUOTES|ENT_SUBSTITUTE, \Yii::$app->charset, true) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
<?php
/* @var $model yii\debug\models\Router */
use \yii\helpers\Html;
?>
<h1>
Router
<small>
<?= Yii::$app->i18n->format('{rulesTested, plural, =0{} =1{tested # rule} other{tested # rules}} {hasMatch, plural, =0{} other{before match}}', [
'rulesTested' => $model->count,
'hasMatch' => (int)$model->hasMatch,
], 'en_US'); ?>
</small>
</h1>
<?php if ($model->message !== null): ?>
<div class="alert alert-info">
<?= Html::encode($model->message); ?>
</div>
<?php endif; ?>
<?php if ($model->logs !== []): ?>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>#</th>
<th>Rule</th>
<th>Parent</th>
</tr>
</thead>
<tbody>
<?php foreach ($model->logs as $i => $log): ?>
<tr<?= $log['match'] ? ' class="success"' : '' ?>>
<td><?= $i + 1; ?></td>
<td><?= Html::encode($log['rule']); ?></td>
<td><?= Html::encode($log['parent']); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
<?php
/* @var $panel yii\debug\panels\TimelinePanel */
/* @var $searchModel \yii\debug\models\timeline\Search */
/* @var $dataProvider \yii\debug\models\timeline\DataProvider */
use yii\helpers\Html;
use yii\widgets\ActiveForm;
use yii\widgets\Pjax;
use yii\debug\TimelineAsset;
use yii\helpers\StringHelper;
TimelineAsset::register($this);
?>
<h1 class="debug-timeline-panel__title">Timeline - <?= number_format($panel->getDuration()); ?> ms</h1>
<?php $form = ActiveForm::begin([
'method' => 'get',
'action' => $panel->getUrl(),
'id' => 'debug-timeline-search',
'enableClientScript' => false,
'options' => [
'class' => 'debug-timeline-panel__search form-inline',
],
]) ?>
<div class="duration">
<?= Html::activeLabel($searchModel, 'duration') ?>
<?= Html::activeInput('number', $searchModel, 'duration', ['min' => 0, 'size' => '3', 'class'=>'form-control']); ?>
<span>ms</span>
</div>
<div class="category">
<?= Html::activeLabel($searchModel, 'category') ?>
<?= Html::activeTextInput($searchModel, 'category', ['class'=>'form-control']); ?>
</div>
<?php ActiveForm::end(); ?>
<div class="debug-timeline-panel">
<div class="debug-timeline-panel__header">
<?php foreach ($dataProvider->getRulers() as $ms => $left): ?>
<span class="ruler" style="margin-left: <?= StringHelper::normalizeNumber($left) ?>%"><b><?= sprintf('%.1f ms', $ms) ?></b></span>
<?php endforeach; ?>
<div class="control">
<button type="button" class="inline btn-link">
<svg aria-hidden="true" height="16" viewBox="0 0 14 16" width="14">
<path d="M7 9l3 3H8v3H6v-3H4l3-3zm3-6H8V0H6v3H4l3 3 3-3zm4 2c0-.55-.45-1-1-1h-2.5l-1 1h3l-2 2h-7l-2-2h3l-1-1H1c-.55 0-1 .45-1 1l2.5 2.5L0 10c0 .55.45 1 1 1h2.5l1-1h-3l2-2h7l2 2h-3l1 1H13c.55 0 1-.45 1-1l-2.5-2.5L14 5z"></path>
</svg>
</button>
<button type="button" class="open btn-link">
<svg aria-hidden="true" height="16" viewBox="0 0 14 16" width="14">
<path d="M11.5 7.5L14 10c0 .55-.45 1-1 1H9v-1h3.5l-2-2h-7l-2 2H5v1H1c-.55 0-1-.45-1-1l2.5-2.5L0 5c0-.55.45-1 1-1h4v1H1.5l2 2h7l2-2H9V4h4c.55 0 1 .45 1 1l-2.5 2.5zM6 6h2V3h2L7 0 4 3h2v3zm2 3H6v3H4l3 3 3-3H8V9z"></path>
</svg>
</button>
</div>
</div>
<?php if(!Yii::$app->request->isPjax && $panel->svg->hasPoints()):?>
<div class="debug-timeline-panel__memory" style="height: <?= StringHelper::normalizeNumber($panel->svg->y) ?>px;">
<div class="scale" style="bottom: 100%;"><?= sprintf('%.2f MB', $panel->memory / 1048576) ?></div>
<?=$panel->svg;?>
</div>
<?php endif;?>
<div class="debug-timeline-panel__items">
<?php Pjax::begin(['formSelector' => '#debug-timeline-search', 'linkSelector' => false, 'options' => ['id' => 'debug-timeline-panel__pjax']]); ?>
<?php if (($models = $dataProvider->models) === []): ?>
<div class="debug-timeline-panel__item empty">
<span>No results found.</span>
</div>
<?php else: ?>
<?php foreach ($models as $key => $model): ?>
<?php
$memory = null;
if (!empty($model['memory'])) {
$diff = null;
if ($model['memoryDiff'] !== 0) {
$diff = ' title="' . (($model['memoryDiff'] > 0) ? '+' : '-') . sprintf('%.3f', $model['memoryDiff'] / 1048576) . '""';
}
$memory = ' / <span class="memory"' . $diff . '>' . sprintf('%.2f', $model['memory'] / 1048576) . ' MB</span>';
}
?>
<div class="debug-timeline-panel__item">
<?php if ($model['child']): ?>
<span class="ruler ruler-start" style="height: <?= StringHelper::normalizeNumber($model['child'] * 21) ?>px; margin-left: <?= StringHelper::normalizeNumber( $model['css']['left']) ?>%"></span>
<?php endif; ?>
<?= Html::tag('a', '
<span class="category">' . Html::encode($model['category']) . ' <span>' . sprintf('%.1f ms', $model['duration']) . '</span>'.$memory.'</span>', ['tabindex'=>$key+1,'title' => $model['info'], 'class' => $dataProvider->getCssClass($model), 'style' => 'background-color: '.$model['css']['color'].';margin-left:' . StringHelper::normalizeNumber($model['css']['left'] . '%;width:' . $model['css']['width']) . '%', 'data-memory'=>$dataProvider->getMemory($model)]); ?>
<?php if ($model['child']): ?>
<span class="ruler ruler-end" style="height: <?= StringHelper::normalizeNumber($model['child'] * 21) ?>px; margin-left: <?= StringHelper::normalizeNumber($model['css']['left'] + $model['css']['width']) . '%'; ?>"></span>
<?php endif; ?>
</div>
<?php endforeach; ?>
<?php endif; ?>
<?php Pjax::end(); ?>
</div>
</div>
<?php
/* @var $this \yii\web\View */
/* @var $panel yii\debug\panels\UserPanel */
use yii\bootstrap\Tabs;
use yii\widgets\DetailView;
?>
<h1>User</h1>
<?php
if (isset($panel->data['identity'])) {
$items = [
[
'label' => 'User',
'content' => '<h2>User Info</h2>' . DetailView::widget([
'model' => $panel->data['identity'],
'attributes' => $panel->data['attributes']
]),
'active' => true,
],
];
if ($panel->data['rolesProvider'] || $panel->data['permissionsProvider']) {
$items[] = [
'label' => 'Roles and Permissions',
'content' => $this->render('roles', ['panel' => $panel])
];
}
if ($panel->canSwitchUser()) {
$items[] = [
'label' => 'Switch User',
'content' => $this->render(
'switch',
[
'panel' => $panel
]
)
];
}
echo Tabs::widget([
'items' => $items,
]);
} else {
echo 'Is guest.';
} ?>
<?php
/* @var $panel yii\debug\panels\UserPanel */
use yii\grid\GridView;
?>
<?php
if ($panel->data['rolesProvider']) {
echo '<h2>Roles</h2>';
echo GridView::widget([
'dataProvider' => $panel->data['rolesProvider'],
'columns' => [
'name',
'description',
'ruleName',
'data',
'createdAt:datetime',
'updatedAt:datetime'
]
]);
}
if ($panel->data['permissionsProvider']) {
echo '<h2>Permissions</h2>';
echo GridView::widget([
'dataProvider' => $panel->data['permissionsProvider'],
'columns' => [
'name',
'description',
'ruleName',
'data',
'createdAt:datetime',
'updatedAt:datetime'
]
]);
} ?>
<?php
/* @var $this \yii\web\View */
/* @var $panel yii\debug\panels\UserPanel */
?>
<div class="yii-debug-toolbar__block">
<a href="<?= $panel->getUrl() ?>">
<?php if (!isset($panel->data['id'])): ?>
<span class="yii-debug-toolbar__label">Guest</span>
<?php else: ?>
<?php if ($panel->getUser()->isGuest || $panel->userSwitch->isMainUser()): ?>
User <span
class="yii-debug-toolbar__label yii-debug-toolbar__label_info"><?= $panel->data['id'] ?></span>
<?php else: ?>
User switching <span
class="yii-debug-toolbar__label yii-debug-toolbar__label_warning"><?= $panel->data['id'] ?></span>
<?php endif; ?>
<?php if ($panel->canSwitchUser()): ?>
<span class="yii-debug-toolbar__switch-icon yii-debug-toolbar__userswitch"
id="yii-debug-toolbar__switch-users">
</span>
<?php endif; ?>
<?php endif; ?>
</a>
</div>
<?php
use yii\bootstrap\ActiveForm;
use yii\bootstrap\Html;
use yii\debug\UserswitchAsset;
use yii\grid\GridView;
/* @var $this \yii\web\View */
/* @var $panel yii\debug\panels\UserPanel */
UserswitchAsset::register($this);
echo '<h2>Switch user</h2>';
?>
<div class="row">
<div class="col-sm-7">
<?php $formSet = ActiveForm::begin([
'action' => \yii\helpers\Url::to(['user/set-identity']),
'layout' => 'horizontal',
'options' => [
'id' => 'debug-userswitch__set-identity',
'style' => $panel->canSearchUsers() ? 'display:none' : ''
]
]);
echo $formSet->field(
$panel->userSwitch,
'user[id]', ['options' => ['class' => '']])
->textInput(['id' => 'user_id', 'name' => 'user_id'])
->label('Switch User');
echo Html::submitButton('Switch', ['class' => 'btn btn-primary']);
ActiveForm::end();
?>
</div>
<div class="col-sm-5">
<?php
if (!$panel->userSwitch->isMainUser()) {
$formReset = ActiveForm::begin([
'action' => \yii\helpers\Url::to(['user/reset-identity']),
'options' => [
'id' => 'debug-userswitch__reset-identity',
]
]);
echo Html::submitButton('Reset to <span class="yii-debug-toolbar__label yii-debug-toolbar__label_info">' .
$panel->userSwitch->getMainUser()->getId() .
'</span>', ['class' => 'btn btn-default']);
ActiveForm::end();
}
?>
</div>
</div>
<?php
if ($panel->canSearchUsers()) {
\yii\widgets\Pjax::begin(['id' => 'debug-userswitch__filter', 'timeout' => false]);
echo GridView::widget([
'dataProvider' => $panel->getUserDataProvider(),
'filterModel' => $panel->getUsersFilterModel(),
'tableOptions' => [
'class' => 'table table-bordered table-responsive table-hover table-pointer'
],
'columns' => $panel->filterColumns
]);
\yii\widgets\Pjax::end();
}
\ No newline at end of file
<?php
/* @var $this \yii\web\View */
/* @var $panels \yii\debug\Panel[] */
/* @var $tag string */
/* @var $position string */
use yii\helpers\Url;
use yii\helpers\Html;
$firstPanel = reset($panels);
$url = $firstPanel->getUrl();
?>
<div id="yii-debug-toolbar" class="yii-debug-toolbar yii-debug-toolbar_position_<?= $position ?>">
<div class="yii-debug-toolbar__bar">
<div class="yii-debug-toolbar__block yii-debug-toolbar__title">
<a href="<?= Url::to(['index']) ?>">
<img width="30" height="30" alt="" src="<?= \yii\debug\Module::getYiiLogo() ?>">
</a>
</div>
<div class="yii-debug-toolbar__block yii-debug-toolbar__ajax" style="display: none">
AJAX <span class="yii-debug-toolbar__label yii-debug-toolbar__ajax_counter">0</span>
<div class="yii-debug-toolbar__ajax_info">
<table>
<thead>
<tr>
<th>Method</th>
<th>Status</th>
<th>URL</th>
<th>Time</th>
<th>Profile</th>
</tr>
</thead>
<tbody class="yii-debug-toolbar__ajax_requests"></tbody>
</table>
</div>
</div>
<?php foreach ($panels as $panel): ?>
<?php if ($panel->hasError()): ?>
<div class="yii-debug-toolbar__block">
<a href="<?= $panel->getUrl() ?>" title="<?= Html::encode($panel->getError()->getMessage()); ?>"><?=Html::encode($panel->getName())?> <span class="yii-debug-toolbar__label yii-debug-toolbar__label_error">error</span></a>
</div>
<?php else: ?>
<?= $panel->getSummary() ?>
<?php endif; ?>
<?php endforeach; ?>
<div class="yii-debug-toolbar__block_last">
</div>
<a class="yii-debug-toolbar__external" href="#" target="_blank">
<span class="yii-debug-toolbar__external-icon"></span>
</a>
<span class="yii-debug-toolbar__toggle">
<span class="yii-debug-toolbar__toggle-icon"></span>
</span>
</div>
<div class="yii-debug-toolbar__view">
<iframe src="about:blank" frameborder="0"></iframe>
</div>
</div>
<?php
/* @var $this \yii\web\View */
/* @var $summary array */
/* @var $tag string */
/* @var $manifest array */
/* @var $panels \yii\debug\Panel[] */
/* @var $activePanel \yii\debug\Panel */
use yii\bootstrap\ButtonDropdown;
use yii\bootstrap\ButtonGroup;
use yii\helpers\Html;
use yii\helpers\Url;
$this->title = 'Yii Debugger';
?>
<div class="default-view">
<div id="yii-debug-toolbar" class="yii-debug-toolbar yii-debug-toolbar_position_top" style="display: none;">
<div class="yii-debug-toolbar__bar">
<div class="yii-debug-toolbar__block yii-debug-toolbar__title">
<a href="<?= Url::to(['index']) ?>">
<img width="29" height="30" alt="" src="<?= \yii\debug\Module::getYiiLogo() ?>">
</a>
</div>
<?php foreach ($panels as $panel): ?>
<?= $panel->getSummary() ?>
<?php endforeach; ?>
</div>
</div>
<div class="container main-container">
<div class="row">
<div class="col-lg-2 col-md-2">
<div class="list-group">
<?php
foreach ($panels as $id => $panel) {
$label = '<i class="glyphicon glyphicon-chevron-right"></i>' . Html::encode($panel->getName());
echo Html::a($label, ['view', 'tag' => $tag, 'panel' => $id], [
'class' => $panel === $activePanel ? 'list-group-item active' : 'list-group-item',
]);
}
?>
</div>
</div>
<div class="col-lg-10 col-md-10">
<?php
$statusCode = $summary['statusCode'];
if ($statusCode === null) {
$statusCode = 200;
}
if ($statusCode >= 200 && $statusCode < 300) {
$calloutClass = 'callout-success';
} elseif ($statusCode >= 300 && $statusCode < 400) {
$calloutClass = 'callout-info';
} else {
$calloutClass = 'callout-important';
}
?>
<div class="callout <?= $calloutClass ?>">
<?php
$count = 0;
$items = [];
foreach ($manifest as $meta) {
$label = ($meta['tag'] == $tag ? Html::tag('strong', '&#9654;&nbsp;'.$meta['tag']) : $meta['tag'])
. ': ' . $meta['method'] . ' ' . $meta['url'] . ($meta['ajax'] ? ' (AJAX)' : '')
. ', ' . date('Y-m-d h:i:s a', $meta['time'])
. ', ' . $meta['ip'];
$url = ['view', 'tag' => $meta['tag'], 'panel' => $activePanel->id];
$items[] = [
'label' => $label,
'url' => $url,
];
if (++$count >= 10) {
break;
}
}
echo ButtonGroup::widget([
'options'=>['class'=>'btn-group-sm'],
'buttons' => [
Html::a('All', ['index'], ['class' => 'btn btn-default']),
Html::a('Latest', ['view', 'panel' => $activePanel->id], ['class' => 'btn btn-default']),
ButtonDropdown::widget([
'label' => 'Last 10',
'options' => ['class' => 'btn-default btn-sm'],
'dropdown' => ['items' => $items, 'encodeLabels' => false],
]),
],
]);
echo "\n" . $summary['tag'] . ': ' . $summary['method'] . ' ' . Html::a(Html::encode($summary['url']), $summary['url']);
echo ' at ' . date('Y-m-d h:i:s a', $summary['time']) . ' by ' . $summary['ip'];
?>
</div>
<?= $activePanel->getDetail() ?>
</div>
</div>
</div>
</div>
<script type="text/javascript">
if (!window.frameElement) {
document.querySelector('#yii-debug-toolbar').style.display = 'block';
}
</script>
<?php
/* @var $this \yii\web\View */
/* @var $content string */
use yii\helpers\Html;
yii\debug\DebugAsset::register($this);
?>
<?php $this->beginPage() ?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="none" />
<?= Html::csrfMetaTags() ?>
<title><?= Html::encode($this->title) ?></title>
<?php $this->head() ?>
</head>
<body>
<?php $this->beginBody() ?>
<?= $content ?>
<?php $this->endBody() ?>
</body>
</html>
<?php $this->endPage() ?>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment