├── .gitignore ├── Bootstrap.php ├── CHANGELOG.md ├── LICENSE ├── README.md ├── behaviors └── ChangelogBehavior.php ├── components └── LogsStorage.php ├── composer.json ├── composer.lock ├── debug ├── models │ └── search │ │ └── ChangelogSearch.php ├── panels │ └── ChangelogPanel.php └── views │ └── panels │ └── changelog │ ├── detail.php │ └── summary.php ├── interfaces ├── ChangelogModelInterface.php ├── LoggableInterface.php └── LogsStorageInterface.php ├── migrations └── m150816_130733_changelog.php └── resources ├── Changelog.php ├── Log.php └── queries └── ChangelogQuery.php /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /vendor 3 | /coverage 4 | -------------------------------------------------------------------------------- /Bootstrap.php: -------------------------------------------------------------------------------- 1 | on(Application::EVENT_AFTER_REQUEST, function () { 27 | /** @var LogsStorage $Storage */ 28 | $Storage = Instance::ensure([ 29 | 'class' => LogsStorage::className(), 30 | ]); 31 | 32 | $Storage::save(); 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2016-10-05 - 1.0.1 2 | ------------------ 3 | * Bugfix #1 4 | 5 | 2016-07-14 - 1.0.0 6 | ------------------ 7 | * Change mysql table engine to `ARCHIVE`. 8 | * Added collector log for the bulk insert. 9 | * Global refactoring. 10 | 11 | 2016-06-01 - 0.1.4 12 | ------------------ 13 | * Added the ability to use his model to save log. 14 | 15 | 2016-04-21 - 0.1.2 16 | ------------------ 17 | * Update summary debug panel view to new css. 18 | * Updated packages. 19 | 20 | 2015-12-28 - 0.1.1 21 | ------------------- 22 | * Rename `excludeAttributes` to `ignoreAttributes`. 23 | 24 | 2015-12-28 - 0.1.0 25 | ------------------- 26 | * Initial extension. 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Revin Roman Borisovich 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Active record changelog extension for Yii 2 framework 2 | ===================================================== 3 | 4 | This extension provides a changelog functional. 5 | 6 | For license information check the [LICENSE](https://github.com/rmrevin/yii2-changelog/blob/master/LICENSE)-file. 7 | 8 | Support 9 | ------- 10 | * [GitHub issues](https://github.com/rmrevin/yii2-changelog/issues) 11 | * [Public chat](https://gitter.im/rmrevin/support) 12 | 13 | Installation 14 | ------------ 15 | 16 | The preferred way to install this extension is through [composer](https://getcomposer.org/). 17 | 18 | Either run 19 | 20 | ```bash 21 | composer require "rmrevin/yii2-changelog:~1.0" 22 | ``` 23 | 24 | or add 25 | 26 | ``` 27 | "rmrevin/yii2-changelog": "~1.0", 28 | ``` 29 | 30 | to the `require` section of your `composer.json` file. 31 | 32 | Execute migrations: 33 | ``` 34 | php yii migrate --migrationPath=@rmrevin/yii/changelog/migrations 35 | ``` 36 | 37 | Usage 38 | ----- 39 | 40 | To view the history, you can use a special panel for debug. 41 | Or make your own section to view the data in your administration panel. 42 | 43 | To enable the debug panel, add the following code in the module configuration debug. 44 | ```php 45 | 'modules' => [ 46 | // ... 47 | 'debug' => [ 48 | 'class' => yii\debug\Module::className(), 49 | 'panels' => [ 50 | rmrevin\yii\changelog\debug\panels\ChangelogPanel::class, 51 | ], 52 | ], 53 | ], 54 | 55 | ``` 56 | 57 | For `ActiveRecord` models for which you want to track changes, 58 | you must implement the interface `rmrevin\yii\changelog\interfaces\LoggableInterface` 59 | and add the behavior of `rmrevin\yii\changelog\behaviors\ChangelogBehavior`. 60 | 61 | Example: 62 | ```php 63 | number, $this->title); 88 | } 89 | 90 | /** 91 | * @inheritdoc 92 | */ 93 | public function behaviors() 94 | { 95 | return [ 96 | // ... 97 | [ 98 | 'class' => ChangelogBehavior::class, 99 | 'ignoreAttributes' => [ // these attributes are not tracked 100 | 'updated_at', 101 | 'synchronized_at', 102 | ], 103 | ], 104 | ]; 105 | } 106 | } 107 | ``` 108 | 109 | Done 110 | ---- 111 | Now when you try to create, modify or delete an instance of a model `ShopItem` 112 | in the table `{{%changelog}}` will be recorded relevant information. 113 | -------------------------------------------------------------------------------- /behaviors/ChangelogBehavior.php: -------------------------------------------------------------------------------- 1 | 'logInsert', 35 | BaseActiveRecord::EVENT_AFTER_UPDATE => 'logUpdate', 36 | BaseActiveRecord::EVENT_AFTER_DELETE => 'logDelete', 37 | ]; 38 | } 39 | 40 | /** 41 | * @param \yii\db\AfterSaveEvent $Event 42 | */ 43 | public function logInsert($Event) 44 | { 45 | $Storage = $this->getStorage(); 46 | 47 | /** @var BaseActiveRecord $Model */ 48 | $Model = $Event->sender; 49 | 50 | $attributes = $Model->attributes; 51 | 52 | $this->unsetIgnoreAttributes($attributes); 53 | 54 | /** @var Log $Log */ 55 | $Log = Instance::ensure(['class' => Log::className()]); 56 | $Log->action($Storage::ACTION_INSERT) 57 | ->entity($Model) 58 | ->changes(['insert' => $attributes]); 59 | 60 | $Storage::store($Log); 61 | } 62 | 63 | /** 64 | * @param \yii\db\AfterSaveEvent $Event 65 | */ 66 | public function logUpdate($Event) 67 | { 68 | $Storage = $this->getStorage(); 69 | 70 | /** @var BaseActiveRecord $Model */ 71 | $Model = $Event->sender; 72 | 73 | $attributes = $Model->attributes; 74 | 75 | $changedAttributes = $Event->changedAttributes; 76 | 77 | $this->unsetIgnoreAttributes($attributes, $changedAttributes); 78 | 79 | $newAttributes = []; 80 | foreach ($attributes as $k => $v) { 81 | if (isset($changedAttributes[$k])) { 82 | $newAttributes[$k] = $v; 83 | } 84 | } 85 | 86 | if (!empty($changedAttributes)) { 87 | /** @var Log $Log */ 88 | $Log = Instance::ensure(['class' => Log::className()]); 89 | $Log->action($Storage::ACTION_UPDATE) 90 | ->entity($Model) 91 | ->changes([ 92 | 'update' => [ 93 | 'from' => $changedAttributes, 94 | 'to' => $newAttributes, 95 | ], 96 | ]); 97 | 98 | $Storage::store($Log); 99 | } 100 | } 101 | 102 | /** 103 | * @param \yii\base\Event $Event 104 | */ 105 | public function logDelete($Event) 106 | { 107 | $Storage = $this->getStorage(); 108 | 109 | /** @var BaseActiveRecord $Model */ 110 | $Model = $Event->sender; 111 | 112 | $attributes = $Model->attributes; 113 | 114 | $this->unsetIgnoreAttributes($attributes); 115 | 116 | /** @var Log $Log */ 117 | $Log = Instance::ensure(['class' => Log::className()]); 118 | $Log->action($Storage::ACTION_DELETE) 119 | ->entity($Model) 120 | ->changes(['delete' => $attributes]); 121 | 122 | $Storage::store($Log); 123 | } 124 | 125 | /** 126 | * @return LogsStorage 127 | */ 128 | protected function getStorage() 129 | { 130 | /** @var LogsStorage $Storage */ 131 | $Storage = Instance::ensure([ 132 | 'class' => LogsStorage::className(), 133 | ]); 134 | 135 | return $Storage; 136 | } 137 | 138 | /** 139 | * @param array $attributes 140 | * @param array $changedAttributes 141 | */ 142 | protected function unsetIgnoreAttributes(array &$attributes, array &$changedAttributes = []) 143 | { 144 | $ignoreAttributes = $this->ignoreAttributes; 145 | 146 | if (!empty($ignoreAttributes)) { 147 | foreach ($ignoreAttributes as $field) { 148 | if (isset($attributes[$field])) { 149 | unset($attributes[$field]); 150 | } 151 | 152 | if (isset($changedAttributes[$field])) { 153 | unset($changedAttributes[$field]); 154 | } 155 | } 156 | } 157 | } 158 | 159 | /** 160 | * @return array 161 | */ 162 | protected function getEnvData() 163 | { 164 | $result = [ 165 | 'ENV' => $_ENV, 166 | 'SERVER' => $_SERVER, 167 | ]; 168 | 169 | $Request = Instance::ensure('request', Request::className()); 170 | 171 | if ($Request instanceof \yii\web\Request) { 172 | $result['COOKIE'] = $_COOKIE; 173 | $result['GET'] = $Request->get(); 174 | $result['POST'] = $Request->post(); 175 | $result['REQUEST_BODY'] = $Request->getRawBody(); 176 | } 177 | 178 | // @todo add gzip compress 179 | return $result; 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /components/LogsStorage.php: -------------------------------------------------------------------------------- 1 | Changelog::className(), 42 | ]); 43 | 44 | /** @var Log $Log */ 45 | $Log = Instance::ensure([ 46 | 'class' => Log::className(), 47 | ]); 48 | 49 | $columns = $Log::schema(); 50 | 51 | $rows = []; 52 | 53 | $Logs = static::$storage; 54 | if (!empty($Logs)) { 55 | foreach ($Logs as $Log) { 56 | $rows[] = $Log->export(); 57 | } 58 | } 59 | 60 | if (!empty($rows)) { 61 | /** @var \yii\db\Connection $DB */ 62 | $DB = \Yii::$app->get('db'); 63 | $DB->createCommand() 64 | ->batchInsert($Model::tableName(), $columns, $rows) 65 | ->execute(); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rmrevin/yii2-changelog", 3 | "description": "Active record changelog extension for Yii 2 framework", 4 | "keywords": [ 5 | "yii", 6 | "activerecord", 7 | "history", 8 | "changelog" 9 | ], 10 | "type": "yii2-extension", 11 | "license": "MIT", 12 | "minimum-stability": "stable", 13 | "support": { 14 | "issues": "https://github.com/rmrevin/yii2-changelog/issues", 15 | "source": "https://github.com/rmrevin/yii2-changelog" 16 | }, 17 | "authors": [ 18 | { 19 | "name": "Revin Roman", 20 | "email": "roman@rmrevin.com", 21 | "homepage": "https://rmrevin.com/" 22 | } 23 | ], 24 | "require": { 25 | "php": ">=5.4.0", 26 | 27 | "yiisoft/yii2": "2.0.*" 28 | }, 29 | "require-dev": { 30 | "yiisoft/yii2-debug": "2.0.*" 31 | }, 32 | "autoload": { 33 | "psr-4": { 34 | "rmrevin\\yii\\changelog\\": "" 35 | } 36 | }, 37 | "extra": { 38 | "bootstrap": "rmrevin\\yii\\changelog\\Bootstrap", 39 | "asset-installer-paths": { 40 | "npm-asset-library": "vendor/npm", 41 | "bower-asset-library": "vendor/bower" 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "hash": "a467b45c2a0f5668335a7dd5c6bfa42f", 8 | "content-hash": "876d3444c9828ddcc9d8a3ee62d63937", 9 | "packages": [ 10 | { 11 | "name": "bower-asset/jquery", 12 | "version": "2.2.3", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/jquery/jquery-dist.git", 16 | "reference": "af22a351b2ea5801ffb1695abb3bb34d5bed9198" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/jquery/jquery-dist/zipball/af22a351b2ea5801ffb1695abb3bb34d5bed9198", 21 | "reference": "af22a351b2ea5801ffb1695abb3bb34d5bed9198", 22 | "shasum": "" 23 | }, 24 | "type": "bower-asset-library", 25 | "extra": { 26 | "bower-asset-main": "dist/jquery.js", 27 | "bower-asset-ignore": [ 28 | "package.json" 29 | ] 30 | }, 31 | "license": [ 32 | "MIT" 33 | ], 34 | "keywords": [ 35 | "browser", 36 | "javascript", 37 | "jquery", 38 | "library" 39 | ] 40 | }, 41 | { 42 | "name": "bower-asset/jquery.inputmask", 43 | "version": "3.2.7", 44 | "source": { 45 | "type": "git", 46 | "url": "https://github.com/RobinHerbots/jquery.inputmask.git", 47 | "reference": "5a72c563b502b8e05958a524cdfffafe9987be38" 48 | }, 49 | "dist": { 50 | "type": "zip", 51 | "url": "https://api.github.com/repos/RobinHerbots/jquery.inputmask/zipball/5a72c563b502b8e05958a524cdfffafe9987be38", 52 | "reference": "5a72c563b502b8e05958a524cdfffafe9987be38", 53 | "shasum": "" 54 | }, 55 | "require": { 56 | "bower-asset/jquery": ">=1.7" 57 | }, 58 | "type": "bower-asset-library", 59 | "extra": { 60 | "bower-asset-main": [ 61 | "./dist/inputmask/inputmask.js" 62 | ], 63 | "bower-asset-ignore": [ 64 | "**/*", 65 | "!dist/*", 66 | "!dist/inputmask/*", 67 | "!dist/min/*", 68 | "!dist/min/inputmask/*", 69 | "!extra/bindings/*", 70 | "!extra/dependencyLibs/*", 71 | "!extra/phone-codes/*" 72 | ] 73 | }, 74 | "license": [ 75 | "http://opensource.org/licenses/mit-license.php" 76 | ], 77 | "description": "jquery.inputmask is a jquery plugin which create an input mask.", 78 | "keywords": [ 79 | "form", 80 | "input", 81 | "inputmask", 82 | "jquery", 83 | "mask", 84 | "plugins" 85 | ] 86 | }, 87 | { 88 | "name": "bower-asset/punycode", 89 | "version": "v1.3.2", 90 | "source": { 91 | "type": "git", 92 | "url": "https://github.com/bestiejs/punycode.js.git", 93 | "reference": "38c8d3131a82567bfef18da09f7f4db68c84f8a3" 94 | }, 95 | "dist": { 96 | "type": "zip", 97 | "url": "https://api.github.com/repos/bestiejs/punycode.js/zipball/38c8d3131a82567bfef18da09f7f4db68c84f8a3", 98 | "reference": "38c8d3131a82567bfef18da09f7f4db68c84f8a3", 99 | "shasum": "" 100 | }, 101 | "type": "bower-asset-library", 102 | "extra": { 103 | "bower-asset-main": "punycode.js", 104 | "bower-asset-ignore": [ 105 | "coverage", 106 | "tests", 107 | ".*", 108 | "component.json", 109 | "Gruntfile.js", 110 | "node_modules", 111 | "package.json" 112 | ] 113 | } 114 | }, 115 | { 116 | "name": "bower-asset/yii2-pjax", 117 | "version": "v2.0.6", 118 | "source": { 119 | "type": "git", 120 | "url": "https://github.com/yiisoft/jquery-pjax.git", 121 | "reference": "60728da6ade5879e807a49ce59ef9a72039b8978" 122 | }, 123 | "dist": { 124 | "type": "zip", 125 | "url": "https://api.github.com/repos/yiisoft/jquery-pjax/zipball/60728da6ade5879e807a49ce59ef9a72039b8978", 126 | "reference": "60728da6ade5879e807a49ce59ef9a72039b8978", 127 | "shasum": "" 128 | }, 129 | "require": { 130 | "bower-asset/jquery": ">=1.8" 131 | }, 132 | "type": "bower-asset-library", 133 | "extra": { 134 | "bower-asset-main": "./jquery.pjax.js", 135 | "bower-asset-ignore": [ 136 | ".travis.yml", 137 | "Gemfile", 138 | "Gemfile.lock", 139 | "CONTRIBUTING.md", 140 | "vendor/", 141 | "script/", 142 | "test/" 143 | ] 144 | }, 145 | "license": [ 146 | "MIT" 147 | ] 148 | }, 149 | { 150 | "name": "cebe/markdown", 151 | "version": "1.1.0", 152 | "source": { 153 | "type": "git", 154 | "url": "https://github.com/cebe/markdown.git", 155 | "reference": "54a2c49de31cc44e864ebf0500a35ef21d0010b2" 156 | }, 157 | "dist": { 158 | "type": "zip", 159 | "url": "https://api.github.com/repos/cebe/markdown/zipball/54a2c49de31cc44e864ebf0500a35ef21d0010b2", 160 | "reference": "54a2c49de31cc44e864ebf0500a35ef21d0010b2", 161 | "shasum": "" 162 | }, 163 | "require": { 164 | "lib-pcre": "*", 165 | "php": ">=5.4.0" 166 | }, 167 | "require-dev": { 168 | "cebe/indent": "*", 169 | "facebook/xhprof": "*@dev", 170 | "phpunit/phpunit": "4.1.*" 171 | }, 172 | "bin": [ 173 | "bin/markdown" 174 | ], 175 | "type": "library", 176 | "extra": { 177 | "branch-alias": { 178 | "dev-master": "1.1.x-dev" 179 | } 180 | }, 181 | "autoload": { 182 | "psr-4": { 183 | "cebe\\markdown\\": "" 184 | } 185 | }, 186 | "notification-url": "https://packagist.org/downloads/", 187 | "license": [ 188 | "MIT" 189 | ], 190 | "authors": [ 191 | { 192 | "name": "Carsten Brandt", 193 | "email": "mail@cebe.cc", 194 | "homepage": "http://cebe.cc/", 195 | "role": "Creator" 196 | } 197 | ], 198 | "description": "A super fast, highly extensible markdown parser for PHP", 199 | "homepage": "https://github.com/cebe/markdown#readme", 200 | "keywords": [ 201 | "extensible", 202 | "fast", 203 | "gfm", 204 | "markdown", 205 | "markdown-extra" 206 | ], 207 | "time": "2015-03-06 05:28:07" 208 | }, 209 | { 210 | "name": "ezyang/htmlpurifier", 211 | "version": "v4.6.0", 212 | "source": { 213 | "type": "git", 214 | "url": "https://github.com/ezyang/htmlpurifier.git", 215 | "reference": "6f389f0f25b90d0b495308efcfa073981177f0fd" 216 | }, 217 | "dist": { 218 | "type": "zip", 219 | "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/6f389f0f25b90d0b495308efcfa073981177f0fd", 220 | "reference": "6f389f0f25b90d0b495308efcfa073981177f0fd", 221 | "shasum": "" 222 | }, 223 | "require": { 224 | "php": ">=5.2" 225 | }, 226 | "type": "library", 227 | "autoload": { 228 | "psr-0": { 229 | "HTMLPurifier": "library/" 230 | }, 231 | "files": [ 232 | "library/HTMLPurifier.composer.php" 233 | ] 234 | }, 235 | "notification-url": "https://packagist.org/downloads/", 236 | "license": [ 237 | "LGPL" 238 | ], 239 | "authors": [ 240 | { 241 | "name": "Edward Z. Yang", 242 | "email": "admin@htmlpurifier.org", 243 | "homepage": "http://ezyang.com" 244 | } 245 | ], 246 | "description": "Standards compliant HTML filter written in PHP", 247 | "homepage": "http://htmlpurifier.org/", 248 | "keywords": [ 249 | "html" 250 | ], 251 | "time": "2013-11-30 08:25:19" 252 | }, 253 | { 254 | "name": "yiisoft/yii2", 255 | "version": "2.0.7", 256 | "source": { 257 | "type": "git", 258 | "url": "https://github.com/yiisoft/yii2-framework.git", 259 | "reference": "f45651582cb853b4326730d9d187a0f7a44a45a3" 260 | }, 261 | "dist": { 262 | "type": "zip", 263 | "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/f45651582cb853b4326730d9d187a0f7a44a45a3", 264 | "reference": "f45651582cb853b4326730d9d187a0f7a44a45a3", 265 | "shasum": "" 266 | }, 267 | "require": { 268 | "bower-asset/jquery": "2.2.*@stable | 2.1.*@stable | 1.11.*@stable", 269 | "bower-asset/jquery.inputmask": "~3.2.2", 270 | "bower-asset/punycode": "1.3.*", 271 | "bower-asset/yii2-pjax": "~2.0.1", 272 | "cebe/markdown": "~1.0.0 | ~1.1.0", 273 | "ext-ctype": "*", 274 | "ext-mbstring": "*", 275 | "ezyang/htmlpurifier": "4.6.*", 276 | "lib-pcre": "*", 277 | "php": ">=5.4.0", 278 | "yiisoft/yii2-composer": "~2.0.4" 279 | }, 280 | "bin": [ 281 | "yii" 282 | ], 283 | "type": "library", 284 | "extra": { 285 | "branch-alias": { 286 | "dev-master": "2.0.x-dev" 287 | } 288 | }, 289 | "autoload": { 290 | "psr-4": { 291 | "yii\\": "" 292 | } 293 | }, 294 | "notification-url": "https://packagist.org/downloads/", 295 | "license": [ 296 | "BSD-3-Clause" 297 | ], 298 | "authors": [ 299 | { 300 | "name": "Qiang Xue", 301 | "email": "qiang.xue@gmail.com", 302 | "homepage": "http://www.yiiframework.com/", 303 | "role": "Founder and project lead" 304 | }, 305 | { 306 | "name": "Alexander Makarov", 307 | "email": "sam@rmcreative.ru", 308 | "homepage": "http://rmcreative.ru/", 309 | "role": "Core framework development" 310 | }, 311 | { 312 | "name": "Maurizio Domba", 313 | "homepage": "http://mdomba.info/", 314 | "role": "Core framework development" 315 | }, 316 | { 317 | "name": "Carsten Brandt", 318 | "email": "mail@cebe.cc", 319 | "homepage": "http://cebe.cc/", 320 | "role": "Core framework development" 321 | }, 322 | { 323 | "name": "Timur Ruziev", 324 | "email": "resurtm@gmail.com", 325 | "homepage": "http://resurtm.com/", 326 | "role": "Core framework development" 327 | }, 328 | { 329 | "name": "Paul Klimov", 330 | "email": "klimov.paul@gmail.com", 331 | "role": "Core framework development" 332 | }, 333 | { 334 | "name": "Dmitry Naumenko", 335 | "email": "d.naumenko.a@gmail.com", 336 | "role": "Core framework development" 337 | } 338 | ], 339 | "description": "Yii PHP Framework Version 2", 340 | "homepage": "http://www.yiiframework.com/", 341 | "keywords": [ 342 | "framework", 343 | "yii2" 344 | ], 345 | "time": "2016-02-14 14:45:55" 346 | }, 347 | { 348 | "name": "yiisoft/yii2-composer", 349 | "version": "2.0.4", 350 | "source": { 351 | "type": "git", 352 | "url": "https://github.com/yiisoft/yii2-composer.git", 353 | "reference": "7452fd908a5023b8bb5ea1b123a174ca080de464" 354 | }, 355 | "dist": { 356 | "type": "zip", 357 | "url": "https://api.github.com/repos/yiisoft/yii2-composer/zipball/7452fd908a5023b8bb5ea1b123a174ca080de464", 358 | "reference": "7452fd908a5023b8bb5ea1b123a174ca080de464", 359 | "shasum": "" 360 | }, 361 | "require": { 362 | "composer-plugin-api": "^1.0" 363 | }, 364 | "type": "composer-plugin", 365 | "extra": { 366 | "class": "yii\\composer\\Plugin", 367 | "branch-alias": { 368 | "dev-master": "2.0.x-dev" 369 | } 370 | }, 371 | "autoload": { 372 | "psr-4": { 373 | "yii\\composer\\": "" 374 | } 375 | }, 376 | "notification-url": "https://packagist.org/downloads/", 377 | "license": [ 378 | "BSD-3-Clause" 379 | ], 380 | "authors": [ 381 | { 382 | "name": "Qiang Xue", 383 | "email": "qiang.xue@gmail.com" 384 | } 385 | ], 386 | "description": "The composer plugin for Yii extension installer", 387 | "keywords": [ 388 | "composer", 389 | "extension installer", 390 | "yii2" 391 | ], 392 | "time": "2016-02-06 00:49:24" 393 | } 394 | ], 395 | "packages-dev": [ 396 | { 397 | "name": "bower-asset/bootstrap", 398 | "version": "v3.3.5", 399 | "source": { 400 | "type": "git", 401 | "url": "https://github.com/twbs/bootstrap.git", 402 | "reference": "16b48259a62f576e52c903c476bd42b90ab22482" 403 | }, 404 | "dist": { 405 | "type": "zip", 406 | "url": "https://api.github.com/repos/twbs/bootstrap/zipball/16b48259a62f576e52c903c476bd42b90ab22482", 407 | "reference": "16b48259a62f576e52c903c476bd42b90ab22482", 408 | "shasum": "" 409 | }, 410 | "require": { 411 | "bower-asset/jquery": ">=1.9.1" 412 | }, 413 | "type": "bower-asset-library", 414 | "extra": { 415 | "bower-asset-main": [ 416 | "less/bootstrap.less", 417 | "dist/js/bootstrap.js" 418 | ], 419 | "bower-asset-ignore": [ 420 | "/.*", 421 | "_config.yml", 422 | "CNAME", 423 | "composer.json", 424 | "CONTRIBUTING.md", 425 | "docs", 426 | "js/tests", 427 | "test-infra" 428 | ] 429 | }, 430 | "license": [ 431 | "MIT" 432 | ], 433 | "description": "The most popular front-end framework for developing responsive, mobile first projects on the web.", 434 | "keywords": [ 435 | "css", 436 | "framework", 437 | "front-end", 438 | "js", 439 | "less", 440 | "mobile-first", 441 | "responsive", 442 | "web" 443 | ] 444 | }, 445 | { 446 | "name": "yiisoft/yii2-bootstrap", 447 | "version": "2.0.6", 448 | "source": { 449 | "type": "git", 450 | "url": "https://github.com/yiisoft/yii2-bootstrap.git", 451 | "reference": "3fd2b8c950cce79d60e9702d6bcb24eb3c80f6c5" 452 | }, 453 | "dist": { 454 | "type": "zip", 455 | "url": "https://api.github.com/repos/yiisoft/yii2-bootstrap/zipball/3fd2b8c950cce79d60e9702d6bcb24eb3c80f6c5", 456 | "reference": "3fd2b8c950cce79d60e9702d6bcb24eb3c80f6c5", 457 | "shasum": "" 458 | }, 459 | "require": { 460 | "bower-asset/bootstrap": "3.3.* | 3.2.* | 3.1.*", 461 | "yiisoft/yii2": ">=2.0.6" 462 | }, 463 | "type": "yii2-extension", 464 | "extra": { 465 | "branch-alias": { 466 | "dev-master": "2.0.x-dev" 467 | }, 468 | "asset-installer-paths": { 469 | "npm-asset-library": "vendor/npm", 470 | "bower-asset-library": "vendor/bower" 471 | } 472 | }, 473 | "autoload": { 474 | "psr-4": { 475 | "yii\\bootstrap\\": "" 476 | } 477 | }, 478 | "notification-url": "https://packagist.org/downloads/", 479 | "license": [ 480 | "BSD-3-Clause" 481 | ], 482 | "authors": [ 483 | { 484 | "name": "Qiang Xue", 485 | "email": "qiang.xue@gmail.com" 486 | } 487 | ], 488 | "description": "The Twitter Bootstrap extension for the Yii framework", 489 | "keywords": [ 490 | "bootstrap", 491 | "yii2" 492 | ], 493 | "time": "2016-03-17 03:29:28" 494 | }, 495 | { 496 | "name": "yiisoft/yii2-debug", 497 | "version": "2.0.6", 498 | "source": { 499 | "type": "git", 500 | "url": "https://github.com/yiisoft/yii2-debug.git", 501 | "reference": "55ed2e853ed8050a34415f63a4da84f88a56f895" 502 | }, 503 | "dist": { 504 | "type": "zip", 505 | "url": "https://api.github.com/repos/yiisoft/yii2-debug/zipball/55ed2e853ed8050a34415f63a4da84f88a56f895", 506 | "reference": "55ed2e853ed8050a34415f63a4da84f88a56f895", 507 | "shasum": "" 508 | }, 509 | "require": { 510 | "yiisoft/yii2": ">=2.0.4", 511 | "yiisoft/yii2-bootstrap": "*" 512 | }, 513 | "type": "yii2-extension", 514 | "extra": { 515 | "branch-alias": { 516 | "dev-master": "2.0.x-dev" 517 | } 518 | }, 519 | "autoload": { 520 | "psr-4": { 521 | "yii\\debug\\": "" 522 | } 523 | }, 524 | "notification-url": "https://packagist.org/downloads/", 525 | "license": [ 526 | "BSD-3-Clause" 527 | ], 528 | "authors": [ 529 | { 530 | "name": "Qiang Xue", 531 | "email": "qiang.xue@gmail.com" 532 | } 533 | ], 534 | "description": "The debugger extension for the Yii framework", 535 | "keywords": [ 536 | "debug", 537 | "debugger", 538 | "yii2" 539 | ], 540 | "time": "2016-03-17 03:50:19" 541 | } 542 | ], 543 | "aliases": [], 544 | "minimum-stability": "stable", 545 | "stability-flags": [], 546 | "prefer-stable": false, 547 | "prefer-lowest": false, 548 | "platform": { 549 | "php": ">=5.4.0" 550 | }, 551 | "platform-dev": [] 552 | } 553 | -------------------------------------------------------------------------------- /debug/models/search/ChangelogSearch.php: -------------------------------------------------------------------------------- 1 | \Yii::t('app', 'Action'), 54 | 'entity_type' => \Yii::t('app', 'Type'), 55 | 'entity_id' => \Yii::t('app', 'ID'), 56 | ]; 57 | } 58 | 59 | /** 60 | * Returns data provider with filled models. Filter applied if needed. 61 | * 62 | * @param array $params an array of parameter values indexed by parameter names 63 | * @return ActiveDataProvider 64 | */ 65 | public function search($params) 66 | { 67 | $this->load($params) && $this->validate(); 68 | 69 | /** @var \yii\db\BaseActiveRecord $Model */ 70 | $Model = Instance::ensure([ 71 | 'class' => Changelog::className(), 72 | ]); 73 | 74 | /** @var ChangelogQuery $ChangelogQuery */ 75 | $ChangelogQuery = $Model::find(); 76 | 77 | if (!empty($this->action)) { 78 | $ChangelogQuery->byAction($this->action); 79 | } 80 | 81 | if (!empty($this->entity_type)) { 82 | $ChangelogQuery->byEntityType($this->entity_type); 83 | } 84 | 85 | if (!empty($this->entity_id)) { 86 | $ChangelogQuery->byEntityId($this->entity_id); 87 | } 88 | 89 | /** @var ActiveDataProvider $DataProvider */ 90 | $DataProvider = \Yii::createObject([ 91 | 'class' => ActiveDataProvider::className(), 92 | 'query' => $ChangelogQuery, 93 | 'sort' => [ 94 | 'defaultOrder' => ['created_at' => SORT_DESC], 95 | ], 96 | ]); 97 | 98 | return $DataProvider; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /debug/panels/ChangelogPanel.php: -------------------------------------------------------------------------------- 1 | view->render($this->summaryViewAlias, [ 53 | 'panel' => $this, 54 | ]); 55 | } 56 | 57 | /** 58 | * @inheritdoc 59 | */ 60 | public function getDetail() 61 | { 62 | /** @var \yii\web\Request $request */ 63 | $request = \Yii::$app->get($this->request); 64 | 65 | /** @var \yii\web\View $view */ 66 | $view = \Yii::$app->get($this->view); 67 | 68 | /** @var ChangelogSearch $searchModel */ 69 | $searchModel = \Yii::createObject(ChangelogSearch::className()); 70 | 71 | $dataProvider = $searchModel 72 | ->search($request->getQueryParams()); 73 | 74 | return $view->render($this->detailViewAlias, [ 75 | 'panel' => $this, 76 | 'dataProvider' => $dataProvider, 77 | 'searchModel' => $searchModel, 78 | ]); 79 | } 80 | } -------------------------------------------------------------------------------- /debug/views/panels/changelog/detail.php: -------------------------------------------------------------------------------- 1 | Changelog::className(), 23 | ]); 24 | 25 | echo Html::tag('h1', $panel->getName()); 26 | 27 | Pjax::begin(); 28 | echo GridView::widget([ 29 | 'dataProvider' => $dataProvider, 30 | 'id' => 'db-panel-detailed-grid', 31 | 'options' => ['class' => 'detail-grid-view table-responsive'], 32 | 'filterModel' => $searchModel, 33 | 'filterUrl' => $panel->getUrl(), 34 | 'columns' => [ 35 | ['class' => 'yii\grid\SerialColumn'], 36 | [ 37 | 'attribute' => 'action', 38 | 'filter' => $Model::getActions(), 39 | 'value' => function ($data) use($Model) { 40 | return empty($data['action']) 41 | ? \Yii::t('app', 'Unknown') 42 | : $Model::getActions()[$data['action']]; 43 | }, 44 | 'options' => [ 45 | 'width' => '10%', 46 | ], 47 | ], 48 | [ 49 | 'format' => 'raw', 50 | 'attribute' => 'entity_type', 51 | 'filter' => $Model::getAllEntityTypes(), 52 | 'value' => function ($data) { 53 | $result = empty($data['entity_type']) ? \Yii::t('app', 'Unknown type') : $data['entity_type']; 54 | 55 | if (!empty($data['present'])) { 56 | $result .= '
' . Html::tag('small', sprintf(' > %s', $data['present'])); 57 | } 58 | 59 | return $result; 60 | }, 61 | ], 62 | [ 63 | 'attribute' => 'entity_id', 64 | 'options' => [ 65 | 'width' => '10%', 66 | ], 67 | ], 68 | [ 69 | 'format' => 'raw', 70 | 'attribute' => 'changes', 71 | 'value' => function ($data) { 72 | $content = Html::tag('div', VarDumper::dumpAsString(unserialize($data['changes']), 10, true), [ 73 | 'class' => 'content changes hidden', 74 | 'data-id' => $data['id'], 75 | ]); 76 | 77 | return empty($data['changes']) ? null : Html::a(\Yii::t('app', 'show'), '#', [ 78 | 'data-role' => 'show-changes', 79 | 'data-id' => $data['id'], 80 | ]) . $content; 81 | }, 82 | 'options' => [ 83 | 'width' => '7%', 84 | ], 85 | ], 86 | [ 87 | 'format' => 'raw', 88 | 'attribute' => 'env', 89 | 'value' => function ($data) { 90 | $content = Html::tag('div', VarDumper::dumpAsString(unserialize($data['env']), 10, true), [ 91 | 'class' => 'content env hidden', 92 | 'data-id' => $data['id'], 93 | ]); 94 | 95 | return empty($data['env']) ? null : Html::a(\Yii::t('app', 'show'), '#', [ 96 | 'data-role' => 'show-env', 97 | 'data-id' => $data['id'], 98 | ]) . $content; 99 | }, 100 | 'options' => [ 101 | 'width' => '7%', 102 | ], 103 | ], 104 | [ 105 | 'attribute' => 'created_at', 106 | 'value' => function ($data) { 107 | return Yii::$app->get('formatter')->asDatetime($data['created_at']); 108 | }, 109 | 'options' => [ 110 | 'width' => '20%', 111 | ], 112 | ], 113 | ], 114 | ]); 115 | Pjax::end(); 116 | 117 | $this->registerJs('initializePopUps();'); 118 | 119 | echo Modal::widget([ 120 | 'id' => 'detail-content', 121 | 'header' => 'Detail information', 122 | ]); 123 | 124 | ?> 125 | 126 | 149 | -------------------------------------------------------------------------------- /debug/views/panels/changelog/summary.php: -------------------------------------------------------------------------------- 1 | 13 |
14 | getSummaryName(), $panel->getUrl(), [ 15 | 'title' => Yii::t('app', 'Show changelog.'), 16 | ]) ?> 17 |
18 | -------------------------------------------------------------------------------- /interfaces/ChangelogModelInterface.php: -------------------------------------------------------------------------------- 1 | db->driverName === 'mysql') { 10 | $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=ARCHIVE'; 11 | } 12 | 13 | $this->createTable('{{%changelog}}', [ 14 | 'id' => $this->primaryKey(), 15 | 'action' => $this->integer(), 16 | 'entity_type' => $this->string(), 17 | 'entity_id' => $this->string(), 18 | 'present' => $this->string(), 19 | 'changes' => $this->binary(), 20 | 'env' => $this->binary(), 21 | 'created_at' => $this->integer(), 22 | 'updated_at' => $this->integer(), 23 | ], $tableOptions); 24 | 25 | $this->createIndex('idx_entity', '{{%changelog}}', ['entity_type', 'entity_id']); 26 | $this->createIndex('idx_created', '{{%changelog}}', ['created_at']); 27 | } 28 | 29 | public function safeDown() 30 | { 31 | $this->dropTable('{{%changelog}}'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /resources/Changelog.php: -------------------------------------------------------------------------------- 1 | 'trim'], 45 | 46 | /** default values */ 47 | ]; 48 | } 49 | 50 | /** 51 | * @return array 52 | */ 53 | public static function getActions() 54 | { 55 | /** @var LogsStorage $Storage */ 56 | $Storage = Instance::ensure([ 57 | 'class' => LogsStorage::className(), 58 | ]); 59 | 60 | return [ 61 | $Storage::ACTION_INSERT => \Yii::t('app', 'Insert'), 62 | $Storage::ACTION_UPDATE => \Yii::t('app', 'Update'), 63 | $Storage::ACTION_DELETE => \Yii::t('app', 'Delete'), 64 | ]; 65 | } 66 | 67 | /** 68 | * @return array 69 | */ 70 | public static function getAllEntityTypes() 71 | { 72 | $types = static::find() 73 | ->select('entity_type') 74 | ->groupBy(['entity_type']) 75 | ->asArray() 76 | ->all(); 77 | 78 | $types = array_column($types, 'entity_type'); 79 | 80 | return empty($types) ? [] : array_combine($types, $types); 81 | } 82 | 83 | /** 84 | * @param string $period 85 | * @return int 86 | */ 87 | public static function prune($period = '-90 days') 88 | { 89 | $threshold = strtotime($period); 90 | 91 | return static::deleteAll(['<', 'created_at', $threshold]); 92 | } 93 | 94 | /** 95 | * @return queries\ChangelogQuery 96 | */ 97 | public static function find() 98 | { 99 | /** @var queries\ChangelogQuery $Query */ 100 | $Query = \Yii::$container->get(queries\ChangelogQuery::className(), [get_called_class()]); 101 | 102 | return $Query; 103 | } 104 | 105 | /** 106 | * @inheritdoc 107 | */ 108 | public static function tableName() 109 | { 110 | return '{{%changelog}}'; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /resources/Log.php: -------------------------------------------------------------------------------- 1 | get('formatter'); 42 | 43 | $this->created_at = $Formatter->asTimestamp(time()); 44 | $this->updated_at = $Formatter->asTimestamp(time()); 45 | } 46 | 47 | /** 48 | * @return array 49 | */ 50 | public function export() 51 | { 52 | return [ 53 | $this->action, 54 | $this->entity_type, 55 | $this->entity_id, 56 | $this->present, 57 | $this->changes, 58 | $this->getEnvData(), 59 | $this->created_at, 60 | $this->updated_at, 61 | ]; 62 | } 63 | 64 | /** 65 | * @return array 66 | */ 67 | public static function schema() 68 | { 69 | return [ 70 | 'action', 71 | 'entity_type', 72 | 'entity_id', 73 | 'present', 74 | 'changes', 75 | 'env', 76 | 'created_at', 77 | 'updated_at', 78 | ]; 79 | } 80 | 81 | /** 82 | * @param integer $action 83 | * @return static 84 | */ 85 | public function action($action) 86 | { 87 | $this->action = $action; 88 | 89 | return $this; 90 | } 91 | 92 | /** 93 | * @param BaseActiveRecord $Model 94 | * @return static 95 | */ 96 | public function entity($Model) 97 | { 98 | $this->entity_type = get_class($Model); 99 | $this->entity_id = Json::encode($Model->primaryKey); 100 | 101 | $this->present = $Model instanceof LoggableInterface 102 | ? (string)$Model 103 | : null; 104 | 105 | return $this; 106 | } 107 | 108 | /** 109 | * @param array $changes 110 | * @return static 111 | */ 112 | public function changes(array $changes) 113 | { 114 | $this->changes = serialize($changes); 115 | 116 | return $this; 117 | } 118 | 119 | /** 120 | * @return array 121 | */ 122 | protected function getEnvData() 123 | { 124 | $result = [ 125 | 'ENV' => $_ENV, 126 | 'SERVER' => $_SERVER, 127 | ]; 128 | 129 | $Request = Instance::ensure('request', Request::className()); 130 | 131 | if ($Request instanceof \yii\web\Request) { 132 | $result['COOKIE'] = $_COOKIE; 133 | $result['GET'] = $Request->get(); 134 | $result['POST'] = $Request->post(); 135 | $result['REQUEST_BODY'] = $Request->getRawBody(); 136 | } 137 | 138 | // @todo add gzip compress 139 | return serialize($result); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /resources/queries/ChangelogQuery.php: -------------------------------------------------------------------------------- 1 | andWhere(['id' => $id]); 26 | 27 | return $this; 28 | } 29 | 30 | /** 31 | * @param integer|array $action 32 | * @return static 33 | */ 34 | public function byAction($action) 35 | { 36 | $this->andWhere(['action' => $action]); 37 | 38 | return $this; 39 | } 40 | 41 | /** 42 | * @param string|array $entity_type 43 | * @param string|array $entity_id 44 | * @return static 45 | */ 46 | public function byEntity($entity_type, $entity_id) 47 | { 48 | $this->andWhere([ 49 | 'entity_type' => $entity_type, 50 | 'entity_id' => $entity_id, 51 | ]); 52 | 53 | return $this; 54 | } 55 | 56 | /** 57 | * @param string|array $entity_type 58 | * @return static 59 | */ 60 | public function byEntityType($entity_type) 61 | { 62 | $this->andWhere(['entity_type' => $entity_type]); 63 | 64 | return $this; 65 | } 66 | 67 | /** 68 | * @param string|array $entity_id 69 | * @return static 70 | */ 71 | public function byEntityId($entity_id) 72 | { 73 | $this->andWhere(['entity_id' => $entity_id]); 74 | 75 | return $this; 76 | } 77 | } 78 | --------------------------------------------------------------------------------