This commit is contained in:
2020-02-01 16:47:12 +07:00
commit 4c619ad6e6
16739 changed files with 3329179 additions and 0 deletions

141
vendor/yiisoft/extensions.php vendored Normal file
View File

@@ -0,0 +1,141 @@
<?php
$vendorDir = dirname(__DIR__);
return array (
'yiisoft/yii2-swiftmailer' =>
array (
'name' => 'yiisoft/yii2-swiftmailer',
'version' => '2.0.7.0',
'alias' =>
array (
'@yii/swiftmailer' => $vendorDir . '/yiisoft/yii2-swiftmailer',
),
),
'yiisoft/yii2-bootstrap' =>
array (
'name' => 'yiisoft/yii2-bootstrap',
'version' => '2.0.8.0',
'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',
),
),
'yiisoft/yii2-gii' =>
array (
'name' => 'yiisoft/yii2-gii',
'version' => '2.0.7.0',
'alias' =>
array (
'@yii/gii' => $vendorDir . '/yiisoft/yii2-gii/src',
),
),
'yiisoft/yii2-faker' =>
array (
'name' => 'yiisoft/yii2-faker',
'version' => '2.0.4.0',
'alias' =>
array (
'@yii/faker' => $vendorDir . '/yiisoft/yii2-faker',
),
),
'rmrevin/yii2-fontawesome' =>
array (
'name' => 'rmrevin/yii2-fontawesome',
'version' => '2.17.1.0',
'alias' =>
array (
'@rmrevin/yii/fontawesome' => $vendorDir . '/rmrevin/yii2-fontawesome',
),
),
'cebe/yii2-gravatar' =>
array (
'name' => 'cebe/yii2-gravatar',
'version' => '1.1.0.0',
'alias' =>
array (
'@cebe/gravatar' => $vendorDir . '/cebe/yii2-gravatar/cebe/gravatar',
),
),
'dmstr/yii2-adminlte-asset' =>
array (
'name' => 'dmstr/yii2-adminlte-asset',
'version' => '2.6.2.0',
'alias' =>
array (
'@dmstr' => $vendorDir . '/dmstr/yii2-adminlte-asset',
),
),
'yiisoft/yii2-smarty' =>
array (
'name' => 'yiisoft/yii2-smarty',
'version' => '2.0.7.0',
'alias' =>
array (
'@yii/smarty' => $vendorDir . '/yiisoft/yii2-smarty/src',
),
),
'yiisoft/yii2-mongodb' =>
array (
'name' => 'yiisoft/yii2-mongodb',
'version' => '2.1.7.0',
'alias' =>
array (
'@yii/mongodb' => $vendorDir . '/yiisoft/yii2-mongodb/src',
),
),
'yiisoft/yii2-jui' =>
array (
'name' => 'yiisoft/yii2-jui',
'version' => '2.0.7.0',
'alias' =>
array (
'@yii/jui' => $vendorDir . '/yiisoft/yii2-jui',
),
),
'2amigos/yii2-arrayquery-component' =>
array (
'name' => '2amigos/yii2-arrayquery-component',
'version' => '1.0.3.0',
'alias' =>
array (
'@dosamigos/arrayquery' => $vendorDir . '/2amigos/yii2-arrayquery-component/src',
),
),
'yii2mod/yii2-rbac' =>
array (
'name' => 'yii2mod/yii2-rbac',
'version' => '2.3.0.0',
'alias' =>
array (
'@yii2mod/rbac' => $vendorDir . '/yii2mod/yii2-rbac',
),
),
'linslin/yii2-curl' =>
array (
'name' => 'linslin/yii2-curl',
'version' => '1.0.3.0',
'alias' =>
array (
'@linslin/yii2/curl' => $vendorDir . '/linslin/yii2-curl',
),
),
'moonlandsoft/yii2-phpexcel' =>
array (
'name' => 'moonlandsoft/yii2-phpexcel',
'version' => '1.1.0.0',
'alias' =>
array (
'@moonland/phpexcel' => $vendorDir . '/moonlandsoft/yii2-phpexcel',
),
),
);

View File

@@ -0,0 +1,149 @@
Yii Framework 2 bootstrap extension Change Log
==============================================
2.0.8 February 16, 2018
-----------------------
- Bug #126: Fixed `yii\bootstrap\ToggleButtonGroup` toggles dropdown for both buttons in case `split` is enabled (klimov-paul)
- Bug #136: Allow overriding `horizontalCssClasses` when extending `\yii\bootstrap\ActiveField` (mikehaertl, klimov-paul)
- Enh #118: Methods `radioList()` and `checkboxList()` overridden at `yii\bootstrap\Html` applying Bootstrap style (klimov-paul)
- Enh #148: `yii\bootstrap\Html::error()` now automatically set 'help-block help-block-error' CSS class for generated tag (klimov-paul)
- Enh #171: Add ability to use a brandImage with the navbar (razvanphp)
- Enh #219: Add ability to use custom HTML in navbar-header (razvanphp)
- Enh #227: Added `yii\bootstrap\Collapse::$itemToggleOptions` allowing setup custom collapse tag name and HTML options (mskayali, klimov-paul)
2.0.7 October 09, 2017
----------------------
- Bug #126: `yii\bootstrap\ToggleButtonGroup` was unable to work without model (makroxyz)
- Bug #130: Fixed `yii\bootstrap\Collapse` to use pure numerical value on `content` property (meysampg)
- Bug #137: Remove `role="navbar"` from `yii\bootstrap\NavBar` according to new aria specification (tino415)
- Bug #143: Fixed `yii\bootstrap\Nav` to use tags according to bootstrap docs (PowerGamer1)
- Bug #157: Active status of multilevel submenu items of the `yii\bootstrap\Nav` class is determined correctly now (PowerGamer1)
- Bug #162: Fixed `yii\bootstrap\Nav` not taking explicit `active` into account when `activateItems` is off (samdark)
- Bug #184: `yii\bootstrap\ButtonDropdown` widget did not use the correct JS plugin, was `button`, changed to `dropdown` (cebe, yukal)
- Bug #196: Remove `role="form"` from `yii\bootstrap\ActiveForm` according to new aria specification (bastardijke)
- Enh #64: Added simplified syntax for specifying `Collapse` widget `$items` (Faryshta, cebe)
- Enh #107: Added `yii\bootstrap\Collapse::$autoCloseItems` to allow keeping multiple items open at the same time (cebe)
- Enh #113: Allow URLs instead of content for Tab Widget Dropdown items (Okeanos)
- Enh #131: Added `tabContentOptions` to set HTML attributes for 'tab-content' container in `Tabs` widget (AndrewKorpusov)
- Enh #145: Added the ability to customize the class used to draw dropdowns in `yii\bootstrap\Nav`, `yii\bootstrapButtonDropdown` and `yii\bootstrap\Tab` widgets (PowerGamer1)
- Enh #174: Added `yii\bootstrap\Tabs::renderPanes()` to allow extending the class to manipulate the content between the tabs and the content (thiagotalma)
- Enh #187: Added `yii\bootstrap\Tabs::activateFirstVisibleTab()` to set the first visible tab as active if no active tab is set (nilsburg)
- Enh #208: Added `yii\bootstrap\Modal::$bodyOptions` to allow add options to body part Modal (KoJIT2009)
2.0.6 March 17, 2016
--------------------
- Bug #68: Fixed `yii\bootstrap\Nav` handling empty items (freezy-sk)
- Bug #81: Fixed `yii\bootstrap\ActiveField::radioList()` and `yii\bootstrap\ActiveField::checkboxList()` ignore `itemOptions` (mikehaertl)
- Bug #98: Fixed `yii\bootstrap\ButtonDropdown` setting `href` attribute for non `a` tags (13nightevil)
- Bug #124: Fixed `yii\bootstrap\Tabs` to use `tag` configuration option for item container (arturf)
- Enh #45: Added support for Bootstrap checkbox/radio toggle buttons (RomeroMsk, klimov-paul)
- Enh #92: Allow overriding `data-toggle` in `yii\bootstrap\Tabs` (machour)
2.0.5 September 23, 2015
------------------------
- Enh #15: Allowed overriding default Bootstrap CSS classes added by widgets (klimov-paul)
- Enh #38: Added object support for `content` option in `Collapse` class (pana1990, ItsReddi)
- Enh #40: Added `visible` option to `yii\bootstrap\Tab` widget items (klimov-paul)
- Enh #41: Added `submenuOptions` support at `yii\bootstrap\Dropdown` (spikyjt, klimov-paul)
- Enh #42: Added support for the glyphicons via `yii\bootstrap\Html::icon()` (klimov-paul)
- Enh #43: Added support for the static form controls via `yii\bootstrap\Html` (klimov-paul)
- Enh #44: Fixed `yii\bootstrap\ButtonDropdown` renders two buttons with the same id, if 'split' is enabled (klimov-paul)
- Enh #50: Added `dropDownOptions` that is passed to `yii\bootstrap\Nav` dropdown items (fbau123)
2.0.4 May 10, 2015
------------------
- Bug #18: `label` option ignored by `yii\bootstrap\Activefield::checkbox()` and `yii\bootstrap\Activefield::radio()` (mikehaertl)
- Bug #5984: `yii\bootstrap\Activefield::checkbox()` caused browser to link label to the wrong input (cebe)
- Bug #7894: Fixed incorrect URL config processing at `yii\bootstrap\Nav::items` if route element is not a first one (nkovacs, klimov-paul)
- Bug #8231: Configuration of Alert, ButtonDropdown, Modal widget where not preserved when used multiple times (cebe, idMolotov)
- Bug (CVE-2015-3397): Using `Json::htmlEncode()` for safer JSON data encoding in HTML code (samdark, Tomasz Tokarski)
- Enh #29: Added support to list-groups for Collapse class (pana1990, skullcrasher)
- Enh #2546: Added `visible` option to `yii\bootstrap\ButtonGroup::$buttons` (samdark, lukBarros)
- Enh #7633: Added `ActionColumn::$buttonOptions` for defining HTML options to be added to the default buttons (cebe)
- Enh: Added `Nav::$dropDownCaret` to allow customization of the dropdown caret symbol (cebe)
- Enh: Added support for using external URLs for `Tabs`. (dynasource, qiangxue)
2.0.3 March 01, 2015
--------------------
- no changes in this release.
2.0.2 January 11, 2015
----------------------
- Bug #6672: `yii\bootstrap\Dropdown` should register client event handlers (qiangxue)
2.0.1 December 07, 2014
-----------------------
- Bug #5570: `yii\bootstrap\Tabs` would throw an exception if `content` is not set for one of its `items` (RomeroMsk)
- Bug #6150: `yii\bootstrap\Tabs` dropdown IDs were generated incorrectly (samdark)
- Enh #4146: Added `yii\bootstrap\ButtonDropdown::$containerOptions` (samdark)
- Enh #4181: Added `yii\bootstrap\Modal::$headerOptions` and `yii\bootstrap\Modal::$footerOptions` (tuxoff, samdark)
- Enh #4450: Added `yii\bootstrap\Nav::renderDropdown()` (qiangxue)
- Enh #5494: Added support for specifying a menu header as a configuration array in `yii\bootstrap\Dropdown` (hiltonjanfield, qiangxue)
- Enh #5735: Added `yii\bootstrap\Tabs::renderTabContent` to support manually rendering tab contents (RomeroMsk)
- Enh #5799: `yii\bootstrap\ButtonGroup::buttons` can take all options that are supported by `yii\bootstrap\Button` (aleksanderd)
- Chg #5874: Upgraded Twitter Bootstrap to 3.3.x (samdark)
2.0.0 October 12, 2014
----------------------
- Bug #5323: Nested dropdown does not work for `yii\bootstrap\DropDown` (aryraditya)
- Bug #5336: `yii\bootstrap\DropDown` should register bootstrap plugin asset (zelenin)
- Chg #5231: Collapse `items` property uses `label` element instead of array key for headers (nkovacs)
- Chg #5232: Collapse encodes headers by default (nkovacs)
- Chg #5217: Tabs no longer requires content since empty tab could be used dynamically (damiandennis)
2.0.0-rc September 27, 2014
---------------------------
- Bug #3292: Fixed dropdown widgets rendering incorrect HTML (it3rmit)
- Bug #3740: Fixed duplicate error message when client validation is enabled (tadaszelvys)
- Bug #3749: Fixed invalid plugin registration and ensure clickable links in dropdown (kartik-v)
- Enh #4024: Added ability to `yii\bootstrap\Tabs` to encode each `Tabs::items['label']` separately (creocoder, umneeq)
- Enh #4120: Added ability for each item to choose it's encoding option in `Dropdown` and `Nav` (Alex-Code)
- Enh #4363: Added `showIndicators` property to make Carousel indicators optional (sdkiller)
- Chg #3036: Upgraded Twitter Bootstrap to 3.1.x (qiangxue)
- Chg #4595: The following properties are now taking `false` instead of `null` for "don't use" case (samdark)
- `yii\bootstrap\NavBar::$brandLabel`.
- `yii\bootstrap\NavBar::$brandUrl`.
- `yii\bootstrap\Modal::$closeButton`.
- `yii\bootstrap\Modal::$toggleButton`.
- `yii\bootstrap\Alert::$closeButton`.
2.0.0-beta April 13, 2014
-------------------------
- Bug #2361: `yii\bootstrap\NavBar::brandUrl` should default to the home URL of application (qiangxue)
- Enh #1474: Added option to make NavBar 100% width (cebe)
- Enh #1552: It is now possible to use multiple bootstrap NavBar in a single page (Alex-Code)
- Enh #1553: Only add navbar-default class to NavBar when no other class is specified (cebe)
- Enh #1562: Added `yii\bootstrap\Tabs::linkOptions` (kartik-v)
- Enh #1601: Added support for tagName and encodeLabel parameters in ButtonDropdown (omnilight)
- Enh #1881: Improved `yii\bootstrap\NavBar` with `containerOptions`, `innerContainerOptions` and `renderInnerContainer` (creocoder)
- Enh #2425: Tabs widget now selects first tab if no active tab is specified (samdark)
- Enh #2634: Submenus will now be checked for being active (Alex-Code)
- Enh #2643: Add size attribute to Modal (tof06)
- Chg #1459: Update Collapse to use bootstrap 3 classes (tonydspaniard)
- Chg #1820: Update Progress to use bootstrap 3 markup (samdark)
- New #3029: Added `yii\bootstrap\ActiveForm` and `yii\bootstrap\ActiveField` (mikehaertl)
2.0.0-alpha, December 1, 2013
-----------------------------
- Initial release.

View File

@@ -0,0 +1,32 @@
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.

26
vendor/yiisoft/yii2-bootstrap/Makefile vendored Normal file
View File

@@ -0,0 +1,26 @@
# default versions to test against
# these can be overridden by setting the environment variables in the shell
PHP_VERSION=php-5.6.8
YII_VERSION=dev-master
# ensure all the configuration variables above are in environment of the shell commands below
export
help:
@echo "make test - run phpunit tests using a docker environment"
# @echo "make clean - stop docker and remove container"
test: docker-php
composer require "yiisoft/yii2:${YII_VERSION}" --prefer-dist
composer install --prefer-dist
docker run --rm=true -v $(shell pwd):/opt/test yiitest/php:${PHP_VERSION} phpunit --verbose --color
docker-php: dockerfiles
cd tests/docker/php && sh build.sh
dockerfiles:
test -d tests/docker || git clone https://github.com/cebe/jenkins-test-docker tests/docker
cd tests/docker && git checkout -- . && git pull
mkdir -p tests/dockerids

49
vendor/yiisoft/yii2-bootstrap/README.md vendored Normal file
View File

