├── .gitignore ├── .scrutinizer.yml ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── Module.php ├── Permission.php ├── README.md ├── composer.json ├── composer.lock ├── forms └── CommentCreateForm.php ├── interfaces └── CommentatorInterface.php ├── migrations ├── m150106_122229_comments.php └── m151005_165040_comment_from.php ├── models ├── Comment.php └── queries │ └── CommentQuery.php ├── phpunit.xml.dist ├── rbac └── ItsMyComment.php ├── tests ├── unit │ ├── .gitignore │ ├── TestCase.php │ ├── bootstrap.php │ ├── comments │ │ └── MainTest.php │ ├── config │ │ ├── .gitignore │ │ └── main.php │ ├── models │ │ └── User.php │ └── runtime │ │ └── .gitignore └── web │ ├── README.md │ ├── assets │ └── AppAssetBundle.php │ ├── config │ ├── console.php │ ├── db.php │ └── web.php │ ├── controllers │ ├── DemoController.php │ └── SignController.php │ ├── migrations │ └── m151005_165039_user.php │ ├── models │ └── User.php │ ├── runtime │ └── .gitignore │ ├── views │ ├── demo │ │ └── index.php │ └── layouts │ │ └── main.php │ ├── web │ ├── assets │ │ └── .gitignore │ └── index.php │ └── yii └── widgets ├── CommentFormAsset.php ├── CommentFormWidget.php ├── CommentListAsset.php ├── CommentListWidget.php ├── _assets ├── comment-form.css ├── comment-list.css └── comment-list.js └── views ├── comment-form.php └── comment-list.php /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /vendor 3 | /coverage 4 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | build: 2 | environment: 3 | php: 4 | version: 5.5.12 5 | dependencies: 6 | before: 7 | - sudo composer self-update && composer --version 8 | - composer global require "fxp/composer-asset-plugin:~1.0" 9 | tests: 10 | override: 11 | - phpunit 12 | imports: 13 | - php 14 | checks: 15 | php: 16 | code_rating: true 17 | duplication: true 18 | tools: 19 | php_sim: false 20 | php_cpd: false 21 | php_pdepend: true 22 | php_analyzer: true 23 | php_changetracking: true 24 | external_code_coverage: 25 | timeout: 2100 # Timeout in seconds. 26 | filter: 27 | excluded_paths: 28 | - tests/* 29 | - vendor/* -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.4 5 | - 5.5 6 | - 5.6 7 | - hhvm 8 | - hhvm-nightly 9 | 10 | matrix: 11 | fast_finish: true 12 | allow_failures: 13 | - php: hhvm 14 | - php: hhvm-nightly 15 | 16 | sudo: false 17 | 18 | cache: 19 | directories: 20 | - vendor 21 | 22 | install: 23 | - travis_retry composer self-update && composer --version 24 | - travis_retry composer global require "fxp/composer-asset-plugin:~1.0" 25 | - export PATH="$HOME/.composer/vendor/bin:$PATH" 26 | - travis_retry composer install --prefer-dist --no-interaction 27 | 28 | script: 29 | - phpunit --verbose --coverage-clover=coverage/coverage.clover 30 | 31 | after_script: 32 | - travis_retry wget https://scrutinizer-ci.com/ocular.phar 33 | - php ocular.phar code-coverage:upload --format=php-clover coverage/coverage.clover -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2016-04-28 - 1.4.4 2 | ------------------ 3 | * Fix #16 4 | * Fix #13 5 | * Refactoring 6 | 7 | 2016-04-28 - 1.4.3 8 | ------------------ 9 | * Fix trouble with delete button, when user delete comment from page with GET parameters in url (like `foo/bar?id=1`) 10 | * Refactoring. 11 | 12 | 2016-03-01 - 1.4.2 13 | ------------------ 14 | * Refactoring. 15 | 16 | 2016-02-28 - 1.4.1 17 | ------------------ 18 | * Fix author check. 19 | * Update packages. 20 | 21 | 2015-10-05 - 1.4.0 22 | ------------------ 23 | * Improve dependency injection. 24 | * Add comment from field. 25 | * Add web test. 26 | * Refactoring. 27 | 28 | 2015-06-27 - 1.3.1 29 | ------------------ 30 | * Add param `theme` to `CommentFormWidget` 31 | 32 | 2015-06-22 - 1.3.0 33 | ------------------ 34 | * Bugfixes and add `theme` parameter into `CommentListWidget` 35 | 36 | 2015-06-19 - 1.2.1 37 | ------------------ 38 | * Refactoring. 39 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Module.php: -------------------------------------------------------------------------------- 1 | 'frontend/models/comments/CommentModel' 34 | * ] 35 | * 36 | * The classes defined here will be merged with getDefaultModels() 37 | * having he manually defined by the user preference. 38 | * 39 | * @var array 40 | */ 41 | public $modelMap = []; 42 | 43 | public function init() 44 | { 45 | parent::init(); 46 | 47 | if ($this->userIdentityClass === null) { 48 | $this->userIdentityClass = \Yii::$app->getUser()->identityClass; 49 | } 50 | 51 | // Merge the default model classes 52 | // with the user defined ones. 53 | $this->defineModelClasses(); 54 | } 55 | 56 | /** 57 | * @return static 58 | */ 59 | public static function instance() 60 | { 61 | return \Yii::$app->getModule(static::$moduleName); 62 | } 63 | 64 | /** 65 | * Merges the default and user defined model classes 66 | * Also let's the developer to set new ones with the 67 | * parameter being those the ones with most preference. 68 | * 69 | * @param array $modelClasses 70 | */ 71 | public function defineModelClasses($modelClasses = []) 72 | { 73 | $this->modelMap = ArrayHelper::merge( 74 | $this->getDefaultModels(), 75 | $this->modelMap, 76 | $modelClasses 77 | ); 78 | } 79 | 80 | /** 81 | * Get default model classes 82 | */ 83 | protected function getDefaultModels() 84 | { 85 | return [ 86 | 'Comment' => Comments\models\Comment::className(), 87 | 'CommentQuery' => Comments\models\queries\CommentQuery::className(), 88 | 'CommentCreateForm' => Comments\forms\CommentCreateForm::className(), 89 | ]; 90 | } 91 | 92 | /** 93 | * Get defined className of model 94 | * 95 | * Returns an string or array compatible 96 | * with the Yii::createObject method. 97 | * 98 | * @param string $name 99 | * @param array $config // You should never send an array with a key defined as "class" since this will 100 | * // overwrite the main className defined by the system. 101 | * @return string|array 102 | */ 103 | public function model($name, $config = []) 104 | { 105 | $modelData = $this->modelMap[ucfirst($name)]; 106 | 107 | if (!empty($config)) { 108 | if (is_string($modelData)) { 109 | $modelData = ['class' => $modelData]; 110 | } 111 | 112 | $modelData = ArrayHelper::merge( 113 | $modelData, 114 | $config 115 | ); 116 | } 117 | 118 | return $modelData; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Permission.php: -------------------------------------------------------------------------------- 1 | [ 30 | // ... 31 | 'comments' => [ 32 | 'class' => 'rmrevin\yii\module\Comments\Module', 33 | 'userIdentityClass' => 'app\models\User', 34 | 'useRbac' => true, 35 | ] 36 | ], 37 | // ... 38 | ]; 39 | ``` 40 | 41 | In your `User` model (or another model implements the interface `IdentityInterface`) need to implement the interface "\rmrevin\yii\module\Comments\interfaces\CommentatorInterface" 42 | ```php 43 | class User extends \yii\db\ActiveRecord 44 | implements 45 | \yii\web\IdentityInterface, 46 | \rmrevin\yii\module\Comments\interfaces\CommentatorInterface 47 | { 48 | // ... 49 | 50 | public function getCommentatorAvatar() 51 | { 52 | return $this->avatar_url; 53 | } 54 | 55 | public function getCommentatorName() 56 | { 57 | return $this->name; 58 | } 59 | 60 | public function getCommentatorUrl() 61 | { 62 | return ['/profile', 'id' => $this->id]; // or false, if user does not have a public page 63 | } 64 | 65 | // ... 66 | } 67 | ``` 68 | 69 | In auth manager add rules (if `Module::$useRbac = true`): 70 | ```php 71 | getAuthManager(); 76 | $ItsMyCommentRule = new ItsMyComment(); 77 | 78 | $AuthManager->add($ItsMyCommentRule); 79 | 80 | $AuthManager->add(new \yii\rbac\Permission([ 81 | 'name' => Permission::CREATE, 82 | 'description' => 'Can create own comments', 83 | ])); 84 | $AuthManager->add(new \yii\rbac\Permission([ 85 | 'name' => Permission::UPDATE, 86 | 'description' => 'Can update all comments', 87 | ])); 88 | $AuthManager->add(new \yii\rbac\Permission([ 89 | 'name' => Permission::UPDATE_OWN, 90 | 'ruleName' => $ItsMyCommentRule->name, 91 | 'description' => 'Can update own comments', 92 | ])); 93 | $AuthManager->add(new \yii\rbac\Permission([ 94 | 'name' => Permission::DELETE, 95 | 'description' => 'Can delete all comments', 96 | ])); 97 | $AuthManager->add(new \yii\rbac\Permission([ 98 | 'name' => Permission::DELETE_OWN, 99 | 'ruleName' => $ItsMyCommentRule->name, 100 | 'description' => 'Can delete own comments', 101 | ])); 102 | ``` 103 | 104 | Updating database schema 105 | ------------------------ 106 | After you downloaded and configured `rmrevin/yii2-comments`, 107 | the last thing you need to do is updating your database schema by applying the migrations: 108 | 109 | In `command line`: 110 | ``` 111 | php yii migrate/up --migrationPath=@vendor/rmrevin/yii2-comments/migrations/ 112 | ``` 113 | 114 | Usage 115 | ----- 116 | In view 117 | ```php 118 | (string) 'photo-15', // type and id 125 | ]); 126 | 127 | ``` 128 | 129 | Parameters 130 | ---------- 131 | 132 | ### Module parameters 133 | 134 | * **userIdentityClass** (required, string) The user identity class that Yii2 uses to provide identity information about the users in the App. 135 | 136 | * **useRbac** (optional, boolean) Default TRUE. Defines if the comment system will use Rbac validation to check the comment permissions when trying to update, delete or add new comments. 137 | 138 | * **modelClasses** (optional, string[]) Stores the user defined model classes that will be used instead of the default ones in the comment system. Must have a key => classname format. e.g. `'Comment' => '@app\comments\CommentModel'` 139 | 140 | 141 | ### Widget parameters 142 | 143 | * **entity** (required, string) The entity that will identify the comments under on section from all the comments in this module. 144 | 145 | * **theme** (optional, string) In case you want to use a theme in your application you should define here it's location. 146 | 147 | * **viewParams** (optional, array) Data that will be sent directly into the widget view files. Must have a key => data format. The key will be the variable name in the view. The variable `CommentsDataProvider` it's already taken. 148 | 149 | * **options** (optional, array) Default `['class' => 'comments-widget']`. Option data array that will be sent into the div holding the comment system in your views. 150 | 151 | * **pagination** (optional, array) Pagination configuration that will be used in the comment panel. 152 | Default data: 153 | ```php 154 | public $pagination = 155 | [ 156 | 'pageParam' => 'page', 157 | 'pageSizeParam' => 'per-page', 158 | 'pageSize' => 20, 159 | 'pageSizeLimit' => [1, 50], 160 | ]; 161 | ``` 162 | 163 | * **sort** (optional, array) Type of sorting used to retrieve the comments into the panel. Can be sorted by any of the columns defined in the `comment` table. 164 | Default data: 165 | ```php 166 | 'defaultOrder' => [ 167 | 'id' => SORT_ASC, 168 | ], 169 | ``` 170 | 171 | * **showDeleted** (optional, boolean) Default `True`. Defines if the comments panel will show a message indicating the deleted comments. 172 | 173 | * **showCreateForm** (optional, boolean) Default `True`. Will show or hide the form to add a comment in this panel. 174 | 175 | 176 | Extending the package 177 | --------------------- 178 | 179 | ### Extending Model files 180 | 181 | Depending on which ones you need, you can set the `modelMap` config property: 182 | 183 | ```php 184 | 185 | // ... 186 | 'modules' => [ 187 | // ... 188 | 'comments' => [ 189 | 'class' => 'rmrevin\yii\module\Comments\Module', 190 | 'userIdentityClass' => 'app\models\User', 191 | 'useRbac' => true, 192 | 'modelMap' => [ 193 | 'Comment' => '@app\comments\CommentModel' 194 | ] 195 | ] 196 | ], 197 | // ... 198 | ``` 199 | 200 | 201 | Attention: keep in mind that if you are changing the `Comment` model, the new class should always extend the package's original `Comment` class. 202 | 203 | 204 | ### Attaching behaviors and event handlers 205 | 206 | The package allows you to attach behavior or event handler to any model. To do this you can set model map like so: 207 | 208 | ```php 209 | 210 | // ... 211 | 'modules' => [ 212 | // ... 213 | 'comments' => [ 214 | 'class' => 'rmrevin\yii\module\Comments\Module', 215 | 'userIdentityClass' => 'app\models\User', 216 | 'useRbac' => true, 217 | 'modelMap' => [ 218 | 'Comment' => [ 219 | 'class' => '@app\comments\CommentModel', 220 | 'on event' => function(){ 221 | // code here 222 | }, 223 | 'as behavior' => 224 | ['class' => 'Foo'], 225 | ] 226 | ] 227 | ], 228 | // ... 229 | ``` 230 | 231 | ### Extending View files 232 | 233 | You can extend the view files supplied by this package using the `theme` component in the config file. 234 | 235 | ```php 236 | // app/config/web.php 237 | 238 | 'components' => [ 239 | 'view' => [ 240 | 'theme' => [ 241 | 'pathMap' => [ 242 | '@vendor/rmrevin/yii2-comments/widgets/views' => '@app/views/comments', // example: @app/views/comment/comment-form.php 243 | ], 244 | ], 245 | ], 246 | ], 247 | 248 | ``` 249 | 250 | ### Extending Widgets 251 | 252 | To extend the widget code and behavior you only have to extend the widget classes and call them instead of the package's ones. -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rmrevin/yii2-comments", 3 | "description": "Comments module for Yii2", 4 | "keywords": [ 5 | "yii", 6 | "comment", 7 | "widget", 8 | "module" 9 | ], 10 | "type": "yii2-extension", 11 | "license": "MIT", 12 | "minimum-stability": "stable", 13 | "support": { 14 | "issues": "https://github.com/rmrevin/yii2-comments/issues", 15 | "source": "https://github.com/rmrevin/yii2-comments" 16 | }, 17 | "authors": [ 18 | { 19 | "name": "Roman Revin", 20 | "email": "xgismox@gmail.com", 21 | "homepage": "http://rmrevin.ru/" 22 | } 23 | ], 24 | "require": { 25 | "php": ">=5.4.0", 26 | 27 | "yiisoft/yii2": "2.0.*", 28 | "rmrevin/yii2-fontawesome": "~2.10" 29 | }, 30 | "autoload": { 31 | "psr-4": { 32 | "rmrevin\\yii\\module\\Comments\\": "" 33 | } 34 | }, 35 | "extra": { 36 | "asset-installer-paths": { 37 | "npm-asset-library": "vendor/npm", 38 | "bower-asset-library": "vendor/bower" 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /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": "ed4934636110d3d30a7d7b8b554cb1d3", 8 | "content-hash": "1a2475ef8226c24df1dc00d303ac7ee5", 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.7.0", 212 | "source": { 213 | "type": "git", 214 | "url": "https://github.com/ezyang/htmlpurifier.git", 215 | "reference": "ae1828d955112356f7677c465f94f7deb7d27a40" 216 | }, 217 | "dist": { 218 | "type": "zip", 219 | "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/ae1828d955112356f7677c465f94f7deb7d27a40", 220 | "reference": "ae1828d955112356f7677c465f94f7deb7d27a40", 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": "2015-08-05 01:03:42" 252 | }, 253 | { 254 | "name": "rmrevin/yii2-fontawesome", 255 | "version": "2.15.1", 256 | "source": { 257 | "type": "git", 258 | "url": "https://github.com/rmrevin/yii2-fontawesome.git", 259 | "reference": "d2777130bc926bc670a4e50c2f504a0fb782efb1" 260 | }, 261 | "dist": { 262 | "type": "zip", 263 | "url": "https://api.github.com/repos/rmrevin/yii2-fontawesome/zipball/d2777130bc926bc670a4e50c2f504a0fb782efb1", 264 | "reference": "d2777130bc926bc670a4e50c2f504a0fb782efb1", 265 | "shasum": "" 266 | }, 267 | "require": { 268 | "php": ">=5.4.0", 269 | "yiisoft/yii2": "2.0.*" 270 | }, 271 | "type": "yii2-extension", 272 | "extra": { 273 | "asset-installer-paths": { 274 | "npm-asset-library": "vendor/npm", 275 | "bower-asset-library": "vendor/bower" 276 | } 277 | }, 278 | "autoload": { 279 | "psr-4": { 280 | "rmrevin\\yii\\fontawesome\\": "" 281 | } 282 | }, 283 | "notification-url": "https://packagist.org/downloads/", 284 | "license": [ 285 | "MIT" 286 | ], 287 | "authors": [ 288 | { 289 | "name": "Revin Roman", 290 | "email": "roman@rmrevin.com", 291 | "homepage": "https://rmrevin.com/" 292 | } 293 | ], 294 | "description": "Asset Bundle for Yii2 with Font Awesome", 295 | "keywords": [ 296 | "asset", 297 | "awesome", 298 | "bundle", 299 | "font", 300 | "yii" 301 | ], 302 | "time": "2016-05-28 21:18:32" 303 | }, 304 | { 305 | "name": "yiisoft/yii2", 306 | "version": "2.0.8", 307 | "source": { 308 | "type": "git", 309 | "url": "https://github.com/yiisoft/yii2-framework.git", 310 | "reference": "53992b136b993e32ca7b6f399cf42b143f8685a6" 311 | }, 312 | "dist": { 313 | "type": "zip", 314 | "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/53992b136b993e32ca7b6f399cf42b143f8685a6", 315 | "reference": "53992b136b993e32ca7b6f399cf42b143f8685a6", 316 | "shasum": "" 317 | }, 318 | "require": { 319 | "bower-asset/jquery": "2.2.*@stable | 2.1.*@stable | 1.11.*@stable", 320 | "bower-asset/jquery.inputmask": "~3.2.2", 321 | "bower-asset/punycode": "1.3.*", 322 | "bower-asset/yii2-pjax": "~2.0.1", 323 | "cebe/markdown": "~1.0.0 | ~1.1.0", 324 | "ext-ctype": "*", 325 | "ext-mbstring": "*", 326 | "ezyang/htmlpurifier": "~4.6", 327 | "lib-pcre": "*", 328 | "php": ">=5.4.0", 329 | "yiisoft/yii2-composer": "~2.0.4" 330 | }, 331 | "bin": [ 332 | "yii" 333 | ], 334 | "type": "library", 335 | "extra": { 336 | "branch-alias": { 337 | "dev-master": "2.0.x-dev" 338 | } 339 | }, 340 | "autoload": { 341 | "psr-4": { 342 | "yii\\": "" 343 | } 344 | }, 345 | "notification-url": "https://packagist.org/downloads/", 346 | "license": [ 347 | "BSD-3-Clause" 348 | ], 349 | "authors": [ 350 | { 351 | "name": "Qiang Xue", 352 | "email": "qiang.xue@gmail.com", 353 | "homepage": "http://www.yiiframework.com/", 354 | "role": "Founder and project lead" 355 | }, 356 | { 357 | "name": "Alexander Makarov", 358 | "email": "sam@rmcreative.ru", 359 | "homepage": "http://rmcreative.ru/", 360 | "role": "Core framework development" 361 | }, 362 | { 363 | "name": "Maurizio Domba", 364 | "homepage": "http://mdomba.info/", 365 | "role": "Core framework development" 366 | }, 367 | { 368 | "name": "Carsten Brandt", 369 | "email": "mail@cebe.cc", 370 | "homepage": "http://cebe.cc/", 371 | "role": "Core framework development" 372 | }, 373 | { 374 | "name": "Timur Ruziev", 375 | "email": "resurtm@gmail.com", 376 | "homepage": "http://resurtm.com/", 377 | "role": "Core framework development" 378 | }, 379 | { 380 | "name": "Paul Klimov", 381 | "email": "klimov.paul@gmail.com", 382 | "role": "Core framework development" 383 | }, 384 | { 385 | "name": "Dmitry Naumenko", 386 | "email": "d.naumenko.a@gmail.com", 387 | "role": "Core framework development" 388 | } 389 | ], 390 | "description": "Yii PHP Framework Version 2", 391 | "homepage": "http://www.yiiframework.com/", 392 | "keywords": [ 393 | "framework", 394 | "yii2" 395 | ], 396 | "time": "2016-04-28 14:50:20" 397 | }, 398 | { 399 | "name": "yiisoft/yii2-composer", 400 | "version": "2.0.4", 401 | "source": { 402 | "type": "git", 403 | "url": "https://github.com/yiisoft/yii2-composer.git", 404 | "reference": "7452fd908a5023b8bb5ea1b123a174ca080de464" 405 | }, 406 | "dist": { 407 | "type": "zip", 408 | "url": "https://api.github.com/repos/yiisoft/yii2-composer/zipball/7452fd908a5023b8bb5ea1b123a174ca080de464", 409 | "reference": "7452fd908a5023b8bb5ea1b123a174ca080de464", 410 | "shasum": "" 411 | }, 412 | "require": { 413 | "composer-plugin-api": "^1.0" 414 | }, 415 | "type": "composer-plugin", 416 | "extra": { 417 | "class": "yii\\composer\\Plugin", 418 | "branch-alias": { 419 | "dev-master": "2.0.x-dev" 420 | } 421 | }, 422 | "autoload": { 423 | "psr-4": { 424 | "yii\\composer\\": "" 425 | } 426 | }, 427 | "notification-url": "https://packagist.org/downloads/", 428 | "license": [ 429 | "BSD-3-Clause" 430 | ], 431 | "authors": [ 432 | { 433 | "name": "Qiang Xue", 434 | "email": "qiang.xue@gmail.com" 435 | } 436 | ], 437 | "description": "The composer plugin for Yii extension installer", 438 | "keywords": [ 439 | "composer", 440 | "extension installer", 441 | "yii2" 442 | ], 443 | "time": "2016-02-06 00:49:24" 444 | } 445 | ], 446 | "packages-dev": [], 447 | "aliases": [], 448 | "minimum-stability": "stable", 449 | "stability-flags": [], 450 | "prefer-stable": false, 451 | "prefer-lowest": false, 452 | "platform": { 453 | "php": ">=5.4.0" 454 | }, 455 | "platform-dev": [] 456 | } 457 | -------------------------------------------------------------------------------- /forms/CommentCreateForm.php: -------------------------------------------------------------------------------- 1 | Comment; 30 | 31 | if (false === $this->Comment->isNewRecord) { 32 | $this->id = $Comment->id; 33 | $this->entity = $Comment->entity; 34 | $this->from = $Comment->from; 35 | $this->text = $Comment->text; 36 | } elseif (!\Yii::$app->getUser()->getIsGuest()) { 37 | $User = \Yii::$app->getUser()->getIdentity(); 38 | 39 | $this->from = $User instanceof Comments\interfaces\CommentatorInterface 40 | ? $User->getCommentatorName() 41 | : null; 42 | } 43 | } 44 | 45 | /** 46 | * @inheritdoc 47 | */ 48 | public function rules() 49 | { 50 | $CommentModelClassName = Comments\Module::instance()->model('comment'); 51 | 52 | return [ 53 | [['entity', 'text'], 'required'], 54 | [['entity', 'from', 'text'], 'string'], 55 | [['id'], 'integer'], 56 | [['id'], 'exist', 'targetClass' => $CommentModelClassName, 'targetAttribute' => 'id'], 57 | ]; 58 | } 59 | 60 | /** 61 | * @inheritdoc 62 | */ 63 | public function attributeLabels() 64 | { 65 | return [ 66 | 'entity' => \Yii::t('app', 'Entity'), 67 | 'from' => \Yii::t('app', 'Your name'), 68 | 'text' => \Yii::t('app', 'Text'), 69 | ]; 70 | } 71 | 72 | /** 73 | * @return bool 74 | * @throws \yii\web\NotFoundHttpException 75 | */ 76 | public function save() 77 | { 78 | $Comment = $this->Comment; 79 | 80 | $CommentModelClassName = Comments\Module::instance()->model('comment'); 81 | 82 | if (empty($this->id)) { 83 | $Comment = \Yii::createObject($CommentModelClassName); 84 | } elseif ($this->id > 0 && $Comment->id !== $this->id) { 85 | /** @var Comments\models\Comment $CommentModel */ 86 | $CommentModel = \Yii::createObject($CommentModelClassName); 87 | $Comment = $CommentModel::find() 88 | ->byId($this->id) 89 | ->one(); 90 | 91 | if (!($Comment instanceof Comments\models\Comment)) { 92 | throw new \yii\web\NotFoundHttpException; 93 | } 94 | } 95 | 96 | $Comment->entity = $this->entity; 97 | $Comment->from = $this->from; 98 | $Comment->text = $this->text; 99 | 100 | $result = $Comment->save(); 101 | 102 | if ($Comment->hasErrors()) { 103 | foreach ($Comment->getErrors() as $attribute => $messages) { 104 | foreach ($messages as $mes) { 105 | $this->addError($attribute, $mes); 106 | } 107 | } 108 | } 109 | 110 | $this->Comment = $Comment; 111 | 112 | return $result; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /interfaces/CommentatorInterface.php: -------------------------------------------------------------------------------- 1 | db->driverName === 'mysql') { 10 | // http://stackoverflow.com/questions/766809/whats-the-difference-between-utf8-general-ci-and-utf8-unicode-ci 11 | $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB'; 12 | } 13 | 14 | $this->createTable('{{%comment}}', [ 15 | 'id' => $this->primaryKey(), 16 | 'entity' => $this->string(), 17 | 'text' => $this->text(), 18 | 'deleted' => $this->boolean()->notNull()->defaultValue(false), 19 | 'created_by' => $this->integer(), 20 | 'updated_by' => $this->integer(), 21 | 'created_at' => $this->integer(), 22 | 'updated_at' => $this->integer(), 23 | ], $tableOptions); 24 | 25 | $this->createIndex('idx_entity', '{{%comment}}', ['entity']); 26 | $this->createIndex('idx_created_by', '{{%comment}}', ['created_by']); 27 | $this->createIndex('idx_created_at', '{{%comment}}', ['created_at']); 28 | } 29 | 30 | public function safeDown() 31 | { 32 | $this->dropTable('{{%comment}}'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /migrations/m151005_165040_comment_from.php: -------------------------------------------------------------------------------- 1 | addColumn('{{%comment}}', 'from', $this->string()->after('entity')); 11 | } 12 | 13 | public function down() 14 | { 15 | $this->dropColumn('{{%comment}}', 'from'); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /models/Comment.php: -------------------------------------------------------------------------------- 1 | BlameableBehavior::className(), 44 | 'timestamp' => TimestampBehavior::className(), 45 | ]; 46 | } 47 | 48 | /** 49 | * @inheritdoc 50 | */ 51 | public function rules() 52 | { 53 | return [ 54 | [['text'], 'required'], 55 | [['from', 'text'], 'string'], 56 | [['created_by', 'updated_by', 'created_at', 'updated_at'], 'integer'], 57 | [['deleted'], 'boolean'], 58 | [['deleted'], 'default', 'value' => self::NOT_DELETED], 59 | ]; 60 | } 61 | 62 | /** 63 | * @inheritdoc 64 | */ 65 | public function attributeLabels() 66 | { 67 | return [ 68 | 'id' => \Yii::t('app', 'ID'), 69 | 'entity' => \Yii::t('app', 'Entity'), 70 | 'from' => \Yii::t('app', 'Comment author'), 71 | 'text' => \Yii::t('app', 'Text'), 72 | 'created_by' => \Yii::t('app', 'Created by'), 73 | 'updated_by' => \Yii::t('app', 'Updated by'), 74 | 'created_at' => \Yii::t('app', 'Created at'), 75 | 'updated_at' => \Yii::t('app', 'Updated at'), 76 | ]; 77 | } 78 | 79 | /** 80 | * @return bool 81 | */ 82 | public function isEdited() 83 | { 84 | return $this->created_at !== $this->updated_at; 85 | } 86 | 87 | /** 88 | * @return bool 89 | */ 90 | public function isDeleted() 91 | { 92 | return $this->deleted === self::DELETED; 93 | } 94 | 95 | /** 96 | * @return bool 97 | */ 98 | public static function canCreate() 99 | { 100 | return Comments\Module::instance()->useRbac === true 101 | ? \Yii::$app->getUser()->can(Comments\Permission::CREATE) 102 | : true; 103 | } 104 | 105 | /** 106 | * @return bool 107 | */ 108 | public function canUpdate() 109 | { 110 | $User = \Yii::$app->getUser(); 111 | 112 | return Comments\Module::instance()->useRbac === true 113 | ? (\Yii::$app->getUser()->can(Comments\Permission::UPDATE) || \Yii::$app->getUser()->can(Comments\Permission::UPDATE_OWN, ['Comment' => $this])) 114 | : ($User->isGuest ? false : ($this->created_by === $User->id)); 115 | } 116 | 117 | /** 118 | * @return bool 119 | */ 120 | public function canDelete() 121 | { 122 | $User = \Yii::$app->getUser(); 123 | 124 | return Comments\Module::instance()->useRbac === true 125 | ? (\Yii::$app->getUser()->can(Comments\Permission::DELETE) || \Yii::$app->getUser()->can(Comments\Permission::DELETE_OWN, ['Comment' => $this])) 126 | : ($User->isGuest ? false : ($this->created_by === $User->id)); 127 | } 128 | 129 | /** 130 | * @return queries\CommentQuery 131 | */ 132 | public function getAuthor() 133 | { 134 | return $this->hasOne(Comments\Module::instance()->userIdentityClass, ['id' => 'created_by']); 135 | } 136 | 137 | /** 138 | * @return queries\CommentQuery 139 | */ 140 | public function getLastUpdateAuthor() 141 | { 142 | return $this->hasOne(Comments\Module::instance()->userIdentityClass, ['id' => 'updated_by']); 143 | } 144 | 145 | /** 146 | * @return queries\CommentQuery 147 | */ 148 | public static function find() 149 | { 150 | return \Yii::createObject( 151 | Comments\Module::instance()->model('commentQuery'), 152 | [get_called_class()] 153 | ); 154 | } 155 | 156 | /** 157 | * @inheritdoc 158 | */ 159 | public static function tableName() 160 | { 161 | return '{{%comment}}'; 162 | } 163 | 164 | const NOT_DELETED = 0; 165 | const DELETED = 1; 166 | } -------------------------------------------------------------------------------- /models/queries/CommentQuery.php: -------------------------------------------------------------------------------- 1 | andWhere(['id' => $id]); 29 | 30 | return $this; 31 | } 32 | 33 | /** 34 | * @param string|array $entity 35 | * @return static 36 | */ 37 | public function byEntity($entity) 38 | { 39 | $this->andWhere(['entity' => $entity]); 40 | 41 | return $this; 42 | } 43 | 44 | /** 45 | * @return static 46 | */ 47 | public function withoutDeleted() 48 | { 49 | /** @var Comments\models\Comment $CommentModel */ 50 | $CommentModel = \Yii::createObject(Comments\Module::instance()->model('comment')); 51 | 52 | $this->andWhere(['deleted' => $CommentModel::NOT_DELETED]); 53 | 54 | return $this; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | . 11 | 12 | 13 | ./tests 14 | ./vendor 15 | 16 | 17 | 18 | 19 | ./tests/unit/comments 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /rbac/ItsMyComment.php: -------------------------------------------------------------------------------- 1 | created_by; 25 | } 26 | } -------------------------------------------------------------------------------- /tests/unit/.gitignore: -------------------------------------------------------------------------------- 1 | runtime/cache/* -------------------------------------------------------------------------------- /tests/unit/TestCase.php: -------------------------------------------------------------------------------- 1 | mock_application(); 26 | } 27 | 28 | /** 29 | * Populates Yii::$app with a new application 30 | * The application will be destroyed on tearDown() automatically. 31 | * @param string $appClass 32 | */ 33 | protected function mock_application($appClass = '\yii\console\Application') 34 | { 35 | // for update self::$params 36 | $this->get_param('id'); 37 | 38 | /** @var \yii\console\Application $app */ 39 | new $appClass(self::$params); 40 | } 41 | 42 | /** 43 | * Returns a test configuration param from /data/config.php 44 | * @param string $name params name 45 | * @param mixed $default default value to use when param is not set. 46 | * @return mixed the value of the configuration param 47 | */ 48 | public function get_param($name, $default = null) 49 | { 50 | if (self::$params === null) { 51 | self::$params = require(__DIR__ . '/config/main.php'); 52 | $main_local = __DIR__ . '/config/main-local.php'; 53 | if (file_exists($main_local)) { 54 | self::$params = ArrayHelper::merge(self::$params, require($main_local)); 55 | } 56 | } 57 | 58 | return isset(self::$params[$name]) ? self::$params[$name] : $default; 59 | } 60 | 61 | protected function tearDown() 62 | { 63 | parent::tearDown(); 64 | } 65 | 66 | /** 67 | * Destroys application in Yii::$app by setting it to null. 68 | */ 69 | protected function destroy_application() 70 | { 71 | \Yii::$app = null; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tests/unit/bootstrap.php: -------------------------------------------------------------------------------- 1 | getModule('comments'); 23 | 24 | $this->assertInstanceOf('rmrevin\yii\module\Comments\Module', $Module); 25 | $this->assertEquals('rmrevin\yii\module\Comments\tests\unit\models\User', $Module->userIdentityClass); 26 | } 27 | } -------------------------------------------------------------------------------- /tests/unit/config/.gitignore: -------------------------------------------------------------------------------- 1 | main-local.php -------------------------------------------------------------------------------- /tests/unit/config/main.php: -------------------------------------------------------------------------------- 1 | 'testapp', 9 | 'basePath' => realpath(__DIR__ . '/..'), 10 | 'modules' => [ 11 | 'comments' => [ 12 | 'class' => 'rmrevin\yii\module\Comments\Module', 13 | 'userIdentityClass' => 'rmrevin\yii\module\Comments\tests\unit\models\User', 14 | ], 15 | ] 16 | ]; -------------------------------------------------------------------------------- /tests/unit/models/User.php: -------------------------------------------------------------------------------- 1 | 'basic-console', 5 | 'basePath' => dirname(__DIR__), 6 | 'bootstrap' => [], 7 | 'controllerNamespace' => 'app\commands', 8 | 'components' => [ 9 | 'db' => require(__DIR__ . '/db.php'), 10 | ], 11 | 'params' => [], 12 | ]; 13 | -------------------------------------------------------------------------------- /tests/web/config/db.php: -------------------------------------------------------------------------------- 1 | 'yii\db\Connection', 5 | 'dsn' => 'mysql:host=localhost;dbname=yii2-playground', 6 | 'username' => 'yii2', 7 | 'password' => 'playground', 8 | 'charset' => 'utf8', 9 | 'tablePrefix' => 'yii2_comments_', 10 | ]; -------------------------------------------------------------------------------- /tests/web/config/web.php: -------------------------------------------------------------------------------- 1 | 'basic', 5 | 'basePath' => dirname(__DIR__), 6 | 'defaultRoute' => 'demo/index', 7 | 'controllerNamespace' => 'rmrevin\yii\module\Comments\tests\web\controllers', 8 | 'bootstrap' => [], 9 | 'aliases' => [ 10 | '@vendor/rmrevin/yii2-comments' => realpath(__DIR__ . '/../../..'), 11 | '@vendor' => realpath(__DIR__ . '/../../../vendor'), 12 | '@bower' => realpath(__DIR__ . '/../../../vendor/bower'), 13 | ], 14 | 'modules' => [ 15 | 'comments' => [ 16 | 'class' => 'rmrevin\yii\module\Comments\Module', 17 | 'userIdentityClass' => 'rmrevin\yii\module\Comments\tests\web\models\User', 18 | 'useRbac' => false, 19 | ], 20 | ], 21 | 'components' => [ 22 | 'db' => require(__DIR__ . '/db.php'), 23 | 'request' => [ 24 | 'cookieValidationKey' => 'secret-key', 25 | ], 26 | 'user' => [ 27 | 'identityClass' => 'rmrevin\yii\module\Comments\tests\web\models\User', 28 | 'enableAutoLogin' => true, 29 | ], 30 | 'errorHandler' => [ 31 | 'errorAction' => 'site/error', 32 | ], 33 | ], 34 | 'params' => [], 35 | ]; 36 | -------------------------------------------------------------------------------- /tests/web/controllers/DemoController.php: -------------------------------------------------------------------------------- 1 | render('index'); 23 | } 24 | } -------------------------------------------------------------------------------- /tests/web/controllers/SignController.php: -------------------------------------------------------------------------------- 1 | one(); 26 | 27 | \Yii::$app->getUser()->login($User); 28 | 29 | return $this->redirect(['/demo/index']); 30 | } 31 | 32 | /** 33 | * @return \yii\web\Response 34 | */ 35 | public function actionOut() 36 | { 37 | \Yii::$app->getUser()->logout(); 38 | 39 | return $this->redirect(['/demo/index']); 40 | } 41 | } -------------------------------------------------------------------------------- /tests/web/migrations/m151005_165039_user.php: -------------------------------------------------------------------------------- 1 | db->driverName === 'mysql') { 13 | // http://stackoverflow.com/questions/766809/whats-the-difference-between-utf8-general-ci-and-utf8-unicode-ci 14 | $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB'; 15 | } 16 | 17 | $this->createTable('{{%user}}', [ 18 | 'id' => Schema::TYPE_PK, 19 | 'name' => Schema::TYPE_STRING, 20 | 'auth_key' => Schema::TYPE_STRING, 21 | 'token' => Schema::TYPE_STRING, 22 | ], $tableOptions); 23 | 24 | $this->insert('{{%user}}', [ 25 | 'id' => 1, 26 | 'name' => 'Demo User', 27 | 'auth_key' => md5(uniqid()), 28 | 'token' => md5(uniqid()), 29 | ]); 30 | } 31 | 32 | public function down() 33 | { 34 | $this->dropTable('{{%user}}'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/web/models/User.php: -------------------------------------------------------------------------------- 1 | name; 39 | } 40 | 41 | /** 42 | * @inheritdoc 43 | */ 44 | public function getCommentatorUrl() 45 | { 46 | return false; 47 | } 48 | 49 | /** 50 | * @inheritdoc 51 | */ 52 | public static function findIdentity($id) 53 | { 54 | return static::findOne($id); 55 | } 56 | 57 | /** 58 | * @inheritdoc 59 | */ 60 | public static function findIdentityByAccessToken($token, $type = null) 61 | { 62 | return static::findOne(['token' => $token]); 63 | } 64 | 65 | /** 66 | * @inheritdoc 67 | */ 68 | public function getId() 69 | { 70 | return $this->getPrimaryKey(); 71 | } 72 | 73 | /** 74 | * @inheritdoc 75 | */ 76 | public function getName() 77 | { 78 | return $this->name; 79 | } 80 | 81 | /** 82 | * @inheritdoc 83 | */ 84 | public function getAuthKey() 85 | { 86 | return $this->auth_key; 87 | } 88 | 89 | /** 90 | * @inheritdoc 91 | */ 92 | public function validateAuthKey($authKey) 93 | { 94 | return $this->getAuthKey() === $authKey; 95 | } 96 | 97 | /** 98 | * @inheritdoc 99 | */ 100 | public static function tableName() 101 | { 102 | return '{{%user}}'; 103 | } 104 | } -------------------------------------------------------------------------------- /tests/web/runtime/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /tests/web/views/demo/index.php: -------------------------------------------------------------------------------- 1 | getUser()->getIdentity(); 14 | 15 | ?> 16 |
17 | getUser()->getIsGuest()) { 19 | echo 'Not logged. '; 20 | echo Html::a('Login', ['/sign/in']); 21 | } else { 22 | echo sprintf('Logged as %s. ', $User->name); 23 | echo Html::a('Logout', ['/sign/out']); 24 | } 25 | ?> 26 |
27 | 'test-1', 31 | ]); -------------------------------------------------------------------------------- /tests/web/views/layouts/main.php: -------------------------------------------------------------------------------- 1 | beginPage(); 16 | 17 | ?> 18 | 19 | 20 | 22 | 23 | 24 | title); 27 | 28 | echo Html::tag('meta', '', ['charset' => Yii::$app->charset]); 29 | 30 | $this->head(); 31 | ?> 32 | 33 | 35 | 36 | 37 | beginBody(); 40 | 41 | ?> 42 |
43 | 44 |
45 | endBody(); 48 | 49 | ?> 50 | 51 | 52 | endPage(); -------------------------------------------------------------------------------- /tests/web/web/assets/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /tests/web/web/index.php: -------------------------------------------------------------------------------- 1 | run(); -------------------------------------------------------------------------------- /tests/web/yii: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | run()); -------------------------------------------------------------------------------- /widgets/CommentFormAsset.php: -------------------------------------------------------------------------------- 1 | getView()); 40 | } 41 | 42 | /** 43 | * @inheritdoc 44 | */ 45 | public function run() 46 | { 47 | $this->registerAssets(); 48 | 49 | /** @var Comments\forms\CommentCreateForm $CommentCreateForm */ 50 | $CommentCreateFormClassData = Comments\Module::instance()->model( 51 | 'commentCreateForm', [ 52 | 'Comment' => $this->Comment, 53 | 'entity' => $this->entity, 54 | ] 55 | ); 56 | 57 | $CommentCreateForm = \Yii::createObject($CommentCreateFormClassData); 58 | 59 | if ($CommentCreateForm->load(\Yii::$app->getRequest()->post())) { 60 | if ($CommentCreateForm->validate()) { 61 | if ($CommentCreateForm->save()) { 62 | \Yii::$app->getResponse() 63 | ->refresh(sprintf($this->anchor, $CommentCreateForm->Comment->id)) 64 | ->send(); 65 | 66 | exit; 67 | } 68 | } 69 | } 70 | 71 | return $this->render($this->viewFile, [ 72 | 'CommentCreateForm' => $CommentCreateForm, 73 | ]); 74 | } 75 | 76 | /** 77 | * @inheritdoc 78 | */ 79 | public function getViewPath() 80 | { 81 | return empty($this->theme) 82 | ? parent::getViewPath() 83 | : (\Yii::$app->getViewPath() . DIRECTORY_SEPARATOR . $this->theme); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /widgets/CommentListAsset.php: -------------------------------------------------------------------------------- 1 | 'comments-widget']; 31 | 32 | /** @var string */ 33 | public $entity; 34 | 35 | /** @var string */ 36 | public $anchorAfterUpdate = '#comment-%d'; 37 | 38 | /** @var array */ 39 | public $pagination = [ 40 | 'pageParam' => 'page', 41 | 'pageSizeParam' => 'per-page', 42 | 'pageSize' => 20, 43 | 'pageSizeLimit' => [1, 50], 44 | ]; 45 | 46 | /** @var array */ 47 | public $sort = [ 48 | 'defaultOrder' => [ 49 | 'id' => SORT_ASC, 50 | ], 51 | ]; 52 | 53 | /** @var bool */ 54 | public $showDeleted = true; 55 | 56 | /** @var bool */ 57 | public $showCreateForm = true; 58 | 59 | public function init() 60 | { 61 | parent::init(); 62 | 63 | if (!isset($this->options['id'])) { 64 | $this->options['id'] = $this->getId(); 65 | } 66 | } 67 | 68 | /** 69 | * Register asset bundle 70 | */ 71 | protected function registerAssets() 72 | { 73 | CommentListAsset::register($this->getView()); 74 | } 75 | 76 | /** 77 | * @inheritdoc 78 | */ 79 | public function run() 80 | { 81 | $this->registerAssets(); 82 | 83 | $this->processDelete(); 84 | 85 | /** @var Comments\models\Comment $CommentModel */ 86 | $CommentModel = \Yii::createObject(Comments\Module::instance()->model('comment')); 87 | $CommentsQuery = $CommentModel::find() 88 | ->byEntity($this->entity); 89 | 90 | if (false === $this->showDeleted) { 91 | $CommentsQuery->withoutDeleted(); 92 | } 93 | 94 | $CommentsDataProvider = \Yii::createObject([ 95 | 'class' => \yii\data\ActiveDataProvider::className(), 96 | 'query' => $CommentsQuery->with(['author', 'lastUpdateAuthor']), 97 | 'pagination' => $this->pagination, 98 | 'sort' => $this->sort, 99 | ]); 100 | 101 | $params = $this->viewParams; 102 | $params['CommentsDataProvider'] = $CommentsDataProvider; 103 | 104 | $content = $this->render($this->viewFile, $params); 105 | 106 | return Html::tag('div', $content, $this->options); 107 | } 108 | 109 | private function processDelete() 110 | { 111 | $delete = (int)\Yii::$app->getRequest()->get('delete-comment'); 112 | if ($delete > 0) { 113 | 114 | /** @var Comments\models\Comment $CommentModel */ 115 | $CommentModel = \Yii::createObject(Comments\Module::instance()->model('comment')); 116 | 117 | /** @var Comments\models\Comment $Comment */ 118 | $Comment = $CommentModel::find() 119 | ->byId($delete) 120 | ->one(); 121 | 122 | if ($Comment->isDeleted()) { 123 | return; 124 | } 125 | 126 | if (!($Comment instanceof Comments\models\Comment)) { 127 | throw new \yii\web\NotFoundHttpException(\Yii::t('app', 'Comment not found.')); 128 | } 129 | 130 | if (!$Comment->canDelete()) { 131 | throw new \yii\web\ForbiddenHttpException(\Yii::t('app', 'Access Denied.')); 132 | } 133 | 134 | $Comment->deleted = $CommentModel::DELETED; 135 | $Comment->update(); 136 | } 137 | } 138 | 139 | /** 140 | * @inheritdoc 141 | */ 142 | public function getViewPath() 143 | { 144 | return empty($this->theme) 145 | ? parent::getViewPath() 146 | : (\Yii::$app->getViewPath() . DIRECTORY_SEPARATOR . $this->theme); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /widgets/_assets/comment-form.css: -------------------------------------------------------------------------------- 1 | .comment-form { margin-top: 20px; } 2 | -------------------------------------------------------------------------------- /widgets/_assets/comment-list.css: -------------------------------------------------------------------------------- 1 | .comment-title { margin-top: 20px; } 2 | .comments-list { } 3 | .comments-list .comment { margin-top: 20px; padding-top: 20px; border-top: 1px solid #DDDDDD; } 4 | .comments-list .comment.last { padding-bottom: 20px; border-bottom: 1px solid #DDDDDD; } 5 | .comments-list .comment .date { font-size: 0.8em; margin-left: 20px; } 6 | .comments-list .comment .author { } 7 | .comments-list .comment .author .avatar { width: 25px; height: 25px; margin-right: 10px; } 8 | .comments-list .comment .author .avatar.fake { float: left; text-align: center; background-color: #DDDDDD; font-size: 19px; } 9 | .comments-list .comment .author strong.name { } 10 | .comments-list .comment .text { margin-left: 35px; } 11 | .comments-list .comment .edit { margin-left: 35px; display: none; } 12 | .comments-list .comment .edit .actions { margin: 0; } 13 | .comments-list .comment .actions { margin: 10px 0 0 35px; } 14 | .comments-list .comment .actions > a { margin-right: 10px; padding: 0 10px; } 15 | .comments-list .comment .actions > a > i.fa { margin-right: 5px; } 16 | .comments-list .comment.deleted .text { font-style: italic; } 17 | -------------------------------------------------------------------------------- /widgets/_assets/comment-list.js: -------------------------------------------------------------------------------- 1 | (function ($) { 2 | $.fn.yiiCommentsList = function (method) { 3 | if (methods[method]) { 4 | return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); 5 | } else if (typeof method === 'object' || !method) { 6 | return methods.init.apply(this, arguments); 7 | } else { 8 | $.error('Method ' + method + ' does not exist on jQuery.yiiCommentsList'); 9 | return false; 10 | } 11 | }; 12 | 13 | var $body = $('body'), 14 | commentsData = []; 15 | 16 | var methods = { 17 | init: function (comments) { 18 | commentsData = comments; 19 | } 20 | }; 21 | 22 | $body.on('click', '[data-role="reply"]', function (e) { 23 | var comment_id = $(this).closest('[data-comment]').data('comment'), 24 | $textarea = $('textarea[data-role="new-comment"]'); 25 | 26 | $textarea 27 | .focus() 28 | .val(blockquote(commentsData[comment_id].text)); 29 | 30 | location.hash = null; 31 | location.hash = 'commentcreateform?tab=comments'; 32 | 33 | e.preventDefault(); 34 | }); 35 | 36 | $body.on('click', '[data-role="edit"]', function (e) { 37 | var $link = $(this), 38 | $comment = $link.closest('[data-comment]'), 39 | comment_id = $comment.data('comment'), 40 | comment = commentsData[comment_id], 41 | $edit_block = $comment.find('.edit'); 42 | 43 | $edit_block.show(); 44 | 45 | $edit_block.find('form').bind('reset', function () { 46 | $edit_block.hide(); 47 | }); 48 | 49 | e.preventDefault(); 50 | }); 51 | 52 | function blockquote(text) { 53 | return text.split('\n').map(function (value) { 54 | return '> ' + value; 55 | }).join('\n') + '\n\n'; 56 | } 57 | })(window.jQuery); 58 | -------------------------------------------------------------------------------- /widgets/views/comment-form.php: -------------------------------------------------------------------------------- 1 | context; 17 | 18 | ?> 19 | 20 | 21 |
22 |
23 | getUser()->getIsGuest()) { 30 | echo $form->field($CommentCreateForm, 'from') 31 | ->textInput(); 32 | } 33 | 34 | $options = []; 35 | 36 | if ($Widget->Comment->isNewRecord) { 37 | $options['data-role'] = 'new-comment'; 38 | } 39 | 40 | echo $form->field($CommentCreateForm, 'text') 41 | ->textarea($options); 42 | 43 | ?> 44 |
45 | 'btn btn-primary', 48 | ]); 49 | echo Html::resetButton(\Yii::t('app', 'Cancel'), [ 50 | 'class' => 'btn btn-link', 51 | ]); 52 | ?> 53 |
54 | 57 |
58 |
-------------------------------------------------------------------------------- /widgets/views/comment-list.php: -------------------------------------------------------------------------------- 1 | context; 20 | 21 | $comments = []; 22 | 23 | echo Html::tag('h3', Yii::t('app', 'Comments'), ['class' => 'comment-title']); 24 | 25 | echo yii\widgets\ListView::widget([ 26 | 'dataProvider' => $CommentsDataProvider, 27 | 'options' => ['class' => 'comments-list'], 28 | 'layout' => "{items}\n{pager}", 29 | 'itemView' => 30 | function (Comments\models\Comment $Comment, $key, $index, yii\widgets\ListView $Widget) 31 | use (&$comments, $CommentListWidget) { 32 | ob_start(); 33 | 34 | $Formatter = Yii::$app->getFormatter(); 35 | 36 | $Author = $Comment->author; 37 | 38 | $comments[$Comment->id] = $Comment->attributes; 39 | 40 | $options = [ 41 | 'data-comment' => $Comment->id, 42 | 'class' => 'row comment', 43 | ]; 44 | 45 | if ($index === 0) { 46 | Html::addCssClass($options, 'first'); 47 | } 48 | 49 | if ($index === ($Widget->dataProvider->getCount() - 1)) { 50 | Html::addCssClass($options, 'last'); 51 | } 52 | 53 | if ($Comment->isDeleted()) { 54 | Html::addCssClass($options, 'deleted'); 55 | } 56 | 57 | ?> 58 |
> 59 |
60 |
61 | from) ? $name : $Comment->from; 68 | } elseif ($Author instanceof Comments\interfaces\CommentatorInterface) { 69 | $avatar = $Author->getCommentatorAvatar(); 70 | $name = $Author->getCommentatorName(); 71 | $name = empty($name) ? Yii::t('app', 'Unknown author') : $name; 72 | $url = $Author->getCommentatorUrl(); 73 | } 74 | 75 | $name_html = Html::tag('strong', $name); 76 | 77 | if (false === $avatar) { 78 | $avatar_html = Html::tag('div', FA::icon('male'), [ 79 | 'class' => 'avatar fake', 80 | 'title' => Yii::t('app', 'Unknown author'), 81 | ]); 82 | } else { 83 | $avatar_html = Html::img($avatar, [ 84 | 'class' => 'avatar', 85 | 'alt' => Yii::t('app', 'Author avatar'), 86 | 'title' => $name, 87 | ]); 88 | } 89 | 90 | if (false !== $url) { 91 | echo Html::a($avatar_html, $url, ['target' => '_blank']); 92 | echo Html::a($name_html, $url, ['target' => '_blank']); 93 | } else { 94 | echo $avatar_html; 95 | echo $name_html; 96 | } 97 | 98 | if ((time() - $Comment->created_at) > (86400 * 2)) { 99 | echo Html::tag('span', $Formatter->asDatetime($Comment->created_at), ['class' => 'date']); 100 | } else { 101 | echo Html::tag('span', $Formatter->asRelativeTime($Comment->created_at), ['class' => 'date']); 102 | } 103 | ?> 104 |
105 |
106 | isDeleted()) { 108 | echo Yii::t('app', 'Comment was deleted.'); 109 | } else { 110 | echo Markdown::process($Comment->text, 'gfm-comment'); 111 | 112 | if ($Comment->isEdited()) { 113 | echo Html::tag('small', Yii::t('app', 'Updated at {date-relative}', [ 114 | 'date' => $Formatter->asDate($Comment->updated_at), 115 | 'date-time' => $Formatter->asDatetime($Comment->updated_at), 116 | 'date-relative' => $Formatter->asRelativeTime($Comment->updated_at), 117 | ])); 118 | } 119 | } 120 | ?> 121 |
122 | canUpdate() && !$Comment->isDeleted()) { 124 | ?> 125 |
126 | $CommentListWidget->entity, 129 | 'Comment' => $Comment, 130 | 'anchor' => $CommentListWidget->anchorAfterUpdate, 131 | ]); 132 | ?> 133 |
134 | 137 |
138 | isDeleted()) { 140 | if ($Comment->canCreate()) { 141 | echo Html::a(FA::icon('reply') . ' ' . Yii::t('app', 'Reply'), '#', [ 142 | 'class' => 'btn btn-info btn-xs', 143 | 'data-role' => 'reply', 144 | ]); 145 | } 146 | 147 | if ($Comment->canUpdate()) { 148 | echo Html::a( 149 | FA::icon('pencil') . ' ' . Yii::t('app', 'Edit'), 150 | '#', 151 | [ 152 | 'data-role' => 'edit', 153 | 'class' => 'btn btn-primary btn-xs', 154 | ] 155 | ); 156 | } 157 | 158 | if ($Comment->canDelete()) { 159 | echo Html::a( 160 | FA::icon('times') . ' ' . Yii::t('app', 'Delete'), 161 | Url::current(['delete-comment' => $Comment->id]), 162 | ['class' => 'btn btn-danger btn-xs'] 163 | ); 164 | } 165 | } 166 | ?> 167 |
168 |
169 |
170 | model('comment')); 178 | 179 | if ($CommentListWidget->showCreateForm && $CommentModel::canCreate()) { 180 | echo Html::tag('h3', Yii::t('app', 'Add comment'), ['class' => 'comment-title']); 181 | 182 | echo Comments\widgets\CommentFormWidget::widget([ 183 | 'theme' => $CommentListWidget->theme, 184 | 'entity' => $CommentListWidget->entity, 185 | 'Comment' => $CommentModel, 186 | 'anchor' => $CommentListWidget->anchorAfterUpdate, 187 | ]); 188 | } 189 | 190 | $CommentListWidget 191 | ->getView() 192 | ->registerJs('jQuery("#' . $CommentListWidget->options['id'] . '").yiiCommentsList(' . Json::encode($comments) . ');'); 193 | --------------------------------------------------------------------------------