├── .bowerrc ├── .env.dist ├── .gitignore ├── .php_cs ├── LICENSE.md ├── README.md ├── assets ├── AdminAsset.php └── AppAsset.php ├── bin └── .gitignore ├── codeception.yml ├── commands ├── AppController.php └── BaseController.php ├── composer.json ├── config ├── .gitignore ├── common.php ├── console.php ├── main.php ├── params.php └── schedule.php ├── controllers └── SiteController.php ├── data ├── docker │ ├── db │ │ └── Dockerfile │ ├── redis │ │ └── Dockerfile │ └── web │ │ ├── .bashrc │ │ ├── apache.conf │ │ ├── crontab │ │ ├── dev │ │ └── Dockerfile │ │ ├── php.ini │ │ ├── prod │ │ └── Dockerfile │ │ └── supervisord.conf └── volumes │ ├── db │ └── .gitignore │ └── redis │ └── .gitignore ├── docker-compose.override.yml.dist ├── docker-compose.yml ├── environments ├── dev │ ├── config │ │ ├── common-local.php │ │ ├── console-local.php │ │ ├── main-local.php │ │ ├── params-local.php │ │ ├── test.php │ │ └── test_db.php │ ├── web │ │ ├── index-test.php │ │ └── index.php │ ├── yii │ ├── yii_test │ └── yii_test.bat ├── index.php └── prod │ ├── config │ ├── common-local.php │ ├── console-local.php │ ├── main-local.php │ └── params-local.php │ ├── web │ └── index.php │ └── yii ├── init ├── init.bat ├── mail ├── layouts │ └── html.php └── passwordResetToken.php ├── messages └── config.php ├── migrations └── m161109_121736_create_session_table.php ├── models ├── UserModel.php └── forms │ ├── ContactForm.php │ └── ResetPasswordForm.php ├── modules └── admin │ ├── Module.php │ ├── controllers │ ├── SettingsController.php │ └── UserController.php │ ├── models │ └── search │ │ └── UserSearch.php │ └── views │ ├── layouts │ └── column2.php │ └── user │ ├── _form.php │ ├── create.php │ ├── index.php │ └── update.php ├── rbac └── migrations │ └── m160722_085418_init.php ├── requirements.php ├── runtime └── .gitignore ├── tests ├── _bootstrap.php ├── _data │ ├── cms.php │ ├── user.php │ └── user_assignment.php ├── _output │ └── .gitignore ├── _support │ ├── AcceptanceTester.php │ ├── FunctionalTester.php │ └── UnitTester.php ├── acceptance.suite.example.yml ├── acceptance │ └── _bootstrap.php ├── fixtures │ ├── CmsFixture.php │ ├── UserAssignmentFixture.php │ └── UserFixture.php ├── functional.suite.yml ├── functional │ ├── AboutCest.php │ ├── AdminLoginCest.php │ ├── ChangePasswordViaAccountPageCest.php │ ├── ContactFormCest.php │ ├── CreateUserCest.php │ ├── HomeCest.php │ ├── LoginCest.php │ ├── SignupCest.php │ └── _bootstrap.php ├── unit.suite.yml └── unit │ ├── _bootstrap.php │ └── models │ ├── ContactFormTest.php │ ├── LoginFormTest.php │ ├── ResetPasswordFormTest.php │ └── SignupFormTest.php ├── traits ├── AjaxValidationTrait.php └── FindModelTrait.php ├── views ├── layouts │ └── main.php └── site │ ├── account.php │ ├── contact.php │ ├── error.php │ └── index.php ├── web ├── .gitignore ├── .htaccess ├── assets │ └── .gitignore ├── css │ ├── admin.css │ └── site.css ├── favicon.ico └── robots.txt └── yii.bat /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory" : "vendor/bower" 3 | } -------------------------------------------------------------------------------- /.env.dist: -------------------------------------------------------------------------------- 1 | HOST_UID=1000 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # yii console commands 2 | /yii 3 | /yii_test 4 | /yii_test.bat 5 | 6 | # phpstorm project files 7 | .idea 8 | 9 | # netbeans project files 10 | nbproject 11 | 12 | # zend studio for eclipse project files 13 | .buildpath 14 | .project 15 | .settings 16 | 17 | # windows thumbnail cache 18 | Thumbs.db 19 | 20 | # composer vendor dir 21 | /vendor 22 | 23 | # runtime dirs 24 | runtime/debug 25 | runtime/logs 26 | 27 | # composer itself is not needed 28 | composer.phar 29 | 30 | # Mac DS_Store Files 31 | .DS_Store 32 | 33 | # phpunit itself is not needed 34 | phpunit.phar 35 | # local phpunit config 36 | /phpunit.xml 37 | 38 | tests/_output/* 39 | tests/_support/_generated 40 | 41 | /.env 42 | /docker-compose.override.yml 43 | /.php_cs.cache 44 | -------------------------------------------------------------------------------- /.php_cs: -------------------------------------------------------------------------------- 1 | exclude([ 5 | 'vendor', 6 | 'runtime', 7 | 'tests/_output', 8 | 'tests/_support', 9 | ]) 10 | ->in([__DIR__]); 11 | 12 | $config = PhpCsFixer\Config::create() 13 | ->setRules([ 14 | '@Symfony' => true, 15 | 'phpdoc_order' => true, 16 | 'phpdoc_align' => false, 17 | 'phpdoc_summary' => false, 18 | 'phpdoc_inline_tag' => false, 19 | 'pre_increment' => false, 20 | 'heredoc_to_nowdoc' => false, 21 | 'cast_spaces' => false, 22 | 'include' => false, 23 | 'phpdoc_no_package' => false, 24 | 'concat_space' => ['spacing' => 'one'], 25 | 'ordered_imports' => true, 26 | 'ordered_class_elements' => true, 27 | 'phpdoc_add_missing_param_annotation' => true, 28 | 'array_syntax' => ['syntax' => 'short'], 29 | 'visibility_required' => true, 30 | 'yoda_style' => false, 31 | ]) 32 | ->setFinder($finder); 33 | 34 | return $config; 35 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 yii2mod 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

Yii 2 Basic Project Template

6 |
7 |

