├── .gitignore ├── README.md ├── README_ru.md ├── Yii2Debug.php ├── Yii2DebugLogRoute.php ├── Yii2DebugModule.php ├── Yii2DebugPanel.php ├── Yii2DebugViewRenderer.php ├── assets ├── css │ ├── bootstrap.css │ ├── bootstrap.min.css │ └── main.css ├── img │ ├── glyphicons-halflings-white.png │ └── glyphicons-halflings.png └── js │ ├── bootstrap.js │ ├── bootstrap.min.js │ └── filter.js ├── composer.json ├── controllers └── DefaultController.php ├── panels ├── Yii2ConfigPanel.php ├── Yii2DbPanel.php ├── Yii2LogPanel.php ├── Yii2ProfilingPanel.php ├── Yii2RequestPanel.php └── Yii2ViewPanel.php └── views ├── default ├── _explain.php ├── _toolbar.php ├── config.php ├── explain.php ├── index.php ├── toolbar.css ├── toolbar.php └── view.php ├── layouts └── main.php └── panels ├── _detail.php ├── _tabs.php ├── config.php ├── config_bar.php ├── db.php ├── db_bar.php ├── log.php ├── log_bar.php ├── profiling.php ├── profiling_bar.php ├── request.php ├── request_bar.php ├── view.php └── view_bar.php /.gitignore: -------------------------------------------------------------------------------- 1 | # phpstorm project files 2 | .idea 3 | 4 | # netbeans project files 5 | nbproject 6 | 7 | # zend studio for eclipse project files 8 | .buildpath 9 | .project 10 | .settings 11 | 12 | # windows thumbnail cache 13 | Thumbs.db 14 | 15 | # composer itself is not needed 16 | composer.phar 17 | composer.lock 18 | /vendor 19 | 20 | # Mac DS_Store Files 21 | .DS_Store 22 | 23 | # phpunit itself is not needed 24 | phpunit.phar 25 | # local phpunit config 26 | /phpunit.xml -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | yii2-debug 2 | ================= 3 | 4 | Debug panel for Yii 1.1 (ported from Yii 2). 5 | 6 | [![Latest Stable Version](https://poser.pugx.org/zhuravljov/yii2-debug/version.svg)](https://packagist.org/packages/zhuravljov/yii2-debug) 7 | [![Total Downloads](https://poser.pugx.org/zhuravljov/yii2-debug/downloads.png)](https://packagist.org/packages/zhuravljov/yii2-debug) 8 | 9 | Installation 10 | ------------- 11 | 12 | This extension is available at packagist.org and can be installed via composer by following command: 13 | 14 | `composer require --dev zhuravljov/yii2-debug`. 15 | 16 | If you want to install this extension manually just copy sources to `/protected/extensions` directory. 17 | 18 | To enable toolbar in your application add following lines to config: 19 | 20 | ```php 21 | return array( 22 | 'preload' => array( 23 | 'debug', 24 | ), 25 | 'components' => array( 26 | 'debug' => array( 27 | 'class' => 'vendor.zhuravljov.yii2-debug.Yii2Debug', // composer installation 28 | //'class' => 'ext.yii2-debug.Yii2Debug', // manual installation 29 | ), 30 | 'db' => array( 31 | 'enableProfiling' => true, 32 | 'enableParamLogging' => true, 33 | ), 34 | ), 35 | ); 36 | ``` 37 | 38 | Configuration 39 | --------- 40 | 41 | You can customize debug panel behavior with this options: 42 | 43 | - `enabled` - enable/disable debug panel. 44 | - `allowedIPs` - list of IPs that are allowed to access debug toolbar. Default `array('127.0.0.1', '::1')`. 45 | - `accessExpression` - additional php expression for access evaluation. 46 | - `logPath` - directory storing the debugger data files. This can be specified using a path alias. Default `/runtime/debug`. 47 | - `historySize` - maximum number of debug data files to keep. If there are more files generated, the oldest ones will be removed. 48 | - `highlightCode` - highlight code. Highlight SQL queries and PHP variables. This parameter can be set for each panel individually. 49 | - `moduleId ` - module ID for viewing stored debug logs. Default `debug`. 50 | - `showConfig` - show brief application configuration page. Default `false`. 51 | - `hiddenConfigOptions` - list of unsecure component options to hide (like login, passwords, secret keys). 52 | Default is to hide `username` and `password` of `db` component. 53 | - `internalUrls` - use nice routes rules in debug module. 54 | - `panels` - list of debug panels. 55 | 56 | Each attached panel can be configured individually, for example: 57 | 58 | ```php 59 | 'debug' => array( 60 | 'class' => 'ext.yii2-debug.Yii2Debug', 61 | 'panels' => array( 62 | 'db' => array( 63 | // Disable code highlighting. 64 | 'highlightCode' => false, 65 | // Disable substitution of placeholders with values in SQL queries. 66 | 'insertParamValues' => false, 67 | ), 68 | ), 69 | ), 70 | ``` 71 | 72 | Each panel have callback option `filterData`. 73 | You can define custom function for filtering input data before writing it in to debug log. 74 | It's useful when you need to hide something secret or just delete data from logs. 75 | Be careful with data structure manipulation. It can lead to log parsing errors. 76 | 77 | Example: 78 | 79 | ```php 80 | 'debug' => array( 81 | 'class' => 'ext.yii2-debug.Yii2Debug', 82 | 'panels' => array( 83 | 'db' => array( 84 | 'filterData' => function($data){ 85 | // Your code here 86 | return $data; 87 | } 88 | ), 89 | ), 90 | ), 91 | ``` 92 | 93 | Creating own panels 94 | ------------------------------- 95 | 96 | To create own debug panel you can extend class `Yii2DebugPanel`, for example: 97 | 98 | ```php 99 | class MyTestPanel extends Yii2DebugPanel 100 | { 101 | /** 102 | * The name of panel printed in debugger 103 | */ 104 | public function getName() 105 | { 106 | return 'Name'; 107 | } 108 | 109 | /** 110 | * Return summary html with results of execution in current request. 111 | * Data is available through $this->data 112 | */ 113 | public function getSummary() 114 | { 115 | return ''; 116 | } 117 | 118 | /** 119 | * Return detailed html report with results of execution in current request. 120 | * Data is available through $this->data 121 | */ 122 | public function getDetail() 123 | { 124 | return ''; 125 | } 126 | 127 | /** 128 | * Return data required for storing in logs. 129 | */ 130 | public function save() 131 | { 132 | return array(); 133 | } 134 | } 135 | ``` 136 | 137 | And attach this panel in config: 138 | 139 | ```php 140 | 'panels' => array( 141 | 'test' => array( 142 | 'class' => 'path.to.panel.MyTestPanel', 143 | // ... 144 | ), 145 | ), 146 | ``` 147 | 148 | Disable individual panels 149 | ------------------------------- 150 | 151 | To disable an individual panel, either a core or custom panel, set the `enabled` property in the panel config to `false`. 152 | 153 | Example: Disable core `profiling` panel 154 | 155 | ```php 156 | 'panels' => array( 157 | 'profiling' => array( 158 | 'enabled' => false, 159 | // ... 160 | ), 161 | ), 162 | ``` 163 | 164 | Variables dumping 165 | --------------- 166 | 167 | With static method `Yii2Debug::dump()` you can dump any data and examine it later in debug log. 168 | 169 | Miscellaneous 170 | ---------------- 171 | 172 | ### Status Code 173 | 174 | If you using PHP < 5.4, debug panel can't detect redirects by himself. 175 | You can use following code as workaround: 176 | 177 | ```php 178 | 'panels' => array( 179 | 'request' => array( 180 | 'filterData' => function($data){ 181 | if (empty($data['statusCode'])) { 182 | if (isset($data['responseHeaders']['Location'])) { 183 | $data['statusCode'] = 302; 184 | } else { 185 | $data['statusCode'] = 200; 186 | } 187 | } 188 | return $data; 189 | }, 190 | ), 191 | ), 192 | ``` 193 | 194 | Such code just set 302 code if `Location` header is present. 195 | Codes like 4xx and 5xx can be detected in debug panel by himself. 196 | In PHP 5.4 and higher debug panel uses native php function `http_response_code()` for detecting http response code, 197 | and there is no need to use this workaround. 198 | -------------------------------------------------------------------------------- /README_ru.md: -------------------------------------------------------------------------------- 1 | yii2-debug 2 | ========== 3 | 4 | Отладочная панель для Yii 1.1 портированная из Yii 2. 5 | 6 | [![Total Downloads](https://poser.pugx.org/zhuravljov/yii2-debug/downloads.png)](https://packagist.org/packages/zhuravljov/yii2-debug) 7 | 8 | Использование 9 | ------------- 10 | 11 | Необходимо скопировать исходники в `/protected/extensions` и дополнить конфиг 12 | своего проекта следующими настройками: 13 | 14 | ```php 15 | return array( 16 | 'preload' => array( 17 | 'debug', 18 | ), 19 | 'components' => array( 20 | 'debug' => array( 21 | 'class' => 'ext.yii2-debug.Yii2Debug', 22 | ), 23 | 'db' => array( 24 | 'enableProfiling' => true, 25 | 'enableParamLogging' => true, 26 | ), 27 | ), 28 | ); 29 | ``` 30 | 31 | Настройка 32 | --------- 33 | 34 | Для более тонкой настройки компонента доступны параметры: 35 | 36 | - `enabled` - включение/выключение дебаггера. 37 | - `allowedIPs` - список ip и масок, которым разрешен доступ к панели. По умолчанию `array('127.0.0.1', '::1')`. 38 | - `accessExpression` - дополнительное условие доступа к панели. 39 | - `logPath` - путь для записи логов. По умолчанию `/runtime/debug`. 40 | - `historySize` - максимальное кол-во записанных логов. Более ранние логи будут удаляться. 41 | - `highlightCode` - подсветка кода. Подсвечиваются sql-запросы и php-массивы данных. 42 | Также параметр `highlightCode` можно настраивать для каждой панели отдельно. 43 | - `moduleId ` - ID модуля для просмотра ранее сохраненных данных. По умолчанию `debug`. 44 | - `showConfig` - показывать или нет страницу с конфигурацией приложения. По умолчанию `false`. 45 | - `hiddenConfigOptions` - список опций значения которых необходимо скрывать при выводе 46 | на страницу с конфигурацией приложения. По умолчанию скрываются свойства `username` 47 | и `password` компонента `db`. 48 | - `internalUrls` - использование внутренних роутов для urlManager 49 | - `panels` - список подключенных к отладчику панелей. 50 | 51 | Каждую подключаемую к отладчику панель так-же можно конфигурировать. Например: 52 | 53 | ```php 54 | 'debug' => array( 55 | 'class' => 'ext.yii2-debug.Yii2Debug', 56 | 'panels' => array( 57 | 'db' => array( 58 | // Отключить подсветку SQL 59 | 'highlightCode' => false, 60 | // Отключить подстановку параметров в SQL-запрос 61 | 'insertParamValues' => false, 62 | ), 63 | ), 64 | ), 65 | ``` 66 | 67 | Для каждой панели доступен callback-параметр `filterData`. Он дает возможность 68 | обработать массив данных перед сохранением этих данных в лог. Это может быть 69 | полезно в том случае, когда в данных проходит какая-то секретная информация, и 70 | ее нужно каким-то образом экранировать либо вообще изъять из массива. 71 | 72 | Пример: 73 | 74 | ```php 75 | 'debug' => array( 76 | 'class' => 'ext.yii2-debug.Yii2Debug', 77 | 'panels' => array( 78 | 'db' => array( 79 | 'filterData' => function($data){ 80 | // Обработка 81 | return $data; 82 | } 83 | ), 84 | ), 85 | ), 86 | ``` 87 | 88 | Будьте осторожны с изменением структуры данных. Это может стать причиной ошибок 89 | при просмотре. 90 | 91 | Подключение собственных панелей 92 | ------------------------------- 93 | 94 | Необходимо разработать свой класс унаследовав его от `Yii2DebugPanel`, например: 95 | 96 | ```php 97 | class MyTestPanel extends Yii2DebugPanel 98 | { 99 | // Имя вашей панели, выводится в меню отладчика 100 | public function getName() 101 | { 102 | return 'Name'; 103 | } 104 | 105 | // Функция должна возвращать HTML для вывода в тулбар 106 | // Данные доступны через свойство $this->data 107 | public function getSummary() 108 | { 109 | return ''; 110 | } 111 | 112 | // Функция должна вернуть HTML с детальной информацией 113 | // Данные доступны через свойство $this->data 114 | public function getDetail() 115 | { 116 | return ''; 117 | } 118 | 119 | // Функция должна вернуть массив данных для сохранения в лог 120 | public function save() 121 | { 122 | return array(); 123 | } 124 | } 125 | ``` 126 | 127 | И подключить его в конфиг: 128 | 129 | ```php 130 | 'panels' => array( 131 | 'test' => array( 132 | 'class' => 'path.to.panel.MyTestPanel', 133 | // ... 134 | ), 135 | ), 136 | ``` 137 | 138 | Dump переменных 139 | --------------- 140 | 141 | С помощью метода `Yii2Debug::dump()` можно писать в лог переменные любого типа, 142 | и просматривать их на странице "Logs". 143 | 144 | Различные приёмы 145 | ---------------- 146 | 147 | ### Status Code 148 | 149 | Если в вашем проекте используется PHP < 5.4, то для записи http-кода в логи и 150 | вывода его на панель можно воспользоваться следующей настройкой: 151 | 152 | ```php 153 | 'panels' => array( 154 | 'request' => array( 155 | 'filterData' => function($data){ 156 | if (empty($data['statusCode'])) { 157 | if (isset($data['responseHeaders']['Location'])) { 158 | $data['statusCode'] = 302; 159 | } else { 160 | $data['statusCode'] = 200; 161 | } 162 | } 163 | return $data; 164 | }, 165 | ), 166 | ), 167 | ``` 168 | 169 | Таким образом 302-й код определяется косвенно, исходя из наличия заголовка `Location`. 170 | Коды 4xx и 5xx определяются расширением самостоятельно. В PHP 5.4 для определения 171 | http-кода расширение использует встроенную функцию `http_response_code()`. -------------------------------------------------------------------------------- /Yii2Debug.php: -------------------------------------------------------------------------------- 1 | 9 | * @package Yii2Debug 10 | * @since 1.1.13 11 | */ 12 | class Yii2Debug extends CApplicationComponent 13 | { 14 | /** 15 | * @var array the list of IPs that are allowed to access this module. 16 | * Each array element represents a single IP filter which can be either an IP address 17 | * or an address with wildcard (e.g. 192.168.0.*) to represent a network segment. 18 | * The default value is `['127.0.0.1', '::1']`, which means the module can only be accessed 19 | * by localhost. 20 | */ 21 | public $allowedIPs = array('127.0.0.1', '::1'); 22 | /** 23 | * @var null|string|callback Additional php expression for access evaluation. 24 | */ 25 | public $accessExpression; 26 | /** 27 | * @var array|Yii2DebugPanel[] list debug panels. The array keys are the panel IDs, and values are the corresponding 28 | * panel class names or configuration arrays. This will be merged with ::corePanels(). 29 | * You may reconfigure a core panel via this property by using the same panel ID. 30 | * You may also disable a core panel by setting the `enabled` property to be false. 31 | */ 32 | public $panels = array(); 33 | /** 34 | * @var string the directory storing the debugger data files. This can be specified using a path alias. 35 | */ 36 | public $logPath; 37 | /** 38 | * @var integer the maximum number of debug data files to keep. If there are more files generated, 39 | * the oldest ones will be removed. 40 | */ 41 | public $historySize = 50; 42 | /** 43 | * @var bool enable/disable component in application. 44 | */ 45 | public $enabled = true; 46 | /** 47 | * @var string module ID for viewing stored debug logs. 48 | */ 49 | public $moduleId = 'debug'; 50 | /** 51 | * @var bool use nice route rules in debug module. 52 | */ 53 | public $internalUrls = true; 54 | /** 55 | * @var bool highlight code in debug logs. 56 | */ 57 | public $highlightCode = true; 58 | /** 59 | * @var bool show brief application configuration. 60 | */ 61 | public $showConfig = false; 62 | /** 63 | * @var array list of unsecure component options (like login, passwords, secret keys) that 64 | * will be hidden in application configuration page. 65 | */ 66 | public $hiddenConfigOptions = array( 67 | 'components/db/username', 68 | 'components/db/password', 69 | ); 70 | 71 | private $_tag; 72 | 73 | /** 74 | * Panel initialization. 75 | * Generate unique tag for page. Attach panels, log watcher. Register scripts for printing debug panel. 76 | */ 77 | public function init() 78 | { 79 | parent::init(); 80 | if (!$this->enabled) return; 81 | 82 | // Do not run on console. 83 | if (Yii::app() instanceof CConsoleApplication) { 84 | return; 85 | } 86 | 87 | Yii::setPathOfAlias('yii2-debug', dirname(__FILE__)); 88 | Yii::app()->setImport(array( 89 | 'yii2-debug.*', 90 | 'yii2-debug.panels.*', 91 | )); 92 | 93 | if ($this->logPath === null) { 94 | $this->logPath = Yii::app()->getRuntimePath() . '/debug'; 95 | } 96 | 97 | $panels = array(); 98 | foreach (CMap::mergeArray($this->corePanels(), $this->panels) as $id => $config) { 99 | if (isset($config['enabled']) && !$config['enabled']) { 100 | continue; 101 | } 102 | 103 | if (!isset($config['highlightCode'])) $config['highlightCode'] = $this->highlightCode; 104 | $panels[$id] = Yii::createComponent($config, $this, $id); 105 | } 106 | $this->panels = $panels; 107 | 108 | Yii::app()->setModules(array( 109 | $this->moduleId => array( 110 | 'class' => 'Yii2DebugModule', 111 | 'owner' => $this, 112 | ), 113 | )); 114 | 115 | if ($this->internalUrls && (Yii::app()->getUrlManager()->urlFormat == 'path')) { 116 | $rules = array(); 117 | foreach ($this->coreUrlRules() as $key => $value) { 118 | $rules[$this->moduleId . '/' . $key] = $this->moduleId . '/' . $value; 119 | } 120 | Yii::app()->getUrlManager()->addRules($rules, false); 121 | } 122 | 123 | Yii::app()->attachEventHandler('onEndRequest', array($this, 'onEndRequest')); 124 | $this->initToolbar(); 125 | } 126 | 127 | /** 128 | * @return string current page tag 129 | */ 130 | public function getTag() 131 | { 132 | if ($this->_tag === null) $this->_tag = uniqid(); 133 | return $this->_tag; 134 | } 135 | 136 | /** 137 | * @return array default panels 138 | */ 139 | protected function corePanels() 140 | { 141 | return array( 142 | 'config' => array( 143 | 'class' => 'Yii2ConfigPanel', 144 | ), 145 | 'request' => array( 146 | 'class' => 'Yii2RequestPanel', 147 | ), 148 | 'log' => array( 149 | 'class' => 'Yii2LogPanel', 150 | ), 151 | 'profiling' => array( 152 | 'class' => 'Yii2ProfilingPanel', 153 | ), 154 | 'db' => array( 155 | 'class' => 'Yii2DbPanel', 156 | ), 157 | ); 158 | } 159 | 160 | protected function coreUrlRules() 161 | { 162 | return array( 163 | '' => 'default/index', 164 | '/' => 'default/', 165 | '/' => 'default/view', 166 | 'latest/' => 'default/view', 167 | '' => 'default/', 168 | ); 169 | } 170 | 171 | /** 172 | * Register debug panel scripts. 173 | */ 174 | protected function initToolbar() 175 | { 176 | if (!$this->checkAccess()) return; 177 | $assetsUrl = CHtml::asset(dirname(__FILE__) . '/assets'); 178 | /* @var CClientScript $cs */ 179 | $cs = Yii::app()->getClientScript(); 180 | $cs->registerCoreScript('jquery'); 181 | $url = Yii::app()->createUrl($this->moduleId . '/default/toolbar', array('tag' => $this->getTag())); 182 | $cs->registerScript(__CLASS__ . '#toolbar', <<').appendTo('body').load('$url', function(){ 185 | if (window.localStorage && localStorage.getItem('yii2-debug-toolbar') == 'minimized') { 186 | $('#yii2-debug-toolbar').hide(); 187 | $('#yii2-debug-toolbar-min').show(); 188 | } else { 189 | $('#yii2-debug-toolbar-min').hide(); 190 | $('#yii2-debug-toolbar').show(); 191 | } 192 | $('#yii2-debug-toolbar .yii2-debug-toolbar-toggler').click(function(){ 193 | $('#yii2-debug-toolbar').hide(); 194 | $('#yii2-debug-toolbar-min').show(); 195 | if (window.localStorage) { 196 | localStorage.setItem('yii2-debug-toolbar', 'minimized'); 197 | } 198 | }); 199 | $('#yii2-debug-toolbar-min .yii2-debug-toolbar-toggler').click(function(){ 200 | $('#yii2-debug-toolbar-min').hide(); 201 | $('#yii2-debug-toolbar').show(); 202 | if (window.localStorage) { 203 | localStorage.setItem('yii2-debug-toolbar', 'maximized'); 204 | } 205 | }); 206 | }); 207 | })(jQuery); 208 | JS 209 | ); 210 | } 211 | 212 | /** 213 | * @param CEvent $event 214 | */ 215 | protected function onEndRequest($event) 216 | { 217 | $this->processDebug(); 218 | } 219 | 220 | /** 221 | * Log processing routine. 222 | */ 223 | protected function processDebug() 224 | { 225 | $data = array(); 226 | foreach ($this->panels as $panel) { 227 | $data[$panel->getId()] = $panel->save(); 228 | if (isset($panel->filterData)) { 229 | $data[$panel->getId()] = $panel->evaluateExpression( 230 | $panel->filterData, 231 | array('data' => $data[$panel->getId()]) 232 | ); 233 | } 234 | $panel->load($data[$panel->getId()]); 235 | } 236 | 237 | $data['summary'] = $this->prepareDataSummary(); 238 | 239 | $path = $this->logPath; 240 | if (!is_dir($path)) mkdir($path); 241 | 242 | file_put_contents("$path/{$this->getTag()}.data", serialize($data)); 243 | $this->updateIndexFile("$path/index.data", $data['summary']); 244 | } 245 | 246 | /** 247 | * Updates index file with summary log data 248 | * 249 | * @param string $indexFile path to index file 250 | * @param array $summary summary log data 251 | * @throws Exception 252 | */ 253 | private function updateIndexFile($indexFile, $summary) 254 | { 255 | touch($indexFile); 256 | if (($fp = @fopen($indexFile, 'r+')) === false) { 257 | throw new Exception("Unable to open debug data index file: $indexFile"); 258 | } 259 | @flock($fp, LOCK_EX); 260 | $manifest = ''; 261 | while (($buffer = fgets($fp)) !== false) { 262 | $manifest .= $buffer; 263 | } 264 | if (!feof($fp) || empty($manifest)) { 265 | // error while reading index data, ignore and create new 266 | $manifest = array(); 267 | } else { 268 | $manifest = unserialize($manifest); 269 | } 270 | 271 | $manifest[$this->tag] = $summary; 272 | $this->resizeHistory($manifest); 273 | 274 | ftruncate($fp, 0); 275 | rewind($fp); 276 | fwrite($fp, serialize($manifest)); 277 | 278 | @flock($fp, LOCK_UN); 279 | @fclose($fp); 280 | } 281 | 282 | /** 283 | * Debug files rotation according to {@link ::$historySize}. 284 | * @param $manifest 285 | */ 286 | protected function resizeHistory(&$manifest) 287 | { 288 | $tags = array_keys($manifest); 289 | $count = 0; 290 | foreach ($tags as $tag) { 291 | if (!$this->getLock($tag)) $count++; 292 | } 293 | if ($count > $this->historySize + 10) { 294 | $path = $this->logPath; 295 | $n = $count - $this->historySize; 296 | foreach ($tags as $tag) { 297 | if (!$this->getLock($tag)) { 298 | @unlink("$path/$tag.data"); 299 | unset($manifest[$tag]); 300 | if (--$n <= 0) break; 301 | } 302 | } 303 | } 304 | } 305 | 306 | /** 307 | * Check access rights. 308 | * @return bool 309 | */ 310 | public function checkAccess() 311 | { 312 | if ( 313 | $this->accessExpression !== null && 314 | !$this->evaluateExpression($this->accessExpression) 315 | ) { 316 | return false; 317 | } 318 | $ip = Yii::app()->getRequest()->getUserHostAddress(); 319 | foreach ($this->allowedIPs as $filter) { 320 | if ( 321 | $filter === '*' || $filter === $ip || ( 322 | ($pos = strpos($filter, '*')) !== false && 323 | !strncmp($ip, $filter, $pos) 324 | ) 325 | ) { 326 | return true; 327 | } 328 | } 329 | return false; 330 | } 331 | 332 | /** 333 | * Dump variable to debug log. 334 | * @param mixed $data 335 | */ 336 | public static function dump($data) 337 | { 338 | Yii::log(serialize($data), CLogger::LEVEL_INFO, Yii2LogPanel::CATEGORY_DUMP); 339 | } 340 | 341 | /** 342 | * @var 343 | */ 344 | private $_locks; 345 | 346 | /** 347 | * @param string $tag 348 | * @return bool 349 | */ 350 | public function getLock($tag) 351 | { 352 | if ($this->_locks === null) { 353 | $locksFile = $this->logPath . '/locks.data'; 354 | if (is_file($locksFile)) { 355 | $this->_locks = array_flip(unserialize(file_get_contents($locksFile))); 356 | } else { 357 | $this->_locks = array(); 358 | } 359 | } 360 | return isset($this->_locks[$tag]); 361 | } 362 | 363 | /** 364 | * @param string $tag 365 | * @param bool $value 366 | */ 367 | public function setLock($tag, $value) 368 | { 369 | $value = !!$value; 370 | if ($this->getLock($tag) !== $value) { 371 | if ($value) { 372 | $this->_locks[$tag] = true; 373 | } else { 374 | unset($this->_locks[$tag]); 375 | } 376 | $locksFile = $this->logPath . '/locks.data'; 377 | file_put_contents($locksFile, serialize(array_keys($this->_locks))); 378 | } 379 | } 380 | 381 | /** 382 | * Convert data to plain array in recursive manner. 383 | * @param mixed $data 384 | * @return array 385 | */ 386 | public static function prepareData($data) 387 | { 388 | static $parents = array(); 389 | 390 | $result = array(); 391 | if (is_array($data) || $data instanceof CMap) { 392 | foreach ($data as $key => $value) { 393 | $result[$key] = self::prepareData($value); 394 | } 395 | } elseif (is_object($data)) { 396 | if (!in_array($data, $parents, true)) { 397 | array_push($parents, $data); 398 | $result['class'] = get_class($data); 399 | if ($data instanceof CActiveRecord) { 400 | foreach ($data->attributes as $field => $value) { 401 | $result[$field] = $value; 402 | } 403 | } 404 | foreach (get_object_vars($data) as $key => $value) { 405 | $result[$key] = self::prepareData($value); 406 | } 407 | array_pop($parents); 408 | } else { 409 | $result = get_class($data) . '()'; 410 | } 411 | } else { 412 | $result = $data; 413 | } 414 | return $result; 415 | } 416 | 417 | protected function prepareDataSummary() 418 | { 419 | $statusCode = null; 420 | if (isset($this->panels['request']) && isset($this->panels['request']->data['statusCode'])) { 421 | $statusCode = $this->panels['request']->data['statusCode']; 422 | } 423 | 424 | $request = Yii::app()->getRequest(); 425 | 426 | return array( 427 | 'tag' => $this->getTag(), 428 | 'url' => $request->getHostInfo() . $request->getUrl(), 429 | 'ajax' => $request->getIsAjaxRequest(), 430 | 'method' => $request->getRequestType(), 431 | 'code' => $statusCode, 432 | 'ip' => $request->getUserHostAddress(), 433 | 'time' => time(), 434 | ); 435 | } 436 | } 437 | -------------------------------------------------------------------------------- /Yii2DebugLogRoute.php: -------------------------------------------------------------------------------- 1 | 5 | * @package Yii2Debug 6 | */ 7 | class Yii2DebugLogRoute extends CLogRoute 8 | { 9 | /** 10 | * @var array 11 | */ 12 | private $localLogs = array(); 13 | 14 | /** 15 | * @return array 16 | */ 17 | public function getLogs() 18 | { 19 | return $this->localLogs; 20 | } 21 | 22 | /** 23 | * Save logs to local variable. 24 | * @param array $logs list of log messages. 25 | */ 26 | protected function processLogs($logs) 27 | { 28 | $this->localLogs = array_merge($this->localLogs, $logs); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Yii2DebugModule.php: -------------------------------------------------------------------------------- 1 | 5 | * @package Yii2Debug 6 | * @since 1.1.13 7 | * 8 | * @property Yii2Debug $owner 9 | */ 10 | class Yii2DebugModule extends CWebModule 11 | { 12 | public function beforeControllerAction($controller, $action) 13 | { 14 | if ( 15 | parent::beforeControllerAction($controller, $action) && 16 | $this->owner->checkAccess() 17 | ) { 18 | // Отключение дебагера на страницах просмотра ранее сохраненных логов 19 | Yii::app()->detachEventHandler('onEndRequest', array($this->owner, 'onEndRequest')); 20 | // Отключение сторонних шаблонизаторов 21 | Yii::app()->setComponents(array('viewRenderer' => array('enabled' => false)), false); 22 | // Сброс скрипта для вывода тулбара 23 | Yii::app()->getClientScript()->reset(); 24 | // Clears client script map defined in app config 25 | Yii::app()->getClientScript()->scriptMap = array(); 26 | return true; 27 | } 28 | else 29 | return false; 30 | } 31 | 32 | private $_owner; 33 | 34 | /** 35 | * @return Yii2Debug 36 | */ 37 | public function getOwner() 38 | { 39 | return $this->_owner; 40 | } 41 | 42 | /** 43 | * @param Yii2Debug $owner 44 | */ 45 | public function setOwner($owner) 46 | { 47 | $this->_owner = $owner; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Yii2DebugPanel.php: -------------------------------------------------------------------------------- 1 | 8 | * @package Yii2Debug 9 | * @since 1.1.13 10 | * 11 | * @property Yii2Debug $owner 12 | * @property string $id страницы 13 | * @property string $tag метка для просмотра информации 14 | * @property string $url 15 | */ 16 | class Yii2DebugPanel extends CComponent 17 | { 18 | /** 19 | * @var bool|null подсветка кода. По умолчанию Yii2Debug::$highlightCode 20 | */ 21 | public $highlightCode; 22 | /** 23 | * @var callback функция для обработки данных панели перед сохранением 24 | */ 25 | public $filterData; 26 | 27 | /** 28 | * @var array array of actions to add to the debug modules default controller. 29 | * This array will be merged with all other panels actions property. 30 | */ 31 | public $actions = array(); 32 | 33 | /** 34 | * @var bool Collect log messages by Yii2DebugLogRoute. 35 | */ 36 | protected $_logsEnabled = false; 37 | /** 38 | * @var string 39 | * @see Yii2DebugLogRoute::categories 40 | */ 41 | protected $_logsCategories = array(); 42 | /** 43 | * @var string 44 | * @see Yii2DebugLogRoute::levels 45 | */ 46 | protected $_logsLevels = ''; 47 | /** 48 | * @var Yii2Debug 49 | */ 50 | private $_owner; 51 | /** 52 | * @var string id страницы 53 | */ 54 | private $_id; 55 | /** 56 | * @var string tag метка для просмотра информации 57 | */ 58 | private $_tag; 59 | /** 60 | * @var array массив отладочных данных 61 | */ 62 | private $_data; 63 | /** 64 | * @var Yii2DebugLogRoute 65 | */ 66 | private $_logRoute; 67 | 68 | /** 69 | * @return string название панели для вывода в меню 70 | */ 71 | public function getName() 72 | { 73 | return ''; 74 | } 75 | 76 | /** 77 | * @return string html-контент для вывода в дебаг-панель 78 | */ 79 | public function getSummary() 80 | { 81 | return ''; 82 | } 83 | 84 | /** 85 | * @return string html-контент для вывода на страницу 86 | */ 87 | public function getDetail() 88 | { 89 | return ''; 90 | } 91 | 92 | /** 93 | * Базовый метод для сбора отладочной информации 94 | * @return mixed 95 | */ 96 | public function save() 97 | { 98 | } 99 | 100 | /** 101 | * @param Yii2Debug $owner 102 | * @param string $id 103 | */ 104 | public function __construct($owner, $id) 105 | { 106 | $this->_owner = $owner; 107 | $this->_id = $id; 108 | $this->_tag = $owner->getTag(); 109 | $this->init(); 110 | } 111 | 112 | /** 113 | * Debug panel initialization. 114 | */ 115 | public function init() 116 | { 117 | if ($this->_logsEnabled) { 118 | $this->initLogRoute(); 119 | } 120 | } 121 | 122 | /** 123 | * @return Yii2Debug 124 | */ 125 | public function getOwner() 126 | { 127 | return $this->_owner; 128 | } 129 | 130 | /** 131 | * @return Yii2Debug 132 | * @deprecated will removed in v1.2 133 | */ 134 | public function getComponent() 135 | { 136 | return $this->_owner; 137 | } 138 | 139 | /** 140 | * @return string 141 | */ 142 | public function getId() 143 | { 144 | return $this->_id; 145 | } 146 | 147 | /** 148 | * @return string 149 | */ 150 | public function getTag() 151 | { 152 | return $this->_tag; 153 | } 154 | 155 | /** 156 | * @return array 157 | */ 158 | protected function getData() 159 | { 160 | return $this->_data; 161 | } 162 | 163 | /** 164 | * @return string URL страницы 165 | */ 166 | public function getUrl() 167 | { 168 | return Yii::app()->createUrl($this->getOwner()->moduleId . '/default/view', array( 169 | 'tag' => $this->getTag(), 170 | 'panel' => $this->getId(), 171 | )); 172 | } 173 | 174 | /** 175 | * @param array $data 176 | * @param null|string $tag 177 | */ 178 | public function load($data, $tag = null) 179 | { 180 | if ($tag) $this->_tag = $tag; 181 | $this->_data = $data; 182 | } 183 | 184 | /** 185 | * Renders a view file 186 | * @param string $_viewFile_ view file 187 | * @param array $_data_ data to be extracted and made available to the view file 188 | * @return string the rendering result 189 | */ 190 | public function render($_viewFile_, $_data_ = null) 191 | { 192 | if (is_array($_data_)) { 193 | extract($_data_); 194 | } else { 195 | $data = $_data_; 196 | } 197 | ob_start(); 198 | ob_implicit_flush(false); 199 | require($_viewFile_); 200 | return ob_get_clean(); 201 | } 202 | 203 | /** 204 | * Рендер блока с массивом key-value 205 | * @param string $caption 206 | * @param array $values 207 | * @return string 208 | * @deprecated 209 | */ 210 | public function renderDetail($caption, $values) 211 | { 212 | return $this->render(dirname(__FILE__) . '/views/panels/_detail.php', array( 213 | 'caption' => $caption, 214 | 'values' => $values, 215 | )); 216 | } 217 | 218 | /** 219 | * Рендер панели с закладками 220 | * @param array $items 221 | * @return string 222 | * @deprecated 223 | */ 224 | public function renderTabs($items) 225 | { 226 | static $counter = 0; 227 | return $this->render(dirname(__FILE__) . '/views/panels/_tabs.php', array( 228 | 'id' => 'tabs' . ($counter++), 229 | 'items' => $items, 230 | )); 231 | } 232 | 233 | /** 234 | * @var CTextHighlighter 235 | */ 236 | private $_hl; 237 | 238 | /** 239 | * Подсветка php-кода 240 | * @param string $code 241 | * @return string 242 | */ 243 | protected function highlightPhp($code) 244 | { 245 | if ($this->_hl === null) { 246 | $this->_hl = Yii::createComponent(array( 247 | 'class' => 'CTextHighlighter', 248 | 'language' => 'php', 249 | 'showLineNumbers' => false, 250 | )); 251 | } 252 | $html = $this->_hl->highlight($code); 253 | return strip_tags($html, '
,'); 254 | } 255 | 256 | /** 257 | * Get logs from Yii2DebugLogRoute. 258 | * @return array 259 | * @throws Exception 260 | */ 261 | protected function getLogs() 262 | { 263 | if (!$this->_logRoute) { 264 | throw new Exception('Yii2DebugLogRoute not initialized.'); 265 | } 266 | 267 | return $this->_logRoute->getLogs(); 268 | } 269 | 270 | /** 271 | * Initialize Yii2DebugLogRoute. 272 | * @throws CException 273 | */ 274 | private function initLogRoute() 275 | { 276 | $config = array( 277 | 'class' => 'yii2-debug.Yii2DebugLogRoute', 278 | 'categories' => $this->_logsCategories, 279 | 'levels' => $this->_logsLevels, 280 | ); 281 | 282 | $this->_logRoute = Yii::createComponent($config); 283 | $this->_logRoute->init(); 284 | 285 | $routeName = 'yii2debug-' . uniqid(); 286 | Yii::app()->log->setRoutes(array($routeName => $this->_logRoute)); 287 | $allRoutes = Yii::app()->log->getRoutes(); 288 | $this->_logRoute = $allRoutes[$routeName]; 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /Yii2DebugViewRenderer.php: -------------------------------------------------------------------------------- 1 | 7 | * @package Yii2Debug 8 | * @since 1.1.13 9 | */ 10 | class Yii2DebugViewRenderer extends CViewRenderer 11 | { 12 | /** 13 | * @var CViewRenderer 14 | */ 15 | public $instance; 16 | /** 17 | * @var array 18 | */ 19 | private $_stack = array(); 20 | 21 | public function getStack() 22 | { 23 | return $this->_stack; 24 | } 25 | 26 | /** 27 | * @param CBaseController $context 28 | * @param string $sourceFile 29 | * @param array $data 30 | * @param boolean $return 31 | * @return mixed 32 | */ 33 | public function renderFile($context, $sourceFile, $data, $return) 34 | { 35 | $this->_stack[] = array( 36 | 'view' => $sourceFile, 37 | 'data' => $data, 38 | ); 39 | if ($this->instance) { 40 | return $this->instance->renderFile($context, $sourceFile, $data, $return); 41 | } 42 | return $context->renderInternal($sourceFile, $data, $return); 43 | } 44 | 45 | /** 46 | * @param string $sourceFile 47 | * @param string $viewFile 48 | */ 49 | public function generateViewFile($sourceFile, $viewFile) 50 | { 51 | if ($this->instance) { 52 | return $this->instance->generateViewFile($sourceFile, $viewFile); 53 | } 54 | return null; 55 | } 56 | } -------------------------------------------------------------------------------- /assets/css/main.css: -------------------------------------------------------------------------------- 1 | #yii2-debug-toolbar { 2 | position: fixed; 3 | top: 0; 4 | left: 0; 5 | right: 0; 6 | margin: 0 0 20px 0; 7 | padding: 0; 8 | z-index: 1000000; 9 | font: 11px Verdana, Arial, sans-serif; 10 | text-align: left; 11 | height: 40px; 12 | border-bottom: 1px solid #e4e4e4; 13 | background: rgb(237,237,237); 14 | background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2VkZWRlZCIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjUzJSIgc3RvcC1jb2xvcj0iI2Y2ZjZmNiIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNmZmZmZmYiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+); 15 | background: -moz-linear-gradient(top, rgba(237,237,237,1) 0%, rgba(246,246,246,1) 53%, rgba(255,255,255,1) 100%); 16 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(237,237,237,1)), color-stop(53%,rgba(246,246,246,1)), color-stop(100%,rgba(255,255,255,1))); 17 | background: -webkit-linear-gradient(top, rgba(237,237,237,1) 0%,rgba(246,246,246,1) 53%,rgba(255,255,255,1) 100%); 18 | background: -o-linear-gradient(top, rgba(237,237,237,1) 0%,rgba(246,246,246,1) 53%,rgba(255,255,255,1) 100%); 19 | background: -ms-linear-gradient(top, rgba(237,237,237,1) 0%,rgba(246,246,246,1) 53%,rgba(255,255,255,1) 100%); 20 | background: linear-gradient(to bottom, rgba(237,237,237,1) 0%,rgba(246,246,246,1) 53%,rgba(255,255,255,1) 100%); 21 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ededed', endColorstr='#ffffff',GradientType=0 ); 22 | } 23 | 24 | .yii2-debug-toolbar-block { 25 | float: left; 26 | margin: 0; 27 | border-right: 1px solid #e4e4e4; 28 | padding: 4px 8px; 29 | line-height: 32px; 30 | } 31 | 32 | .yii2-debug-toolbar-block.title { 33 | font-size: 1.4em; 34 | } 35 | 36 | .yii2-debug-toolbar-block a { 37 | text-decoration: none; 38 | color: black; 39 | } 40 | 41 | .yii2-debug-toolbar-block span { 42 | } 43 | 44 | .yii2-debug-toolbar-block img { 45 | vertical-align: middle; 46 | } 47 | 48 | #yii2-debug-toolbar .label { 49 | display: inline-block; 50 | padding: 2px 4px; 51 | font-size: 11.844px; 52 | font-weight: normal; 53 | line-height: 14px; 54 | color: #ffffff; 55 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); 56 | white-space: nowrap; 57 | vertical-align: baseline; 58 | background-color: #999999; 59 | } 60 | 61 | #yii2-debug-toolbar .label { 62 | -webkit-border-radius: 3px; 63 | -moz-border-radius: 3px; 64 | border-radius: 3px; 65 | } 66 | 67 | #yii2-debug-toolbar .label:empty { 68 | display: none; 69 | } 70 | 71 | #yii2-debug-toolbar a.label:hover, 72 | #yii2-debug-toolbar a.label:focus { 73 | color: #ffffff; 74 | text-decoration: none; 75 | cursor: pointer; 76 | } 77 | 78 | #yii2-debug-toolbar .label-important { 79 | background-color: #b94a48; 80 | } 81 | 82 | #yii2-debug-toolbar .label-important[href] { 83 | background-color: #953b39; 84 | } 85 | 86 | #yii2-debug-toolbar .label-warning, 87 | #yii2-debug-toolbar .badge-warning { 88 | background-color: #f89406; 89 | } 90 | 91 | #yii2-debug-toolbar .label-warning[href] { 92 | background-color: #c67605; 93 | } 94 | 95 | #yii2-debug-toolbar .label-success { 96 | background-color: #468847; 97 | } 98 | 99 | #yii2-debug-toolbar .label-success[href] { 100 | background-color: #356635; 101 | } 102 | 103 | #yii2-debug-toolbar .label-info { 104 | background-color: #3a87ad; 105 | } 106 | 107 | #yii2-debug-toolbar .label-info[href] { 108 | background-color: #2d6987; 109 | } 110 | 111 | #yii2-debug-toolbar .label-inverse, 112 | #yii2-debug-toolbar .badge-inverse { 113 | background-color: #333333; 114 | } 115 | 116 | #yii2-debug-toolbar .label-inverse[href], 117 | #yii2-debug-toolbar .badge-inverse[href] { 118 | background-color: #1a1a1a; 119 | } 120 | span.indent { 121 | color: #ccc; 122 | } 123 | 124 | ul.trace { 125 | font-size: 12px; 126 | color: #999; 127 | margin: 2px 0 0 0; 128 | padding: 0; 129 | list-style: none; 130 | } 131 | 132 | tr.error ul.trace { 133 | color: #555; 134 | } 135 | 136 | .callout { 137 | color: #000; 138 | } 139 | 140 | .sql-hl-main, .php-hl-main { 141 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif !important; 142 | font-size: 14px !important; 143 | } 144 | 145 | div.explain { 146 | position: relative; 147 | padding: 29px 9px 9px; 148 | *padding-top: 19px; 149 | font-size: 12px; 150 | } 151 | 152 | /* Echo out a label for the example */ 153 | div.explain:after { 154 | content: "Explain"; 155 | position: absolute; 156 | top: -5px; 157 | left: -6px; 158 | padding: 2px 7px; 159 | font-size: 12px; 160 | font-weight: bold; 161 | background-color: #f5f5f5; 162 | border: 1px solid #ddd; 163 | color: #9da0a4; 164 | -webkit-border-radius: 0 0 4px 0; 165 | -moz-border-radius: 0 0 4px 0; 166 | border-radius: 0 0 4px 0; 167 | } 168 | 169 | .src code { 170 | border-width: 0; 171 | padding: 0; 172 | background-color: inherit; 173 | line-height: 20px; 174 | } 175 | .src.no-hl { 176 | white-space: pre; 177 | line-height: 21px; 178 | } 179 | .src code, .src.no-hl { 180 | font-family: Monaco, Menlo, Consolas, "Courier New", monospace; 181 | font-size: 14px; 182 | 183 | } -------------------------------------------------------------------------------- /assets/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhuravljov/yii2-debug/0003a865d235ef51e681ad13e3e2b023a06cb7b9/assets/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /assets/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhuravljov/yii2-debug/0003a865d235ef51e681ad13e3e2b023a06cb7b9/assets/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /assets/js/bootstrap.js: -------------------------------------------------------------------------------- 1 | /* =================================================== 2 | * bootstrap-transition.js v2.3.2 3 | * http://getbootstrap.com/2.3.2/javascript.html#transitions 4 | * =================================================== 5 | * Copyright 2013 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ========================================================== */ 19 | 20 | 21 | !function ($) { 22 | 23 | "use strict"; // jshint ;_; 24 | 25 | 26 | /* CSS TRANSITION SUPPORT (http://www.modernizr.com/) 27 | * ======================================================= */ 28 | 29 | $(function () { 30 | 31 | $.support.transition = (function () { 32 | 33 | var transitionEnd = (function () { 34 | 35 | var el = document.createElement('bootstrap') 36 | , transEndEventNames = { 37 | 'WebkitTransition' : 'webkitTransitionEnd' 38 | , 'MozTransition' : 'transitionend' 39 | , 'OTransition' : 'oTransitionEnd otransitionend' 40 | , 'transition' : 'transitionend' 41 | } 42 | , name 43 | 44 | for (name in transEndEventNames){ 45 | if (el.style[name] !== undefined) { 46 | return transEndEventNames[name] 47 | } 48 | } 49 | 50 | }()) 51 | 52 | return transitionEnd && { 53 | end: transitionEnd 54 | } 55 | 56 | })() 57 | 58 | }) 59 | 60 | }(window.jQuery); 61 | /* ========================================================= 62 | * bootstrap-modal.js v2.3.2 63 | * http://getbootstrap.com/2.3.2/javascript.html#modals 64 | * ========================================================= 65 | * Copyright 2013 Twitter, Inc. 66 | * 67 | * Licensed under the Apache License, Version 2.0 (the "License"); 68 | * you may not use this file except in compliance with the License. 69 | * You may obtain a copy of the License at 70 | * 71 | * http://www.apache.org/licenses/LICENSE-2.0 72 | * 73 | * Unless required by applicable law or agreed to in writing, software 74 | * distributed under the License is distributed on an "AS IS" BASIS, 75 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 76 | * See the License for the specific language governing permissions and 77 | * limitations under the License. 78 | * ========================================================= */ 79 | 80 | 81 | !function ($) { 82 | 83 | "use strict"; // jshint ;_; 84 | 85 | 86 | /* MODAL CLASS DEFINITION 87 | * ====================== */ 88 | 89 | var Modal = function (element, options) { 90 | this.options = options 91 | this.$element = $(element) 92 | .delegate('[data-dismiss="modal"]', 'click.dismiss.modal', $.proxy(this.hide, this)) 93 | this.options.remote && this.$element.find('.modal-body').load(this.options.remote) 94 | } 95 | 96 | Modal.prototype = { 97 | 98 | constructor: Modal 99 | 100 | , toggle: function () { 101 | return this[!this.isShown ? 'show' : 'hide']() 102 | } 103 | 104 | , show: function () { 105 | var that = this 106 | , e = $.Event('show') 107 | 108 | this.$element.trigger(e) 109 | 110 | if (this.isShown || e.isDefaultPrevented()) return 111 | 112 | this.isShown = true 113 | 114 | this.escape() 115 | 116 | this.backdrop(function () { 117 | var transition = $.support.transition && that.$element.hasClass('fade') 118 | 119 | if (!that.$element.parent().length) { 120 | that.$element.appendTo(document.body) //don't move modals dom position 121 | } 122 | 123 | that.$element.show() 124 | 125 | if (transition) { 126 | that.$element[0].offsetWidth // force reflow 127 | } 128 | 129 | that.$element 130 | .addClass('in') 131 | .attr('aria-hidden', false) 132 | 133 | that.enforceFocus() 134 | 135 | transition ? 136 | that.$element.one($.support.transition.end, function () { that.$element.focus().trigger('shown') }) : 137 | that.$element.focus().trigger('shown') 138 | 139 | }) 140 | } 141 | 142 | , hide: function (e) { 143 | e && e.preventDefault() 144 | 145 | var that = this 146 | 147 | e = $.Event('hide') 148 | 149 | this.$element.trigger(e) 150 | 151 | if (!this.isShown || e.isDefaultPrevented()) return 152 | 153 | this.isShown = false 154 | 155 | this.escape() 156 | 157 | $(document).off('focusin.modal') 158 | 159 | this.$element 160 | .removeClass('in') 161 | .attr('aria-hidden', true) 162 | 163 | $.support.transition && this.$element.hasClass('fade') ? 164 | this.hideWithTransition() : 165 | this.hideModal() 166 | } 167 | 168 | , enforceFocus: function () { 169 | var that = this 170 | $(document).on('focusin.modal', function (e) { 171 | if (that.$element[0] !== e.target && !that.$element.has(e.target).length) { 172 | that.$element.focus() 173 | } 174 | }) 175 | } 176 | 177 | , escape: function () { 178 | var that = this 179 | if (this.isShown && this.options.keyboard) { 180 | this.$element.on('keyup.dismiss.modal', function ( e ) { 181 | e.which == 27 && that.hide() 182 | }) 183 | } else if (!this.isShown) { 184 | this.$element.off('keyup.dismiss.modal') 185 | } 186 | } 187 | 188 | , hideWithTransition: function () { 189 | var that = this 190 | , timeout = setTimeout(function () { 191 | that.$element.off($.support.transition.end) 192 | that.hideModal() 193 | }, 500) 194 | 195 | this.$element.one($.support.transition.end, function () { 196 | clearTimeout(timeout) 197 | that.hideModal() 198 | }) 199 | } 200 | 201 | , hideModal: function () { 202 | var that = this 203 | this.$element.hide() 204 | this.backdrop(function () { 205 | that.removeBackdrop() 206 | that.$element.trigger('hidden') 207 | }) 208 | } 209 | 210 | , removeBackdrop: function () { 211 | this.$backdrop && this.$backdrop.remove() 212 | this.$backdrop = null 213 | } 214 | 215 | , backdrop: function (callback) { 216 | var that = this 217 | , animate = this.$element.hasClass('fade') ? 'fade' : '' 218 | 219 | if (this.isShown && this.options.backdrop) { 220 | var doAnimate = $.support.transition && animate 221 | 222 | this.$backdrop = $('