@@ -0,0 +1,49 @@
<p align="center">
<a href="http://getbootstrap.com/" target="_blank" rel="external">
<img src="https://v4-alpha.getbootstrap.com/assets/brand/bootstrap-solid.svg" height="80px">
</a>
<h1 align="center">Twitter Bootstrap Extension for Yii 2</h1>
<br>
</p>
This is the Twitter Bootstrap extension for [Yii framework 2.0](http://www.yiiframework.com). It encapsulates [Bootstrap](http://getbootstrap.com/) components
and plugins in terms of Yii widgets, and thus makes using Bootstrap components/plugins
in Yii applications extremely easy.
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-bootstrap/v/stable.png)](https://packagist.org/packages/yiisoft/yii2-bootstrap)
[![Total Downloads](https://poser.pugx.org/yiisoft/yii2-bootstrap/downloads.png)](https://packagist.org/packages/yiisoft/yii2-bootstrap)
[![Build Status](https://travis-ci.org/yiisoft/yii2-bootstrap.svg?branch=master)](https://travis-ci.org/yiisoft/yii2-bootstrap)
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-bootstrap
```
or add
```
"yiisoft/yii2-bootstrap": "~2.0.0"
```
to the require section of your `composer.json` file.
Usage
----
For example, the following
single line of code in a view file would render a Bootstrap Progress plugin:
```php
<?= yii\bootstrap\Progress::widget(['percent' => 60, 'label' => 'test']) ?>
```

View File

@@ -0,0 +1,54 @@
{
"name": "yiisoft/yii2-bootstrap",
"description": "The Twitter Bootstrap extension for the Yii framework",
"keywords": ["yii2", "bootstrap"],
"type": "yii2-extension",
"license": "BSD-3-Clause",
"support": {
"issues": "https://github.com/yiisoft/yii2-bootstrap/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-bootstrap"
},
"authors": [
{
"name": "Qiang Xue",
"email": "qiang.xue@gmail.com",
"homepage": "http://www.yiiframework.com/"
},
{
"name": "Alexander Makarov",
"email": "sam@rmcreative.ru",
"homepage": "http://rmcreative.ru/"
},
{
"name": "Antonio Ramirez",
"email": "amigo.cobos@gmail.com"
},
{
"name": "Paul Klimov",
"email": "klimov.paul@gmail.com"
}
],
"require": {
"yiisoft/yii2": "~2.0.6",
"bower-asset/bootstrap": "3.3.* | 3.2.* | 3.1.*"
},
"repositories": [
{
"type": "composer",
"url": "https://asset-packagist.org"
}
],
"autoload": {
"psr-4": {
"yii\\bootstrap\\": "src"
}
},
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
}
}

View File

@@ -0,0 +1,423 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\bootstrap;
use yii\helpers\ArrayHelper;
/**
* A Bootstrap 3 enhanced version of [[\yii\widgets\ActiveField]].
*
* This class adds some useful features to [[\yii\widgets\ActiveField|ActiveField]] to render all
* sorts of Bootstrap 3 form fields in different form layouts:
*
* - [[inputTemplate]] is an optional template to render complex inputs, for example input groups
* - [[horizontalCssClasses]] defines the CSS grid classes to add to label, wrapper, error and hint
* in horizontal forms
* - [[inline]]/[[inline()]] is used to render inline [[checkboxList()]] and [[radioList()]]
* - [[enableError]] can be set to `false` to disable to the error
* - [[enableLabel]] can be set to `false` to disable to the label
* - [[label()]] can be used with a `bool` argument to enable/disable the label
*
* There are also some new placeholders that you can use in the [[template]] configuration:
*
* - `{beginLabel}`: the opening label tag
* - `{labelTitle}`: the label title for use with `{beginLabel}`/`{endLabel}`
* - `{endLabel}`: the closing label tag
* - `{beginWrapper}`: the opening wrapper tag
* - `{endWrapper}`: the closing wrapper tag
*
* The wrapper tag is only used for some layouts and form elements.
*
* Note that some elements use slightly different defaults for [[template]] and other options.
* You may want to override those predefined templates for checkboxes, radio buttons, checkboxLists
* and radioLists in the [[\yii\widgets\ActiveForm::fieldConfig|fieldConfig]] of the
* [[\yii\widgets\ActiveForm]]:
*
* - [[checkboxTemplate]] the template for checkboxes in default layout
* - [[radioTemplate]] the template for radio buttons in default layout
* - [[horizontalCheckboxTemplate]] the template for checkboxes in horizontal layout
* - [[horizontalRadioTemplate]] the template for radio buttons in horizontal layout
* - [[inlineCheckboxListTemplate]] the template for inline checkboxLists
* - [[inlineRadioListTemplate]] the template for inline radioLists
*
* Example:
*
* ```php
* use yii\bootstrap\ActiveForm;
*
* $form = ActiveForm::begin(['layout' => 'horizontal']);
*
* // Form field without label
* echo $form->field($model, 'demo', [
* 'inputOptions' => [
* 'placeholder' => $model->getAttributeLabel('demo'),
* ],
* ])->label(false);
*
* // Inline radio list
* echo $form->field($model, 'demo')->inline()->radioList($items);
*
* // Control sizing in horizontal mode
* echo $form->field($model, 'demo', [
* 'horizontalCssClasses' => [
* 'wrapper' => 'col-sm-2',
* ]
* ]);
*
* // With 'default' layout you would use 'template' to size a specific field:
* echo $form->field($model, 'demo', [
* 'template' => '{label} <div class="row"><div class="col-sm-4">{input}{error}{hint}</div></div>'
* ]);
*
* // Input group
* echo $form->field($model, 'demo', [
* 'inputTemplate' => '<div class="input-group"><span class="input-group-addon">@</span>{input}</div>',
* ]);
*
* ActiveForm::end();
* ```
*
* @see \yii\bootstrap\ActiveForm
* @see http://getbootstrap.com/css/#forms
*
* @author Michael Härtl <haertl.mike@gmail.com>
* @since 2.0
*/
class ActiveField extends \yii\widgets\ActiveField
{
/**
* @var bool whether to render [[checkboxList()]] and [[radioList()]] inline.
*/
public $inline = false;
/**
* @var string|null optional template to render the `{input}` placeholder content
*/
public $inputTemplate;
/**
* @var array options for the wrapper tag, used in the `{beginWrapper}` placeholder
*/
public $wrapperOptions = [];
/**
* @var null|array CSS grid classes for horizontal layout. This must be an array with these keys:
* - 'offset' the offset grid class to append to the wrapper if no label is rendered
* - 'label' the label grid class
* - 'wrapper' the wrapper grid class
* - 'error' the error grid class
* - 'hint' the hint grid class
*/
public $horizontalCssClasses = [];
/**
* @var string the template for checkboxes in default layout
*/
public $checkboxTemplate = "<div class=\"checkbox\">\n{beginLabel}\n{input}\n{labelTitle}\n{endLabel}\n{error}\n{hint}\n</div>";
/**
* @var string the template for radios in default layout
*/
public $radioTemplate = "<div class=\"radio\">\n{beginLabel}\n{input}\n{labelTitle}\n{endLabel}\n{error}\n{hint}\n</div>";
/**
* @var string the template for checkboxes in horizontal layout
*/
public $horizontalCheckboxTemplate = "{beginWrapper}\n<div class=\"checkbox\">\n{beginLabel}\n{input}\n{labelTitle}\n{endLabel}\n</div>\n{error}\n{endWrapper}\n{hint}";
/**
* @var string the template for radio buttons in horizontal layout
*/
public $horizontalRadioTemplate = "{beginWrapper}\n<div class=\"radio\">\n{beginLabel}\n{input}\n{labelTitle}\n{endLabel}\n</div>\n{error}\n{endWrapper}\n{hint}";
/**
* @var string the template for inline checkboxLists
*/
public $inlineCheckboxListTemplate = "{label}\n{beginWrapper}\n{input}\n{error}\n{endWrapper}\n{hint}";
/**
* @var string the template for inline radioLists
*/
public $inlineRadioListTemplate = "{label}\n{beginWrapper}\n{input}\n{error}\n{endWrapper}\n{hint}";
/**
* @var bool whether to render the error. Default is `true` except for layout `inline`.
*/
public $enableError = true;
/**
* @var bool whether to render the label. Default is `true`.
*/
public $enableLabel = true;
/**
* {@inheritdoc}
*/
public function __construct($config = [])
{
$layoutConfig = $this->createLayoutConfig($config);
$config = ArrayHelper::merge($layoutConfig, $config);
parent::__construct($config);
}
/**
* {@inheritdoc}
*/
public function render($content = null)
{
if ($content === null) {
if (!isset($this->parts['{beginWrapper}'])) {
$options = $this->wrapperOptions;
$tag = ArrayHelper::remove($options, 'tag', 'div');
$this->parts['{beginWrapper}'] = Html::beginTag($tag, $options);
$this->parts['{endWrapper}'] = Html::endTag($tag);
}
if ($this->enableLabel === false) {
$this->parts['{label}'] = '';
$this->parts['{beginLabel}'] = '';
$this->parts['{labelTitle}'] = '';
$this->parts['{endLabel}'] = '';
} elseif (!isset($this->parts['{beginLabel}'])) {
$this->renderLabelParts();
}
if ($this->enableError === false) {
$this->parts['{error}'] = '';
}
if ($this->inputTemplate) {
$input = isset($this->parts['{input}']) ?
$this->parts['{input}'] : Html::activeTextInput($this->model, $this->attribute, $this->inputOptions);
$this->parts['{input}'] = strtr($this->inputTemplate, ['{input}' => $input]);
}
}
return parent::render($content);
}
/**
* {@inheritdoc}
*/
public function checkbox($options = [], $enclosedByLabel = true)
{
if ($enclosedByLabel) {
if (!isset($options['template'])) {
$this->template = $this->form->layout === 'horizontal' ?
$this->horizontalCheckboxTemplate : $this->checkboxTemplate;
} else {
$this->template = $options['template'];
unset($options['template']);
}
if (isset($options['label'])) {
$this->parts['{labelTitle}'] = $options['label'];
}
if ($this->form->layout === 'horizontal') {
Html::addCssClass($this->wrapperOptions, $this->horizontalCssClasses['offset']);
}
$this->labelOptions['class'] = null;
}
return parent::checkbox($options, false);
}
/**
* {@inheritdoc}
*/
public function radio($options = [], $enclosedByLabel = true)
{
if ($enclosedByLabel) {
if (!isset($options['template'])) {
$this->template = $this->form->layout === 'horizontal' ?
$this->horizontalRadioTemplate : $this->radioTemplate;
} else {
$this->template = $options['template'];
unset($options['template']);
}
if (isset($options['label'])) {
$this->parts['{labelTitle}'] = $options['label'];
}
if ($this->form->layout === 'horizontal') {
Html::addCssClass($this->wrapperOptions, $this->horizontalCssClasses['offset']);
}
$this->labelOptions['class'] = null;
}
return parent::radio($options, false);
}
/**
* {@inheritdoc}
*/
public function checkboxList($items, $options = [])
{
if ($this->inline) {
if (!isset($options['template'])) {
$this->template = $this->inlineCheckboxListTemplate;
} else {
$this->template = $options['template'];
unset($options['template']);
}
if (!isset($options['itemOptions'])) {
$options['itemOptions'] = [
'labelOptions' => ['class' => 'checkbox-inline'],
];
}
} elseif (!isset($options['item'])) {
$itemOptions = isset($options['itemOptions']) ? $options['itemOptions'] : [];
$encode = ArrayHelper::getValue($options, 'encode', true);
$options['item'] = function ($index, $label, $name, $checked, $value) use ($itemOptions, $encode) {
$options = array_merge([
'label' => $encode ? Html::encode($label) : $label,
'value' => $value
], $itemOptions);
return '<div class="checkbox">' . Html::checkbox($name, $checked, $options) . '</div>';
};
}
parent::checkboxList($items, $options);
return $this;
}
/**
* {@inheritdoc}
*/
public function radioList($items, $options = [])
{
if ($this->inline) {
if (!isset($options['template'])) {
$this->template = $this->inlineRadioListTemplate;
} else {
$this->template = $options['template'];
unset($options['template']);
}
if (!isset($options['itemOptions'])) {
$options['itemOptions'] = [
'labelOptions' => ['class' => 'radio-inline'],
];
}
} elseif (!isset($options['item'])) {
$itemOptions = isset($options['itemOptions']) ? $options['itemOptions'] : [];
$encode = ArrayHelper::getValue($options, 'encode', true);
$options['item'] = function ($index, $label, $name, $checked, $value) use ($itemOptions, $encode) {
$options = array_merge([
'label' => $encode ? Html::encode($label) : $label,
'value' => $value
], $itemOptions);
return '<div class="radio">' . Html::radio($name, $checked, $options) . '</div>';
};
}
parent::radioList($items, $options);
return $this;
}
/**
* Renders Bootstrap static form control.
* @param array $options the tag options in terms of name-value pairs. These will be rendered as
* the attributes of the resulting tag. There are also a special options:
*
* - encode: bool, whether value should be HTML-encoded or not.
*
* @return $this the field object itself
* @since 2.0.5
* @see http://getbootstrap.com/css/#forms-controls-static
*/
public function staticControl($options = [])
{
$this->adjustLabelFor($options);
$this->parts['{input}'] = Html::activeStaticControl($this->model, $this->attribute, $options);
return $this;
}
/**
* {@inheritdoc}
*/
public function label($label = null, $options = [])
{
if (is_bool($label)) {
$this->enableLabel = $label;
if ($label === false && $this->form->layout === 'horizontal') {
Html::addCssClass($this->wrapperOptions, $this->horizontalCssClasses['offset']);
}
} else {
$this->enableLabel = true;
$this->renderLabelParts($label, $options);
parent::label($label, $options);
}
return $this;
}
/**
* @param bool $value whether to render a inline list
* @return $this the field object itself
* Make sure you call this method before [[checkboxList()]] or [[radioList()]] to have any effect.
*/
public function inline($value = true)
{
$this->inline = (bool) $value;
return $this;
}
/**
* @param array $instanceConfig the configuration passed to this instance's constructor
* @return array the layout specific default configuration for this instance
*/
protected function createLayoutConfig($instanceConfig)
{
$config = [
'hintOptions' => [
'tag' => 'p',
'class' => 'help-block',
],
'errorOptions' => [
'tag' => 'p',
'class' => 'help-block help-block-error',
],
'inputOptions' => [
'class' => 'form-control',
],
];
$layout = $instanceConfig['form']->layout;
if ($layout === 'horizontal') {
$config['template'] = "{label}\n{beginWrapper}\n{input}\n{error}\n{endWrapper}\n{hint}";
$cssClasses = array_merge([
'offset' => 'col-sm-offset-3',
'label' => 'col-sm-3',
'wrapper' => 'col-sm-6',
'error' => '',
'hint' => 'col-sm-3',
], $this->horizontalCssClasses);
if (isset($instanceConfig['horizontalCssClasses'])) {
$cssClasses = ArrayHelper::merge($cssClasses, $instanceConfig['horizontalCssClasses']);
}
$config['horizontalCssClasses'] = $cssClasses;
$config['wrapperOptions'] = ['class' => $cssClasses['wrapper']];
$config['labelOptions'] = ['class' => 'control-label ' . $cssClasses['label']];
$config['errorOptions']['class'] = 'help-block help-block-error ' . $cssClasses['error'];
$config['hintOptions']['class'] = 'help-block ' . $cssClasses['hint'];
} elseif ($layout === 'inline') {
$config['labelOptions'] = ['class' => 'sr-only'];
$config['enableError'] = false;
}
return $config;
}
/**
* @param string|null $label the label or null to use model label
* @param array $options the tag options
*/
protected function renderLabelParts($label = null, $options = [])
{
$options = array_merge($this->labelOptions, $options);
if ($label === null) {
if (isset($options['label'])) {
$label = $options['label'];
unset($options['label']);
} else {
$attribute = Html::getAttributeName($this->attribute);
$label = Html::encode($this->model->getAttributeLabel($attribute));
}
}
if (!isset($options['for'])) {
$options['for'] = Html::getInputId($this->model, $this->attribute);
}
$this->parts['{beginLabel}'] = Html::beginTag('label', $options);
$this->parts['{endLabel}'] = Html::endTag('label');
if (!isset($this->parts['{labelTitle}'])) {
$this->parts['{labelTitle}'] = $label;
}
}
}

View File

@@ -0,0 +1,109 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\bootstrap;
use Yii;
use yii\base\InvalidConfigException;
/**
* A Bootstrap 3 enhanced version of [[\yii\widgets\ActiveForm]].
*
* This class mainly adds the [[layout]] property to choose a Bootstrap 3 form layout.
* So for example to render a horizontal form you would:
*
* ```php
* use yii\bootstrap\ActiveForm;
*
* $form = ActiveForm::begin(['layout' => 'horizontal'])
* ```
*
* This will set default values for the [[ActiveField]]
* to render horizontal form fields. In particular the [[ActiveField::template|template]]
* is set to `{label} {beginWrapper} {input} {error} {endWrapper} {hint}` and the
* [[ActiveField::horizontalCssClasses|horizontalCssClasses]] are set to:
*
* ```php
* [
* 'offset' => 'col-sm-offset-3',
* 'label' => 'col-sm-3',
* 'wrapper' => 'col-sm-6',
* 'error' => '',
* 'hint' => 'col-sm-3',
* ]
* ```
*
* To get a different column layout in horizontal mode you can modify those options
* through [[fieldConfig]]:
*
* ```php
* $form = ActiveForm::begin([
* 'layout' => 'horizontal',
* 'fieldConfig' => [
* 'template' => "{label}\n{beginWrapper}\n{input}\n{hint}\n{error}\n{endWrapper}",
* 'horizontalCssClasses' => [
* 'label' => 'col-sm-4',
* 'offset' => 'col-sm-offset-4',
* 'wrapper' => 'col-sm-8',
* 'error' => '',
* 'hint' => '',
* ],
* ],
* ]);
* ```
*
* @see ActiveField for details on the [[fieldConfig]] options
* @see http://getbootstrap.com/css/#forms
*
* @author Michael Härtl <haertl.mike@gmail.com>
* @since 2.0
*/
class ActiveForm extends \yii\widgets\ActiveForm
{
/**
* @var string the default field class name when calling [[field()]] to create a new field.
* @see fieldConfig
*/
public $fieldClass = 'yii\bootstrap\ActiveField';
/**
* @var array HTML attributes for the form tag. Default is `[]`.
*/
public $options = [];
/**
* @var string the form layout. Either 'default', 'horizontal' or 'inline'.
* By choosing a layout, an appropriate default field configuration is applied. This will
* render the form fields with slightly different markup for each layout. You can
* override these defaults through [[fieldConfig]].
* @see \yii\bootstrap\ActiveField for details on Bootstrap 3 field configuration
*/
public $layout = 'default';
/**
* {@inheritdoc}
*/
public function init()
{
if (!in_array($this->layout, ['default', 'horizontal', 'inline'])) {
throw new InvalidConfigException('Invalid layout type: ' . $this->layout);
}
if ($this->layout !== 'default') {
Html::addCssClass($this->options, 'form-' . $this->layout);
}
parent::init();
}
/**
* {@inheritdoc}
* @return ActiveField the created ActiveField object
*/
public function field($model, $attribute, $options = [])
{
return parent::field($model, $attribute, $options);
}
}

View File

@@ -0,0 +1,148 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\bootstrap;
use Yii;
use yii\helpers\ArrayHelper;
/**
* Alert renders an alert bootstrap component.
*
* For example,
*
* ```php
* echo Alert::widget([
* 'options' => [
* 'class' => 'alert-info',
* ],
* 'body' => 'Say hello...',
* ]);
* ```
*
* The following example will show the content enclosed between the [[begin()]]
* and [[end()]] calls within the alert box:
*
* ```php
* Alert::begin([
* 'options' => [
* 'class' => 'alert-warning',
* ],
* ]);
*
* echo 'Say hello...';
*
* Alert::end();
* ```
*
* @see http://getbootstrap.com/components/#alerts
* @author Antonio Ramirez <amigo.cobos@gmail.com>
* @since 2.0
*/
class Alert extends Widget
{
/**
* @var string the body content in the alert component. Note that anything between
* the [[begin()]] and [[end()]] calls of the Alert widget will also be treated
* as the body content, and will be rendered before this.
*/
public $body;
/**
* @var array|false the options for rendering the close button tag.
* The close button is displayed in the header of the modal window. Clicking
* on the button will hide the modal window. If this is false, no close button will be rendered.
*
* The following special options are supported:
*
* - tag: string, the tag name of the button. Defaults to 'button'.
* - label: string, the label of the button. Defaults to '&times;'.
*
* The rest of the options will be rendered as the HTML attributes of the button tag.
* Please refer to the [Alert documentation](http://getbootstrap.com/components/#alerts)
* for the supported HTML attributes.
*/
public $closeButton = [];
/**
* Initializes the widget.
*/
public function init()
{
parent::init();
$this->initOptions();
echo Html::beginTag('div', $this->options) . "\n";
echo $this->renderBodyBegin() . "\n";
}
/**
* Renders the widget.
*/
public function run()
{
echo "\n" . $this->renderBodyEnd();
echo "\n" . Html::endTag('div');
$this->registerPlugin('alert');
}
/**
* Renders the close button if any before rendering the content.
* @return string the rendering result
*/
protected function renderBodyBegin()
{
return $this->renderCloseButton();
}
/**
* Renders the alert body (if any).
* @return string the rendering result
*/
protected function renderBodyEnd()
{
return $this->body . "\n";
}
/**
* Renders the close button.
* @return string the rendering result
*/
protected function renderCloseButton()
{
if (($closeButton = $this->closeButton) !== false) {
$tag = ArrayHelper::remove($closeButton, 'tag', 'button');
$label = ArrayHelper::remove($closeButton, 'label', '&times;');
if ($tag === 'button' && !isset($closeButton['type'])) {
$closeButton['type'] = 'button';
}
return Html::tag($tag, $label, $closeButton);
} else {
return null;
}
}
/**
* Initializes the widget options.
* This method sets the default values for various options.
*/
protected function initOptions()
{
Html::addCssClass($this->options, ['alert', 'fade', 'in']);
if ($this->closeButton !== false) {
$this->closeButton = array_merge([
'data-dismiss' => 'alert',
'aria-hidden' => 'true',
'class' => 'close',
], $this->closeButton);
}
}
}

View File

@@ -0,0 +1,145 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\bootstrap;
use yii\helpers\ArrayHelper;
/**
* BaseHtml provides concrete implementation for [[Html]].
*
* Do not use BaseHtml. Use [[Html]] instead.
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0.5
*/
class BaseHtml extends \yii\helpers\Html
{
/**
* Composes icon HTML for bootstrap Glyphicons.
* @param string $name icon short name, for example: 'star'
* @param array $options the tag options in terms of name-value pairs. These will be rendered as
* the attributes of the resulting tag. There are also a special options:
*
* - tag: string, tag to be rendered, by default 'span' is used.
* - prefix: string, prefix which should be used to compose tag class, by default 'glyphicon glyphicon-' is used.
*
* @return string icon HTML.
* @see http://getbootstrap.com/components/#glyphicons
*/
public static function icon($name, $options = [])
{
$tag = ArrayHelper::remove($options, 'tag', 'span');
$classPrefix = ArrayHelper::remove($options, 'prefix', 'glyphicon glyphicon-');
static::addCssClass($options, $classPrefix . $name);
return static::tag($tag, '', $options);
}
/**
* Renders Bootstrap static form control.
*
* By default value will be HTML-encoded using [[encode()]], you may control this behavior
* via 'encode' option.
* @param string $value static control value.
* @param array $options the tag options in terms of name-value pairs. These will be rendered as
* the attributes of the resulting tag. There are also a special options:
*
* - encode: bool, whether value should be HTML-encoded or not.
*
* @return string generated HTML
* @see http://getbootstrap.com/css/#forms-controls-static
*/
public static function staticControl($value, $options = [])
{
static::addCssClass($options, 'form-control-static');
$value = (string) $value;
if (isset($options['encode'])) {
$encode = $options['encode'];
unset($options['encode']);
} else {
$encode = true;
}
return static::tag('p', $encode ? static::encode($value) : $value, $options);
}
/**
* Generates a Bootstrap static form control for the given model attribute.
* @param \yii\base\Model $model the model object.
* @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format
* about attribute expression.
* @param array $options the tag options in terms of name-value pairs. See [[staticControl()]] for details.
* @return string generated HTML
* @see staticControl()
*/
public static function activeStaticControl($model, $attribute, $options = [])
{
if (isset($options['value'])) {
$value = $options['value'];
unset($options['value']);
} else {
$value = static::getAttributeValue($model, $attribute);
}
return static::staticControl($value, $options);
}
/**
* {@inheritdoc}
* @since 2.0.8
*/
public static function radioList($name, $selection = null, $items = [], $options = [])
{
if (!isset($options['item'])) {
$itemOptions = ArrayHelper::remove($options, 'itemOptions', []);
$encode = ArrayHelper::getValue($options, 'encode', true);
$options['item'] = function ($index, $label, $name, $checked, $value) use ($itemOptions, $encode) {
$options = array_merge([
'label' => $encode ? static::encode($label) : $label,
'value' => $value
], $itemOptions);
return '<div class="radio">' . static::radio($name, $checked, $options) . '</div>';
};
}
return parent::radioList($name, $selection, $items, $options);
}
/**
* {@inheritdoc}
* @since 2.0.8
*/
public static function checkboxList($name, $selection = null, $items = [], $options = [])
{
if (!isset($options['item'])) {
$itemOptions = ArrayHelper::remove($options, 'itemOptions', []);
$encode = ArrayHelper::getValue($options, 'encode', true);
$options['item'] = function ($index, $label, $name, $checked, $value) use ($itemOptions, $encode) {
$options = array_merge([
'label' => $encode ? static::encode($label) : $label,
'value' => $value
], $itemOptions);
return '<div class="checkbox">' . Html::checkbox($name, $checked, $options) . '</div>';
};
}
return parent::checkboxList($name, $selection, $items, $options);
}
/**
* {@inheritdoc}
* @since 2.0.8
*/
public static function error($model, $attribute, $options = [])
{
if (!array_key_exists('tag', $options)) {
$options['tag'] = 'p';
}
if (!array_key_exists('class', $options)) {
$options['class'] = 'help-block help-block-error';
}
return parent::error($model, $attribute, $options);
}
}

View File

@@ -0,0 +1,24 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\bootstrap;
use yii\web\AssetBundle;
/**
* Asset bundle for the Twitter bootstrap css files.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class BootstrapAsset extends AssetBundle
{
public $sourcePath = '@bower/bootstrap/dist';
public $css = [
'css/bootstrap.css',
];
}

View File

@@ -0,0 +1,28 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\bootstrap;
use yii\web\AssetBundle;
/**
* Asset bundle for the Twitter bootstrap javascript files.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class BootstrapPluginAsset extends AssetBundle
{
public $sourcePath = '@bower/bootstrap/dist';
public $js = [
'js/bootstrap.js',
];
public $depends = [
'yii\web\JqueryAsset',
'yii\bootstrap\BootstrapAsset',
];
}

View File

@@ -0,0 +1,27 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\bootstrap;
use yii\web\AssetBundle;
/**
* Asset bundle for the Twitter bootstrap default theme.
*
* @author Alexander Makarov <sam@rmcreative.ru>
* @since 2.0
*/
class BootstrapThemeAsset extends AssetBundle
{
public $sourcePath = '@bower/bootstrap/dist';
public $css = [
'css/bootstrap-theme.css',
];
public $depends = [
'yii\bootstrap\BootstrapAsset',
];
}

View File

@@ -0,0 +1,107 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\bootstrap;
use Yii;
use yii\helpers\Json;
/**
* BootstrapWidgetTrait is the trait, which provides basic for all bootstrap widgets features.
*
* Note: class, which uses this trait must declare public field named `options` with the array default value:
*
* ```php
* class MyWidget extends \yii\base\Widget
* {
* use BootstrapWidgetTrait;
*
* public $options = [];
* }
* ```
*
* This field is not present in the trait in order to avoid possible PHP Fatal error on definition conflict.
*
* @author Antonio Ramirez <amigo.cobos@gmail.com>
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0.6
*/
trait BootstrapWidgetTrait
{
/**
* @var array the options for the underlying Bootstrap JS plugin.
* Please refer to the corresponding Bootstrap plugin Web page for possible options.
* For example, [this page](http://getbootstrap.com/javascript/#modals) shows
* how to use the "Modal" plugin and the supported options (e.g. "remote").
*/
public $clientOptions = [];
/**
* @var array the event handlers for the underlying Bootstrap JS plugin.
* Please refer to the corresponding Bootstrap plugin Web page for possible events.
* For example, [this page](http://getbootstrap.com/javascript/#modals) shows
* how to use the "Modal" plugin and the supported events (e.g. "shown").
*/
public $clientEvents = [];
/**
* Initializes the widget.
* This method will register the bootstrap asset bundle. If you override this method,
* make sure you call the parent implementation first.
*/
public function init()
{
parent::init();
if (!isset($this->options['id'])) {
$this->options['id'] = $this->getId();
}
}
/**
* Registers a specific Bootstrap plugin and the related events
* @param string $name the name of the Bootstrap plugin
*/
protected function registerPlugin($name)
{
$view = $this->getView();
BootstrapPluginAsset::register($view);
$id = $this->options['id'];
if ($this->clientOptions !== false) {
$options = empty($this->clientOptions) ? '' : Json::htmlEncode($this->clientOptions);
$js = "jQuery('#$id').$name($options);";
$view->registerJs($js);
}
$this->registerClientEvents();
}
/**
* Registers JS event handlers that are listed in [[clientEvents]].
* @since 2.0.2
*/
protected function registerClientEvents()
{
if (!empty($this->clientEvents)) {
$id = $this->options['id'];
$js = [];
foreach ($this->clientEvents as $event => $handler) {
$js[] = "jQuery('#$id').on('$event', $handler);";
}
$this->getView()->registerJs(implode("\n", $js));
}
}
/**
* @return \yii\web\View the view object that can be used to render views or view files.
* @see \yii\base\Widget::getView()
*/
abstract function getView();
}

View File

@@ -0,0 +1,60 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\bootstrap;
/**
* Button renders a bootstrap button.
*
* For example,
*
* ```php
* echo Button::widget([
* 'label' => 'Action',
* 'options' => ['class' => 'btn-lg'],
* ]);
* ```
* @see http://getbootstrap.com/javascript/#buttons
* @author Antonio Ramirez <amigo.cobos@gmail.com>
* @since 2.0
*/
class Button extends Widget
{
/**
* @var string the tag to use to render the button
*/
public $tagName = 'button';
/**
* @var string the button label
*/
public $label = 'Button';
/**
* @var bool whether the label should be HTML-encoded.
*/
public $encodeLabel = true;
/**
* Initializes the widget.
* If you override this method, make sure you call the parent implementation first.
*/
public function init()
{
parent::init();
$this->clientOptions = false;
Html::addCssClass($this->options, ['widget' => 'btn']);
}
/**
* Renders the widget.
*/
public function run()
{
$this->registerPlugin('button');
return Html::tag($this->tagName, $this->encodeLabel ? Html::encode($this->label) : $this->label, $this->options);
}
}

View File

@@ -0,0 +1,160 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\bootstrap;
use yii\helpers\ArrayHelper;
use yii\helpers\Url;
/**
* ButtonDropdown renders a group or split button dropdown bootstrap component.
*
* For example,
*
* ```php
* // a button group using Dropdown widget
* echo ButtonDropdown::widget([
* 'label' => 'Action',
* 'dropdown' => [
* 'items' => [
* ['label' => 'DropdownA', 'url' => '/'],
* ['label' => 'DropdownB', 'url' => '#'],
* ],
* ],
* ]);
* ```
* @see http://getbootstrap.com/javascript/#buttons
* @see http://getbootstrap.com/components/#btn-dropdowns
* @author Antonio Ramirez <amigo.cobos@gmail.com>
* @since 2.0
*/
class ButtonDropdown extends Widget
{
/**
* @var string the button label
*/
public $label = 'Button';
/**
* @var array the HTML attributes for the container tag. The following special options are recognized:
*
* - tag: string, defaults to "div", the name of the container tag.
*
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
* @since 2.0.1
*/
public $containerOptions = [];
/**
* @var array the HTML attributes of the button.
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
*/
public $options = [];
/**
* @var array the configuration array for [[Dropdown]].
*/
public $dropdown = [];
/**
* @var bool whether to display a group of split-styled button group.
*/
public $split = false;
/**
* @var string the tag to use to render the button
*/
public $tagName = 'button';
/**
* @var bool whether the label should be HTML-encoded.
*/
public $encodeLabel = true;
/**
* @var string name of a class to use for rendering dropdowns withing this widget. Defaults to [[Dropdown]].
* @since 2.0.7
*/
public $dropdownClass = 'yii\bootstrap\Dropdown';
/**
* Renders the widget.
*/
public function run()
{
// @todo use [[options]] instead of [[containerOptions]] and introduce [[buttonOptions]] before 2.1 release
Html::addCssClass($this->containerOptions, ['widget' => 'btn-group']);
$options = $this->containerOptions;
$tag = ArrayHelper::remove($options, 'tag', 'div');
$this->registerPlugin('dropdown');
return implode("\n", [
Html::beginTag($tag, $options),
$this->renderButton(),
$this->renderDropdown(),
Html::endTag($tag)
]);
}
/**
* Generates the button dropdown.
* @return string the rendering result.
*/
protected function renderButton()
{
Html::addCssClass($this->options, ['widget' => 'btn']);
$label = $this->label;
if ($this->encodeLabel) {
$label = Html::encode($label);
}
if ($this->split) {
$options = $this->options;
$this->options['data-toggle'] = 'dropdown';
Html::addCssClass($this->options, ['toggle' => 'dropdown-toggle']);
unset($options['id']);
$splitButton = Button::widget([
'label' => '<span class="caret"></span>',
'encodeLabel' => false,
'options' => $this->options,
'view' => $this->getView(),
]);
} else {
$label .= ' <span class="caret"></span>';
$options = $this->options;
Html::addCssClass($options, ['toggle' => 'dropdown-toggle']);
$options['data-toggle'] = 'dropdown';
$splitButton = '';
}
if (isset($options['href'])) {
if (is_array($options['href'])) {
$options['href'] = Url::to($options['href']);
}
} else {
if ($this->tagName === 'a') {
$options['href'] = '#';
}
}
return Button::widget([
'tagName' => $this->tagName,
'label' => $label,
'options' => $options,
'encodeLabel' => false,
'view' => $this->getView(),
]) . "\n" . $splitButton;
}
/**
* Generates the dropdown menu.
* @return string the rendering result.
*/
protected function renderDropdown()
{
$config = $this->dropdown;
$config['clientOptions'] = false;
$config['view'] = $this->getView();
/** @var Widget $dropdownClass */
$dropdownClass = $this->dropdownClass;
return $dropdownClass::widget($config);
}
}

View File

@@ -0,0 +1,106 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\bootstrap;
use yii\helpers\ArrayHelper;
/**
* ButtonGroup renders a button group bootstrap component.
*
* For example,
*
* ```php
* // a button group with items configuration
* echo ButtonGroup::widget([
* 'buttons' => [
* ['label' => 'A'],
* ['label' => 'B'],
* ['label' => 'C', 'visible' => false],
* ]
* ]);
*
* // button group with an item as a string
* echo ButtonGroup::widget([
* 'buttons' => [
* Button::widget(['label' => 'A']),
* ['label' => 'B'],
* ]
* ]);
* ```
*
* Pressing on the button should be handled via JavaScript. See the following for details:
*
* @see http://getbootstrap.com/javascript/#buttons
* @see http://getbootstrap.com/components/#btn-groups
*
* @author Antonio Ramirez <amigo.cobos@gmail.com>
* @since 2.0
*/
class ButtonGroup extends Widget
{
/**
* @var array list of buttons. Each array element represents a single button
* which can be specified as a string or an array of the following structure:
*
* - label: string, required, the button label.
* - options: array, optional, the HTML attributes of the button.
* - visible: bool, optional, whether this button is visible. Defaults to true.
*/
public $buttons = [];
/**
* @var bool whether to HTML-encode the button labels.
*/
public $encodeLabels = true;
/**
* Initializes the widget.
* If you override this method, make sure you call the parent implementation first.
*/
public function init()
{
parent::init();
Html::addCssClass($this->options, ['widget' => 'btn-group']);
}
/**
* Renders the widget.
*/
public function run()
{
BootstrapAsset::register($this->getView());
return Html::tag('div', $this->renderButtons(), $this->options);
}
/**
* Generates the buttons that compound the group as specified on [[buttons]].
* @return string the rendering result.
*/
protected function renderButtons()
{
$buttons = [];
foreach ($this->buttons as $button) {
if (is_array($button)) {
$visible = ArrayHelper::remove($button, 'visible', true);
if ($visible === false) {
continue;
}
$button['view'] = $this->getView();
if (!isset($button['encodeLabel'])) {
$button['encodeLabel'] = $this->encodeLabels;
}
$buttons[] = Button::widget($button);
} else {
$buttons[] = $button;
}
}
return implode("\n", $buttons);
}
}

View File

@@ -0,0 +1,180 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\bootstrap;
use yii\base\InvalidConfigException;
use yii\helpers\ArrayHelper;
/**
* Carousel renders a carousel bootstrap javascript component.
*
* For example:
*
* ```php
* echo Carousel::widget([
* 'items' => [
* // the item contains only the image
* '<img src="http://twitter.github.io/bootstrap/assets/img/bootstrap-mdo-sfmoma-01.jpg"/>',
* // equivalent to the above
* ['content' => '<img src="http://twitter.github.io/bootstrap/assets/img/bootstrap-mdo-sfmoma-02.jpg"/>'],
* // the item contains both the image and the caption
* [
* 'content' => '<img src="http://twitter.github.io/bootstrap/assets/img/bootstrap-mdo-sfmoma-03.jpg"/>',
* 'caption' => '<h4>This is title</h4><p>This is the caption text</p>',
* 'options' => [...],
* ],
* ]
* ]);
* ```
*
* @see http://getbootstrap.com/javascript/#carousel
* @author Antonio Ramirez <amigo.cobos@gmail.com>
* @since 2.0
*/
class Carousel extends Widget
{
/**
* @var array|bool the labels for the previous and the next control buttons.
* If false, it means the previous and the next control buttons should not be displayed.
*/
public $controls = ['&lsaquo;', '&rsaquo;'];
/**
* @var bool whether carousel indicators (<ol> tag with anchors to items) should be displayed or not.
*/
public $showIndicators = true;
/**
* @var array list of slides in the carousel. Each array element represents a single
* slide with the following structure:
*
* ```php
* [
* // required, slide content (HTML), such as an image tag
* 'content' => '<img src="http://twitter.github.io/bootstrap/assets/img/bootstrap-mdo-sfmoma-01.jpg"/>',
* // optional, the caption (HTML) of the slide
* 'caption' => '<h4>This is title</h4><p>This is the caption text</p>',
* // optional the HTML attributes of the slide container
* 'options' => [],
* ]
* ```
*/
public $items = [];
/**
* Initializes the widget.
*/
public function init()
{
parent::init();
Html::addCssClass($this->options, ['widget' => 'carousel']);
}
/**
* Renders the widget.
*/
public function run()
{
$this->registerPlugin('carousel');
return implode("\n", [
Html::beginTag('div', $this->options),
$this->renderIndicators(),
$this->renderItems(),
$this->renderControls(),
Html::endTag('div')
]) . "\n";
}
/**
* Renders carousel indicators.
* @return string the rendering result
*/
public function renderIndicators()
{
if ($this->showIndicators === false) {
return '';
}
$indicators = [];
for ($i = 0, $count = count($this->items); $i < $count; $i++) {
$options = ['data-target' => '#' . $this->options['id'], 'data-slide-to' => $i];
if ($i === 0) {
Html::addCssClass($options, 'active');
}
$indicators[] = Html::tag('li', '', $options);
}
return Html::tag('ol', implode("\n", $indicators), ['class' => 'carousel-indicators']);
}
/**
* Renders carousel items as specified on [[items]].
* @return string the rendering result
*/
public function renderItems()
{
$items = [];
for ($i = 0, $count = count($this->items); $i < $count; $i++) {
$items[] = $this->renderItem($this->items[$i], $i);
}
return Html::tag('div', implode("\n", $items), ['class' => 'carousel-inner']);
}
/**
* Renders a single carousel item
* @param string|array $item a single item from [[items]]
* @param int $index the item index as the first item should be set to `active`
* @return string the rendering result
* @throws InvalidConfigException if the item is invalid
*/
public function renderItem($item, $index)
{
if (is_string($item)) {
$content = $item;
$caption = null;
$options = [];
} elseif (isset($item['content'])) {
$content = $item['content'];
$caption = ArrayHelper::getValue($item, 'caption');
if ($caption !== null) {
$caption = Html::tag('div', $caption, ['class' => 'carousel-caption']);
}
$options = ArrayHelper::getValue($item, 'options', []);
} else {
throw new InvalidConfigException('The "content" option is required.');
}
Html::addCssClass($options, ['widget' => 'item']);
if ($index === 0) {
Html::addCssClass($options, 'active');
}
return Html::tag('div', $content . "\n" . $caption, $options);
}
/**
* Renders previous and next control buttons.
* @throws InvalidConfigException if [[controls]] is invalid.
*/
public function renderControls()
{
if (isset($this->controls[0], $this->controls[1])) {
return Html::a($this->controls[0], '#' . $this->options['id'], [
'class' => 'left carousel-control',
'data-slide' => 'prev',
]) . "\n"
. Html::a($this->controls[1], '#' . $this->options['id'], [
'class' => 'right carousel-control',
'data-slide' => 'next',
]);
} elseif ($this->controls === false) {
return '';
} else {
throw new InvalidConfigException('The "controls" property must be either false or an array of two elements.');
}
}
}

View File

@@ -0,0 +1,233 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\bootstrap;
use yii\base\InvalidConfigException;
use yii\helpers\ArrayHelper;
/**
* Collapse renders an accordion bootstrap javascript component.
*
* For example:
*
* ```php
* echo Collapse::widget([
* 'items' => [
* // equivalent to the above
* [
* 'label' => 'Collapsible Group Item #1',
* 'content' => 'Anim pariatur cliche...',
* // open its content by default
* 'contentOptions' => ['class' => 'in']
* ],
* // another group item
* [
* 'label' => 'Collapsible Group Item #1',
* 'content' => 'Anim pariatur cliche...',
* 'contentOptions' => [...],
* 'options' => [...],
* ],
* // if you want to swap out .panel-body with .list-group, you may use the following
* [
* 'label' => 'Collapsible Group Item #1',
* 'content' => [
* 'Anim pariatur cliche...',
* 'Anim pariatur cliche...'
* ],
* 'contentOptions' => [...],
* 'options' => [...],
* 'footer' => 'Footer' // the footer label in list-group
* ],
* ]
* ]);
* ```
*
* @see http://getbootstrap.com/javascript/#collapse
* @author Antonio Ramirez <amigo.cobos@gmail.com>
* @since 2.0
*/
class Collapse extends Widget
{
/**
* @var array list of groups in the collapse widget. Each array element represents a single
* group with the following structure:
*
* - label: string, required, the group header label.
* - encode: bool, optional, whether this label should be HTML-encoded. This param will override
* global `$this->encodeLabels` param.
* - content: array|string|object, required, the content (HTML) of the group
* - options: array, optional, the HTML attributes of the group
* - contentOptions: optional, the HTML attributes of the group's content
*
* Since version 2.0.7 you may also specify this property as key-value pairs, where the key refers to the
* `label` and the value refers to `content`. If value is a string it is interpreted as label. If it is
* an array, it is interpreted as explained above.
*
* For example:
*
* ```php
* echo Collapse::widget([
* 'items' => [
* 'Introduction' => 'This is the first collapsable menu',
* 'Second panel' => [
* 'content' => 'This is the second collapsable menu',
* ],
* [
* 'label' => 'Third panel',
* 'content' => 'This is the third collapsable menu',
* ],
* ]
* ])
* ```
*/
public $items = [];
/**
* @var bool whether the labels for header items should be HTML-encoded.
*/
public $encodeLabels = true;
/**
* @var bool whether to close other items if an item is opened. Defaults to `true` which causes an
* accordion effect. Set this to `false` to allow keeping multiple items open at once.
* @since 2.0.7
*/
public $autoCloseItems = true;
/**
* @var string the HTML options for the item toggle tag. Key 'tag' might be used here for the tag name specification.
* For example:
*
* ```php
* [
* 'tag' => 'div',
* 'class' => 'custom-toggle',
* ]
* ```
*
* @since 2.0.8
*/
public $itemToggleOptions = [];
/**
* Initializes the widget.
*/
public function init()
{
parent::init();
Html::addCssClass($this->options, ['widget' => 'panel-group']);
}
/**
* Renders the widget.
*/
public function run()
{
$this->registerPlugin('collapse');
return implode("\n", [
Html::beginTag('div', $this->options),
$this->renderItems(),
Html::endTag('div')
]) . "\n";
}
/**
* Renders collapsible items as specified on [[items]].
* @throws InvalidConfigException if label isn't specified
* @return string the rendering result
*/
public function renderItems()
{
$items = [];
$index = 0;
foreach ($this->items as $key => $item) {
if (!is_array($item)) {
$item = ['content' => $item];
}
if (!array_key_exists('label', $item)) {
if (is_int($key)) {
throw new InvalidConfigException("The 'label' option is required.");
} else {
$item['label'] = $key;
}
}
$header = $item['label'];
$options = ArrayHelper::getValue($item, 'options', []);
Html::addCssClass($options, ['panel' => 'panel', 'widget' => 'panel-default']);
$items[] = Html::tag('div', $this->renderItem($header, $item, ++$index), $options);
}
return implode("\n", $items);
}
/**
* Renders a single collapsible item group
* @param string $header a label of the item group [[items]]
* @param array $item a single item from [[items]]
* @param int $index the item index as each item group content must have an id
* @return string the rendering result
* @throws InvalidConfigException
*/
public function renderItem($header, $item, $index)
{
if (array_key_exists('content', $item)) {
$id = $this->options['id'] . '-collapse' . $index;
$options = ArrayHelper::getValue($item, 'contentOptions', []);
$options['id'] = $id;
Html::addCssClass($options, ['widget' => 'panel-collapse', 'collapse' => 'collapse']);
$encodeLabel = isset($item['encode']) ? $item['encode'] : $this->encodeLabels;
if ($encodeLabel) {
$header = Html::encode($header);
}
$itemToggleOptions = array_merge([
'tag' => 'a',
'data-toggle' => 'collapse',
], $this->itemToggleOptions);
Html::addCssClass($itemToggleOptions, ['widget' => 'collapse-toggle']);
if ($this->autoCloseItems) {
$itemToggleOptions['data-parent'] = '#' . $this->options['id'];
}
$itemToggleTag = ArrayHelper::remove($itemToggleOptions, 'tag', 'a');
if ($itemToggleTag === 'a') {
$headerToggle = Html::a($header, '#' . $id, $itemToggleOptions) . "\n";
} else {
$itemToggleOptions['data-target'] = '#' . $id;
$headerToggle = Html::tag($itemToggleTag, $header, $itemToggleOptions) . "\n";
}
$header = Html::tag('h4', $headerToggle, ['class' => 'panel-title']);
if (is_string($item['content']) || is_numeric($item['content']) || is_object($item['content'])) {
$content = Html::tag('div', $item['content'], ['class' => 'panel-body']) . "\n";
} elseif (is_array($item['content'])) {
$content = Html::ul($item['content'], [
'class' => 'list-group',
'itemOptions' => [
'class' => 'list-group-item'
],
'encode' => false,
]) . "\n";
if (isset($item['footer'])) {
$content .= Html::tag('div', $item['footer'], ['class' => 'panel-footer']) . "\n";
}
} else {
throw new InvalidConfigException('The "content" option should be a string, array or object.');
}
} else {
throw new InvalidConfigException('The "content" option is required.');
}
$group = [];
$group[] = Html::tag('div', $header, ['class' => 'panel-heading']);
$group[] = Html::tag('div', $content, $options);
return implode("\n", $group);
}
}

View File

@@ -0,0 +1,143 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\bootstrap;
use yii\base\InvalidConfigException;
use yii\helpers\ArrayHelper;
/**
* Dropdown renders a Bootstrap dropdown menu component.
*
* For example,
*
* ```php
* <div class="dropdown">
* <a href="#" data-toggle="dropdown" class="dropdown-toggle">Label <b class="caret"></b></a>
* <?php
* echo Dropdown::widget([
* 'items' => [
* ['label' => 'DropdownA', 'url' => '/'],
* ['label' => 'DropdownB', 'url' => '#'],
* ],
* ]);
* ?>
* </div>
* ```
* @see http://getbootstrap.com/javascript/#dropdowns
* @author Antonio Ramirez <amigo.cobos@gmail.com>
* @since 2.0
*/
class Dropdown extends Widget
{
/**
* @var array list of menu items in the dropdown. Each array element can be either an HTML string,
* or an array representing a single menu with the following structure:
*
* - label: string, required, the label of the item link.
* - encode: bool, optional, whether to HTML-encode item label.
* - url: string|array, optional, the URL of the item link. This will be processed by [[\yii\helpers\Url::to()]].
* If not set, the item will be treated as a menu header when the item has no sub-menu.
* - visible: bool, optional, whether this menu item is visible. Defaults to true.
* - linkOptions: array, optional, the HTML attributes of the item link.
* - options: array, optional, the HTML attributes of the item.
* - items: array, optional, the submenu items. The structure is the same as this property.
* Note that Bootstrap doesn't support dropdown submenu. You have to add your own CSS styles to support it.
* - submenuOptions: array, optional, the HTML attributes for sub-menu container tag. If specified it will be
* merged with [[submenuOptions]].
*
* To insert divider use `<li role="presentation" class="divider"></li>`.
*/
public $items = [];
/**
* @var bool whether the labels for header items should be HTML-encoded.
*/
public $encodeLabels = true;
/**
* @var array|null the HTML attributes for sub-menu container tags.
* If not set - [[options]] value will be used for it.
* @since 2.0.5
*/
public $submenuOptions;
/**
* Initializes the widget.
* If you override this method, make sure you call the parent implementation first.
*/
public function init()
{
if ($this->submenuOptions === null) {
// copying of [[options]] kept for BC
// @todo separate [[submenuOptions]] from [[options]] completely before 2.1 release
$this->submenuOptions = $this->options;
unset($this->submenuOptions['id']);
}
parent::init();
Html::addCssClass($this->options, ['widget' => 'dropdown-menu']);
}
/**
* Renders the widget.
*/
public function run()
{
BootstrapPluginAsset::register($this->getView());
$this->registerClientEvents();
return $this->renderItems($this->items, $this->options);
}
/**
* Renders menu items.
* @param array $items the menu items to be rendered
* @param array $options the container HTML attributes
* @return string the rendering result.
* @throws InvalidConfigException if the label option is not specified in one of the items.
*/
protected function renderItems($items, $options = [])
{
$lines = [];
foreach ($items as $item) {
if (is_string($item)) {
$lines[] = $item;
continue;
}
if (isset($item['visible']) && !$item['visible']) {
continue;
}
if (!array_key_exists('label', $item)) {
throw new InvalidConfigException("The 'label' option is required.");
}
$encodeLabel = isset($item['encode']) ? $item['encode'] : $this->encodeLabels;
$label = $encodeLabel ? Html::encode($item['label']) : $item['label'];
$itemOptions = ArrayHelper::getValue($item, 'options', []);
$linkOptions = ArrayHelper::getValue($item, 'linkOptions', []);
$linkOptions['tabindex'] = '-1';
$url = array_key_exists('url', $item) ? $item['url'] : null;
if (empty($item['items'])) {
if ($url === null) {
$content = $label;
Html::addCssClass($itemOptions, ['widget' => 'dropdown-header']);
} else {
$content = Html::a($label, $url, $linkOptions);
}
} else {
$submenuOptions = $this->submenuOptions;
if (isset($item['submenuOptions'])) {
$submenuOptions = array_merge($submenuOptions, $item['submenuOptions']);
}
$content = Html::a($label, $url === null ? '#' : $url, $linkOptions)
. $this->renderItems($item['items'], $submenuOptions);
Html::addCssClass($itemOptions, ['widget' => 'dropdown-submenu']);
}
$lines[] = Html::tag('li', $content, $itemOptions);
}
return Html::tag('ul', implode("\n", $lines), $options);
}
}

View File

@@ -0,0 +1,22 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\bootstrap;
/**
* Html is an enhanced version of [[\yii\helpers\Html]] helper class dedicated to the Bootstrap needs.
* This class inherits all functionality available at [[\yii\helpers\Html]] and can be used as substitute.
*
* Attention: do not confuse [[\yii\bootstrap\Html]] and [[\yii\helpers\Html]], be careful in which class
* you are using inside your views.
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0.5
*/
class Html extends BaseHtml
{
}

View File

@@ -0,0 +1,19 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\bootstrap;
/**
* InputWidget is an adjusted for bootstrap needs version of [[\yii\widgets\InputWidget]].
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0.6
*/
class InputWidget extends \yii\widgets\InputWidget
{
use BootstrapWidgetTrait;
}

View File

@@ -0,0 +1,256 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\bootstrap;
use Yii;
use yii\helpers\ArrayHelper;
/**
* Modal renders a modal window that can be toggled by clicking on a button.
*
* The following example will show the content enclosed between the [[begin()]]
* and [[end()]] calls within the modal window:
*
* ~~~php
* Modal::begin([
* 'header' => '<h2>Hello world</h2>',
* 'toggleButton' => ['label' => 'click me'],
* ]);
*
* echo 'Say hello...';
*
* Modal::end();
* ~~~
*
* @see http://getbootstrap.com/javascript/#modals
* @author Antonio Ramirez <amigo.cobos@gmail.com>
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Modal extends Widget
{
const SIZE_LARGE = "modal-lg";
const SIZE_SMALL = "modal-sm";
const SIZE_DEFAULT = "";
/**
* @var string the header content in the modal window.
*/
public $header;
/**
* @var string additional header options
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
* @since 2.0.1
*/
public $headerOptions;
/**
* @var array body options
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
* @since 2.0.7
*/
public $bodyOptions = ['class' => 'modal-body'];
/**
* @var string the footer content in the modal window.
*/
public $footer;
/**
* @var string additional footer options
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
* @since 2.0.1
*/
public $footerOptions;
/**
* @var string the modal size. Can be [[SIZE_LARGE]] or [[SIZE_SMALL]], or empty for default.
*/
public $size;
/**
* @var array|false the options for rendering the close button tag.
* The close button is displayed in the header of the modal window. Clicking
* on the button will hide the modal window. If this is false, no close button will be rendered.
*
* The following special options are supported:
*
* - tag: string, the tag name of the button. Defaults to 'button'.
* - label: string, the label of the button. Defaults to '&times;'.
*
* The rest of the options will be rendered as the HTML attributes of the button tag.
* Please refer to the [Modal plugin help](http://getbootstrap.com/javascript/#modals)
* for the supported HTML attributes.
*/
public $closeButton = [];
/**
* @var array the options for rendering the toggle button tag.
* The toggle button is used to toggle the visibility of the modal window.
* If this property is false, no toggle button will be rendered.
*
* The following special options are supported:
*
* - tag: string, the tag name of the button. Defaults to 'button'.
* - label: string, the label of the button. Defaults to 'Show'.
*
* The rest of the options will be rendered as the HTML attributes of the button tag.
* Please refer to the [Modal plugin help](http://getbootstrap.com/javascript/#modals)
* for the supported HTML attributes.
*/
public $toggleButton = false;
/**
* Initializes the widget.
*/
public function init()
{
parent::init();
$this->initOptions();
echo $this->renderToggleButton() . "\n";
echo Html::beginTag('div', $this->options) . "\n";
echo Html::beginTag('div', ['class' => 'modal-dialog ' . $this->size]) . "\n";
echo Html::beginTag('div', ['class' => 'modal-content']) . "\n";
echo $this->renderHeader() . "\n";
echo $this->renderBodyBegin() . "\n";
}
/**
* Renders the widget.
*/
public function run()
{
echo "\n" . $this->renderBodyEnd();
echo "\n" . $this->renderFooter();
echo "\n" . Html::endTag('div'); // modal-content
echo "\n" . Html::endTag('div'); // modal-dialog
echo "\n" . Html::endTag('div');
$this->registerPlugin('modal');
}
/**
* Renders the header HTML markup of the modal
* @return string the rendering result
*/
protected function renderHeader()
{
$button = $this->renderCloseButton();
if ($button !== null) {
$this->header = $button . "\n" . $this->header;
}
if ($this->header !== null) {
Html::addCssClass($this->headerOptions, ['widget' => 'modal-header']);
return Html::tag('div', "\n" . $this->header . "\n", $this->headerOptions);
} else {
return null;
}
}
/**
* Renders the opening tag of the modal body.
* @return string the rendering result
*/
protected function renderBodyBegin()
{
return Html::beginTag('div', $this->bodyOptions);
}
/**
* Renders the closing tag of the modal body.
* @return string the rendering result
*/
protected function renderBodyEnd()
{
return Html::endTag('div');
}
/**
* Renders the HTML markup for the footer of the modal
* @return string the rendering result
*/
protected function renderFooter()
{
if ($this->footer !== null) {
Html::addCssClass($this->footerOptions, ['widget' => 'modal-footer']);
return Html::tag('div', "\n" . $this->footer . "\n", $this->footerOptions);
} else {
return null;
}
}
/**
* Renders the toggle button.
* @return string the rendering result
*/
protected function renderToggleButton()
{
if (($toggleButton = $this->toggleButton) !== false) {
$tag = ArrayHelper::remove($toggleButton, 'tag', 'button');
$label = ArrayHelper::remove($toggleButton, 'label', 'Show');
if ($tag === 'button' && !isset($toggleButton['type'])) {
$toggleButton['type'] = 'button';
}
return Html::tag($tag, $label, $toggleButton);
} else {
return null;
}
}
/**
* Renders the close button.
* @return string the rendering result
*/
protected function renderCloseButton()
{
if (($closeButton = $this->closeButton) !== false) {
$tag = ArrayHelper::remove($closeButton, 'tag', 'button');
$label = ArrayHelper::remove($closeButton, 'label', '&times;');
if ($tag === 'button' && !isset($closeButton['type'])) {
$closeButton['type'] = 'button';
}
return Html::tag($tag, $label, $closeButton);
} else {
return null;
}
}
/**
* Initializes the widget options.
* This method sets the default values for various options.
*/
protected function initOptions()
{
$this->options = array_merge([
'class' => 'fade',
'role' => 'dialog',
'tabindex' => -1,
], $this->options);
Html::addCssClass($this->options, ['widget' => 'modal']);
if ($this->clientOptions !== false) {
$this->clientOptions = array_merge(['show' => false], $this->clientOptions);
}
if ($this->closeButton !== false) {
$this->closeButton = array_merge([
'data-dismiss' => 'modal',
'aria-hidden' => 'true',
'class' => 'close',
], $this->closeButton);
}
if ($this->toggleButton !== false) {
$this->toggleButton = array_merge([
'data-toggle' => 'modal',
], $this->toggleButton);
if (!isset($this->toggleButton['data-target']) && !isset($this->toggleButton['href'])) {
$this->toggleButton['data-target'] = '#' . $this->options['id'];
}
}
}
}

View File

@@ -0,0 +1,298 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\bootstrap;
use Yii;
use yii\base\InvalidConfigException;
use yii\helpers\ArrayHelper;
/**
* Nav renders a nav HTML component.
*
* For example:
*
* ```php
* echo Nav::widget([
* 'items' => [
* [
* 'label' => 'Home',
* 'url' => ['site/index'],
* 'linkOptions' => [...],
* ],
* [
* 'label' => 'Dropdown',
* 'items' => [
* ['label' => 'Level 1 - Dropdown A', 'url' => '#'],
* '<li class="divider"></li>',
* '<li class="dropdown-header">Dropdown Header</li>',
* ['label' => 'Level 1 - Dropdown B', 'url' => '#'],
* ],
* ],
* [
* 'label' => 'Login',
* 'url' => ['site/login'],
* 'visible' => Yii::$app->user->isGuest
* ],
* ],
* 'options' => ['class' =>'nav-pills'], // set this to nav-tab to get tab-styled navigation
* ]);
* ```
*
* Note: Multilevel dropdowns beyond Level 1 are not supported in Bootstrap 3.
*
* @see http://getbootstrap.com/components/#dropdowns
* @see http://getbootstrap.com/components/#nav
*
* @author Antonio Ramirez <amigo.cobos@gmail.com>
* @since 2.0
*/
class Nav extends Widget
{
/**
* @var array list of items in the nav widget. Each array element represents a single
* menu item which can be either a string or an array with the following structure:
*
* - label: string, required, the nav item label.
* - url: optional, the item's URL. Defaults to "#".
* - visible: bool, optional, whether this menu item is visible. Defaults to true.
* - linkOptions: array, optional, the HTML attributes of the item's link.
* - options: array, optional, the HTML attributes of the item container (LI).
* - active: bool, optional, whether the item should be on active state or not.
* - dropDownOptions: array, optional, the HTML options that will passed to the [[Dropdown]] widget.
* - items: array|string, optional, the configuration array for creating a [[Dropdown]] widget,
* or a string representing the dropdown menu. Note that Bootstrap does not support sub-dropdown menus.
* - encode: bool, optional, whether the label will be HTML-encoded. If set, supersedes the $encodeLabels option for only this item.
*
* If a menu item is a string, it will be rendered directly without HTML encoding.
*/
public $items = [];
/**
* @var bool whether the nav items labels should be HTML-encoded.
*/
public $encodeLabels = true;
/**
* @var bool whether to automatically activate items according to whether their route setting
* matches the currently requested route.
* @see isItemActive
*/
public $activateItems = true;
/**
* @var bool whether to activate parent menu items when one of the corresponding child menu items is active.
*/
public $activateParents = false;
/**
* @var string the route used to determine if a menu item is active or not.
* If not set, it will use the route of the current request.
* @see params
* @see isItemActive
*/
public $route;
/**
* @var array the parameters used to determine if a menu item is active or not.
* If not set, it will use `$_GET`.
* @see route
* @see isItemActive
*/
public $params;
/**
* @var string this property allows you to customize the HTML which is used to generate the drop down caret symbol,
* which is displayed next to the button text to indicate the drop down functionality.
* Defaults to `null` which means `<span class="caret"></span>` will be used. To disable the caret, set this property to be an empty string.
*/
public $dropDownCaret;
/**
* @var string name of a class to use for rendering dropdowns within this widget. Defaults to [[Dropdown]].
* @since 2.0.7
*/
public $dropdownClass = 'yii\bootstrap\Dropdown';
/**
* Initializes the widget.
*/
public function init()
{
parent::init();
if ($this->route === null && Yii::$app->controller !== null) {
$this->route = Yii::$app->controller->getRoute();
}
if ($this->params === null) {
$this->params = Yii::$app->request->getQueryParams();
}
if ($this->dropDownCaret === null) {
$this->dropDownCaret = '<span class="caret"></span>';
}
Html::addCssClass($this->options, ['widget' => 'nav']);
}
/**
* Renders the widget.
*/
public function run()
{
BootstrapAsset::register($this->getView());
return $this->renderItems();
}
/**
* Renders widget items.
*/
public function renderItems()
{
$items = [];
foreach ($this->items as $i => $item) {
if (isset($item['visible']) && !$item['visible']) {
continue;
}
$items[] = $this->renderItem($item);
}
return Html::tag('ul', implode("\n", $items), $this->options);
}
/**
* Renders a widget's item.
* @param string|array $item the item to render.
* @return string the rendering result.
* @throws InvalidConfigException
*/
public function renderItem($item)
{
if (is_string($item)) {
return $item;
}
if (!isset($item['label'])) {
throw new InvalidConfigException("The 'label' option is required.");
}
$encodeLabel = isset($item['encode']) ? $item['encode'] : $this->encodeLabels;
$label = $encodeLabel ? Html::encode($item['label']) : $item['label'];
$options = ArrayHelper::getValue($item, 'options', []);
$items = ArrayHelper::getValue($item, 'items');
$url = ArrayHelper::getValue($item, 'url', '#');
$linkOptions = ArrayHelper::getValue($item, 'linkOptions', []);
if (isset($item['active'])) {
$active = ArrayHelper::remove($item, 'active', false);
} else {
$active = $this->isItemActive($item);
}
if (empty($items)) {
$items = '';
} else {
$linkOptions['data-toggle'] = 'dropdown';
Html::addCssClass($options, ['widget' => 'dropdown']);
Html::addCssClass($linkOptions, ['widget' => 'dropdown-toggle']);
if ($this->dropDownCaret !== '') {
$label .= ' ' . $this->dropDownCaret;
}
if (is_array($items)) {
$items = $this->isChildActive($items, $active);
$items = $this->renderDropdown($items, $item);
}
}
if ($active) {
Html::addCssClass($options, 'active');
}
return Html::tag('li', Html::a($label, $url, $linkOptions) . $items, $options);
}
/**
* Renders the given items as a dropdown.
* This method is called to create sub-menus.
* @param array $items the given items. Please refer to [[Dropdown::items]] for the array structure.
* @param array $parentItem the parent item information. Please refer to [[items]] for the structure of this array.
* @return string the rendering result.
* @since 2.0.1
*/
protected function renderDropdown($items, $parentItem)
{
/** @var Widget $dropdownClass */
$dropdownClass = $this->dropdownClass;
return $dropdownClass::widget([
'options' => ArrayHelper::getValue($parentItem, 'dropDownOptions', []),
'items' => $items,
'encodeLabels' => $this->encodeLabels,
'clientOptions' => false,
'view' => $this->getView(),
]);
}
/**
* Check to see if a child item is active optionally activating the parent.
* @param array $items @see items
* @param bool $active should the parent be active too
* @return array @see items
*/
protected function isChildActive($items, &$active)
{
foreach ($items as $i => $child) {
if (is_array($child) && !ArrayHelper::getValue($child, 'visible', true)) {
continue;
}
if (ArrayHelper::remove($items[$i], 'active', false) || $this->isItemActive($child)) {
Html::addCssClass($items[$i]['options'], 'active');
if ($this->activateParents) {
$active = true;
}
}
$childItems = ArrayHelper::getValue($child, 'items');
if (is_array($childItems)) {
$activeParent = false;
$items[$i]['items'] = $this->isChildActive($childItems, $activeParent);
if ($activeParent) {
Html::addCssClass($items[$i]['options'], 'active');
$active = true;
}
}
}
return $items;
}
/**
* Checks whether a menu item is active.
* This is done by checking if [[route]] and [[params]] match that specified in the `url` option of the menu item.
* When the `url` option of a menu item is specified in terms of an array, its first element is treated
* as the route for the item and the rest of the elements are the associated parameters.
* Only when its route and parameters match [[route]] and [[params]], respectively, will a menu item
* be considered active.
* @param array $item the menu item to be checked
* @return bool whether the menu item is active
*/
protected function isItemActive($item)
{
if (!$this->activateItems) {
return false;
}
if (isset($item['url']) && is_array($item['url']) && isset($item['url'][0])) {
$route = $item['url'][0];
if ($route[0] !== '/' && Yii::$app->controller) {
$route = Yii::$app->controller->module->getUniqueId() . '/' . $route;
}
if (ltrim($route, '/') !== $this->route) {
return false;
}
unset($item['url']['#']);
if (count($item['url']) > 1) {
$params = $item['url'];
unset($params[0]);
foreach ($params as $name => $value) {
if ($value !== null && (!isset($this->params[$name]) || $this->params[$name] != $value)) {
return false;
}
}
}
return true;
}
return false;
}
}

View File

@@ -0,0 +1,173 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\bootstrap;
use Yii;
use yii\helpers\ArrayHelper;
/**
* NavBar renders a navbar HTML component.
*
* Any content enclosed between the [[begin()]] and [[end()]] calls of NavBar
* is treated as the content of the navbar. You may use widgets such as [[Nav]]
* or [[\yii\widgets\Menu]] to build up such content. For example,
*
* ```php
* use yii\bootstrap\NavBar;
* use yii\bootstrap\Nav;
*
* NavBar::begin(['brandLabel' => 'NavBar Test']);
* echo Nav::widget([
* 'items' => [
* ['label' => 'Home', 'url' => ['/site/index']],
* ['label' => 'About', 'url' => ['/site/about']],
* ],
* 'options' => ['class' => 'navbar-nav'],
* ]);
* NavBar::end();
* ```
*
* @see https://getbootstrap.com/docs/3.3/components/#navbar
* @author Antonio Ramirez <amigo.cobos@gmail.com>
* @author Alexander Kochetov <creocoder@gmail.com>
* @since 2.0
*/
class NavBar extends Widget
{
/**
* @var array the HTML attributes for the widget container tag. The following special options are recognized:
*
* - tag: string, defaults to "nav", the name of the container tag.
*
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
*/
public $options = [];
/**
* @var array the HTML attributes for the container tag. The following special options are recognized:
*
* - tag: string, defaults to "div", the name of the container tag.
*
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
*/
public $containerOptions = [];
/**
* @var string|bool the text of the brand or false if it's not used. Note that this is not HTML-encoded.
* @see https://getbootstrap.com/docs/3.3/components/#navbar
*/
public $brandLabel = false;
/**
* @var string|bool src of the brand image or false if it's not used. Note that this param will override `$this->brandLabel` param.
* @see https://getbootstrap.com/docs/3.3/components/#navbar
* @since 2.0.8
*/
public $brandImage = false;
/**
* @var array|string|bool $url the URL for the brand's hyperlink tag. This parameter will be processed by [[\yii\helpers\Url::to()]]
* and will be used for the "href" attribute of the brand link. Default value is false that means
* [[\yii\web\Application::homeUrl]] will be used.
* You may set it to `null` if you want to have no link at all.
*/
public $brandUrl = false;
/**
* @var array the HTML attributes of the brand link.
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
*/
public $brandOptions = [];
/**
* @var string HTML content to be added in navbar-header div, for example, mobile search form.
* @since 2.0.8
*/
public $headerContent;
/**
* @var string text to show for screen readers for the button to toggle the navbar.
*/
public $screenReaderToggleText = 'Toggle navigation';
/**
* @var bool whether the navbar content should be included in an inner div container which by default
* adds left and right padding. Set this to false for a 100% width navbar.
*/
public $renderInnerContainer = true;
/**
* @var array the HTML attributes of the inner container.
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
*/
public $innerContainerOptions = [];
/**
* Initializes the widget.
*/
public function init()
{
parent::init();
$this->clientOptions = false;
if (empty($this->options['class'])) {
Html::addCssClass($this->options, ['navbar', 'navbar-default']);
} else {
Html::addCssClass($this->options, ['widget' => 'navbar']);
}
$options = $this->options;
$tag = ArrayHelper::remove($options, 'tag', 'nav');
echo Html::beginTag($tag, $options);
if ($this->renderInnerContainer) {
if (!isset($this->innerContainerOptions['class'])) {
Html::addCssClass($this->innerContainerOptions, 'container');
}
echo Html::beginTag('div', $this->innerContainerOptions);
}
echo Html::beginTag('div', ['class' => 'navbar-header']);
if (!isset($this->containerOptions['id'])) {
$this->containerOptions['id'] = "{$this->options['id']}-collapse";
}
echo $this->renderToggleButton();
if ($this->brandImage !== false) {
$this->brandLabel = Html::img($this->brandImage);
}
if ($this->brandLabel !== false) {
Html::addCssClass($this->brandOptions, ['widget' => 'navbar-brand']);
echo Html::a($this->brandLabel, $this->brandUrl === false ? Yii::$app->homeUrl : $this->brandUrl, $this->brandOptions);
}
echo $this->headerContent;
echo Html::endTag('div');
Html::addCssClass($this->containerOptions, ['collapse' => 'collapse', 'widget' => 'navbar-collapse']);
$options = $this->containerOptions;
$tag = ArrayHelper::remove($options, 'tag', 'div');
echo Html::beginTag($tag, $options);
}
/**
* Renders the widget.
*/
public function run()
{
$tag = ArrayHelper::remove($this->containerOptions, 'tag', 'div');
echo Html::endTag($tag);
if ($this->renderInnerContainer) {
echo Html::endTag('div');
}
$tag = ArrayHelper::remove($this->options, 'tag', 'nav');
echo Html::endTag($tag);
BootstrapPluginAsset::register($this->getView());
}
/**
* Renders collapsible toggle button.
* @return string the rendering toggle button.
*/
protected function renderToggleButton()
{
$bar = Html::tag('span', '', ['class' => 'icon-bar']);
$screenReader = "<span class=\"sr-only\">{$this->screenReaderToggleText}</span>";
return Html::button("{$screenReader}\n{$bar}\n{$bar}\n{$bar}", [
'class' => 'navbar-toggle',
'data-toggle' => 'collapse',
'data-target' => "#{$this->containerOptions['id']}",
]);
}
}

View File

@@ -0,0 +1,166 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\bootstrap;
use yii\base\InvalidConfigException;
use yii\helpers\ArrayHelper;
/**
* Progress renders a bootstrap progress bar component.
*
* For example,
*
* ```php
* // default with label
* echo Progress::widget([
* 'percent' => 60,
* 'label' => 'test',
* ]);
*
* // styled
* echo Progress::widget([
* 'percent' => 65,
* 'barOptions' => ['class' => 'progress-bar-danger']
* ]);
*
* // striped
* echo Progress::widget([
* 'percent' => 70,
* 'barOptions' => ['class' => 'progress-bar-warning'],
* 'options' => ['class' => 'progress-striped']
* ]);
*
* // striped animated
* echo Progress::widget([
* 'percent' => 70,
* 'barOptions' => ['class' => 'progress-bar-success'],
* 'options' => ['class' => 'active progress-striped']
* ]);
*
* // stacked bars
* echo Progress::widget([
* 'bars' => [
* ['percent' => 30, 'options' => ['class' => 'progress-bar-danger']],
* ['percent' => 30, 'label' => 'test', 'options' => ['class' => 'progress-bar-success']],
* ['percent' => 35, 'options' => ['class' => 'progress-bar-warning']],
* ]
* ]);
* ```
* @see http://getbootstrap.com/components/#progress
* @author Antonio Ramirez <amigo.cobos@gmail.com>
* @author Alexander Makarov <sam@rmcreative.ru>
* @since 2.0
*/
class Progress extends Widget
{
/**
* @var string the button label.
*/
public $label;
/**
* @var int the amount of progress as a percentage.
*/
public $percent = 0;
/**
* @var array the HTML attributes of the bar.
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
*/
public $barOptions = [];
/**
* @var array a set of bars that are stacked together to form a single progress bar.
* Each bar is an array of the following structure:
*
* ```php
* [
* // required, the amount of progress as a percentage.
* 'percent' => 30,
* // optional, the label to be displayed on the bar
* 'label' => '30%',
* // optional, array, additional HTML attributes for the bar tag
* 'options' => [],
* ]
* ```
*/
public $bars;
/**
* Initializes the widget.
* If you override this method, make sure you call the parent implementation first.
*/
public function init()
{
parent::init();
Html::addCssClass($this->options, ['widget' => 'progress']);
}
/**
* Renders the widget.
*/
public function run()
{
BootstrapAsset::register($this->getView());
return implode("\n", [
Html::beginTag('div', $this->options),
$this->renderProgress(),
Html::endTag('div')
]) . "\n";
}
/**
* Renders the progress.
* @return string the rendering result.
* @throws InvalidConfigException if the "percent" option is not set in a stacked progress bar.
*/
protected function renderProgress()
{
if (empty($this->bars)) {
return $this->renderBar($this->percent, $this->label, $this->barOptions);
}
$bars = [];
foreach ($this->bars as $bar) {
$label = ArrayHelper::getValue($bar, 'label', '');
if (!isset($bar['percent'])) {
throw new InvalidConfigException("The 'percent' option is required.");
}
$options = ArrayHelper::getValue($bar, 'options', []);
$bars[] = $this->renderBar($bar['percent'], $label, $options);
}
return implode("\n", $bars);
}
/**
* Generates a bar
* @param int $percent the percentage of the bar
* @param string $label, optional, the label to display at the bar
* @param array $options the HTML attributes of the bar
* @return string the rendering result.
*/
protected function renderBar($percent, $label = '', $options = [])
{
$defaultOptions = [
'role' => 'progressbar',
'aria-valuenow' => $percent,
'aria-valuemin' => 0,
'aria-valuemax' => 100,
'style' => "width:{$percent}%",
];
$options = array_merge($defaultOptions, $options);
Html::addCssClass($options, ['widget' => 'progress-bar']);
$out = Html::beginTag('div', $options);
$out .= $label;
$out .= Html::tag('span', \Yii::t('yii', '{percent}% Complete', ['percent' => $percent]), [
'class' => 'sr-only'
]);
$out .= Html::endTag('div');
return $out;
}
}

View File

@@ -0,0 +1,319 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\bootstrap;
use yii\base\InvalidConfigException;
use yii\helpers\ArrayHelper;
/**
* Tabs renders a Tab bootstrap javascript component.
*
* For example:
*
* ```php
* echo Tabs::widget([
* 'items' => [
* [
* 'label' => 'One',
* 'content' => 'Anim pariatur cliche...',
* 'active' => true
* ],
* [
* 'label' => 'Two',
* 'content' => 'Anim pariatur cliche...',
* 'headerOptions' => [...],
* 'options' => ['id' => 'myveryownID'],
* ],
* [
* 'label' => 'Example',
* 'url' => 'http://www.example.com',
* ],
* [
* 'label' => 'Dropdown',
* 'items' => [
* [
* 'label' => 'DropdownA',
* 'content' => 'DropdownA, Anim pariatur cliche...',
* ],
* [
* 'label' => 'DropdownB',
* 'content' => 'DropdownB, Anim pariatur cliche...',
* ],
* [
* 'label' => 'External Link',
* 'url' => 'http://www.example.com',
* ],
* ],
* ],
* ],
* ]);
* ```
*
* @see http://getbootstrap.com/javascript/#tabs
* @author Antonio Ramirez <amigo.cobos@gmail.com>
* @since 2.0
*/
class Tabs extends Widget
{
/**
* @var array list of tabs in the tabs widget. Each array element represents a single
* tab with the following structure:
*
* - label: string, required, the tab header label.
* - encode: bool, optional, whether this label should be HTML-encoded. This param will override
* global `$this->encodeLabels` param.
* - headerOptions: array, optional, the HTML attributes of the tab header.
* - linkOptions: array, optional, the HTML attributes of the tab header link tags.
* - content: string, optional, the content (HTML) of the tab pane.
* - url: string, optional, an external URL. When this is specified, clicking on this tab will bring
* the browser to this URL. This option is available since version 2.0.4.
* - options: array, optional, the HTML attributes of the tab pane container.
* - active: bool, optional, whether this item tab header and pane should be active. If no item is marked as
* 'active' explicitly - the first one will be activated.
* - visible: bool, optional, whether the item tab header and pane should be visible or not. Defaults to true.
* - items: array, optional, can be used instead of `content` to specify a dropdown items
* configuration array. Each item can hold three extra keys, besides the above ones:
* * active: bool, optional, whether the item tab header and pane should be visible or not.
* * content: string, required if `items` is not set. The content (HTML) of the tab pane.
* * contentOptions: optional, array, the HTML attributes of the tab content container.
*/
public $items = [];
/**
* @var array list of HTML attributes for the item container tags. This will be overwritten
* by the "options" set in individual [[items]]. The following special options are recognized:
*
* - tag: string, defaults to "div", the tag name of the item container tags.
*
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
*/
public $itemOptions = [];
/**
* @var array list of HTML attributes for the header container tags. This will be overwritten
* by the "headerOptions" set in individual [[items]].
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
*/
public $headerOptions = [];
/**
* @var array list of HTML attributes for the tab header link tags. This will be overwritten
* by the "linkOptions" set in individual [[items]].
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
*/
public $linkOptions = [];
/**
* @var bool whether the labels for header items should be HTML-encoded.
*/
public $encodeLabels = true;
/**
* @var string specifies the Bootstrap tab styling.
*/
public $navType = 'nav-tabs';
/**
* @var bool whether to render the `tab-content` container and its content. You may set this property
* to be false so that you can manually render `tab-content` yourself in case your tab contents are complex.
* @since 2.0.1
*/
public $renderTabContent = true;
/**
* @var array list of HTML attributes for the `tab-content` container. This will always contain the CSS class `tab-content`.
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
* @since 2.0.7
*/
public $tabContentOptions = [];
/**
* @var string name of a class to use for rendering dropdowns withing this widget. Defaults to [[Dropdown]].
* @since 2.0.7
*/
public $dropdownClass = 'yii\bootstrap\Dropdown';
/**
* Initializes the widget.
*/
public function init()
{
parent::init();
Html::addCssClass($this->options, ['widget' => 'nav', $this->navType]);
Html::addCssClass($this->tabContentOptions, 'tab-content');
}
/**
* Renders the widget.
*/
public function run()
{
$this->registerPlugin('tab');
return $this->renderItems();
}
/**
* Renders tab items as specified on [[items]].
* @return string the rendering result.
* @throws InvalidConfigException.
*/
protected function renderItems()
{
$headers = [];
$panes = [];
if (!$this->hasActiveTab()) {
$this->activateFirstVisibleTab();
}
foreach ($this->items as $n => $item) {
if (!ArrayHelper::remove($item, 'visible', true)) {
continue;
}
if (!array_key_exists('label', $item)) {
throw new InvalidConfigException("The 'label' option is required.");
}
$encodeLabel = isset($item['encode']) ? $item['encode'] : $this->encodeLabels;
$label = $encodeLabel ? Html::encode($item['label']) : $item['label'];
$headerOptions = array_merge($this->headerOptions, ArrayHelper::getValue($item, 'headerOptions', []));
$linkOptions = array_merge($this->linkOptions, ArrayHelper::getValue($item, 'linkOptions', []));
if (isset($item['items'])) {
$label .= ' <b class="caret"></b>';
Html::addCssClass($headerOptions, ['widget' => 'dropdown']);
if ($this->renderDropdown($n, $item['items'], $panes)) {
Html::addCssClass($headerOptions, 'active');
}
Html::addCssClass($linkOptions, ['widget' => 'dropdown-toggle']);
if (!isset($linkOptions['data-toggle'])) {
$linkOptions['data-toggle'] = 'dropdown';
}
/** @var Widget $dropdownClass */
$dropdownClass = $this->dropdownClass;
$header = Html::a($label, "#", $linkOptions) . "\n"
. $dropdownClass::widget(['items' => $item['items'], 'clientOptions' => false, 'view' => $this->getView()]);
} else {
$options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', []));
$options['id'] = ArrayHelper::getValue($options, 'id', $this->options['id'] . '-tab' . $n);
Html::addCssClass($options, ['widget' => 'tab-pane']);
if (ArrayHelper::remove($item, 'active')) {
Html::addCssClass($options, 'active');
Html::addCssClass($headerOptions, 'active');
}
if (isset($item['url'])) {
$header = Html::a($label, $item['url'], $linkOptions);
} else {
if (!isset($linkOptions['data-toggle'])) {
$linkOptions['data-toggle'] = 'tab';
}
$header = Html::a($label, '#' . $options['id'], $linkOptions);
}
if ($this->renderTabContent) {
$tag = ArrayHelper::remove($options, 'tag', 'div');
$panes[] = Html::tag($tag, isset($item['content']) ? $item['content'] : '', $options);
}
}
$headers[] = Html::tag('li', $header, $headerOptions);
}
return Html::tag('ul', implode("\n", $headers), $this->options) . $this->renderPanes($panes);
}
/**
* @return bool if there's active tab defined
*/
protected function hasActiveTab()
{
foreach ($this->items as $item) {
if (isset($item['active']) && $item['active'] === true) {
return true;
}
}
return false;
}
/**
* Sets the first visible tab as active.
*
* This method activates the first tab that is visible and
* not explicitly set to inactive (`'active' => false`).
* @since 2.0.7
*/
protected function activateFirstVisibleTab()
{
foreach ($this->items as $i => $item) {
$active = ArrayHelper::getValue($item, 'active', null);
$visible = ArrayHelper::getValue($item, 'visible', true);
if ($visible && $active !== false) {
$this->items[$i]['active'] = true;
return;
}
}
}
/**
* Normalizes dropdown item options by removing tab specific keys `content` and `contentOptions`, and also
* configure `panes` accordingly.
* @param string $itemNumber number of the item
* @param array $items the dropdown items configuration.
* @param array $panes the panes reference array.
* @return bool whether any of the dropdown items is `active` or not.
* @throws InvalidConfigException
*/
protected function renderDropdown($itemNumber, &$items, &$panes)
{
$itemActive = false;
foreach ($items as $n => &$item) {
if (is_string($item)) {
continue;
}
if (isset($item['visible']) && !$item['visible']) {
continue;
}
if (!(array_key_exists('content', $item) xor array_key_exists('url', $item))) {
throw new InvalidConfigException("Either the 'content' or the 'url' option is required, but only one can be set.");
}
if (array_key_exists('url', $item)) {
continue;
}
$content = ArrayHelper::remove($item, 'content');
$options = ArrayHelper::remove($item, 'contentOptions', []);
Html::addCssClass($options, ['widget' => 'tab-pane']);
if (ArrayHelper::remove($item, 'active')) {
Html::addCssClass($options, 'active');
Html::addCssClass($item['options'], 'active');
$itemActive = true;
}
$options['id'] = ArrayHelper::getValue($options, 'id', $this->options['id'] . '-dd' . $itemNumber . '-tab' . $n);
$item['url'] = '#' . $options['id'];
if (!isset($item['linkOptions']['data-toggle'])) {
$item['linkOptions']['data-toggle'] = 'tab';
}
$panes[] = Html::tag('div', $content, $options);
unset($item);
}
return $itemActive;
}
/**
* Renders tab panes.
*
* @param array $panes
* @return string the rendering result.
* @since 2.0.7
*/
public function renderPanes($panes)
{
return $this->renderTabContent ? "\n" . Html::tag('div', implode("\n", $panes), $this->tabContentOptions) : '';
}
}

View File

@@ -0,0 +1,115 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\bootstrap;
use yii\base\InvalidConfigException;
/**
* ToggleButtonGroup allows rendering form inputs Checkbox/Radio toggle button groups.
*
* You can use this widget in an [[yii\bootstrap\ActiveForm|ActiveForm]] using the [[yii\widgets\ActiveField::widget()|widget()]]
* method, for example like this:
*
* ```php
* <?= $form->field($model, 'item_id')->widget(\yii\bootstrap\ToggleButtonGroup::classname(), [
* // configure additional widget properties here
* ]) ?>
* ```
*
* @see http://getbootstrap.com/javascript/#buttons-checkbox-radio
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0.6
*/
class ToggleButtonGroup extends InputWidget
{
/**
* @var string input type, can be:
* - 'checkbox'
* - 'radio'
*/
public $type;
/**
* @var array the data item used to generate the checkboxes.
* The array values are the labels, while the array keys are the corresponding checkbox or radio values.
*/
public $items = [];
/**
* @var array, the HTML attributes for the label (button) tag.
* @see Html::checkbox()
* @see Html::radio()
*/
public $labelOptions = [];
/**
* @var bool whether the items labels should be HTML-encoded.
*/
public $encodeLabels = true;
/**
* {@inheritdoc}
*/
public function init()
{
parent::init();
$this->registerPlugin('button');
Html::addCssClass($this->options, 'btn-group');
$this->options['data-toggle'] = 'buttons';
}
/**
* {@inheritdoc}
*/
public function run()
{
if (!isset($this->options['item'])) {
$this->options['item'] = [$this, 'renderItem'];
}
switch ($this->type) {
case 'checkbox':
if ($this->hasModel()) {
return Html::activeCheckboxList($this->model, $this->attribute, $this->items, $this->options);
} else {
return Html::checkboxList($this->name, $this->value, $this->items, $this->options);
}
case 'radio':
if ($this->hasModel()) {
return Html::activeRadioList($this->model, $this->attribute, $this->items, $this->options);
} else {
return Html::radioList($this->name, $this->value, $this->items, $this->options);
}
default:
throw new InvalidConfigException("Unsupported type '{$this->type}'");
}
}
/**
* Default callback for checkbox/radio list item rendering.
* @param int $index item index.
* @param string $label item label.
* @param string $name input name.
* @param bool $checked whether value is checked or not.
* @param string $value input value.
* @return string generated HTML.
* @see Html::checkbox()
* @see Html::radio()
*/
public function renderItem($index, $label, $name, $checked, $value)
{
$labelOptions = $this->labelOptions;
Html::addCssClass($labelOptions, 'btn');
if ($checked) {
Html::addCssClass($labelOptions, 'active');
}
$type = $this->type;
if ($this->encodeLabels) {
$label = Html::encode($label);
}
return Html::$type($name, $checked, ['label' => $label, 'labelOptions' => $labelOptions, 'value' => $value]);
}
}

View File

@@ -0,0 +1,26 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\bootstrap;
/**
* \yii\bootstrap\Widget is the base class for all bootstrap widgets.
*
* @author Antonio Ramirez <amigo.cobos@gmail.com>
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Widget extends \yii\base\Widget
{
use BootstrapWidgetTrait;
/**
* @var array the HTML attributes for the widget container tag.
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
*/
public $options = [];
}

View File

@@ -0,0 +1,72 @@
Yii Framework 2 composer extension Change Log
=============================================
2.0.7 July 05, 2018
-------------------
- Bug #18: Fixed an error that would occur if the Zend OPcache extension was installed, but its "restrict_api" setting was enabled (angrybrad)
2.0.6 March 21, 2018
--------------------
- Bug #16: Upgrade notes were not shown when upgrading from a patch version (cebe)
2.0.5 December 20, 2016
-----------------------
- Bug #11: `generateCookieValidationKey()` now saves config file only when `cookieValidationKey` was generated (rob006)
- Enh #10: Added `yii\composer\Installer::postInstall()` method (rob006)
- Enh #12: Added `yii\composer\Installer::copyFiles()` method (rob006)
- Enh #14: A note about yii UPGRADE notes file is shown after upgrading Yii to make user aware of it (cebe)
2.0.4 February 06, 2016
-----------------------
- Bug #7735: Composer failed to install extensions with multiple base paths in "psr-4" autoload section (cebe)
- Enh #2: Better error handling for the case when installer is unable to change permissions (dbavscc)
- Enh #3: `loadExtensions()` and `saveExtensions()` now access `EXTENSION_FILE` constant with late static binding (karneds)
2.0.3 March 01, 2015
--------------------
- no changes in this release.
2.0.2 January 11, 2015
----------------------
- no changes in this release.
2.0.1 December 07, 2014
-----------------------
- no changes in this release.
2.0.0 October 12, 2014
----------------------
- no changes in this release.
2.0.0-rc September 27, 2014
---------------------------
- Bug #3438: Fixed support for non-lowercase package names (cebe)
- Chg: Added `yii\composer\Installer::postCreateProject()` and modified the syntax of calling installer methods in composer.json (qiangxue)
2.0.0-beta April 13, 2014
-------------------------
- Bug #1480: Fixed issue with creating extensions.php when php opcache is enabled (cebe)
- Enh: Added support for installing packages conforming to PSR-4 standard (qiangxue)
2.0.0-alpha, December 1, 2013
-----------------------------
- Initial release.

View File

@@ -0,0 +1,360 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\composer;
use Composer\Package\PackageInterface;
use Composer\Installer\LibraryInstaller;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Script\CommandEvent;
use Composer\Script\Event;
use Composer\Util\Filesystem;
/**
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Installer extends LibraryInstaller
{
const EXTRA_BOOTSTRAP = 'bootstrap';
const EXTENSION_FILE = 'yiisoft/extensions.php';
/**
* @inheritdoc
*/
public function supports($packageType)
{
return $packageType === 'yii2-extension';
}
/**
* @inheritdoc
*/
public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
{
// install the package the normal composer way
parent::install($repo, $package);
// add the package to yiisoft/extensions.php
$this->addPackage($package);
// ensure the yii2-dev package also provides Yii.php in the same place as yii2 does
if ($package->getName() == 'yiisoft/yii2-dev') {
$this->linkBaseYiiFiles();
}
}
/**
* @inheritdoc
*/
public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
{
parent::update($repo, $initial, $target);
$this->removePackage($initial);
$this->addPackage($target);
// ensure the yii2-dev package also provides Yii.php in the same place as yii2 does
if ($initial->getName() == 'yiisoft/yii2-dev') {
$this->linkBaseYiiFiles();
}
}
/**
* @inheritdoc
*/
public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
{
// uninstall the package the normal composer way
parent::uninstall($repo, $package);
// remove the package from yiisoft/extensions.php
$this->removePackage($package);
// remove links for Yii.php
if ($package->getName() == 'yiisoft/yii2-dev') {
$this->removeBaseYiiFiles();
}
}
protected function addPackage(PackageInterface $package)
{
$extension = [
'name' => $package->getName(),
'version' => $package->getVersion(),
];
$alias = $this->generateDefaultAlias($package);
if (!empty($alias)) {
$extension['alias'] = $alias;
}
$extra = $package->getExtra();
if (isset($extra[self::EXTRA_BOOTSTRAP])) {
$extension['bootstrap'] = $extra[self::EXTRA_BOOTSTRAP];
}
$extensions = $this->loadExtensions();
$extensions[$package->getName()] = $extension;
$this->saveExtensions($extensions);
}
protected function generateDefaultAlias(PackageInterface $package)
{
$fs = new Filesystem;
$vendorDir = $fs->normalizePath($this->vendorDir);
$autoload = $package->getAutoload();
$aliases = [];
if (!empty($autoload['psr-0'])) {
foreach ($autoload['psr-0'] as $name => $path) {
$name = str_replace('\\', '/', trim($name, '\\'));
if (!$fs->isAbsolutePath($path)) {
$path = $this->vendorDir . '/' . $package->getPrettyName() . '/' . $path;
}
$path = $fs->normalizePath($path);
if (strpos($path . '/', $vendorDir . '/') === 0) {
$aliases["@$name"] = '<vendor-dir>' . substr($path, strlen($vendorDir)) . '/' . $name;
} else {
$aliases["@$name"] = $path . '/' . $name;
}
}
}
if (!empty($autoload['psr-4'])) {
foreach ($autoload['psr-4'] as $name => $path) {
if (is_array($path)) {
// ignore psr-4 autoload specifications with multiple search paths
// we can not convert them into aliases as they are ambiguous
continue;
}
$name = str_replace('\\', '/', trim($name, '\\'));
if (!$fs->isAbsolutePath($path)) {
$path = $this->vendorDir . '/' . $package->getPrettyName() . '/' . $path;
}
$path = $fs->normalizePath($path);
if (strpos($path . '/', $vendorDir . '/') === 0) {
$aliases["@$name"] = '<vendor-dir>' . substr($path, strlen($vendorDir));
} else {
$aliases["@$name"] = $path;
}
}
}
return $aliases;
}
protected function removePackage(PackageInterface $package)
{
$packages = $this->loadExtensions();
unset($packages[$package->getName()]);
$this->saveExtensions($packages);
}
protected function loadExtensions()
{
$file = $this->vendorDir . '/' . static::EXTENSION_FILE;
if (!is_file($file)) {
return [];
}
// invalidate opcache of extensions.php if exists
if (function_exists('opcache_invalidate')) {
@opcache_invalidate($file, true);
}
$extensions = require($file);
$vendorDir = str_replace('\\', '/', $this->vendorDir);
$n = strlen($vendorDir);
foreach ($extensions as &$extension) {
if (isset($extension['alias'])) {
foreach ($extension['alias'] as $alias => $path) {
$path = str_replace('\\', '/', $path);
if (strpos($path . '/', $vendorDir . '/') === 0) {
$extension['alias'][$alias] = '<vendor-dir>' . substr($path, $n);
}
}
}
}
return $extensions;
}
protected function saveExtensions(array $extensions)
{
$file = $this->vendorDir . '/' . static::EXTENSION_FILE;
if (!file_exists(dirname($file))) {
mkdir(dirname($file), 0777, true);
}
$array = str_replace("'<vendor-dir>", '$vendorDir . \'', var_export($extensions, true));
file_put_contents($file, "<?php\n\n\$vendorDir = dirname(__DIR__);\n\nreturn $array;\n");
// invalidate opcache of extensions.php if exists
if (function_exists('opcache_invalidate')) {
opcache_invalidate($file, true);
}
}
protected function linkBaseYiiFiles()
{
$yiiDir = $this->vendorDir . '/yiisoft/yii2';
if (!file_exists($yiiDir)) {
mkdir($yiiDir, 0777, true);
}
foreach (['Yii.php', 'BaseYii.php', 'classes.php'] as $file) {
file_put_contents($yiiDir . '/' . $file, <<<EOF
<?php
/**
* This is a link provided by the yiisoft/yii2-dev package via yii2-composer plugin.
*
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
return require(__DIR__ . '/../yii2-dev/framework/$file');
EOF
);
}
}
protected function removeBaseYiiFiles()
{
$yiiDir = $this->vendorDir . '/yiisoft/yii2';
foreach (['Yii.php', 'BaseYii.php', 'classes.php'] as $file) {
if (file_exists($yiiDir . '/' . $file)) {
unlink($yiiDir . '/' . $file);
}
}
if (file_exists($yiiDir)) {
rmdir($yiiDir);
}
}
/**
* Special method to run tasks defined in `[extra][yii\composer\Installer::postCreateProject]` key in `composer.json`
*
* @param Event $event
*/
public static function postCreateProject($event)
{
static::runCommands($event, __METHOD__);
}
/**
* Special method to run tasks defined in `[extra][yii\composer\Installer::postInstall]` key in `composer.json`
*
* @param Event $event
* @since 2.0.5
*/
public static function postInstall($event)
{
static::runCommands($event, __METHOD__);
}
/**
* Special method to run tasks defined in `[extra][$extraKey]` key in `composer.json`
*
* @param Event $event
* @param string $extraKey
* @since 2.0.5
*/
protected static function runCommands($event, $extraKey)
{
$params = $event->getComposer()->getPackage()->getExtra();
if (isset($params[$extraKey]) && is_array($params[$extraKey])) {
foreach ($params[$extraKey] as $method => $args) {
call_user_func_array([__CLASS__, $method], (array) $args);
}
}
}
/**
* Sets the correct permission for the files and directories listed in the extra section.
* @param array $paths the paths (keys) and the corresponding permission octal strings (values)
*/
public static function setPermission(array $paths)
{
foreach ($paths as $path => $permission) {
echo "chmod('$path', $permission)...";
if (is_dir($path) || is_file($path)) {
try {
if (chmod($path, octdec($permission))) {
echo "done.\n";
};
} catch (\Exception $e) {
echo $e->getMessage() . "\n";
}
} else {
echo "file not found.\n";
}
}
}
/**
* Generates a cookie validation key for every app config listed in "config" in extra section.
* You can provide one or multiple parameters as the configuration files which need to have validation key inserted.
*/
public static function generateCookieValidationKey()
{
$configs = func_get_args();
$key = self::generateRandomString();
foreach ($configs as $config) {
if (is_file($config)) {
$content = preg_replace('/(("|\')cookieValidationKey("|\')\s*=>\s*)(""|\'\')/', "\\1'$key'", file_get_contents($config), -1, $count);
if ($count > 0) {
file_put_contents($config, $content);
}
}
}
}
protected static function generateRandomString()
{
if (!extension_loaded('openssl')) {
throw new \Exception('The OpenSSL PHP extension is required by Yii2.');
}
$length = 32;
$bytes = openssl_random_pseudo_bytes($length);
return strtr(substr(base64_encode($bytes), 0, $length), '+/=', '_-.');
}
/**
* Copy files to specified locations.
* @param array $paths The source files paths (keys) and the corresponding target locations
* for copied files (values). Location can be specified as an array - first element is target
* location, second defines whether file can be overwritten (by default method don't overwrite
* existing files).
* @since 2.0.5
*/
public static function copyFiles(array $paths)
{
foreach ($paths as $source => $target) {
// handle file target as array [path, overwrite]
$target = (array) $target;
echo "Copying file $source to $target[0] - ";
if (!is_file($source)) {
echo "source file not found.\n";
continue;
}
if (is_file($target[0]) && empty($target[1])) {
echo "target file exists - skip.\n";
continue;
} elseif (is_file($target[0]) && !empty($target[1])) {
echo "target file exists - overwrite - ";
}
try {
if (!is_dir(dirname($target[0]))) {
mkdir(dirname($target[0]), 0777, true);
}
if (copy($source, $target[0])) {
echo "done.\n";
}
} catch (\Exception $e) {
echo $e->getMessage() . "\n";
}
}
}
}

29
vendor/yiisoft/yii2-composer/LICENSE.md vendored Normal file
View File

@@ -0,0 +1,29 @@
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.

219
vendor/yiisoft/yii2-composer/Plugin.php vendored Normal file
View File

@@ -0,0 +1,219 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\composer;
use Composer\Composer;
use Composer\DependencyResolver\Operation\UpdateOperation;
use Composer\EventDispatcher\EventSubscriberInterface;
use Composer\Installer\PackageEvent;
use Composer\Installer\PackageEvents;
use Composer\IO\IOInterface;
use Composer\Plugin\PluginInterface;
use Composer\Script;
use Composer\Script\ScriptEvents;
/**
* Plugin is the composer plugin that registers the Yii composer installer.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Plugin implements PluginInterface, EventSubscriberInterface
{
/**
* @var array noted package updates.
*/
private $_packageUpdates = [];
/**
* @var string path to the vendor directory.
*/
private $_vendorDir;
/**
* @inheritdoc
*/
public function activate(Composer $composer, IOInterface $io)
{
$installer = new Installer($io, $composer);
$composer->getInstallationManager()->addInstaller($installer);
$this->_vendorDir = rtrim($composer->getConfig()->get('vendor-dir'), '/');
$file = $this->_vendorDir . '/yiisoft/extensions.php';
if (!is_file($file)) {
@mkdir(dirname($file), 0777, true);
file_put_contents($file, "<?php\n\nreturn [];\n");
}
}
/**
* @inheritdoc
* @return array The event names to listen to.
*/
public static function getSubscribedEvents()
{
return [
PackageEvents::POST_PACKAGE_UPDATE => 'checkPackageUpdates',
ScriptEvents::POST_UPDATE_CMD => 'showUpgradeNotes',
];
}
/**
* Listen to POST_PACKAGE_UPDATE event and take note of the package updates.
* @param PackageEvent $event
*/
public function checkPackageUpdates(PackageEvent $event)
{
$operation = $event->getOperation();
if ($operation instanceof UpdateOperation) {
$this->_packageUpdates[$operation->getInitialPackage()->getName()] = [
'from' => $operation->getInitialPackage()->getVersion(),
'fromPretty' => $operation->getInitialPackage()->getPrettyVersion(),
'to' => $operation->getTargetPackage()->getVersion(),
'toPretty' => $operation->getTargetPackage()->getPrettyVersion(),
'direction' => $event->getPolicy()->versionCompare(
$operation->getInitialPackage(),
$operation->getTargetPackage(),
'<'
) ? 'up' : 'down',
];
}
}
/**
* Listen to POST_UPDATE_CMD event to display information about upgrade notes if appropriate.
* @param Script\Event $event
*/
public function showUpgradeNotes(Script\Event $event)
{
$packageName = 'yiisoft/yii2';
if (!isset($this->_packageUpdates[$packageName])) {
return;
}
$package = $this->_packageUpdates['yiisoft/yii2'];
// do not show a notice on up/downgrades between dev versions
// avoid messages like from version dev-master to dev-master
if ($package['fromPretty'] == $package['toPretty']) {
return;
}
$io = $event->getIO();
// print the relevant upgrade notes for the upgrade
// - only on upgrade, not on downgrade
// - only if the "from" version is non-dev, otherwise we have no idea which notes to show
if ($package['direction'] === 'up' && $this->isNumericVersion($package['fromPretty'])) {
$notes = $this->findUpgradeNotes($packageName, $package['fromPretty']);
if ($notes !== false && empty($notes)) {
// no relevent upgrade notes, do not show anything.
return;
}
$this->printUpgradeIntro($io, $package);
if ($notes) {
// safety check: do not display notes if they are too many
if (count($notes) > 250) {
$io->write("\n <fg=yellow;options=bold>The relevant notes for your upgrade are too long to be displayed here.</>");
} else {
$io->write("\n " . trim(implode("\n ", $notes)));
}
}
$io->write("\n You can find the upgrade notes for all versions online at:");
} else {
$this->printUpgradeIntro($io, $package);
$io->write("\n You can find the upgrade notes online at:");
}
$this->printUpgradeLink($io, $package);
}
/**
* Print link to upgrade notes
* @param IOInterface $io
* @param array $package
*/
private function printUpgradeLink($io, $package)
{
$maxVersion = $package['direction'] === 'up' ? $package['toPretty'] : $package['fromPretty'];
// make sure to always show a valid link, even if $maxVersion is something like dev-master
if (!$this->isNumericVersion($maxVersion)) {
$maxVersion = 'master';
}
$io->write(" https://github.com/yiisoft/yii2/blob/$maxVersion/framework/UPGRADE.md\n");
}
/**
* Print upgrade intro
* @param IOInterface $io
* @param array $package
*/
private function printUpgradeIntro($io, $package)
{
$io->write("\n <fg=yellow;options=bold>Seems you have "
. ($package['direction'] === 'up' ? 'upgraded' : 'downgraded')
. ' Yii Framework from version '
. $package['fromPretty'] . ' to ' . $package['toPretty'] . '.</>'
);
$io->write("\n <options=bold>Please check the upgrade notes for possible incompatible changes");
$io->write(' and adjust your application code accordingly.</>');
}
/**
* Read upgrade notes from a files and returns an array of lines
* @param string $packageName
* @param string $fromVersion until which version to read the notes
* @return array|false
*/
private function findUpgradeNotes($packageName, $fromVersion)
{
if (preg_match('/^([0-9]\.[0-9]+\.?[0-9]*)/', $fromVersion, $m)) {
$fromVersionMajor = $m[1];
} else {
$fromVersionMajor = $fromVersion;
}
$upgradeFile = $this->_vendorDir . '/' . $packageName . '/UPGRADE.md';
if (!is_file($upgradeFile) || !is_readable($upgradeFile)) {
return false;
}
$lines = preg_split('~\R~', file_get_contents($upgradeFile));
$relevantLines = [];
$consuming = false;
// whether an exact match on $fromVersion has been encountered
$foundExactMatch = false;
foreach($lines as $line) {
if (preg_match('/^Upgrade from Yii ([0-9]\.[0-9]+\.?[0-9\.]*)/i', $line, $matches)) {
if ($matches[1] === $fromVersion) {
$foundExactMatch = true;
}
if (version_compare($matches[1], $fromVersion, '<') && ($foundExactMatch || version_compare($matches[1], $fromVersionMajor, '<'))) {
break;
}
$consuming = true;
}
if ($consuming) {
$relevantLines[] = $line;
}
}
return $relevantLines;
}
/**
* Check whether a version is numeric, e.g. 2.0.10.
* @param string $version
* @return bool
*/
private function isNumericVersion($version)
{
return (bool) preg_match('~^([0-9]\.[0-9]+\.?[0-9\.]*)~', $version);
}
}

96
vendor/yiisoft/yii2-composer/README.md vendored Normal file
View File

@@ -0,0 +1,96 @@
<p align="center">
<a href="https://getcomposer.org/" target="_blank" rel="external">
<img src="https://getcomposer.org/img/logo-composer-transparent3.png" height="178px">
</a>
<h1 align="center">Yii 2 Composer Installer</h1>
<br>
</p>
This is the composer installer for [Yii framework 2.0](http://www.yiiframework.com) extensions.
It implements a new composer package type named `yii2-extension`,
which should be used by all Yii 2 extensions if they are distributed as composer packages.
For license information check the [LICENSE](LICENSE.md)-file.
[![Latest Stable Version](https://poser.pugx.org/yiisoft/yii2-composer/v/stable.png)](https://packagist.org/packages/yiisoft/yii2-composer)
[![Total Downloads](https://poser.pugx.org/yiisoft/yii2-composer/downloads.png)](https://packagist.org/packages/yiisoft/yii2-composer)
[![Build Status](https://travis-ci.org/yiisoft/yii2-composer.svg?branch=master)](https://travis-ci.org/yiisoft/yii2-composer)
Usage
-----
The Yii 2 Composer Installer is automatically installed with when installing the framework via Composer.
To use Yii 2 composer installer, simply set the package `type` to be `yii2-extension` in your `composer.json`,
like the following:
```json
{
"type": "yii2-extension",
"require": {
"yiisoft/yii2": "~2.0.0"
},
...
}
```
You may specify a bootstrapping class in the `extra` section. The `init()` method of the class will be executed each time
the Yii 2 application is responding to a request. For example,
```json
{
"type": "yii2-extension",
...,
"extra": {
"bootstrap": "yii\\jui\\Extension"
}
}
```
The `Installer` class also implements a static method `postCreateProject()` that can be called after
a Yii 2 project is created, through the `post-create-project-cmd` composer script.
A similar method exists for running tasks after each `composer install` call, which sis `postInstall()`.
These methods allow to run other `Installer` class methods like `setPermission()` or `generateCookieValidationKey()`,
depending on the corresponding parameters set in the `extra` section of the `composer.json` file.
For example,
```json
{
"name": "yiisoft/yii2-app-basic",
"type": "project",
...
"scripts": {
"post-create-project-cmd": [
"yii\\composer\\Installer::postCreateProject"
],
"post-install-cmd": [
"yii\\composer\\Installer::postInstall"
]
},
"extra": {
"yii\\composer\\Installer::postCreateProject": {
"setPermission": [
{
"runtime": "0777",
"web/assets": "0777",
"yii": "0755"
}
]
},
"yii\\composer\\Installer::postInstall": {
"copyFiles": [
{
"config/templates/console-local.php": "config/console-local.php",
"config/templates/web-local.php": "config/web-local.php",
"config/templates/db-local.php": "config/db-local.php",
"config/templates/cache.json": ["runtime/cache.json", true]
}
],
"generateCookieValidationKey": [
"config/web-local.php"
]
}
}
}
```

View File

@@ -0,0 +1,42 @@
{
"name": "yiisoft/yii2-composer",
"description": "The composer plugin for Yii extension installer",
"keywords": ["yii2", "composer", "extension installer"],
"type": "composer-plugin",
"license": "BSD-3-Clause",
"support": {
"issues": "https://github.com/yiisoft/yii2-composer/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-composer"
},
"authors": [
{
"name": "Qiang Xue",
"email": "qiang.xue@gmail.com"
},
{
"name": "Carsten Brandt",
"email": "mail@cebe.cc"
}
],
"require": {
"composer-plugin-api": "^1.0"
},
"require-dev": {
"composer/composer": "^1.0"
},
"autoload": {
"psr-4": { "yii\\composer\\": "" }
},
"autoload-dev": {
"psr-4": { "tests\\": "tests" }
},
"extra": {
"class": "yii\\composer\\Plugin",
"branch-alias": {
"dev-master": "2.0.x-dev"
}
}
}

204
vendor/yiisoft/yii2-debug/CHANGELOG.md vendored Normal file
View File

@@ -0,0 +1,204 @@
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.

View File

@@ -0,0 +1,29 @@
<?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',
];
}

View File

@@ -0,0 +1,298 @@
<?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'];
}
}

32
vendor/yiisoft/yii2-debug/LICENSE.md vendored Normal file
View File

@@ -0,0 +1,32 @@
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.

194
vendor/yiisoft/yii2-debug/LogTarget.php vendored Normal file
View File

@@ -0,0 +1,194 @@
<?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;
}
}

358
vendor/yiisoft/yii2-debug/Module.php vendored Normal file
View File

@@ -0,0 +1,358 @@
<?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();
}
}

181
vendor/yiisoft/yii2-debug/Panel.php vendored Normal file
View File

@@ -0,0 +1,181 @@
<?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;
}
}

101
vendor/yiisoft/yii2-debug/README.md vendored Normal file
View File

@@ -0,0 +1,101 @@
<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]);
},
```

View File

@@ -0,0 +1,27 @@
<?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',
];
}

View File

@@ -0,0 +1,25 @@
<?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',
];
}

View File

@@ -0,0 +1,54 @@
<?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);
}
}