8 | 9 | Yii 2 Basic Application Template is a skeleton Yii 2 application best for 10 | rapidly creating small projects. 11 | 12 | It includes all commonly used configurations that would allow you to focus on adding new 13 | features to your application. 14 | 15 | [![Latest Stable Version](https://poser.pugx.org/yii2mod/base/v/stable)](https://packagist.org/packages/yii2mod/base) 16 | [![Total Downloads](https://poser.pugx.org/yii2mod/base/downloads)](https://packagist.org/packages/yii2mod/base) 17 | [![License](https://poser.pugx.org/yii2mod/base/license)](https://packagist.org/packages/yii2mod/base) 18 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/yii2mod/base/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/yii2mod/base/?branch=master) 19 | [![Build Status](https://travis-ci.org/yii2mod/base.svg?branch=master)](https://travis-ci.org/yii2mod/base) 20 | 21 | ## Support us 22 | 23 | Does your business depend on our contributions? Reach out and support us on [Patreon](https://www.patreon.com/yii2mod). 24 | All pledges will be dedicated to allocating workforce on maintenance and new awesome stuff. 25 | 26 | DIRECTORY STRUCTURE 27 | ------------------- 28 | 29 | assets/ contains assets definition 30 | commands/ contains console commands (controllers) 31 | config/ contains application configurations 32 | controllers/ contains Web controller classes 33 | mail/ contains view files for e-mails 34 | models/ contains model classes 35 | runtime/ contains files generated during runtime 36 | tests/ contains various tests for the basic application 37 | vendor/ contains dependent 3rd-party packages 38 | views/ contains view files for the Web application 39 | web/ contains the entry script and Web resources 40 | 41 | ## FEATURES 42 | - [Sign in, Sign up, Forgot Password, etc.](https://github.com/yii2mod/yii2-user) 43 | - User management 44 | - [RBAC with predefined `guest`, `user` and `admin` roles](https://github.com/yii2mod/yii2-rbac) 45 | - Content management components: [cms](https://github.com/yii2mod/yii2-cms), [comments](https://github.com/yii2mod/yii2-comments) 46 | - [Yii2 component for logging cron jobs](https://github.com/yii2mod/yii2-cron-log) 47 | - Account page 48 | - [Key-value storage component](https://github.com/yii2mod/yii2-settings) 49 | - [Scheduling extension for running cron jobs](https://github.com/yii2mod/yii2-scheduling) 50 | - [Support multipath migrations](https://github.com/yii2mod/base/blob/master/config/console.php#L10) 51 | - [Support Docker](https://github.com/yii2mod/base#installing-using-docker) 52 | - [Included PHP Coding Standards Fixer](https://github.com/FriendsOfPHP/PHP-CS-Fixer) 53 | - Support environments (dev, prod) 54 | 55 | 56 | REQUIREMENTS 57 | ------------ 58 | 59 | The minimum requirement by this application template that your Web server supports PHP 5.6 60 | 61 | 62 | INSTALLATION 63 | ------------ 64 | 65 | ## Installing using Composer 66 | 67 | If you do not have [Composer](http://getcomposer.org/), follow the instructions in the 68 | [Installing Yii](https://github.com/yiisoft/yii2/blob/master/docs/guide/start-installation.md#installing-via-composer) section of the definitive guide to install it. 69 | 70 | With Composer installed, you can then install the application using the following commands: 71 | 72 | composer create-project --prefer-dist --stability=dev yii2mod/base application 73 | 74 | The first command installs the [composer asset plugin](https://github.com/francoispluchino/composer-asset-plugin/) 75 | which allows managing bower and npm package dependencies through Composer. You only need to run this command 76 | once for all. The second command installs the yii2mod/base application in a directory named `application`. 77 | You can choose a different directory name if you want. 78 | 79 | CONFIGURATION 80 | ------------- 81 | After you install the application, you have to conduct the following steps to initialize 82 | the installed application. You only need to do these once for all. 83 | 84 | 1) Init the application by the following command: 85 | ```bash 86 | ./init --env=Development 87 | ``` 88 | 89 | 2) Create a new database and adjust the `components['db']` configuration in `config/common-local.php` accordingly. 90 | 91 | 3) Apply migrations: 92 | 93 | - `php yii migrate` - create default tables for application 94 | - `php yii rbac/migrate` - create roles, permissions and rules 95 | - `php yii fixture "*"` - load fixtures (cms pages and users) 96 | 97 | 4) Set document root of your web server to `/path/to/application/web/` folder. 98 | 99 | 100 | Installing using Docker 101 | ----------------------- 102 | 103 | > You need to have [docker](http://www.docker.com) (1.10.0+) and 104 | [docker-compose](https://docs.docker.com/compose/install/) (1.6.0+) installed. 105 | 106 | You can install the application using the following commands: 107 | 108 | ```sh 109 | composer create-project --no-install --stability=dev yii2mod/base yii2mod-base 110 | cd yii2mod-base 111 | ./init --env=Development 112 | cp .env{.dist,} && cp docker-compose.override.yml{.dist,} 113 | docker-compose up -d --build 114 | ``` 115 | > In `.env` file your need to set your UID. 116 | > You can get your UID by the following command in the terminal: `id -u ` 117 | 118 | It may take some minutes to download the required docker images. When 119 | done, you need to install vendors as follows: 120 | 121 | ```sh 122 | docker-compose exec web bash 123 | composer install 124 | chown -R www-data:www-data runtime web/assets vendor 125 | ``` 126 | 127 | After this steps, you need to update `db` section in the `common-local.php` file as follows: 128 | ```php 129 | [ 133 | 'db' => [ 134 | 'dsn' => 'mysql:host=db;dbname=yii2mod_base', 135 | 'username' => 'docker', 136 | 'password' => 'secret', 137 | ], 138 | 'mailer' => [ 139 | 'useFileTransport' => true, 140 | ], 141 | 'redis' => [ 142 | 'hostname' => 'redis', 143 | ], 144 | ], 145 | ]; 146 | 147 | return $config; 148 | ``` 149 | 150 | When done, you need to execute the following commands in the web container: 151 | - `php yii migrate` 152 | - `php yii rbac/migrate` 153 | - `php yii fixture "*"` 154 | 155 | After this steps, you can access your app from [http://localhost](http://localhost). 156 | 157 | TESTING 158 | ------- 159 | 160 | Tests are located in `tests` directory. They are developed with [Codeception PHP Testing Framework](http://codeception.com/). 161 | By default there are 3 test suites: 162 | 163 | - `unit` 164 | - `functional` 165 | - `acceptance` 166 | 167 | ### Running tests 168 | 169 | 1. Create a new database and configure database connection in `config/test_db.php` accordingly. 170 | 2. Execute migrations by the following command: 171 | - `./yii_test migrate --interactive=0 && ./yii_test rbac/migrate --interactive=0` 172 | 3. Run unit and functional tests: 173 | ``` 174 | bin/codecept run 175 | ``` 176 | 177 | The command above will execute unit and functional tests. Unit tests are testing the system components, while functional 178 | tests are for testing user interaction. Acceptance tests are disabled by default as they require additional setup since 179 | they perform testing in real browser. 180 | 181 | 182 | ### Running acceptance tests 183 | 184 | To execute acceptance tests do the following: 185 | 186 | 1. Rename `tests/acceptance.suite.yml.example` to `tests/acceptance.suite.yml` to enable suite configuration 187 | 188 | 2. Replace `codeception/base` package in `composer.json` with `codeception/codeception` to install full featured 189 | version of Codeception 190 | 191 | 3. Update dependencies with Composer 192 | 193 | ``` 194 | composer update 195 | ``` 196 | 197 | 4. Download [Selenium Server](http://www.seleniumhq.org/download/) and launch it: 198 | 199 | ``` 200 | java -jar ~/selenium-server-standalone-x.xx.x.jar 201 | ``` 202 | 203 | 5. Start web server: 204 | 205 | ``` 206 | ./yii_test serve 207 | ``` 208 | 209 | 6. Now you can run all available tests 210 | 211 | ``` 212 | # run all available tests 213 | bin/codecept run 214 | 215 | # run acceptance tests 216 | bin/codecept run acceptance 217 | 218 | # run only unit and functional tests 219 | bin/codecept run unit,functional 220 | ``` 221 | 222 | ### Code coverage support 223 | 224 | By default, code coverage is disabled in `codeception.yml` configuration file, you should uncomment needed rows to be able 225 | to collect code coverage. You can run your tests and collect coverage with the following command: 226 | 227 | ``` 228 | #collect coverage for all tests 229 | bin/codecept run -- --coverage-html --coverage-xml 230 | 231 | #collect coverage only for unit tests 232 | bin/codecept run unit -- --coverage-html --coverage-xml 233 | 234 | #collect coverage for unit and functional tests 235 | bin/codecept run functional,unit -- --coverage-html --coverage-xml 236 | ``` 237 | 238 | You can see code coverage output under the `tests/_output` directory. 239 | -------------------------------------------------------------------------------- /assets/AdminAsset.php: -------------------------------------------------------------------------------- 1 | 18 | * 19 | * @since 1.0 20 | */ 21 | class AppController extends BaseController 22 | { 23 | /** 24 | * Generate sitemap 25 | */ 26 | public function actionGenerateSitemap(): int 27 | { 28 | $siteMapFile = new File(); 29 | 30 | $siteMapFile->writeUrl(['site/index'], ['priority' => '0.9']); 31 | $siteMapFile->writeUrl(['site/contact']); 32 | $pages = CmsModel::find()->enabled()->all(); 33 | foreach ($pages as $page) { 34 | $siteMapFile->writeUrl([$page->url]); 35 | } 36 | 37 | $siteMapFile->close(); 38 | 39 | return self::EXIT_CODE_NORMAL; 40 | } 41 | 42 | /** 43 | * Delete all data from specific table 44 | * 45 | * @param $tableName 46 | * 47 | * @throws \yii\db\Exception 48 | * 49 | * @return int 50 | */ 51 | public function actionClearTable($tableName): int 52 | { 53 | if ($this->confirm(Yii::t('app', 'Are you sure you want to clear this table?'))) { 54 | Yii::$app->db->createCommand()->delete($tableName)->execute(); 55 | } 56 | 57 | return self::EXIT_CODE_NORMAL; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /commands/BaseController.php: -------------------------------------------------------------------------------- 1 | 14 | * 15 | * @since 1.0 16 | */ 17 | class BaseController extends Controller 18 | { 19 | /** 20 | * @var array list of disabled actions 21 | */ 22 | public $disabledActions = []; 23 | 24 | /** 25 | * @var string the running command name 26 | */ 27 | protected $command; 28 | 29 | /** 30 | * @inheritdoc 31 | */ 32 | public function behaviors(): array 33 | { 34 | return [ 35 | 'cronLogger' => [ 36 | 'class' => 'yii2mod\cron\behaviors\CronLoggerBehavior', 37 | 'actions' => ['*'], 38 | ], 39 | ]; 40 | } 41 | 42 | /** 43 | * Before action event 44 | * 45 | * @param \yii\base\Action $action 46 | * 47 | * @return bool 48 | */ 49 | public function beforeAction($action): bool 50 | { 51 | $this->command = $action->controller->id . '/' . $action->id; 52 | 53 | if ($this->isDisabledAction($action->id)) { 54 | $this->stdout("Command '{$this->command}' is disabled.\n", Console::FG_RED); 55 | 56 | return false; 57 | } 58 | 59 | if (!parent::beforeAction($action)) { 60 | return false; 61 | } 62 | 63 | $this->stdout("Running the command `{$this->command}` at the " . Yii::$app->formatter->asDatetime(time()) . "\n", Console::FG_GREEN); 64 | 65 | return true; 66 | } 67 | 68 | /** 69 | * After action event 70 | * 71 | * @param \yii\base\Action $action 72 | * @param mixed $result 73 | * 74 | * @return mixed 75 | */ 76 | public function afterAction($action, $result) 77 | { 78 | $result = parent::afterAction($action, $result); 79 | 80 | $this->stdout("Command `{$this->command}` finished at the " . Yii::$app->formatter->asDatetime(time()) . "\n", Console::FG_GREEN); 81 | 82 | return $result; 83 | } 84 | 85 | /** 86 | * Check whether the current action is disabled 87 | * 88 | * @param $id string action id 89 | * 90 | * @throws InvalidConfigException 91 | * 92 | * @return bool 93 | */ 94 | protected function isDisabledAction($id): bool 95 | { 96 | if (!is_array($this->disabledActions)) { 97 | throw new InvalidConfigException('The "disabledActions" property must be an array.'); 98 | } 99 | 100 | if (in_array('*', $this->disabledActions) || in_array($id, $this->disabledActions)) { 101 | return true; 102 | } 103 | 104 | return false; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yii2mod/base", 3 | "description": "Base application template for Yii2", 4 | "keywords": [ 5 | "yii2mod", 6 | "base", 7 | "project template", 8 | "yii2", 9 | "framework", 10 | "basic" 11 | ], 12 | "homepage": "https://github.com/yii2mod/base/", 13 | "type": "project", 14 | "license": "MIT", 15 | "authors": [ 16 | { 17 | "name": "Dmitry Semenov", 18 | "email": "disemx@gmail.com", 19 | "homepage": "http://disem.org/" 20 | }, 21 | { 22 | "name": "Igor Chepurnoi", 23 | "email": "chepurnoi.igor@gmail.com" 24 | } 25 | ], 26 | "minimum-stability": "dev", 27 | "prefer-stable": true, 28 | "require": { 29 | "php": ">=7.0.0", 30 | "yiisoft/yii2": "~2.0.14", 31 | "yiisoft/yii2-bootstrap": "~2.0.0", 32 | "yiisoft/yii2-swiftmailer": "~2.0.0 || ~2.1.0", 33 | "yiisoft/yii2-redis": "~2.0.0", 34 | "yii2mod/yii2-cms": "~1.9", 35 | "yii2mod/yii2-user": "~2.1", 36 | "yii2mod/yii2-rbac": "~2.2", 37 | "yii2mod/yii2-cron-log": "~1.5", 38 | "yii2mod/yii2-settings": "~2.4", 39 | "yii2mod/yii2-scheduling": "~1.1", 40 | "yii2mod/yii2-bootstrap-notify": "~1.2", 41 | "yii2mod/yii2-editable": "~1.5", 42 | "yii2tech/sitemap": "^1.0", 43 | "yii2tech/admin": "~1.0" 44 | }, 45 | "require-dev": { 46 | "yiisoft/yii2-debug": "~2.0.0", 47 | "yiisoft/yii2-gii": "~2.0.0", 48 | "yiisoft/yii2-faker": "~2.0.0", 49 | "friendsofphp/php-cs-fixer": "~2.0", 50 | "codeception/base": "~2.3.0", 51 | "codeception/verify": "~0.4.0", 52 | "codeception/specify": "~0.4.6" 53 | }, 54 | "config": { 55 | "process-timeout": 1800, 56 | "bin-dir": "bin", 57 | "sort-packages": true, 58 | "fxp-asset": { 59 | "installer-paths": { 60 | "npm-asset-library": "vendor/npm", 61 | "bower-asset-library": "vendor/bower" 62 | } 63 | } 64 | }, 65 | "repositories": [ 66 | { 67 | "type": "composer", 68 | "url": "https://asset-packagist.org" 69 | } 70 | ] 71 | } 72 | -------------------------------------------------------------------------------- /config/.gitignore: -------------------------------------------------------------------------------- 1 | common-local.php 2 | main-local.php 3 | console-local.php 4 | params-local.php 5 | test_db.php 6 | test.php 7 | -------------------------------------------------------------------------------- /config/common.php: -------------------------------------------------------------------------------- 1 | 'Yii2 Basic Template', 12 | 'language' => 'en-US', 13 | 'basePath' => dirname(__DIR__), 14 | 'bootstrap' => ['log'], 15 | 'aliases' => [ 16 | '@bower' => '@vendor/bower-asset', 17 | '@npm' => '@vendor/npm-asset', 18 | ], 19 | 'container' => [ 20 | 'definitions' => [ 21 | 'yii\test\InitDbFixture' => [ 22 | 'schemas' => [], 23 | ], 24 | ], 25 | ], 26 | 'components' => [ 27 | 'authManager' => [ 28 | 'class' => 'yii\rbac\DbManager', 29 | 'defaultRoles' => ['guest', 'user'], 30 | ], 31 | 'formatter' => [ 32 | 'class' => 'yii\i18n\Formatter', 33 | ], 34 | 'cache' => [ 35 | 'class' => 'yii\caching\ArrayCache', 36 | ], 37 | 'log' => [ 38 | 'traceLevel' => YII_DEBUG ? 3 : 0, 39 | 'targets' => [ 40 | [ 41 | 'class' => 'yii\log\FileTarget', 42 | 'levels' => ['error', 'warning'], 43 | ], 44 | ], 45 | ], 46 | 'db' => [ 47 | 'class' => 'yii\db\Connection', 48 | 'charset' => 'utf8', 49 | ], 50 | 'redis' => [ 51 | 'class' => 'yii\redis\Connection', 52 | ], 53 | 'mailer' => [ 54 | 'class' => 'yii\swiftmailer\Mailer', 55 | ], 56 | 'i18n' => [ 57 | 'translations' => [ 58 | '*' => [ 59 | 'class' => 'yii\i18n\PhpMessageSource', 60 | ], 61 | ], 62 | ], 63 | ], 64 | 'params' => $params, 65 | ]; 66 | 67 | return $config; 68 | -------------------------------------------------------------------------------- /config/console.php: -------------------------------------------------------------------------------- 1 | 'console', 5 | 'controllerNamespace' => 'app\commands', 6 | 'controllerMap' => [ 7 | 'migrate' => [ 8 | 'class' => 'yii\console\controllers\MigrateController', 9 | 'migrationPath' => [ 10 | '@app/migrations', 11 | '@vendor/yii2mod/yii2-cms/migrations', 12 | '@vendor/yii2mod/yii2-cron-log/migrations', 13 | '@vendor/yii2mod/yii2-user/migrations', 14 | '@vendor/yii2mod/yii2-comments/migrations', 15 | '@vendor/yii2mod/yii2-settings/migrations', 16 | '@yii/rbac/migrations', 17 | ], 18 | ], 19 | 'fixture' => [ 20 | 'class' => 'yii\console\controllers\FixtureController', 21 | 'namespace' => 'app\tests\fixtures', 22 | ], 23 | ], 24 | 'components' => [ 25 | 'errorHandler' => [ 26 | 'class' => 'yii2mod\cron\components\ErrorHandler', 27 | ], 28 | 'mutex' => [ 29 | 'class' => 'yii\mutex\FileMutex', 30 | ], 31 | 'urlManager' => [ 32 | 'class' => 'yii\web\UrlManager', 33 | 'hostInfo' => 'http://example.com', 34 | 'scriptUrl' => '/index.php', 35 | ], 36 | ], 37 | 'modules' => [ 38 | 'rbac' => [ 39 | 'class' => 'yii2mod\rbac\ConsoleModule', 40 | ], 41 | 'user' => [ 42 | 'class' => 'yii2mod\user\ConsoleModule', 43 | ], 44 | ], 45 | ]; 46 | 47 | return $config; 48 | -------------------------------------------------------------------------------- /config/main.php: -------------------------------------------------------------------------------- 1 | 'main', 5 | 'defaultRoute' => 'site/index', 6 | 'modules' => [ 7 | 'admin' => [ 8 | 'class' => 'app\modules\admin\Module', 9 | 'modules' => [ 10 | 'rbac' => [ 11 | 'class' => 'yii2mod\rbac\Module', 12 | 'controllerMap' => [ 13 | 'route' => [ 14 | 'class' => 'yii2mod\rbac\controllers\RouteController', 15 | 'modelClass' => [ 16 | 'class' => 'yii2mod\rbac\models\RouteModel', 17 | 'excludeModules' => ['debug', 'gii'], 18 | ], 19 | ], 20 | ], 21 | ], 22 | 'settings-storage' => [ 23 | 'class' => 'yii2mod\settings\Module', 24 | ], 25 | ], 26 | ], 27 | 'comment' => [ 28 | 'class' => 'yii2mod\comments\Module', 29 | 'controllerMap' => [ 30 | 'manage' => [ 31 | 'class' => 'yii2mod\comments\controllers\ManageController', 32 | 'layout' => '@app/modules/admin/views/layouts/column2', 33 | ], 34 | ], 35 | ], 36 | 'cms' => [ 37 | 'class' => 'yii2mod\cms\Module', 38 | 'controllerMap' => [ 39 | 'manage' => [ 40 | 'class' => 'yii2mod\cms\controllers\ManageController', 41 | 'layout' => '@app/modules/admin/views/layouts/column2', 42 | ], 43 | ], 44 | ], 45 | ], 46 | 'components' => [ 47 | 'settings' => [ 48 | 'class' => 'yii2mod\settings\components\Settings', 49 | ], 50 | 'session' => [ 51 | 'class' => 'yii\web\DbSession', 52 | ], 53 | 'user' => [ 54 | 'identityClass' => 'app\models\UserModel', 55 | 'enableAutoLogin' => true, 56 | 'on afterLogin' => function ($event) { 57 | $event->identity->updateLastLogin(); 58 | }, 59 | ], 60 | 'errorHandler' => [ 61 | 'errorAction' => 'site/error', 62 | ], 63 | 'urlManager' => [ 64 | 'enablePrettyUrl' => true, 65 | 'showScriptName' => false, 66 | 'rules' => [ 67 | '/' => '/', 68 | '//' => '//', 69 | ['class' => 'yii2mod\cms\components\PageUrlRule'], 70 | ], 71 | ], 72 | ], 73 | ]; 74 | 75 | return $config; 76 | -------------------------------------------------------------------------------- /config/params.php: -------------------------------------------------------------------------------- 1 | 'admin@example.com', 5 | 'user.passwordResetTokenExpire' => 3600, 6 | ]; 7 | -------------------------------------------------------------------------------- /config/schedule.php: -------------------------------------------------------------------------------- 1 | command('app/generate-sitemap')->description('Generate Sitemap')->weekly(); 21 | -------------------------------------------------------------------------------- /controllers/SiteController.php: -------------------------------------------------------------------------------- 1 | [ 26 | 'class' => AccessControl::class, 27 | 'only' => [ 28 | 'login', 29 | 'logout', 30 | 'signup', 31 | 'request-password-reset', 32 | 'password-reset', 33 | 'account', 34 | ], 35 | 'rules' => [ 36 | [ 37 | 'allow' => true, 38 | 'actions' => ['login', 'signup', 'request-password-reset', 'password-reset'], 39 | 'roles' => ['?'], 40 | ], 41 | [ 42 | 'allow' => true, 43 | 'actions' => ['logout', 'account'], 44 | 'roles' => ['@'], 45 | ], 46 | ], 47 | ], 48 | 'verbs' => [ 49 | 'class' => VerbFilter::class, 50 | 'actions' => [ 51 | 'index' => ['get'], 52 | 'contact' => ['get', 'post'], 53 | 'account' => ['get', 'post'], 54 | 'login' => ['get', 'post'], 55 | 'logout' => ['post'], 56 | 'signup' => ['get', 'post'], 57 | 'request-password-reset' => ['get', 'post'], 58 | 'password-reset' => ['get', 'post'], 59 | 'page' => ['get', 'post'], 60 | ], 61 | ], 62 | ]; 63 | } 64 | 65 | /** 66 | * @inheritdoc 67 | */ 68 | public function actions(): array 69 | { 70 | return [ 71 | 'error' => [ 72 | 'class' => 'yii\web\ErrorAction', 73 | ], 74 | 'captcha' => [ 75 | 'class' => 'yii\captcha\CaptchaAction', 76 | 'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null, 77 | ], 78 | 'login' => [ 79 | 'class' => 'yii2mod\user\actions\LoginAction', 80 | ], 81 | 'logout' => [ 82 | 'class' => 'yii2mod\user\actions\LogoutAction', 83 | ], 84 | 'signup' => [ 85 | 'class' => 'yii2mod\user\actions\SignupAction', 86 | ], 87 | 'request-password-reset' => [ 88 | 'class' => 'yii2mod\user\actions\RequestPasswordResetAction', 89 | ], 90 | 'password-reset' => [ 91 | 'class' => 'yii2mod\user\actions\PasswordResetAction', 92 | ], 93 | 'page' => [ 94 | 'class' => 'yii2mod\cms\actions\PageAction', 95 | ], 96 | ]; 97 | } 98 | 99 | /** 100 | * Displays homepage. 101 | * 102 | * @return string 103 | */ 104 | public function actionIndex() 105 | { 106 | return $this->render('index'); 107 | } 108 | 109 | /** 110 | * Displays contact page. 111 | * 112 | * @return string|\yii\web\Response 113 | */ 114 | public function actionContact() 115 | { 116 | $model = new ContactForm(); 117 | 118 | if ($model->load(Yii::$app->request->post()) && $model->validate()) { 119 | if ($model->contact(Yii::$app->params['adminEmail'])) { 120 | Yii::$app->session->setFlash('success', Yii::t('app', 'Thank you for contacting us. We will respond to you as soon as possible.')); 121 | } else { 122 | Yii::$app->session->setFlash('error', Yii::t('app', 'There was an error sending email.')); 123 | } 124 | 125 | return $this->refresh(); 126 | } 127 | 128 | return $this->render('contact', [ 129 | 'model' => $model, 130 | ]); 131 | } 132 | 133 | /** 134 | * Displays account page. 135 | * 136 | * @return string|\yii\web\Response 137 | */ 138 | public function actionAccount() 139 | { 140 | $resetPasswordForm = new ResetPasswordForm(Yii::$app->user->identity); 141 | 142 | if ($resetPasswordForm->load(Yii::$app->request->post()) && $resetPasswordForm->resetPassword()) { 143 | Yii::$app->session->setFlash('success', Yii::t('app', 'Password has been updated.')); 144 | 145 | return $this->refresh(); 146 | } 147 | 148 | return $this->render('account', [ 149 | 'resetPasswordForm' => $resetPasswordForm, 150 | ]); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /data/docker/db/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mysql:5.7 2 | 3 | MAINTAINER Igor Chepurnoi 4 | 5 | ARG HOST_UID=1000 6 | 7 | VOLUME ["/var/lib/mysql"] 8 | 9 | RUN usermod -u ${HOST_UID} mysql 10 | 11 | EXPOSE 3306 12 | -------------------------------------------------------------------------------- /data/docker/redis/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM redis:4.0 2 | 3 | MAINTAINER Igor Chepurnoi 4 | 5 | ARG HOST_UID=1000 6 | 7 | RUN usermod -u ${HOST_UID} redis 8 | RUN groupmod -g ${HOST_UID} redis 9 | -------------------------------------------------------------------------------- /data/docker/web/.bashrc: -------------------------------------------------------------------------------- 1 | umask 002 2 | 3 | export TERM=xterm 4 | 5 | if [ -x /usr/bin/dircolors ]; then 6 | test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)" 7 | alias ls='ls --color=auto' 8 | alias grep='grep --color=auto' 9 | alias fgrep='fgrep --color=auto' 10 | alias egrep='egrep --color=auto' 11 | fi 12 | 13 | alias ll='ls -ahl' 14 | alias ownr='chown -R www-data:www-data .' 15 | alias usr='su www-data' 16 | alias cs-fix='bin/php-cs-fixer fix' 17 | alias yii-test='bin/codecept run' 18 | 19 | if [ -f ~/.bash_aliases ]; then 20 | . ~/.bash_aliases 21 | fi 22 | -------------------------------------------------------------------------------- /data/docker/web/apache.conf: -------------------------------------------------------------------------------- 1 | 2 | DocumentRoot "/var/www/html/web" 3 | 4 | ErrorLog ${APACHE_LOG_DIR}/error.log 5 | CustomLog ${APACHE_LOG_DIR}/access.log mainlog 6 | 7 | 8 | # use mod_rewrite for pretty URL support 9 | RewriteEngine on 10 | # If a directory or a file exists, use the request directly 11 | RewriteCond %{REQUEST_FILENAME} !-f 12 | RewriteCond %{REQUEST_FILENAME} !-d 13 | # Otherwise forward the request to index.php 14 | RewriteRule . index.php 15 | 16 | # ...other settings... 17 | 18 | 19 | -------------------------------------------------------------------------------- /data/docker/web/crontab: -------------------------------------------------------------------------------- 1 | * * * * * root /usr/local/bin/php /var/www/html/yii schedule/run >> /var/log/cron.log 2>&1 2 | -------------------------------------------------------------------------------- /data/docker/web/dev/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:7.1-apache 2 | 3 | MAINTAINER Igor Chepurnoi 4 | 5 | ARG HOST_UID=1000 6 | 7 | VOLUME ["/var/www/html"] 8 | 9 | ENV DEBIAN_FRONTEND noninteractive 10 | 11 | RUN apt-get update && apt-get install -y apt-utils && apt-get install -y \ 12 | zlib1g-dev \ 13 | libicu-dev \ 14 | libpq-dev \ 15 | git \ 16 | nano \ 17 | zip \ 18 | libfreetype6-dev \ 19 | libjpeg62-turbo-dev \ 20 | libmcrypt-dev \ 21 | libpng-dev \ 22 | supervisor \ 23 | cron \ 24 | && docker-php-ext-install -j$(nproc) iconv mcrypt \ 25 | && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \ 26 | && docker-php-ext-install -j$(nproc) gd \ 27 | && docker-php-ext-install intl zip pdo_mysql 28 | 29 | RUN yes | pecl install xdebug \ 30 | && echo "zend_extension=$(find /usr/local/lib/php/extensions/ -name xdebug.so)" \ 31 | > /usr/local/etc/php/conf.d/xdebug.ini 32 | 33 | COPY ./.bashrc /root/.bashrc 34 | COPY ./apache.conf /etc/apache2/sites-available/000-default.conf 35 | COPY ./php.ini /usr/local/etc/php/ 36 | 37 | RUN echo "LogFormat \"%a %l %u %t \\\"%r\\\" %>s %O \\\"%{User-Agent}i\\\"\" mainlog" >> /etc/apache2/apache2.conf 38 | RUN a2enmod rewrite remoteip 39 | 40 | RUN set -x && curl -sS https://getcomposer.org/installer | php \ 41 | && mv composer.phar /usr/local/bin/composer \ 42 | && composer global require hirak/prestissimo --prefer-dist --no-interaction 43 | 44 | RUN usermod -u ${HOST_UID} www-data && groupmod -g ${HOST_UID} www-data && chsh -s /bin/bash www-data 45 | 46 | RUN cp /root/.bashrc /var/www 47 | 48 | # setup cron 49 | ADD ./crontab /etc/cron.d/cron-jobs 50 | RUN chmod 0644 /etc/cron.d/cron-jobs && touch /var/log/cron.log 51 | 52 | # setup supervisor 53 | RUN mkdir -p /var/log/supervisor 54 | COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf 55 | 56 | EXPOSE 80 57 | 58 | CMD ["/usr/bin/supervisord"] 59 | -------------------------------------------------------------------------------- /data/docker/web/prod/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:7.1-apache 2 | 3 | MAINTAINER Igor Chepurnoi 4 | 5 | ARG HOST_UID=1000 6 | 7 | VOLUME ["/var/www/html"] 8 | 9 | ENV DEBIAN_FRONTEND noninteractive 10 | 11 | RUN apt-get update && apt-get install -y apt-utils && apt-get install -y \ 12 | zlib1g-dev \ 13 | libicu-dev \ 14 | libpq-dev \ 15 | git \ 16 | nano \ 17 | zip \ 18 | libfreetype6-dev \ 19 | libjpeg62-turbo-dev \ 20 | libmcrypt-dev \ 21 | libpng-dev \ 22 | supervisor \ 23 | cron \ 24 | && docker-php-ext-install -j$(nproc) iconv mcrypt \ 25 | && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \ 26 | && docker-php-ext-install -j$(nproc) gd \ 27 | && docker-php-ext-install intl zip pdo_mysql 28 | 29 | COPY ./.bashrc /root/.bashrc 30 | COPY ./apache.conf /etc/apache2/sites-available/000-default.conf 31 | COPY ./php.ini /usr/local/etc/php/ 32 | 33 | RUN echo "LogFormat \"%a %l %u %t \\\"%r\\\" %>s %O \\\"%{User-Agent}i\\\"\" mainlog" >> /etc/apache2/apache2.conf 34 | RUN a2enmod rewrite remoteip 35 | 36 | RUN set -x && curl -sS https://getcomposer.org/installer | php \ 37 | && mv composer.phar /usr/local/bin/composer \ 38 | && composer global require hirak/prestissimo --prefer-dist --no-interaction 39 | 40 | RUN usermod -u ${HOST_UID} www-data && groupmod -g ${HOST_UID} www-data && chsh -s /bin/bash www-data 41 | 42 | RUN cp /root/.bashrc /var/www 43 | 44 | # setup cron 45 | ADD ./crontab /etc/cron.d/cron-jobs 46 | RUN chmod 0644 /etc/cron.d/cron-jobs && touch /var/log/cron.log 47 | 48 | # setup supervisor 49 | RUN mkdir -p /var/log/supervisor 50 | COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf 51 | 52 | EXPOSE 80 53 | 54 | CMD ["/usr/bin/supervisord"] 55 | -------------------------------------------------------------------------------- /data/docker/web/supervisord.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | 4 | [program:apache2] 5 | command=/bin/bash -c "source /etc/apache2/envvars && exec /usr/sbin/apache2 -DFOREGROUND" 6 | autorestart=true 7 | autostart=true 8 | killasgroup=true 9 | stopasgroup=true 10 | 11 | [program:cron] 12 | command=cron -f -L 15 13 | autostart=true 14 | autorestart=true 15 | -------------------------------------------------------------------------------- /data/volumes/db/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /data/volumes/redis/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /docker-compose.override.yml.dist: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | web: 4 | ports: 5 | - "80:80" 6 | build: 7 | dockerfile: dev/Dockerfile 8 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | db: 4 | build: 5 | context: ./data/docker/db 6 | args: 7 | HOST_UID: ${HOST_UID} 8 | restart: always 9 | volumes: 10 | - ./data/volumes/db:/var/lib/mysql 11 | environment: 12 | MYSQL_ROOT_PASSWORD: secret 13 | MYSQL_DATABASE: yii2mod_base 14 | MYSQL_USER: docker 15 | MYSQL_PASSWORD: secret 16 | 17 | web: 18 | build: 19 | context: ./data/docker/web 20 | args: 21 | HOST_UID: ${HOST_UID} 22 | restart: always 23 | volumes: 24 | - .:/var/www/html 25 | links: 26 | - db 27 | - redis 28 | depends_on: 29 | - db 30 | - redis 31 | 32 | redis: 33 | restart: always 34 | build: 35 | context: ./data/docker/redis 36 | args: 37 | HOST_UID: ${HOST_UID} 38 | volumes: 39 | - ./data/volumes/redis:/data 40 | -------------------------------------------------------------------------------- /environments/dev/config/common-local.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'db' => [ 6 | 'dsn' => 'mysql:host=;dbname=', 7 | 'username' => '', 8 | 'password' => '', 9 | ], 10 | 'mailer' => [ 11 | 'useFileTransport' => true, 12 | ], 13 | 'redis' => [ 14 | 'hostname' => 'redis', 15 | ], 16 | ], 17 | ]; 18 | 19 | return $config; 20 | -------------------------------------------------------------------------------- /environments/dev/config/console-local.php: -------------------------------------------------------------------------------- 1 | ['gii'], 5 | 'modules' => [ 6 | 'gii' => 'yii\gii\Module', 7 | ], 8 | ]; 9 | -------------------------------------------------------------------------------- /environments/dev/config/main-local.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'request' => [ 6 | 'cookieValidationKey' => '', 7 | ], 8 | ], 9 | ]; 10 | 11 | if (!YII_ENV_TEST) { 12 | // configuration adjustments for 'dev' environment 13 | $config['bootstrap'][] = 'debug'; 14 | $config['modules']['debug'] = [ 15 | 'class' => 'yii\debug\Module', 16 | 'allowedIPs' => ['*'], 17 | ]; 18 | $config['bootstrap'][] = 'gii'; 19 | $config['modules']['gii'] = [ 20 | 'class' => 'yii\gii\Module', 21 | 'allowedIPs' => ['*'], 22 | ]; 23 | } 24 | 25 | return $config; 26 | -------------------------------------------------------------------------------- /environments/dev/config/params-local.php: -------------------------------------------------------------------------------- 1 | 'basic-tests', 12 | 'components' => [ 13 | 'db' => require __DIR__ . '/test_db.php', 14 | 'mailer' => [ 15 | 'useFileTransport' => true, 16 | ], 17 | 'assetManager' => [ 18 | 'basePath' => __DIR__ . '/../web/assets', 19 | ], 20 | 'urlManager' => [ 21 | 'showScriptName' => true, 22 | ], 23 | 'request' => [ 24 | 'cookieValidationKey' => 'test', 25 | 'enableCsrfValidation' => false, 26 | // but if you absolutely need it set cookie domain to localhost 27 | /* 28 | 'csrfCookie' => [ 29 | 'domain' => 'localhost', 30 | ], 31 | */ 32 | ], 33 | ], 34 | ] 35 | ); 36 | -------------------------------------------------------------------------------- /environments/dev/config/test_db.php: -------------------------------------------------------------------------------- 1 | 'mysql:host=localhost;dbname=yii2mod_base_test', 5 | 'username' => 'root', 6 | 'password' => '', 7 | ]; 8 | -------------------------------------------------------------------------------- /environments/dev/web/index-test.php: -------------------------------------------------------------------------------- 1 | run(); 17 | -------------------------------------------------------------------------------- /environments/dev/web/index.php: -------------------------------------------------------------------------------- 1 | run(); 17 | -------------------------------------------------------------------------------- /environments/dev/yii: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | run(); 26 | exit($exitCode); 27 | -------------------------------------------------------------------------------- /environments/dev/yii_test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | [ 17 | 'db' => require __DIR__ . '/config/test_db.php' 18 | ], 19 | ] 20 | ); 21 | 22 | $application = new yii\console\Application($config); 23 | $exitCode = $application->run(); 24 | exit($exitCode); 25 | -------------------------------------------------------------------------------- /environments/dev/yii_test.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | rem ------------------------------------------------------------- 3 | rem Yii command line bootstrap script for Windows. 4 | rem 5 | rem @author Qiang Xue 6 | rem @link http://www.yiiframework.com/ 7 | rem @copyright Copyright (c) 2008 Yii Software LLC 8 | rem @license http://www.yiiframework.com/license/ 9 | rem ------------------------------------------------------------- 10 | 11 | @setlocal 12 | 13 | set YII_PATH=%~dp0 14 | 15 | if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe 16 | 17 | "%PHP_COMMAND%" "%YII_PATH%yii_test" %* 18 | 19 | @endlocal 20 | -------------------------------------------------------------------------------- /environments/index.php: -------------------------------------------------------------------------------- 1 | [ 11 | * 'path' => 'directory storing the local files', 12 | * 'skipFiles' => [ 13 | * // list of files that should only copied once and skipped if they already exist 14 | * ], 15 | * 'setWritable' => [ 16 | * // list of directories that should be set writable 17 | * ], 18 | * 'setExecutable' => [ 19 | * // list of files that should be set executable 20 | * ], 21 | * 'setCookieValidationKey' => [ 22 | * // list of config files that need to be inserted with automatically generated cookie validation keys 23 | * ], 24 | * 'createSymlink' => [ 25 | * // list of symlinks to be created. Keys are symlinks, and values are the targets. 26 | * ], 27 | * ], 28 | * ]; 29 | * ``` 30 | */ 31 | return [ 32 | 'Development' => [ 33 | 'path' => 'dev', 34 | 'setWritable' => [ 35 | 'runtime', 36 | 'web/assets', 37 | ], 38 | 'setExecutable' => [ 39 | 'yii', 40 | 'yii_test', 41 | ], 42 | 'setCookieValidationKey' => [ 43 | 'config/main-local.php', 44 | ], 45 | ], 46 | 'Production' => [ 47 | 'path' => 'prod', 48 | 'setWritable' => [ 49 | 'runtime', 50 | 'web/assets', 51 | ], 52 | 'setExecutable' => [ 53 | 'yii', 54 | ], 55 | 'setCookieValidationKey' => [ 56 | 'config/main-local.php', 57 | ], 58 | ], 59 | ]; 60 | -------------------------------------------------------------------------------- /environments/prod/config/common-local.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'db' => [ 6 | 'dsn' => 'mysql:host=;dbname=', 7 | 'username' => '', 8 | 'password' => '', 9 | 'enableSchemaCache' => true, 10 | ], 11 | 'redis' => [ 12 | 'hostname' => 'redis', 13 | ], 14 | 'cache' => [ 15 | 'class' => 'yii\redis\Cache', 16 | ], 17 | 'authManager' => [ 18 | 'cache' => 'cache', 19 | ], 20 | ], 21 | ]; 22 | 23 | return $config; 24 | -------------------------------------------------------------------------------- /environments/prod/config/console-local.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'request' => [ 6 | 'cookieValidationKey' => '', 7 | ], 8 | ], 9 | ]; 10 | 11 | return $config; 12 | -------------------------------------------------------------------------------- /environments/prod/config/params-local.php: -------------------------------------------------------------------------------- 1 | run(); 17 | -------------------------------------------------------------------------------- /environments/prod/yii: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | run(); 26 | exit($exitCode); 27 | -------------------------------------------------------------------------------- /init: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 11 | * 12 | * @link http://www.yiiframework.com/ 13 | * @copyright Copyright (c) 2008 Yii Software LLC 14 | * @license http://www.yiiframework.com/license/ 15 | */ 16 | 17 | if (!extension_loaded('openssl')) { 18 | die('The OpenSSL PHP extension is required by Yii2.'); 19 | } 20 | 21 | $params = getParams(); 22 | $root = str_replace('\\', '/', __DIR__); 23 | $envs = require("$root/environments/index.php"); 24 | $envNames = array_keys($envs); 25 | 26 | echo "Yii Application Initialization Tool v1.0\n\n"; 27 | 28 | $envName = null; 29 | if (empty($params['env']) || $params['env'] === '1') { 30 | echo "Which environment do you want the application to be initialized in?\n\n"; 31 | foreach ($envNames as $i => $name) { 32 | echo " [$i] $name\n"; 33 | } 34 | echo "\n Your choice [0-" . (count($envs) - 1) . ', or "q" to quit] '; 35 | $answer = trim(fgets(STDIN)); 36 | 37 | if (!ctype_digit($answer) || !in_array($answer, range(0, count($envs) - 1))) { 38 | echo "\n Quit initialization.\n"; 39 | exit(0); 40 | } 41 | 42 | if (isset($envNames[$answer])) { 43 | $envName = $envNames[$answer]; 44 | } 45 | } else { 46 | $envName = $params['env']; 47 | } 48 | 49 | if (!in_array($envName, $envNames)) { 50 | $envsList = implode(', ', $envNames); 51 | echo "\n $envName is not a valid environment. Try one of the following: $envsList. \n"; 52 | exit(2); 53 | } 54 | 55 | $env = $envs[$envName]; 56 | 57 | if (empty($params['env'])) { 58 | echo "\n Initialize the application under '{$envNames[$answer]}' environment? [yes|no] "; 59 | $answer = trim(fgets(STDIN)); 60 | if (strncasecmp($answer, 'y', 1)) { 61 | echo "\n Quit initialization.\n"; 62 | exit(0); 63 | } 64 | } 65 | 66 | echo "\n Start initialization ...\n\n"; 67 | $files = getFileList("$root/environments/{$env['path']}"); 68 | if (isset($env['skipFiles'])) { 69 | $skipFiles = $env['skipFiles']; 70 | array_walk($skipFiles, function(&$value) use($env, $root) { $value = "$root/$value"; }); 71 | $files = array_diff($files, array_intersect_key($env['skipFiles'], array_filter($skipFiles, 'file_exists'))); 72 | } 73 | $all = false; 74 | foreach ($files as $file) { 75 | if (!copyFile($root, "environments/{$env['path']}/$file", $file, $all, $params)) { 76 | break; 77 | } 78 | } 79 | 80 | $callbacks = ['setCookieValidationKey', 'setWritable', 'setExecutable', 'createSymlink']; 81 | foreach ($callbacks as $callback) { 82 | if (!empty($env[$callback])) { 83 | $callback($root, $env[$callback]); 84 | } 85 | } 86 | 87 | echo "\n ... initialization completed.\n\n"; 88 | 89 | function getFileList($root, $basePath = '') 90 | { 91 | $files = []; 92 | $handle = opendir($root); 93 | while (($path = readdir($handle)) !== false) { 94 | if ($path === '.git' || $path === '.svn' || $path === '.' || $path === '..') { 95 | continue; 96 | } 97 | $fullPath = "$root/$path"; 98 | $relativePath = $basePath === '' ? $path : "$basePath/$path"; 99 | if (is_dir($fullPath)) { 100 | $files = array_merge($files, getFileList($fullPath, $relativePath)); 101 | } else { 102 | $files[] = $relativePath; 103 | } 104 | } 105 | closedir($handle); 106 | return $files; 107 | } 108 | 109 | function copyFile($root, $source, $target, &$all, $params) 110 | { 111 | if (!is_file($root . '/' . $source)) { 112 | echo " skip $target ($source not exist)\n"; 113 | return true; 114 | } 115 | if (is_file($root . '/' . $target)) { 116 | if (file_get_contents($root . '/' . $source) === file_get_contents($root . '/' . $target)) { 117 | echo " unchanged $target\n"; 118 | return true; 119 | } 120 | if ($all) { 121 | echo " overwrite $target\n"; 122 | } else { 123 | echo " exist $target\n"; 124 | echo " ...overwrite? [Yes|No|All|Quit] "; 125 | 126 | 127 | $answer = !empty($params['overwrite']) ? $params['overwrite'] : trim(fgets(STDIN)); 128 | if (!strncasecmp($answer, 'q', 1)) { 129 | return false; 130 | } else { 131 | if (!strncasecmp($answer, 'y', 1)) { 132 | echo " overwrite $target\n"; 133 | } else { 134 | if (!strncasecmp($answer, 'a', 1)) { 135 | echo " overwrite $target\n"; 136 | $all = true; 137 | } else { 138 | echo " skip $target\n"; 139 | return true; 140 | } 141 | } 142 | } 143 | } 144 | file_put_contents($root . '/' . $target, file_get_contents($root . '/' . $source)); 145 | return true; 146 | } 147 | echo " generate $target\n"; 148 | @mkdir(dirname($root . '/' . $target), 0777, true); 149 | file_put_contents($root . '/' . $target, file_get_contents($root . '/' . $source)); 150 | return true; 151 | } 152 | 153 | function getParams() 154 | { 155 | $rawParams = []; 156 | if (isset($_SERVER['argv'])) { 157 | $rawParams = $_SERVER['argv']; 158 | array_shift($rawParams); 159 | } 160 | 161 | $params = []; 162 | foreach ($rawParams as $param) { 163 | if (preg_match('/^--(\w+)(=(.*))?$/', $param, $matches)) { 164 | $name = $matches[1]; 165 | $params[$name] = isset($matches[3]) ? $matches[3] : true; 166 | } else { 167 | $params[] = $param; 168 | } 169 | } 170 | return $params; 171 | } 172 | 173 | function setWritable($root, $paths) 174 | { 175 | foreach ($paths as $writable) { 176 | if (is_dir("$root/$writable")) { 177 | if (@chmod("$root/$writable", 0777)) { 178 | echo " chmod 0777 $writable\n"; 179 | } else { 180 | printError("Operation chmod not permitted for directory $writable."); 181 | } 182 | } else { 183 | printError("Directory $writable does not exist."); 184 | } 185 | } 186 | } 187 | 188 | function setExecutable($root, $paths) 189 | { 190 | foreach ($paths as $executable) { 191 | if (file_exists("$root/$executable")) { 192 | if (@chmod("$root/$executable", 0755)) { 193 | echo " chmod 0755 $executable\n"; 194 | } else { 195 | printError("Operation chmod not permitted for $executable."); 196 | } 197 | } else { 198 | printError("$executable does not exist."); 199 | } 200 | } 201 | } 202 | 203 | function setCookieValidationKey($root, $paths) 204 | { 205 | foreach ($paths as $file) { 206 | echo " generate cookie validation key in $file\n"; 207 | $file = $root . '/' . $file; 208 | $length = 32; 209 | $bytes = openssl_random_pseudo_bytes($length); 210 | $key = strtr(substr(base64_encode($bytes), 0, $length), '+/=', '_-.'); 211 | $content = preg_replace('/(("|\')cookieValidationKey("|\')\s*=>\s*)(""|\'\')/', "\\1'$key'", file_get_contents($file)); 212 | file_put_contents($file, $content); 213 | } 214 | } 215 | 216 | function createSymlink($root, $links) 217 | { 218 | foreach ($links as $link => $target) { 219 | //first removing folders to avoid errors if the folder already exists 220 | @rmdir($root . "/" . $link); 221 | //next removing existing symlink in order to update the target 222 | if (is_link($root . "/" . $link)) { 223 | @unlink($root . "/" . $link); 224 | } 225 | if (@symlink($root . "/" . $target, $root . "/" . $link)) { 226 | echo " symlink $root/$target $root/$link\n"; 227 | } else { 228 | printError("Cannot create symlink $root/$target $root/$link."); 229 | } 230 | } 231 | } 232 | 233 | /** 234 | * Prints error message. 235 | * @param string $message message 236 | */ 237 | function printError($message) 238 | { 239 | echo "\n " . formatMessage("Error. $message", ['fg-red']) . " \n"; 240 | } 241 | 242 | /** 243 | * Returns true if the stream supports colorization. ANSI colors are disabled if not supported by the stream. 244 | * 245 | * - windows without ansicon 246 | * - not tty consoles 247 | * 248 | * @return boolean true if the stream supports ANSI colors, otherwise false. 249 | */ 250 | function ansiColorsSupported() 251 | { 252 | return DIRECTORY_SEPARATOR === '\\' 253 | ? getenv('ANSICON') !== false || getenv('ConEmuANSI') === 'ON' 254 | : function_exists('posix_isatty') && @posix_isatty(STDOUT); 255 | } 256 | 257 | /** 258 | * Get ANSI code of style. 259 | * @param string $name style name 260 | * @return integer ANSI code of style. 261 | */ 262 | function getStyleCode($name) 263 | { 264 | $styles = [ 265 | 'bold' => 1, 266 | 'fg-black' => 30, 267 | 'fg-red' => 31, 268 | 'fg-green' => 32, 269 | 'fg-yellow' => 33, 270 | 'fg-blue' => 34, 271 | 'fg-magenta' => 35, 272 | 'fg-cyan' => 36, 273 | 'fg-white' => 37, 274 | 'bg-black' => 40, 275 | 'bg-red' => 41, 276 | 'bg-green' => 42, 277 | 'bg-yellow' => 43, 278 | 'bg-blue' => 44, 279 | 'bg-magenta' => 45, 280 | 'bg-cyan' => 46, 281 | 'bg-white' => 47, 282 | ]; 283 | return $styles[$name]; 284 | } 285 | 286 | /** 287 | * Formats message using styles if STDOUT supports it. 288 | * @param string $message message 289 | * @param string[] $styles styles 290 | * @return string formatted message. 291 | */ 292 | function formatMessage($message, $styles) 293 | { 294 | if (empty($styles) || !ansiColorsSupported()) { 295 | return $message; 296 | } 297 | 298 | return sprintf("\x1b[%sm", implode(';', array_map('getStyleCode', $styles))) . $message . "\x1b[0m"; 299 | } 300 | -------------------------------------------------------------------------------- /init.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem ------------------------------------------------------------- 4 | rem Yii command line init script for Windows. 5 | rem 6 | rem @author Qiang Xue 7 | rem @link http://www.yiiframework.com/ 8 | rem @copyright Copyright (c) 2008 Yii Software LLC 9 | rem @license http://www.yiiframework.com/license/ 10 | rem ------------------------------------------------------------- 11 | 12 | @setlocal 13 | 14 | set YII_PATH=%~dp0 15 | 16 | if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe 17 | 18 | "%PHP_COMMAND%" "%YII_PATH%init" %* 19 | 20 | @endlocal 21 | -------------------------------------------------------------------------------- /mail/layouts/html.php: -------------------------------------------------------------------------------- 1 | 9 | beginPage(); ?> 10 | 11 | 12 | 13 | 14 | <?= Html::encode($this->title); ?> 15 | head(); ?> 16 | 17 | 18 | beginBody(); ?> 19 | 20 | endBody(); ?> 21 | 22 | 23 | endPage(); ?> 24 | -------------------------------------------------------------------------------- /mail/passwordResetToken.php: -------------------------------------------------------------------------------- 1 | urlManager->createAbsoluteUrl(['site/password-reset', 'token' => $user->password_reset_token]); 9 | ?> 10 | 11 | Hello username); ?>, 12 | 13 | Follow the link below to reset your password: 14 | 15 | 16 | -------------------------------------------------------------------------------- /messages/config.php: -------------------------------------------------------------------------------- 1 | __DIR__ . DIRECTORY_SEPARATOR . '..', 13 | // array, required, list of language codes that the extracted messages 14 | // should be translated to. For example, ['zh-CN', 'de']. 15 | 'languages' => ['en'], 16 | // string, the name of the function for translating messages. 17 | // Defaults to 'Yii::t'. This is used as a mark to find the messages to be 18 | // translated. You may use a string for single function name or an array for 19 | // multiple function names. 20 | 'translator' => 'Yii::t', 21 | // boolean, whether to sort messages by keys when merging new messages 22 | // with the existing ones. Defaults to false, which means the new (untranslated) 23 | // messages will be separated from the old (translated) ones. 24 | 'sort' => true, 25 | // boolean, whether to remove messages that no longer appear in the source code. 26 | // Defaults to false, which means each of these messages will be enclosed with a pair of '@@' marks. 27 | 'removeUnused' => false, 28 | // array, list of patterns that specify which files (not directories) should be processed. 29 | // If empty or not set, all files will be processed. 30 | // Please refer to "except" for details about the patterns. 31 | 'only' => ['*.php'], 32 | // array, list of patterns that specify which files/directories should NOT be processed. 33 | // If empty or not set, all files/directories will be processed. 34 | // A path matches a pattern if it contains the pattern string at its end. For example, 35 | // '/a/b' will match all files and directories ending with '/a/b'; 36 | // the '*.svn' will match all files and directories whose name ends with '.svn'. 37 | // and the '.svn' will match all files and directories named exactly '.svn'. 38 | // Note, the '/' characters in a pattern matches both '/' and '\'. 39 | // See helpers/FileHelper::findFiles() description for more details on pattern matching rules. 40 | // If a file/directory matches both a pattern in "only" and "except", it will NOT be processed. 41 | 'except' => [ 42 | '.svn', 43 | '.git', 44 | '.gitignore', 45 | '.gitkeep', 46 | '.hg', 47 | '.hgignore', 48 | '.hgkeep', 49 | '/assets', 50 | '/messages', 51 | '/migrations', 52 | '/tests', 53 | '/runtime', 54 | '/vendor', 55 | '/web', 56 | '/webstub', 57 | ], 58 | // 'php' output format is for saving messages to php files. 59 | 'format' => 'php', 60 | // Root directory containing message translations. 61 | 'messagePath' => __DIR__, 62 | // boolean, whether the message file should be overwritten with the merged messages 63 | 'overwrite' => true, 64 | // Message categories to ignore 65 | 'ignoreCategories' => [ 66 | 'yii', 67 | ], 68 | ]; 69 | -------------------------------------------------------------------------------- /migrations/m161109_121736_create_session_table.php: -------------------------------------------------------------------------------- 1 | db->driverName) { 13 | case 'mysql': 14 | case 'mariadb': 15 | $dataType = 'LONGBLOB'; 16 | break; 17 | case 'pgsql': 18 | $dataType = 'BYTEA'; 19 | break; 20 | default: 21 | $dataType = 'TEXT'; 22 | } 23 | 24 | $this->createTable('{{%session}}', [ 25 | 'id' => 'CHAR(40) NOT NULL PRIMARY KEY', 26 | 'expire' => 'INTEGER', 27 | 'data' => $dataType, 28 | ]); 29 | } 30 | 31 | public function down() 32 | { 33 | $this->dropTable('{{%session}}'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /models/UserModel.php: -------------------------------------------------------------------------------- 1 | Yii::t('contact', 'Name'), 57 | 'email' => Yii::t('contact', 'Email'), 58 | 'subject' => Yii::t('contact', 'Subject'), 59 | 'body' => Yii::t('contact', 'Body'), 60 | 'verifyCode' => Yii::t('contact', 'Verification Code'), 61 | ]; 62 | } 63 | 64 | /** 65 | * Sends an email to the specified email address using the information collected by this model. 66 | * 67 | * @param string $email the target email address 68 | * 69 | * @return bool whether the model passes validation 70 | */ 71 | public function contact(string $email): bool 72 | { 73 | if ($this->validate()) { 74 | Yii::$app->mailer->compose() 75 | ->setTo($email) 76 | ->setFrom([$this->email => $this->name]) 77 | ->setSubject($this->subject) 78 | ->setTextBody($this->body) 79 | ->send(); 80 | 81 | return true; 82 | } 83 | 84 | return false; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /models/forms/ResetPasswordForm.php: -------------------------------------------------------------------------------- 1 | _user = $user; 41 | 42 | parent::__construct($config); 43 | } 44 | 45 | /** 46 | * @inheritdoc 47 | */ 48 | public function rules(): array 49 | { 50 | return [ 51 | [['password', 'confirmPassword'], 'trim'], 52 | ['password', 'required'], 53 | ['confirmPassword', 'required'], 54 | [['password', 'confirmPassword'], 'string', 'min' => 6], 55 | ['confirmPassword', 'compare', 'compareAttribute' => 'password'], 56 | ]; 57 | } 58 | 59 | /** 60 | * @inheritdoc 61 | */ 62 | public function attributeLabels(): array 63 | { 64 | return [ 65 | 'password' => Yii::t('app', 'New Password'), 66 | 'confirmPassword' => Yii::t('app', 'Confirm New Password'), 67 | ]; 68 | } 69 | 70 | /** 71 | * Resets password. 72 | * 73 | * @return bool if password was reset 74 | */ 75 | public function resetPassword(): bool 76 | { 77 | if ($this->validate()) { 78 | $this->_user->setPassword($this->password); 79 | 80 | return $this->_user->save(true, ['password_hash']); 81 | } 82 | 83 | return false; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /modules/admin/Module.php: -------------------------------------------------------------------------------- 1 | [ 36 | 'class' => AccessControl::class, 37 | 'rules' => [ 38 | [ 39 | 'allow' => true, 40 | 'roles' => ['admin'], 41 | ], 42 | ], 43 | ], 44 | ]; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /modules/admin/controllers/SettingsController.php: -------------------------------------------------------------------------------- 1 | 'yii2mod\cron\actions\CronLogAction', 21 | ]; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /modules/admin/controllers/UserController.php: -------------------------------------------------------------------------------- 1 | [ 35 | 'class' => VerbFilter::class, 36 | 'actions' => [ 37 | 'index' => ['get'], 38 | 'create' => ['get', 'post'], 39 | 'update' => ['get', 'post'], 40 | 'delete' => ['post'], 41 | ], 42 | ], 43 | ]; 44 | } 45 | 46 | /** 47 | * @inheritdoc 48 | */ 49 | public function actions(): array 50 | { 51 | return [ 52 | 'edit-user' => [ 53 | 'class' => EditableAction::class, 54 | 'modelClass' => UserModel::class, 55 | 'forceCreate' => false, 56 | ], 57 | 'index' => [ 58 | 'class' => 'yii2tech\admin\actions\Index', 59 | 'newSearchModel' => function () { 60 | return new UserSearch(); 61 | }, 62 | ], 63 | 'delete' => [ 64 | 'class' => 'yii2tech\admin\actions\Delete', 65 | 'findModel' => function (int $id) { 66 | return $this->findModel(UserModel::class, $id); 67 | }, 68 | 'flash' => Yii::t('app', 'User has been deleted.'), 69 | ], 70 | ]; 71 | } 72 | 73 | /** 74 | * Creates a new user. 75 | * 76 | * If creation is successful, the browser will be redirected to the 'index' page. 77 | * 78 | * @return mixed 79 | */ 80 | public function actionCreate() 81 | { 82 | $model = new UserModel(['scenario' => 'create']); 83 | 84 | if ($model->load(Yii::$app->request->post())) { 85 | if ($model->create()) { 86 | Yii::$app->session->setFlash('success', Yii::t('app', 'User has been created.')); 87 | 88 | return $this->redirect(['index']); 89 | } 90 | } 91 | 92 | return $this->render('create', [ 93 | 'model' => $model, 94 | ]); 95 | } 96 | 97 | /** 98 | * Updates an existing user. 99 | * 100 | * If update is successful, the browser will be redirected to the 'index' page. 101 | * 102 | * @param int $id 103 | * 104 | * @return mixed 105 | */ 106 | public function actionUpdate(int $id) 107 | { 108 | /* @var $model UserModel */ 109 | $model = $this->findModel(UserModel::class, $id); 110 | 111 | if ($model->load(Yii::$app->request->post()) && $model->validate()) { 112 | if (!empty($model->plainPassword)) { 113 | $model->setPassword($model->plainPassword); 114 | } 115 | $model->save(false); 116 | Yii::$app->session->setFlash('success', Yii::t('app', 'User has been saved.')); 117 | 118 | return $this->redirect(['index']); 119 | } 120 | 121 | return $this->render('update', [ 122 | 'model' => $model, 123 | ]); 124 | } 125 | 126 | /** 127 | * Switches to the given user for the rest of the Session. 128 | * 129 | * @param int $id 130 | * 131 | * @throws ForbiddenHttpException 132 | * 133 | * @return string 134 | */ 135 | public function actionSwitch(int $id) 136 | { 137 | if (Yii::$app->session->has(self::ORIGINAL_USER_SESSION_KEY)) { 138 | $user = $this->findModel(UserModel::class, Yii::$app->session->get(self::ORIGINAL_USER_SESSION_KEY)); 139 | Yii::$app->session->remove(self::ORIGINAL_USER_SESSION_KEY); 140 | } else { 141 | $user = $this->findModel(UserModel::class, $id); 142 | Yii::$app->session->set(self::ORIGINAL_USER_SESSION_KEY, Yii::$app->user->id); 143 | } 144 | 145 | Yii::$app->user->switchIdentity($user, 3600); 146 | 147 | return $this->goHome(); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /modules/admin/models/search/UserSearch.php: -------------------------------------------------------------------------------- 1 | $query, 40 | 'pagination' => [ 41 | 'pageSize' => $pageSize, 42 | ], 43 | 'sort' => [ 44 | 'defaultOrder' => ['id' => SORT_DESC], 45 | ], 46 | ]); 47 | 48 | $this->load($params); 49 | 50 | if (!$this->validate()) { 51 | return $dataProvider; 52 | } 53 | 54 | $query->andFilterWhere([ 55 | 'id' => $this->id, 56 | 'status' => $this->status, 57 | ]); 58 | 59 | $query->andFilterWhere(['like', 'username', $this->username]) 60 | ->andFilterWhere(['like', 'email', $this->email]); 61 | 62 | return $dataProvider; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /modules/admin/views/layouts/column2.php: -------------------------------------------------------------------------------- 1 | 16 | beginPage(); ?> 17 | 18 | 19 | 20 | registerMetaTag(['charset' => Yii::$app->charset]); ?> 21 | registerMetaTag(['name' => 'viewport', 'content' => 'width=device-width, initial-scale=1']); ?> 22 | 23 | <?php echo implode(' | ', array_filter([Html::encode($this->title), Yii::$app->name])); ?> 24 | head(); ?> 25 | 26 | 27 | beginBody(); ?> 28 | 29 |
30 | 'Admin Panel', 32 | 'brandUrl' => '/admin', 33 | 'options' => [ 34 | 'class' => 'navbar-inverse navbar-fixed-top', 35 | ], 36 | ]); 37 | echo Nav::widget([ 38 | 'options' => ['class' => 'navbar-nav'], 39 | 'encodeLabels' => false, 40 | 'items' => [ 41 | [ 42 | 'label' => ' Users', 43 | 'items' => [ 44 | [ 45 | 'label' => ' User List', 46 | 'url' => ['/admin/user/index'], 47 | ], 48 | [ 49 | 'label' => ' Create User', 50 | 'url' => ['/admin/user/create'], 51 | ], 52 | ], 53 | ], 54 | [ 55 | 'label' => ' CMS', 56 | 'url' => ['/cms/manage/index'], 57 | ], 58 | [ 59 | 'label' => ' RBAC', 60 | 'url' => ['/admin/rbac/assignment/index'], 61 | 'active' => $this->context->module->id == 'rbac', 62 | ], 63 | [ 64 | 'label' => ' Settings Storage', 65 | 'url' => ['/admin/settings-storage'], 66 | 'active' => $this->context->module->id == 'settings-storage', 67 | ], 68 | [ 69 | 'label' => ' Cron Schedule Log', 70 | 'url' => ['/admin/settings/cron'], 71 | ], 72 | ], 73 | ]); 74 | 75 | echo Nav::widget([ 76 | 'options' => ['class' => 'navbar-nav navbar-right'], 77 | 'items' => [ 78 | ['label' => ' Public Area', 'url' => ['/']], 79 | ['label' => ' Logout (' . Yii::$app->user->identity->username . ')', 80 | 'url' => ['/site/logout'], 81 | 'linkOptions' => ['data-method' => 'post'], 82 | ], 83 | ], 84 | 'encodeLabels' => false, 85 | ]); 86 | NavBar::end(); 87 | ?> 88 |
89 | isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [], 91 | ]); ?> 92 |
93 | params['sidebar'])): ?> 94 |
95 | $this->params['sidebar'], 97 | 'encodeLabels' => false, 98 | 'options' => [ 99 | 'class' => 'nav nav-pills nav-stacked admin-side-nav', 100 | ], 101 | ]); 102 | ?> 103 |
104 | 105 |
106 | 107 |
108 |
109 |
110 |
111 | 112 | endBody(); ?> 113 | 114 | 115 | endPage(); ?> 116 | -------------------------------------------------------------------------------- /modules/admin/views/user/_form.php: -------------------------------------------------------------------------------- 1 | 11 | 12 |
13 | 14 | 'create-user-form']); ?> 15 | 16 |
17 |
18 | field($model, 'username')->textInput(['maxlength' => 255]); ?> 19 | 20 | field($model, 'email')->textInput(['maxlength' => 255]); ?> 21 | 22 | field($model, 'status')->dropDownList(UserStatus::listData()); ?> 23 | 24 | field($model, 'plainPassword')->passwordInput(['autocomplete' => 'off']); ?> 25 |
26 |
27 | 28 |
29 | isNewRecord ? Yii::t('app', 'Create') : Yii::t('app', 'Update'), ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']); ?> 30 |
31 | 32 | 33 | 34 |
35 | -------------------------------------------------------------------------------- /modules/admin/views/user/create.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Create User'); 9 | $this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Users'), 'url' => ['index']]; 10 | $this->params['breadcrumbs'][] = $this->title; 11 | ?> 12 |
13 | 14 |

