├── .env.dist
├── bin
└── .gitignore
├── runtime
└── .gitignore
├── tests
├── _output
│ └── .gitignore
├── unit
│ ├── _bootstrap.php
│ └── models
│ │ ├── ContactFormTest.php
│ │ ├── SignupFormTest.php
│ │ ├── LoginFormTest.php
│ │ └── ResetPasswordFormTest.php
├── acceptance
│ └── _bootstrap.php
├── functional
│ ├── _bootstrap.php
│ ├── HomeCest.php
│ ├── AboutCest.php
│ ├── AdminLoginCest.php
│ ├── LoginCest.php
│ ├── CreateUserCest.php
│ ├── SignupCest.php
│ ├── ChangePasswordViaAccountPageCest.php
│ └── ContactFormCest.php
├── _bootstrap.php
├── acceptance.suite.example.yml
├── unit.suite.yml
├── _data
│ ├── user_assignment.php
│ ├── user.php
│ └── cms.php
├── fixtures
│ ├── UserFixture.php
│ ├── CmsFixture.php
│ └── UserAssignmentFixture.php
├── functional.suite.yml
└── _support
│ ├── UnitTester.php
│ ├── AcceptanceTester.php
│ └── FunctionalTester.php
├── web
├── assets
│ └── .gitignore
├── robots.txt
├── .gitignore
├── favicon.ico
├── .htaccess
└── css
│ ├── admin.css
│ └── site.css
├── data
├── volumes
│ ├── db
│ │ └── .gitignore
│ └── redis
│ │ └── .gitignore
└── docker
│ ├── web
│ ├── crontab
│ ├── supervisord.conf
│ ├── .bashrc
│ ├── apache.conf
│ ├── prod
│ │ └── Dockerfile
│ └── dev
│ │ └── Dockerfile
│ ├── redis
│ └── Dockerfile
│ └── db
│ └── Dockerfile
├── .bowerrc
├── environments
├── dev
│ ├── config
│ │ ├── params-local.php
│ │ ├── console-local.php
│ │ ├── test_db.php
│ │ ├── common-local.php
│ │ ├── main-local.php
│ │ └── test.php
│ ├── web
│ │ ├── index.php
│ │ └── index-test.php
│ ├── yii_test.bat
│ ├── yii_test
│ └── yii
├── prod
│ ├── config
│ │ ├── params-local.php
│ │ ├── console-local.php
│ │ ├── main-local.php
│ │ └── common-local.php
│ ├── web
│ │ └── index.php
│ └── yii
└── index.php
├── config
├── .gitignore
├── params.php
├── schedule.php
├── console.php
├── common.php
└── main.php
├── docker-compose.override.yml.dist
├── models
├── UserModel.php
└── forms
│ ├── ResetPasswordForm.php
│ └── ContactForm.php
├── views
├── site
│ ├── index.php
│ ├── error.php
│ ├── contact.php
│ └── account.php
└── layouts
│ └── main.php
├── mail
├── passwordResetToken.php
└── layouts
│ └── html.php
├── modules
└── admin
│ ├── controllers
│ ├── SettingsController.php
│ └── UserController.php
│ ├── views
│ ├── user
│ │ ├── create.php
│ │ ├── update.php
│ │ ├── _form.php
│ │ └── index.php
│ └── layouts
│ │ └── column2.php
│ ├── Module.php
│ └── models
│ └── search
│ └── UserSearch.php
├── init.bat
├── yii.bat
├── rbac
└── migrations
│ └── m160722_085418_init.php
├── assets
├── AdminAsset.php
└── AppAsset.php
├── .gitignore
├── traits
├── AjaxValidationTrait.php
└── FindModelTrait.php
├── codeception.yml
├── migrations
└── m161109_121736_create_session_table.php
├── LICENSE.md
├── .php_cs
├── docker-compose.yml
├── commands
├── AppController.php
└── BaseController.php
├── composer.json
├── messages
└── config.php
├── controllers
└── SiteController.php
├── requirements.php
├── README.md
└── init
/.env.dist:
--------------------------------------------------------------------------------
1 | HOST_UID=1000
2 |
--------------------------------------------------------------------------------
/bin/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/runtime/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/tests/_output/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
--------------------------------------------------------------------------------
/web/assets/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/web/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow:
--------------------------------------------------------------------------------
/data/volumes/db/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory" : "vendor/bower"
3 | }
--------------------------------------------------------------------------------
/data/volumes/redis/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/web/.gitignore:
--------------------------------------------------------------------------------
1 | /index.php
2 | /index-test.php
3 |
--------------------------------------------------------------------------------
/environments/dev/config/params-local.php:
--------------------------------------------------------------------------------
1 | > /var/log/cron.log 2>&1
2 |
--------------------------------------------------------------------------------
/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/params.php:
--------------------------------------------------------------------------------
1 | 'admin@example.com',
5 | 'user.passwordResetTokenExpire' => 3600,
6 | ];
7 |
--------------------------------------------------------------------------------
/docker-compose.override.yml.dist:
--------------------------------------------------------------------------------
1 | version: '2'
2 | services:
3 | web:
4 | ports:
5 | - "80:80"
6 | build:
7 | dockerfile: dev/Dockerfile
8 |
--------------------------------------------------------------------------------
/environments/dev/config/console-local.php:
--------------------------------------------------------------------------------
1 | ['gii'],
5 | 'modules' => [
6 | 'gii' => 'yii\gii\Module',
7 | ],
8 | ];
9 |
--------------------------------------------------------------------------------
/environments/dev/config/test_db.php:
--------------------------------------------------------------------------------
1 | 'mysql:host=localhost;dbname=yii2mod_base_test',
5 | 'username' => 'root',
6 | 'password' => '',
7 | ];
8 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/environments/prod/config/main-local.php:
--------------------------------------------------------------------------------
1 | [
5 | 'request' => [
6 | 'cookieValidationKey' => '',
7 | ],
8 | ],
9 | ];
10 |
11 | return $config;
12 |
--------------------------------------------------------------------------------
/tests/_bootstrap.php:
--------------------------------------------------------------------------------
1 |
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 |
--------------------------------------------------------------------------------
/tests/acceptance.suite.example.yml:
--------------------------------------------------------------------------------
1 | class_name: AcceptanceTester
2 | modules:
3 | enabled:
4 | - WebDriver:
5 | url: http://localhost:8080
6 | browser: firefox
7 | - Yii2:
8 | part: init
--------------------------------------------------------------------------------
/models/UserModel.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/tests/functional/HomeCest.php:
--------------------------------------------------------------------------------
1 | amOnPage(\Yii::$app->homeUrl);
12 | $I->seeLink('About Us');
13 | $I->seeLink('Contact');
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/tests/fixtures/UserFixture.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/tests/functional.suite.yml:
--------------------------------------------------------------------------------
1 | # Codeception Test Suite Configuration
2 |
3 | # suite for functional (integration) tests.
4 | # emulate web requests and make application process them.
5 | # (tip: better to use with frameworks).
6 |
7 | # RUN `build` COMMAND AFTER ADDING/REMOVING MODULES.
8 | #basic/web/index.php
9 | class_name: FunctionalTester
10 | modules:
11 | enabled:
12 | - Filesystem
13 | - Yii2
14 | config:
15 | Yii2:
16 | cleanup: false
17 |
--------------------------------------------------------------------------------
/modules/admin/controllers/SettingsController.php:
--------------------------------------------------------------------------------
1 | 'yii2mod\cron\actions\CronLogAction',
21 | ];
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/config/schedule.php:
--------------------------------------------------------------------------------
1 | command('app/generate-sitemap')->description('Generate Sitemap')->weekly();
21 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/tests/functional/AboutCest.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 |
--------------------------------------------------------------------------------
/environments/dev/web/index.php:
--------------------------------------------------------------------------------
1 | run();
17 |
--------------------------------------------------------------------------------
/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/web/index.php:
--------------------------------------------------------------------------------
1 | run();
17 |
--------------------------------------------------------------------------------
/environments/dev/web/index-test.php:
--------------------------------------------------------------------------------
1 | run();
17 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/tests/fixtures/UserAssignmentFixture.php:
--------------------------------------------------------------------------------
1 | tableName = Yii::$app->authManager->assignmentTable;
26 |
27 | parent::init();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/views/site/error.php:
--------------------------------------------------------------------------------
1 | title = $name;
11 | ?>
12 |
13 |
14 |
= Html::encode($this->title); ?>
15 |
16 |
17 | = nl2br(Html::encode($message)); ?>
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/assets/AdminAsset.php:
--------------------------------------------------------------------------------
1 |
9 | beginPage(); ?>
10 |
11 |
12 |
13 |
14 | = Html::encode($this->title); ?>
15 | head(); ?>
16 |
17 |
18 | beginBody(); ?>
19 | = $content; ?>
20 | endBody(); ?>
21 |
22 |
23 | endPage(); ?>
24 |
--------------------------------------------------------------------------------
/tests/_support/UnitTester.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 |
--------------------------------------------------------------------------------
/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:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | run();
26 | exit($exitCode);
27 |
--------------------------------------------------------------------------------
/environments/prod/yii:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | run();
26 | exit($exitCode);
27 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/codeception.yml:
--------------------------------------------------------------------------------
1 | actor: Tester
2 | paths:
3 | tests: tests
4 | log: tests/_output
5 | data: tests/_data
6 | helpers: tests/_support
7 | settings:
8 | bootstrap: _bootstrap.php
9 | memory_limit: 1024M
10 | colors: true
11 | modules:
12 | config:
13 | Yii2:
14 | configFile: 'config/test.php'
15 |
16 | # To enable code coverage:
17 | #coverage:
18 | # #c3_url: http://localhost:8080/index-test.php/
19 | # enabled: true
20 | # #remote: true
21 | # #remote_config: '../tests/codeception.yml'
22 | # whitelist:
23 | # include:
24 | # - models/*
25 | # - controllers/*
26 | # - commands/*
27 | # - mail/*
28 | # blacklist:
29 | # include:
30 | # - assets/*
31 | # - config/*
32 | # - runtime/*
33 | # - vendor/*
34 | # - views/*
35 | # - web/*
36 | # - tests/*
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/tests/_support/FunctionalTester.php:
--------------------------------------------------------------------------------
1 | see($message, '.help-block');
26 | }
27 |
28 | public function dontSeeValidationError($message)
29 | {
30 | $this->dontSee($message, '.help-block');
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/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.
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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]
--------------------------------------------------------------------------------
/tests/_data/cms.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/environments/dev/config/test.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 |
--------------------------------------------------------------------------------
/modules/admin/views/user/_form.php:
--------------------------------------------------------------------------------
1 |
11 |
12 |
35 |
--------------------------------------------------------------------------------
/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/site/contact.php:
--------------------------------------------------------------------------------
1 | title = Yii::t('contact', 'Contact');
12 | $this->params['breadcrumbs'][] = $this->title;
13 | ?>
14 |
36 |
--------------------------------------------------------------------------------
/commands/AppController.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/unit/models/ContactFormTest.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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/models/forms/ContactForm.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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | 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 |
83 |
84 | endBody(); ?>
85 |
86 |
87 | endPage(); ?>
--------------------------------------------------------------------------------
/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/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 | 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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | [](https://packagist.org/packages/yii2mod/base)
16 | [](https://packagist.org/packages/yii2mod/base)
17 | [](https://packagist.org/packages/yii2mod/base)
18 | [](https://scrutinizer-ci.com/g/yii2mod/base/?branch=master)
19 | [](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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------