View File

@@ -0,0 +1 @@
<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>

After

Width:  |  Height:  |  Size: 249 B

View File

@@ -0,0 +1,158 @@
.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;
}

View File

@@ -0,0 +1 @@
<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>

After

Width:  |  Height:  |  Size: 430 B

View File

@@ -0,0 +1 @@
<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>

After

Width:  |  Height:  |  Size: 585 B

View File

@@ -0,0 +1,213 @@
.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;
}
}

View File

@@ -0,0 +1,66 @@
(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')
}));
})();

View File

@@ -0,0 +1,353 @@
#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;
}

View File

@@ -0,0 +1,348 @@
(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;
};
}
})();

View File

@@ -0,0 +1,34 @@
(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();
});
})();

View File

@@ -0,0 +1,81 @@
<?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;
}
}

View File

@@ -0,0 +1,41 @@
<?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');
}
}

View File

@@ -0,0 +1,25 @@
<?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);
}
}

View File

@@ -0,0 +1,25 @@
<?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;
}
}

View File

@@ -0,0 +1,25 @@
<?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);
}
}

View File

@@ -0,0 +1,39 @@
<?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();
}

View File

@@ -0,0 +1,40 @@
<?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;
}
}

41
vendor/yiisoft/yii2-debug/composer.json vendored Normal file
View File