title); ?>

15 | 16 | render('_form', [ 17 | 'model' => $model, 18 | ]); ?> 19 | 20 |
21 | -------------------------------------------------------------------------------- /modules/admin/views/user/index.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Users'); 14 | $this->params['breadcrumbs'][] = $this->title; 15 | ?> 16 |
17 | 18 |

title); ?>

19 | 20 |

21 | 'btn btn-success']); ?> 22 |

23 | 10000]); ?> 24 | $dataProvider, 26 | 'filterModel' => $searchModel, 27 | 'columns' => [ 28 | 'id', 29 | [ 30 | 'class' => EditableColumn::class, 31 | 'attribute' => 'username', 32 | 'url' => ['edit-user'], 33 | ], 34 | 'email:email', 35 | [ 36 | 'class' => EditableColumn::class, 37 | 'attribute' => 'status', 38 | 'url' => ['edit-user'], 39 | 'value' => function ($model) { 40 | return UserStatus::getLabel($model->status); 41 | }, 42 | 'type' => 'select', 43 | 'editableOptions' => function ($model) { 44 | return [ 45 | 'source' => UserStatus::listData(), 46 | 'value' => $model->status, 47 | ]; 48 | }, 49 | 'filter' => UserStatus::listData(), 50 | 'filterInputOptions' => ['prompt' => Yii::t('app', 'Select Status'), 'class' => 'form-control'], 51 | ], 52 | [ 53 | 'attribute' => 'created_at', 54 | 'format' => 'date', 55 | 'filter' => false, 56 | ], 57 | [ 58 | 'header' => Yii::t('app', 'Action'), 59 | 'class' => 'yii\grid\ActionColumn', 60 | 'template' => '{switch} {update} {delete}', 61 | 'buttons' => [ 62 | 'switch' => function ($url, $model) { 63 | $options = [ 64 | 'title' => Yii::t('app', 'Become this user'), 65 | 'aria-label' => Yii::t('app', 'Become this user'), 66 | 'data-pjax' => '0', 67 | 'data-confirm' => Yii::t('app', 'Are you sure you want to switch to this user for the rest of this Session?'), 68 | 'data-method' => 'POST', 69 | ]; 70 | 71 | $url = ['switch', 'id' => $model->id]; 72 | $icon = ''; 73 | 74 | return Html::a($icon, $url, $options); 75 | }, 76 | ], 77 | ], 78 | ], 79 | ]); 80 | ?> 81 | 82 | 83 |
84 | -------------------------------------------------------------------------------- /modules/admin/views/user/update.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Update User: {0}', $model->username); 9 | $this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Users'), 'url' => ['index']]; 10 | $this->params['breadcrumbs'][] = Yii::t('app', 'Update'); 11 | ?> 12 |
13 | 14 |

title); ?>

15 | 16 | render('_form', [ 17 | 'model' => $model, 18 | ]); ?> 19 | 20 |
21 | -------------------------------------------------------------------------------- /rbac/migrations/m160722_085418_init.php: -------------------------------------------------------------------------------- 1 | createRule('user', UserRule::class); 11 | 12 | $this->createRole('admin', 'Admin has all available permissions.'); 13 | $this->createRole('user', 'Authenticated user.', 'user'); 14 | } 15 | 16 | public function safeDown() 17 | { 18 | $this->removeRule('user'); 19 | 20 | $this->removeRole('admin'); 21 | $this->removeRole('user'); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /requirements.php: -------------------------------------------------------------------------------- 1 | Error'; 18 | echo '

The path to yii framework seems to be incorrect.

'; 19 | echo '

You need to install Yii framework via composer or adjust the framework path in file ' . basename(__FILE__) . '.

'; 20 | echo '

Please refer to the README on how to install Yii.

'; 21 | } 22 | 23 | require_once($frameworkPath . '/requirements/YiiRequirementChecker.php'); 24 | $requirementsChecker = new YiiRequirementChecker(); 25 | 26 | $gdMemo = $imagickMemo = 'Either GD PHP extension with FreeType support or ImageMagick PHP extension with PNG support is required for image CAPTCHA.'; 27 | $gdOK = $imagickOK = false; 28 | 29 | if (extension_loaded('imagick')) { 30 | $imagick = new Imagick(); 31 | $imagickFormats = $imagick->queryFormats('PNG'); 32 | if (in_array('PNG', $imagickFormats)) { 33 | $imagickOK = true; 34 | } else { 35 | $imagickMemo = 'Imagick extension should be installed with PNG support in order to be used for image CAPTCHA.'; 36 | } 37 | } 38 | 39 | if (extension_loaded('gd')) { 40 | $gdInfo = gd_info(); 41 | if (!empty($gdInfo['FreeType Support'])) { 42 | $gdOK = true; 43 | } else { 44 | $gdMemo = 'GD extension should be installed with FreeType support in order to be used for image CAPTCHA.'; 45 | } 46 | } 47 | 48 | /** 49 | * Adjust requirements according to your application specifics. 50 | */ 51 | $requirements = [ 52 | // Database : 53 | [ 54 | 'name' => 'PDO extension', 55 | 'mandatory' => true, 56 | 'condition' => extension_loaded('pdo'), 57 | 'by' => 'All DB-related classes', 58 | ], 59 | [ 60 | 'name' => 'PDO SQLite extension', 61 | 'mandatory' => false, 62 | 'condition' => extension_loaded('pdo_sqlite'), 63 | 'by' => 'All DB-related classes', 64 | 'memo' => 'Required for SQLite database.', 65 | ], 66 | [ 67 | 'name' => 'PDO MySQL extension', 68 | 'mandatory' => false, 69 | 'condition' => extension_loaded('pdo_mysql'), 70 | 'by' => 'All DB-related classes', 71 | 'memo' => 'Required for MySQL database.', 72 | ], 73 | [ 74 | 'name' => 'PDO PostgreSQL extension', 75 | 'mandatory' => false, 76 | 'condition' => extension_loaded('pdo_pgsql'), 77 | 'by' => 'All DB-related classes', 78 | 'memo' => 'Required for PostgreSQL database.', 79 | ], 80 | // Cache : 81 | [ 82 | 'name' => 'Memcache extension', 83 | 'mandatory' => false, 84 | 'condition' => extension_loaded('memcache') || extension_loaded('memcached'), 85 | 'by' => 'MemCache', 86 | 'memo' => extension_loaded('memcached') ? 'To use memcached set MemCache::useMemcached to true.' : '', 87 | ], 88 | // CAPTCHA: 89 | [ 90 | 'name' => 'GD PHP extension with FreeType support', 91 | 'mandatory' => false, 92 | 'condition' => $gdOK, 93 | 'by' => 'Captcha', 94 | 'memo' => $gdMemo, 95 | ], 96 | [ 97 | 'name' => 'ImageMagick PHP extension with PNG support', 98 | 'mandatory' => false, 99 | 'condition' => $imagickOK, 100 | 'by' => 'Captcha', 101 | 'memo' => $imagickMemo, 102 | ], 103 | // PHP ini : 104 | 'phpExposePhp' => [ 105 | 'name' => 'Expose PHP', 106 | 'mandatory' => false, 107 | 'condition' => $requirementsChecker->checkPhpIniOff('expose_php'), 108 | 'by' => 'Security reasons', 109 | 'memo' => '"expose_php" should be disabled at php.ini', 110 | ], 111 | 'phpAllowUrlInclude' => [ 112 | 'name' => 'PHP allow url include', 113 | 'mandatory' => false, 114 | 'condition' => $requirementsChecker->checkPhpIniOff('allow_url_include'), 115 | 'by' => 'Security reasons', 116 | 'memo' => '"allow_url_include" should be disabled at php.ini', 117 | ], 118 | 'phpSmtp' => [ 119 | 'name' => 'PHP mail SMTP', 120 | 'mandatory' => false, 121 | 'condition' => strlen(ini_get('SMTP')) > 0, 122 | 'by' => 'Email sending', 123 | 'memo' => 'PHP mail SMTP server required', 124 | ], 125 | ]; 126 | 127 | // OPcache check 128 | if (!version_compare(phpversion(), '5.5', '>=')) { 129 | $requirements[] = [ 130 | 'name' => 'APC extension', 131 | 'mandatory' => false, 132 | 'condition' => extension_loaded('apc'), 133 | 'by' => 'ApcCache', 134 | ]; 135 | } 136 | 137 | $requirementsChecker->checkYii()->check($requirements)->render(); 138 | -------------------------------------------------------------------------------- /runtime/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /tests/_bootstrap.php: -------------------------------------------------------------------------------- 1 | 'about-us', 6 | 'title' => 'About us', 7 | 'content' => 'About us content', 8 | 'meta_title' => 'About us', 9 | 'meta_description' => 'About us description', 10 | 'meta_keywords' => 'About us keywords', 11 | 'created_at' => time(), 12 | 'updated_at' => time(), 13 | ], 14 | [ 15 | 'url' => 'terms-and-conditions', 16 | 'title' => 'Terms & Conditions', 17 | 'content' => 'Content', 18 | 'meta_title' => 'Terms & Conditions', 19 | 'meta_description' => 'Terms & Conditions description', 20 | 'meta_keywords' => 'Terms & Conditions keywords', 21 | 'created_at' => time(), 22 | 'updated_at' => time(), 23 | ], 24 | [ 25 | 'url' => 'privacy-policy', 26 | 'title' => 'Privacy Policy', 27 | 'content' => 'Content', 28 | 'meta_title' => 'Privacy Policy', 29 | 'meta_description' => 'Privacy Policy description', 30 | 'meta_keywords' => 'Privacy Policy keywords', 31 | 'created_at' => time(), 32 | 'updated_at' => time(), 33 | ], 34 | ]; 35 | -------------------------------------------------------------------------------- /tests/_data/user.php: -------------------------------------------------------------------------------- 1 | 'admin', 6 | 'auth_key' => Yii::$app->getSecurity()->generateRandomString(), 7 | 'password_hash' => Yii::$app->getSecurity()->generatePasswordHash(123123), 8 | 'email' => 'admin@example.org', 9 | 'status' => 1, 10 | 'created_at' => time(), 11 | 'updated_at' => time(), 12 | ], 13 | [ 14 | 'username' => 'test-user', 15 | 'auth_key' => Yii::$app->getSecurity()->generateRandomString(), 16 | 'password_hash' => Yii::$app->getSecurity()->generatePasswordHash(123123), 17 | 'email' => 'test-user@example.com', 18 | 'status' => 1, 19 | 'created_at' => time(), 20 | 'updated_at' => time(), 21 | ], 22 | ]; 23 | -------------------------------------------------------------------------------- /tests/_data/user_assignment.php: -------------------------------------------------------------------------------- 1 | 'admin', 6 | 'user_id' => 1, 7 | 'created_at' => time(), 8 | ], 9 | [ 10 | 'item_name' => 'user', 11 | 'user_id' => 2, 12 | 'created_at' => time(), 13 | ], 14 | ]; 15 | -------------------------------------------------------------------------------- /tests/_output/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /tests/_support/AcceptanceTester.php: -------------------------------------------------------------------------------- 1 | see($message, '.help-block'); 26 | } 27 | 28 | public function dontSeeValidationError($message) 29 | { 30 | $this->dontSee($message, '.help-block'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/_support/UnitTester.php: -------------------------------------------------------------------------------- 1 | tableName = Yii::$app->authManager->assignmentTable; 26 | 27 | parent::init(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/fixtures/UserFixture.php: -------------------------------------------------------------------------------- 1 | haveFixtures([ 13 | 'cms' => [ 14 | 'class' => CmsFixture::class, 15 | ], 16 | ]); 17 | } 18 | 19 | public function checkAbout(FunctionalTester $I) 20 | { 21 | $I->amOnRoute('/about-us'); 22 | $I->see('About us', 'h1'); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/functional/AdminLoginCest.php: -------------------------------------------------------------------------------- 1 | haveFixtures([ 18 | 'user' => [ 19 | 'class' => UserAssignmentFixture::class, 20 | ], 21 | ]); 22 | 23 | $I->amOnRoute('site/login'); 24 | } 25 | 26 | public function checkLogin(FunctionalTester $I) 27 | { 28 | $I->submitForm($this->formId, $this->formParams('admin@example.org', '123123')); 29 | $I->see('Logout (admin)', 'form button[type=submit]'); 30 | $I->seeLink('Administration'); 31 | } 32 | 33 | public function checkAdminPanel(FunctionalTester $I) 34 | { 35 | $I->submitForm($this->formId, $this->formParams('admin@example.org', '123123')); 36 | $I->see('Logout (admin)', 'form button[type=submit]'); 37 | $I->seeLink('Administration'); 38 | $I->click('Administration'); 39 | $I->see('Users'); 40 | $I->seeLink('CMS'); 41 | $I->seeLink('RBAC'); 42 | $I->seeLink('Settings Storage'); 43 | $I->seeLink('Cron Schedule Log'); 44 | } 45 | 46 | protected function formParams($login, $password) 47 | { 48 | return [ 49 | 'LoginForm[email]' => $login, 50 | 'LoginForm[password]' => $password, 51 | ]; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/functional/ChangePasswordViaAccountPageCest.php: -------------------------------------------------------------------------------- 1 | haveFixtures([ 13 | 'user' => [ 14 | 'class' => UserAssignmentFixture::class, 15 | ], 16 | ]); 17 | $I->amOnRoute('site/login'); 18 | } 19 | 20 | public function checkEmpty(FunctionalTester $I) 21 | { 22 | $I->submitForm('#login-form', $this->loginFormParams('test-user@example.com', '123123')); 23 | $I->click('My Account'); 24 | $I->submitForm('#change-password-form', $this->resetPasswordFormParams('', '')); 25 | $I->see('New Password cannot be blank.'); 26 | $I->see('Confirm New Password cannot be blank.'); 27 | } 28 | 29 | public function checkNotEqual(FunctionalTester $I) 30 | { 31 | $I->submitForm('#login-form', $this->loginFormParams('test-user@example.com', '123123')); 32 | $I->click('My Account'); 33 | $I->submitForm('#change-password-form', $this->resetPasswordFormParams('123123', '123456')); 34 | $I->see('Confirm New Password must be equal to "New Password".'); 35 | } 36 | 37 | public function checkCorrectChangePassword(FunctionalTester $I) 38 | { 39 | $I->submitForm('#login-form', $this->loginFormParams('test-user@example.com', '123123')); 40 | $I->click('My Account'); 41 | $I->submitForm('#change-password-form', $this->resetPasswordFormParams('123456', '123456')); 42 | $I->canSeeInSource('Password has been updated.'); 43 | } 44 | 45 | protected function loginFormParams($login, $password) 46 | { 47 | return [ 48 | 'LoginForm[email]' => $login, 49 | 'LoginForm[password]' => $password, 50 | ]; 51 | } 52 | 53 | protected function resetPasswordFormParams($newPassword, $confirmPassword) 54 | { 55 | return [ 56 | 'ResetPasswordForm[password]' => $newPassword, 57 | 'ResetPasswordForm[confirmPassword]' => $confirmPassword, 58 | ]; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/functional/ContactFormCest.php: -------------------------------------------------------------------------------- 1 | amOnPage(['site/contact']); 15 | } 16 | 17 | /** 18 | * @param FunctionalTester $I 19 | */ 20 | public function checkContact(FunctionalTester $I) 21 | { 22 | $I->see('Contact', 'h1'); 23 | } 24 | 25 | /** 26 | * @param FunctionalTester $I 27 | */ 28 | public function checkContactSubmitNoData(FunctionalTester $I) 29 | { 30 | $I->submitForm('#contact-form', []); 31 | 32 | $I->see('Contact', 'h1'); 33 | $I->seeValidationError('Name cannot be blank'); 34 | $I->seeValidationError('Email cannot be blank'); 35 | $I->seeValidationError('Subject cannot be blank'); 36 | $I->seeValidationError('Body cannot be blank'); 37 | $I->seeValidationError('The verification code is incorrect'); 38 | } 39 | 40 | /** 41 | * @param FunctionalTester $I 42 | */ 43 | public function checkContactSubmitNotCorrectEmail(FunctionalTester $I) 44 | { 45 | $I->submitForm('#contact-form', [ 46 | 'ContactForm[name]' => 'tester', 47 | 'ContactForm[email]' => 'tester.email', 48 | 'ContactForm[subject]' => 'test subject', 49 | 'ContactForm[body]' => 'test content', 50 | 'ContactForm[verifyCode]' => 'testme', 51 | ]); 52 | 53 | $I->seeValidationError('Email is not a valid email address.'); 54 | $I->dontSeeValidationError('Name cannot be blank'); 55 | $I->dontSeeValidationError('Subject cannot be blank'); 56 | $I->dontSeeValidationError('Body cannot be blank'); 57 | $I->dontSeeValidationError('The verification code is incorrect'); 58 | } 59 | 60 | /** 61 | * @param FunctionalTester $I 62 | */ 63 | public function checkContactSubmitCorrectData(FunctionalTester $I) 64 | { 65 | $I->submitForm('#contact-form', [ 66 | 'ContactForm[name]' => 'tester', 67 | 'ContactForm[email]' => 'tester@example.com', 68 | 'ContactForm[subject]' => 'test subject', 69 | 'ContactForm[body]' => 'test content', 70 | 'ContactForm[verifyCode]' => 'testme', 71 | ]); 72 | 73 | $I->seeEmailIsSent(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tests/functional/CreateUserCest.php: -------------------------------------------------------------------------------- 1 | haveFixtures([ 23 | 'user' => [ 24 | 'class' => UserAssignmentFixture::class, 25 | ], 26 | ]); 27 | } 28 | 29 | public function createUser(FunctionalTester $I) 30 | { 31 | $I->amOnRoute('site/login'); 32 | $I->submitForm($this->loginFormId, $this->loginFormParams('admin@example.org', '123123')); 33 | $I->amOnRoute('/admin/user/create'); 34 | $I->see('Create User'); 35 | $I->submitForm($this->createUserFormId, $this->createUserFormParams('created-user', 'created-user@example.com', '123123')); 36 | $I->see('Users', 'h1'); 37 | $I->seeRecord('app\models\UserModel', [ 38 | 'username' => 'created-user', 39 | 'email' => 'created-user@example.com', 40 | ]); 41 | } 42 | 43 | protected function loginFormParams($login, $password) 44 | { 45 | return [ 46 | 'LoginForm[email]' => $login, 47 | 'LoginForm[password]' => $password, 48 | ]; 49 | } 50 | 51 | protected function createUserFormParams($username, $email, $password) 52 | { 53 | return [ 54 | 'UserModel[username]' => $username, 55 | 'UserModel[email]' => $email, 56 | 'UserModel[plainPassword]' => $password, 57 | ]; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/functional/HomeCest.php: -------------------------------------------------------------------------------- 1 | amOnPage(\Yii::$app->homeUrl); 12 | $I->seeLink('About Us'); 13 | $I->seeLink('Contact'); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/functional/LoginCest.php: -------------------------------------------------------------------------------- 1 | haveFixtures([ 13 | 'user' => [ 14 | 'class' => UserAssignmentFixture::class, 15 | ], 16 | ]); 17 | $I->amOnRoute('site/login'); 18 | } 19 | 20 | public function checkEmpty(FunctionalTester $I) 21 | { 22 | $I->submitForm('#login-form', $this->formParams('', '')); 23 | $I->see('Email cannot be blank.'); 24 | $I->see('Password cannot be blank.'); 25 | } 26 | 27 | public function checkWrongEmail(FunctionalTester $I) 28 | { 29 | $I->submitForm('#login-form', $this->formParams('admin', 'wrong')); 30 | $I->see('Email is not a valid email address.'); 31 | } 32 | 33 | public function checkWrongPassword(FunctionalTester $I) 34 | { 35 | $I->submitForm('#login-form', $this->formParams('admin@example.org', 'wrong')); 36 | $I->see('Incorrect email or password.'); 37 | } 38 | 39 | public function checkValidLogin(FunctionalTester $I) 40 | { 41 | $I->submitForm('#login-form', $this->formParams('admin@example.org', '123123')); 42 | $I->see('Logout (admin)', 'form button[type=submit]'); 43 | $I->dontSeeLink('Login'); 44 | $I->dontSeeLink('Signup'); 45 | } 46 | 47 | protected function formParams($login, $password) 48 | { 49 | return [ 50 | 'LoginForm[email]' => $login, 51 | 'LoginForm[password]' => $password, 52 | ]; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/functional/SignupCest.php: -------------------------------------------------------------------------------- 1 | amOnRoute('site/signup'); 17 | } 18 | 19 | public function signupWithEmptyFields(FunctionalTester $I) 20 | { 21 | $I->see('Signup', 'h1'); 22 | $I->see('Please fill out the following fields to signup:'); 23 | $I->submitForm($this->formId, []); 24 | $I->see('Username cannot be blank.'); 25 | $I->see('Email cannot be blank.'); 26 | $I->see('Password cannot be blank.'); 27 | } 28 | 29 | public function signupWithWrongEmail(FunctionalTester $I) 30 | { 31 | $I->submitForm( 32 | $this->formId, [ 33 | 'SignupForm[username]' => 'tester', 34 | 'SignupForm[email]' => 'ttttt', 35 | 'SignupForm[password]' => 'tester_password', 36 | ] 37 | ); 38 | $I->dontSee('Username cannot be blank.', '.help-block'); 39 | $I->dontSee('Password cannot be blank.', '.help-block'); 40 | $I->see('Email is not a valid email address.', '.help-block'); 41 | } 42 | 43 | public function signupSuccessfully(FunctionalTester $I) 44 | { 45 | $I->submitForm($this->formId, [ 46 | 'SignupForm[username]' => 'tester', 47 | 'SignupForm[email]' => 'tester.email@example.com', 48 | 'SignupForm[password]' => 'tester_password', 49 | ]); 50 | $I->seeRecord('app\models\UserModel', [ 51 | 'username' => 'tester', 52 | 'email' => 'tester.email@example.com', 53 | ]); 54 | $I->see('Logout (tester)', 'form button[type=submit]'); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/functional/_bootstrap.php: -------------------------------------------------------------------------------- 1 | _model = $this->getMockBuilder('app\models\forms\ContactForm') 23 | ->setMethods(['validate']) 24 | ->getMock(); 25 | 26 | $this->_model->expects($this->once()) 27 | ->method('validate') 28 | ->will($this->returnValue(true)); 29 | 30 | $this->_model->attributes = [ 31 | 'name' => 'Tester', 32 | 'email' => 'tester@example.com', 33 | 'subject' => 'very important letter subject', 34 | 'body' => 'body of current message', 35 | ]; 36 | 37 | expect_that($this->_model->contact('admin@example.com')); 38 | 39 | // using Yii2 module actions to check email was sent 40 | $this->tester->seeEmailIsSent(); 41 | 42 | $emailMessage = $this->tester->grabLastSentEmail(); 43 | expect('valid email is sent', $emailMessage)->isInstanceOf('yii\mail\MessageInterface'); 44 | expect($emailMessage->getTo())->hasKey('admin@example.com'); 45 | expect($emailMessage->getFrom())->hasKey('tester@example.com'); 46 | expect($emailMessage->getSubject())->equals('very important letter subject'); 47 | expect($emailMessage->toString())->contains('body of current message'); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/unit/models/LoginFormTest.php: -------------------------------------------------------------------------------- 1 | _model = new LoginForm([ 24 | 'email' => 'not_existing_username', 25 | 'password' => 'not_existing_password', 26 | ]); 27 | 28 | expect_not($this->_model->login()); 29 | expect_that(Yii::$app->user->isGuest); 30 | } 31 | 32 | public function testLoginWrongPassword() 33 | { 34 | $this->_model = new LoginForm([ 35 | 'email' => 'demo@example.com', 36 | 'password' => 'wrong_password', 37 | ]); 38 | 39 | expect_not($this->_model->login()); 40 | expect_that(Yii::$app->user->isGuest); 41 | expect($this->_model->errors)->hasKey('password'); 42 | } 43 | 44 | public function testLoginCorrect() 45 | { 46 | $this->_model = new LoginForm([ 47 | 'email' => 'admin@example.org', 48 | 'password' => '123123', 49 | ]); 50 | 51 | expect_that($this->_model->login()); 52 | expect_not(Yii::$app->user->isGuest); 53 | expect($this->_model->errors)->hasntKey('password'); 54 | } 55 | 56 | /** 57 | * @inheritdoc 58 | */ 59 | protected function _before() 60 | { 61 | $this->tester->haveFixtures([ 62 | 'user' => [ 63 | 'class' => UserAssignmentFixture::class, 64 | ], 65 | ]); 66 | } 67 | 68 | /** 69 | * @inheritdoc 70 | */ 71 | protected function _after() 72 | { 73 | Yii::$app->user->logout(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tests/unit/models/ResetPasswordFormTest.php: -------------------------------------------------------------------------------- 1 | 'test-user@example.com']); 25 | $this->_model = new ResetPasswordForm($user); 26 | 27 | expect($this->_model->resetPassword())->false(); 28 | expect($this->_model->errors)->hasKey('password'); 29 | expect($this->_model->errors)->hasKey('confirmPassword'); 30 | } 31 | 32 | public function testCheckInvalidConfirmPassword() 33 | { 34 | $user = UserModel::findOne(['email' => 'test-user@example.com']); 35 | $this->_model = new ResetPasswordForm($user, [ 36 | 'password' => '123123', 37 | 'confirmPassword' => '123456', 38 | ]); 39 | 40 | expect($this->_model->resetPassword())->false(); 41 | expect($this->_model->errors)->hasKey('confirmPassword'); 42 | } 43 | 44 | public function testCorrectResetPassword() 45 | { 46 | $user = UserModel::findOne(['email' => 'test-user@example.com']); 47 | $this->_model = new ResetPasswordForm($user, [ 48 | 'password' => '123456', 49 | 'confirmPassword' => '123456', 50 | ]); 51 | 52 | expect($this->_model->resetPassword())->true(); 53 | expect($this->_model->errors)->isEmpty(); 54 | } 55 | 56 | /** 57 | * @inheritdoc 58 | */ 59 | protected function _before() 60 | { 61 | $this->tester->haveFixtures([ 62 | 'user' => [ 63 | 'class' => UserAssignmentFixture::class, 64 | ], 65 | ]); 66 | } 67 | 68 | /** 69 | * @inheritdoc 70 | */ 71 | protected function _after() 72 | { 73 | Yii::$app->user->logout(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tests/unit/models/SignupFormTest.php: -------------------------------------------------------------------------------- 1 | 'some_username', 19 | 'email' => 'some_email@example.com', 20 | 'password' => 'some_password', 21 | ]); 22 | 23 | $user = $model->signup(); 24 | 25 | expect($user)->isInstanceOf('yii2mod\user\models\UserModel'); 26 | 27 | expect($user->username)->equals('some_username'); 28 | expect($user->email)->equals('some_email@example.com'); 29 | expect($user->validatePassword('some_password'))->true(); 30 | } 31 | 32 | public function testNotCorrectSignup() 33 | { 34 | $model = new SignupForm([ 35 | 'username' => 'test-user', 36 | 'email' => 'test-user@example.com', 37 | 'password' => 'some_password', 38 | ]); 39 | 40 | expect_not($model->signup()); 41 | expect_that($model->getErrors('username')); 42 | expect_that($model->getErrors('email')); 43 | 44 | expect($model->getFirstError('username')) 45 | ->equals('This username has already been taken.'); 46 | expect($model->getFirstError('email')) 47 | ->equals('This email address has already been taken.'); 48 | } 49 | 50 | /** 51 | * @inheritdoc 52 | */ 53 | protected function _before() 54 | { 55 | $this->tester->haveFixtures([ 56 | 'user' => [ 57 | 'class' => UserAssignmentFixture::class, 58 | ], 59 | ]); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /traits/AjaxValidationTrait.php: -------------------------------------------------------------------------------- 1 | 15 | * 16 | * @since 1.0 17 | */ 18 | trait AjaxValidationTrait 19 | { 20 | /** 21 | * Performs ajax validation. 22 | * 23 | * @param Model $model 24 | * 25 | * @throws \yii\base\ExitException 26 | */ 27 | protected function performAjaxValidation(Model $model) 28 | { 29 | if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) { 30 | Yii::$app->response->format = Response::FORMAT_JSON; 31 | echo Json::encode(ActiveForm::validate($model)); 32 | Yii::$app->end(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /traits/FindModelTrait.php: -------------------------------------------------------------------------------- 1 | 12 | * 13 | * @since 1.0 14 | */ 15 | trait FindModelTrait 16 | { 17 | /** 18 | * Finds model 19 | * 20 | * @param mixed $modelClass 21 | * @param mixed $condition primary key value or a set of column values 22 | * @param string $notFoundMessage 23 | * 24 | * @throws NotFoundHttpException 25 | * 26 | * @return ActiveRecord 27 | */ 28 | protected function findModel($modelClass, $condition, string $notFoundMessage = 'The requested page does not exist.') 29 | { 30 | if (($model = $modelClass::findOne($condition)) !== null) { 31 | return $model; 32 | } else { 33 | throw new NotFoundHttpException($notFoundMessage); 34 | } 35 | } 36 | 37 | /** 38 | * @param mixed $modelClass 39 | * @param mixed $condition primary key value or a set of column values 40 | * 41 | * @return ActiveRecord 42 | */ 43 | protected function findModelOrCreate($modelClass, $condition) 44 | { 45 | if (($model = $modelClass::findOne($condition)) !== null) { 46 | return $model; 47 | } else { 48 | return new $modelClass($condition); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /views/layouts/main.php: -------------------------------------------------------------------------------- 1 | 16 | beginPage(); ?> 17 | 18 | 19 | 20 | registerMetaTag(['charset' => Yii::$app->charset]); ?> 21 | registerMetaTag(['name' => 'viewport', 'content' => 'width=device-width, initial-scale=1']); ?> 22 | 23 | <?php echo implode(' | ', array_filter([Html::encode($this->title), Yii::$app->name])); ?> 24 | head(); ?> 25 | 26 | 27 | beginBody(); ?> 28 | 29 |
30 | Yii::$app->name, 32 | 'brandUrl' => Yii::$app->homeUrl, 33 | 'options' => [ 34 | 'class' => 'navbar-inverse navbar-fixed-top', 35 | ], 36 | ]); 37 | $menuItems = [ 38 | ['label' => 'Home', 'url' => ['/site/index']], 39 | ['label' => 'About Us', 'url' => ['/about-us'], 'active' => Yii::$app->getRequest()->pathInfo == 'about-us'], 40 | ['label' => 'Contact', 'url' => ['/site/contact']], 41 | ]; 42 | if (Yii::$app->user->isGuest) { 43 | $menuItems[] = ['label' => 'Signup', 'url' => ['/site/signup']]; 44 | $menuItems[] = ['label' => 'Login', 'url' => ['/site/login']]; 45 | } else { 46 | $menuItems[] = ['label' => 'My Account', 'url' => ['/site/account']]; 47 | $menuItems[] = ['label' => 'Administration', 'url' => ['/admin'], 'visible' => Yii::$app->getUser()->can('admin')]; 48 | $menuItems[] = '
  • ' 49 | . Html::beginForm(['/site/logout'], 'post') 50 | . Html::submitButton( 51 | 'Logout (' . Yii::$app->user->identity->username . ')', 52 | ['class' => 'btn btn-link'] 53 | ) 54 | . Html::endForm() 55 | . '
  • '; 56 | } 57 | echo Nav::widget([ 58 | 'options' => ['class' => 'navbar-nav navbar-right'], 59 | 'items' => $menuItems, 60 | ]); 61 | NavBar::end(); 62 | ?> 63 |
    64 | isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [], 66 | ]); ?> 67 | 68 |
    69 |
    70 | 71 |
    72 |
    73 |

    © name . ' ' . date('Y'); ?>

    74 | [ 76 | ['label' => 'Terms & Conditions', 'url' => ['/terms-and-conditions']], 77 | ['label' => 'Privacy Policy', 'url' => ['/privacy-policy']], 78 | ], 79 | ]); 80 | ?> 81 |
    82 |
    83 | 84 | endBody(); ?> 85 | 86 | 87 | endPage(); ?> -------------------------------------------------------------------------------- /views/site/account.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'My Account'); 10 | ?> 11 |
    12 |
    13 |
    14 |
    15 |
    16 | 17 |
    18 | 19 |
    20 | 'change-password-form']); ?> 21 | field($resetPasswordForm, 'password')->passwordInput(); ?> 22 | field($resetPasswordForm, 'confirmPassword')->passwordInput(); ?> 23 |
    24 | 'btn btn-default']); ?> 25 | 'btn btn-success']); ?> 26 |
    27 | 28 |
    29 |
    30 |
    31 |
    32 |
    33 |
    34 | 35 |
    36 |
    37 | Yii::$app->user->identity, 39 | 'attributes' => [ 40 | 'username', 41 | 'email', 42 | 'last_login:date', 43 | ], 44 | ]); ?> 45 |
    46 |
    47 |
    48 |
    49 |
    50 | -------------------------------------------------------------------------------- /views/site/contact.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('contact', 'Contact'); 12 | $this->params['breadcrumbs'][] = $this->title; 13 | ?> 14 |
    15 |

    title); ?>

    16 |

    17 | 18 |

    19 |
    20 |
    21 | 'contact-form']); ?> 22 | field($model, 'name'); ?> 23 | field($model, 'email'); ?> 24 | field($model, 'subject'); ?> 25 | field($model, 'body')->textArea(['rows' => 6]); ?> 26 | field($model, 'verifyCode')->widget(Captcha::class, [ 27 | 'template' => '
    {image}
    {input}
    ', 28 | ]); ?> 29 |
    30 | 'btn btn-primary', 'name' => 'contact-button']); ?> 31 |
    32 | 33 |
    34 |
    35 |
    36 | -------------------------------------------------------------------------------- /views/site/error.php: -------------------------------------------------------------------------------- 1 | title = $name; 11 | ?> 12 |
    13 | 14 |

    title); ?>

    15 | 16 |
    17 | 18 |
    19 | 20 |

    21 | The above error occurred while the Web server was processing your request. 22 |

    23 |

    24 | Please contact us if you think this is a server error. Thank you. 25 |

    26 | 27 |
    28 | -------------------------------------------------------------------------------- /views/site/index.php: -------------------------------------------------------------------------------- 1 | title = 'My Yii Application'; 4 | ?> 5 |
    6 |
    7 |

    Congratulations!

    8 |

    You have successfully created your Yii-powered application.

    9 |
    10 |
    11 | -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | /index.php 2 | /index-test.php 3 | -------------------------------------------------------------------------------- /web/.htaccess: -------------------------------------------------------------------------------- 1 | # Block access to "hidden" directories whose names begin with a period. This 2 | # includes directories used by version control systems such as Subversion or Git. 3 | 4 | RewriteCond %{SCRIPT_FILENAME} -d 5 | RewriteCond %{SCRIPT_FILENAME} -f 6 | RewriteRule "(^|/)\." - [F] 7 | 8 | 9 | 10 | # Block access to backup and source files 11 | # This files may be left by some text/html editors and 12 | # pose a great security danger, when someone can access them 13 | 14 | Order allow,deny 15 | Deny from all 16 | Satisfy All 17 | 18 | 19 | # Increase cookie security 20 | 21 | php_value session.cookie_httponly true 22 | 23 | 24 | # Settings to hide index.php and ensure pretty urls 25 | RewriteEngine on 26 | 27 | # if a directory or a file exists, use it directly 28 | RewriteCond %{REQUEST_FILENAME} !-f 29 | RewriteCond %{REQUEST_FILENAME} !-d 30 | 31 | # otherwise forward it to index.php 32 | RewriteRule ^([^/].*)$ /index.php/$1 [L] -------------------------------------------------------------------------------- /web/assets/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /web/css/admin.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | height: 100%; 4 | } 5 | 6 | .wrap { 7 | min-height: 100%; 8 | height: auto; 9 | margin: 0 auto -60px; 10 | padding: 0 0 60px; 11 | } 12 | 13 | .wrap > .container { 14 | padding: 70px 15px 20px; 15 | } 16 | 17 | .footer { 18 | height: 60px; 19 | background-color: #f5f5f5; 20 | border-top: 1px solid #ddd; 21 | padding-top: 20px; 22 | } 23 | 24 | .jumbotron { 25 | text-align: center; 26 | background-color: transparent; 27 | } 28 | 29 | .jumbotron .btn { 30 | font-size: 21px; 31 | padding: 14px 24px; 32 | } 33 | 34 | .not-set { 35 | color: #c55; 36 | font-style: italic; 37 | } 38 | 39 | /* add sorting icons to gridview sort links */ 40 | a.asc:after, a.desc:after { 41 | position: relative; 42 | top: 1px; 43 | display: inline-block; 44 | font-family: 'Glyphicons Halflings'; 45 | font-style: normal; 46 | font-weight: normal; 47 | line-height: 1; 48 | padding-left: 5px; 49 | } 50 | 51 | a.asc:after { 52 | content: /*"\e113"*/ "\e151"; 53 | } 54 | 55 | a.desc:after { 56 | content: /*"\e114"*/ "\e152"; 57 | } 58 | 59 | .sort-numerical a.asc:after { 60 | content: "\e153"; 61 | } 62 | 63 | .sort-numerical a.desc:after { 64 | content: "\e154"; 65 | } 66 | 67 | .sort-ordinal a.asc:after { 68 | content: "\e155"; 69 | } 70 | 71 | .sort-ordinal a.desc:after { 72 | content: "\e156"; 73 | } 74 | 75 | .grid-view th { 76 | white-space: nowrap; 77 | } 78 | 79 | .hint-block { 80 | display: block; 81 | margin-top: 5px; 82 | color: #999; 83 | } 84 | 85 | .error-summary { 86 | color: #a94442; 87 | background: #fdf7f7; 88 | border-left: 3px solid #eed3d7; 89 | padding: 10px 20px; 90 | margin: 0 0 15px 0; 91 | } 92 | 93 | .navbar { 94 | border-radius: 0 !important; 95 | } 96 | 97 | .admin-side-nav { 98 | background-color: rgba(0, 0, 0, 0); 99 | border: 1px solid rgba(216, 207, 207, 0.9); 100 | border-radius: 4px; 101 | } 102 | 103 | .admin-side-nav > li > a { 104 | border-radius: 0; 105 | } 106 | 107 | .admin-side-nav li a { 108 | border-radius: 0; 109 | border-bottom: 1px solid #ddd; 110 | } 111 | -------------------------------------------------------------------------------- /web/css/site.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | height: 100%; 4 | } 5 | 6 | .wrap { 7 | min-height: 100%; 8 | height: auto; 9 | margin: 0 auto -60px; 10 | padding: 0 0 60px; 11 | } 12 | 13 | .wrap > .container { 14 | padding: 70px 15px 20px; 15 | } 16 | 17 | .footer { 18 | height: 60px; 19 | background-color: #f5f5f5; 20 | border-top: 1px solid #ddd; 21 | padding-top: 20px; 22 | } 23 | 24 | .jumbotron { 25 | text-align: center; 26 | background-color: transparent; 27 | } 28 | 29 | .jumbotron .btn { 30 | font-size: 21px; 31 | padding: 14px 24px; 32 | } 33 | 34 | .not-set { 35 | color: #c55; 36 | font-style: italic; 37 | } 38 | 39 | /* add sorting icons to gridview sort links */ 40 | a.asc:after, a.desc:after { 41 | position: relative; 42 | top: 1px; 43 | display: inline-block; 44 | font-family: 'Glyphicons Halflings'; 45 | font-style: normal; 46 | font-weight: normal; 47 | line-height: 1; 48 | padding-left: 5px; 49 | } 50 | 51 | a.asc:after { 52 | content: /*"\e113"*/ "\e151"; 53 | } 54 | 55 | a.desc:after { 56 | content: /*"\e114"*/ "\e152"; 57 | } 58 | 59 | .sort-numerical a.asc:after { 60 | content: "\e153"; 61 | } 62 | 63 | .sort-numerical a.desc:after { 64 | content: "\e154"; 65 | } 66 | 67 | .sort-ordinal a.asc:after { 68 | content: "\e155"; 69 | } 70 | 71 | .sort-ordinal a.desc:after { 72 | content: "\e156"; 73 | } 74 | 75 | .grid-view th { 76 | white-space: nowrap; 77 | } 78 | 79 | .hint-block { 80 | display: block; 81 | margin-top: 5px; 82 | color: #999; 83 | } 84 | 85 | .error-summary { 86 | color: #a94442; 87 | background: #fdf7f7; 88 | border-left: 3px solid #eed3d7; 89 | padding: 10px 20px; 90 | margin: 0 0 15px 0; 91 | } 92 | .footer ul { 93 | margin: 0; 94 | padding: 0; 95 | list-style: none; 96 | float:right; 97 | 98 | } 99 | .footer ul li { 100 | display: inline; 101 | margin: 5px; 102 | } 103 | .footer ul li a { 104 | text-decoration: none; 105 | color: #333; 106 | } 107 | .footer ul li a:hover { 108 | color: #505050; 109 | } 110 | .bs-callout+.bs-callout { 111 | margin-top: -5px; 112 | } 113 | .bs-callout-info { 114 | border-left-color: #5bc0de; 115 | } 116 | .bs-callout { 117 | padding: 20px; 118 | margin: 20px 0; 119 | border: 1px solid #eee; 120 | border-left-width: 5px; 121 | border-radius: 3px; 122 | } 123 | /* align the logout "link" (button in form) of the navbar */ 124 | .nav > li > form { 125 | padding: 8px; 126 | } 127 | 128 | .nav > li > form > button:hover { 129 | text-decoration: none; 130 | } -------------------------------------------------------------------------------- /web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yii2mod/base/01536f96e454df49a49acbeb2c00099c0e99c643/web/favicon.ico -------------------------------------------------------------------------------- /web/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: -------------------------------------------------------------------------------- /yii.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem ------------------------------------------------------------- 4 | rem Yii command line bootstrap script for Windows. 5 | rem 6 | rem @author Qiang Xue 7 | rem @link http://www.yiiframework.com/ 8 | rem @copyright Copyright © 2012 Yii Software LLC 9 | rem @license http://www.yiiframework.com/license/ 10 | rem ------------------------------------------------------------- 11 | 12 | @setlocal 13 | 14 | set YII_PATH=%~dp0 15 | 16 | if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe 17 | 18 | "%PHP_COMMAND%" "%YII_PATH%yii" %* 19 | 20 | @endlocal 21 | --------------------------------------------------------------------------------