@@ -0,0 +1,41 @@
{
"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"
}
]
}

View File

@@ -0,0 +1,181 @@
<?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'.");
}
}

View File

@@ -0,0 +1,60 @@
<?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;
}
}

View File

@@ -0,0 +1,76 @@
<?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];
}
}
}
}

View File

@@ -0,0 +1,149 @@
<?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());
}
}

View File

@@ -0,0 +1,44 @@
<?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]));
}
}
}

View File

@@ -0,0 +1,80 @@
<?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;
}
}

View File

@@ -0,0 +1,133 @@
<?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);
}
}

View File

@@ -0,0 +1,90 @@
<?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;
}
}

View File

@@ -0,0 +1,124 @@
<?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;
}
}

View File

@@ -0,0 +1,84 @@
<?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;
}
}

View File

@@ -0,0 +1,116 @@
<?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;
}
}

View File

@@ -0,0 +1,28 @@
<?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);
}

View File

@@ -0,0 +1,167 @@
<?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)
];
}
}

View File

@@ -0,0 +1,84 @@
<?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;
}
}

View File

@@ -0,0 +1,180 @@
<?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>';
}
}

View File

@@ -0,0 +1,140 @@
<?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;
}
}

View File

@@ -0,0 +1,109 @@
<?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,
];
}
}

View File

@@ -0,0 +1,347 @@
<?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);
}
}

View File

@@ -0,0 +1,110 @@
<?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;
}
}

View File

@@ -0,0 +1,157 @@
<?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;
}
}

View File

@@ -0,0 +1,104 @@
<?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;
}
}

View File

@@ -0,0 +1,148 @@
<?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;
}
}

View File

@@ -0,0 +1,84 @@
<?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)
];
}
}

View File

@@ -0,0 +1,247 @@
<?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;
}
}

View File

@@ -0,0 +1,332 @@
<?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);
}
}

View File

@@ -0,0 +1,143 @@
<?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>

View File

@@ -0,0 +1,90 @@
<?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>

View File

@@ -0,0 +1,8 @@
<?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; ?>

View File

@@ -0,0 +1,55 @@
<?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();

View File

@@ -0,0 +1,10 @@
<?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>

View File

@@ -0,0 +1,33 @@
<?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; ?>

View File

@@ -0,0 +1,136 @@
<?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>

View File

@@ -0,0 +1,12 @@
<?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; ?>

View File

@@ -0,0 +1,75 @@
<?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%',
],
],
],
]);

View File

@@ -0,0 +1,38 @@
<?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>

View File

@@ -0,0 +1,37 @@
<?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']]),
],
],
]);

View File

@@ -0,0 +1,59 @@
<?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() ?>

Some files were not shown because too many files have changed in this diff Show More