Browse Source

first commit

Oleg K 2 years ago
commit
a0dd11fa3d
94 changed files with 10416 additions and 0 deletions
  1. 3 0
      .bowerrc
  2. 11 0
      .env-dist
  3. 33 0
      .gitignore
  4. 29 0
      LICENSE.md
  5. 233 0
      README.md
  6. 92 0
      Vagrantfile
  7. 29 0
      assets/AdminLteAsset.php
  8. 31 0
      assets/AppAsset.php
  9. 27 0
      codeception.yml
  10. 34 0
      commands/HelloController.php
  11. 64 0
      components/GlobalAccessBehavior.php
  12. 18 0
      components/TranslationEventHandler.php
  13. 30 0
      components/User.php
  14. 76 0
      composer.json
  15. 6210 0
      composer.lock
  16. 71 0
      config/console.php
  17. 15 0
      config/db.php
  18. 7 0
      config/params.php
  19. 42 0
      config/test.php
  20. 6 0
      config/test_db.php
  21. 224 0
      config/web.php
  22. 16 0
      controllers/PricingController.php
  23. 128 0
      controllers/SiteController.php
  24. 9 0
      docker-compose.yml
  25. 22 0
      mail/layouts/html.php
  26. 4 0
      messages/ru/user.php
  27. 25 0
      migrations/m210602_124525_user_type_field.php
  28. 65 0
      models/ContactForm.php
  29. 81 0
      models/LoginForm.php
  30. 104 0
      models/User.php
  31. 32 0
      models/user/User.php
  32. 162 0
      requirements.php
  33. 2 0
      runtime/.gitignore
  34. 6 0
      tests/_bootstrap.php
  35. 1 0
      tests/_data/.gitkeep
  36. 2 0
      tests/_output/.gitignore
  37. 26 0
      tests/_support/AcceptanceTester.php
  38. 23 0
      tests/_support/FunctionalTester.php
  39. 26 0
      tests/_support/UnitTester.php
  40. 10 0
      tests/acceptance.suite.yml.example
  41. 12 0
      tests/acceptance/AboutCest.php
  42. 34 0
      tests/acceptance/ContactCest.php
  43. 18 0
      tests/acceptance/HomeCest.php
  44. 21 0
      tests/acceptance/LoginCest.php
  45. 1 0
      tests/acceptance/_bootstrap.php
  46. 29 0
      tests/bin/yii
  47. 20 0
      tests/bin/yii.bat
  48. 13 0
      tests/functional.suite.yml
  49. 57 0
      tests/functional/ContactFormCest.php
  50. 59 0
      tests/functional/LoginFormCest.php
  51. 1 0
      tests/functional/_bootstrap.php
  52. 11 0
      tests/unit.suite.yml
  53. 3 0
      tests/unit/_bootstrap.php
  54. 41 0
      tests/unit/models/ContactFormTest.php
  55. 51 0
      tests/unit/models/LoginFormTest.php
  56. 44 0
      tests/unit/models/UserTest.php
  57. 2 0
      vagrant/config/.gitignore
  58. 22 0
      vagrant/config/vagrant-local.example.yml
  59. 38 0
      vagrant/nginx/app.conf
  60. 3 0
      vagrant/nginx/log/.gitignore
  61. 18 0
      vagrant/provision/always-as-root.sh
  62. 79 0
      vagrant/provision/once-as-root.sh
  63. 31 0
      vagrant/provision/once-as-vagrant.sh
  64. 50 0
      vagrant/provision/provision.awk
  65. 42 0
      views/layouts/guest.php
  66. 224 0
      views/layouts/main.php
  67. 65 0
      views/pricing/index.php
  68. 18 0
      views/site/about.php
  69. 68 0
      views/site/contact.php
  70. 27 0
      views/site/error.php
  71. 53 0
      views/site/index.php
  72. 47 0
      views/site/login.php
  73. 32 0
      views/user/_alert.php
  74. 44 0
      views/user/admin/_account.php
  75. 31 0
      views/user/admin/_assignments.php
  76. 53 0
      views/user/admin/_info.php
  77. 68 0
      views/user/admin/_menu.php
  78. 50 0
      views/user/admin/_profile.php
  79. 20 0
      views/user/admin/_user.php
  80. 83 0
      views/user/admin/create.php
  81. 142 0
      views/user/admin/index.php
  82. 104 0
      views/user/admin/update.php
  83. 110 0
      views/user/security/login.php
  84. 4 0
      web/.htaccess
  85. 2 0
      web/assets/.gitignore
  86. 115 0
      web/css/site.css
  87. BIN
      web/favicon.ico
  88. 16 0
      web/index-test.php
  89. 17 0
      web/index.php
  90. 2 0
      web/robots.txt
  91. 75 0
      widgets/Alert.php
  92. 172 0
      widgets/MainSidebarMenu.php
  93. 25 0
      yii
  94. 20 0
      yii.bat

+ 3 - 0
.bowerrc

@@ -0,0 +1,3 @@
+{
+    "directory" : "vendor/bower-asset"
+}

+ 11 - 0
.env-dist

@@ -0,0 +1,11 @@
+DB_HOST=''
+DB_NAME=''
+DB_USER=''
+DB_PASS=''
+DB_TBL_PREFIX=''
+
+SMTP_HOST=''
+SMTP_LOGIN=''
+SMTP_PASS=''
+SMTP_PORT=587
+SMTP_ENC='tls'

+ 33 - 0
.gitignore

@@ -0,0 +1,33 @@
+# phpstorm project files
+.idea
+
+# netbeans project files
+nbproject
+
+# zend studio for eclipse project files
+.buildpath
+.project
+.settings
+
+# windows thumbnail cache
+Thumbs.db
+
+# composer vendor dir
+/vendor
+
+# composer itself is not needed
+composer.phar
+
+# Mac DS_Store Files
+.DS_Store
+
+# phpunit itself is not needed
+phpunit.phar
+# local phpunit config
+/phpunit.xml
+
+tests/_output/*
+tests/_support/_generated
+
+#vagrant folder
+/.vagrant

+ 29 - 0
LICENSE.md

@@ -0,0 +1,29 @@
+Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com)
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+ * Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in
+   the documentation and/or other materials provided with the
+   distribution.
+ * Neither the name of Yii Software LLC nor the names of its
+   contributors may be used to endorse or promote products derived
+   from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.

+ 233 - 0
README.md

@@ -0,0 +1,233 @@
+<p align="center">
+    <a href="https://github.com/yiisoft" target="_blank">
+        <img src="https://avatars0.githubusercontent.com/u/993323" height="100px">
+    </a>
+    <h1 align="center">Yii 2 Basic Project Template</h1>
+    <br>
+</p>
+
+Yii 2 Basic Project Template is a skeleton [Yii 2](http://www.yiiframework.com/) application best for
+rapidly creating small projects.
+
+The template contains the basic features including user login/logout and a contact page.
+It includes all commonly used configurations that would allow you to focus on adding new
+features to your application.
+
+[![Latest Stable Version](https://img.shields.io/packagist/v/yiisoft/yii2-app-basic.svg)](https://packagist.org/packages/yiisoft/yii2-app-basic)
+[![Total Downloads](https://img.shields.io/packagist/dt/yiisoft/yii2-app-basic.svg)](https://packagist.org/packages/yiisoft/yii2-app-basic)
+[![build](https://github.com/yiisoft/yii2-app-basic/workflows/build/badge.svg)](https://github.com/yiisoft/yii2-app-basic/actions?query=workflow%3Abuild)
+
+DIRECTORY STRUCTURE
+-------------------
+
+      assets/             contains assets definition
+      commands/           contains console commands (controllers)
+      config/             contains application configurations
+      controllers/        contains Web controller classes
+      mail/               contains view files for e-mails
+      models/             contains model classes
+      runtime/            contains files generated during runtime
+      tests/              contains various tests for the basic application
+      vendor/             contains dependent 3rd-party packages
+      views/              contains view files for the Web application
+      web/                contains the entry script and Web resources
+
+
+
+REQUIREMENTS
+------------
+
+The minimum requirement by this project template that your Web server supports PHP 5.6.0.
+
+
+INSTALLATION
+------------
+
+### Install via Composer
+
+If you do not have [Composer](http://getcomposer.org/), you may install it by following the instructions
+at [getcomposer.org](http://getcomposer.org/doc/00-intro.md#installation-nix).
+
+You can then install this project template using the following command:
+
+~~~
+composer create-project --prefer-dist yiisoft/yii2-app-basic basic
+~~~
+
+Now you should be able to access the application through the following URL, assuming `basic` is the directory
+directly under the Web root.
+
+~~~
+http://localhost/basic/web/
+~~~
+
+### Install from an Archive File
+
+Extract the archive file downloaded from [yiiframework.com](http://www.yiiframework.com/download/) to
+a directory named `basic` that is directly under the Web root.
+
+Set cookie validation key in `config/web.php` file to some random secret string:
+
+```php
+'request' => [
+    // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
+    'cookieValidationKey' => '<secret random string goes here>',
+],
+```
+
+You can then access the application through the following URL:
+
+~~~
+http://localhost/basic/web/
+~~~
+
+
+### Install with Docker
+
+Update your vendor packages
+
+    docker-compose run --rm php composer update --prefer-dist
+    
+Run the installation triggers (creating cookie validation code)
+
+    docker-compose run --rm php composer install    
+    
+Start the container
+
+    docker-compose up -d
+    
+You can then access the application through the following URL:
+
+    http://127.0.0.1:8000
+
+**NOTES:** 
+- Minimum required Docker engine version `17.04` for development (see [Performance tuning for volume mounts](https://docs.docker.com/docker-for-mac/osxfs-caching/))
+- The default configuration uses a host-volume in your home directory `.docker-composer` for composer caches
+
+
+CONFIGURATION
+-------------
+
+### Database
+
+Edit the file `config/db.php` with real data, for example:
+
+```php
+return [
+    'class' => 'yii\db\Connection',
+    'dsn' => 'mysql:host=localhost;dbname=yii2basic',
+    'username' => 'root',
+    'password' => '1234',
+    'charset' => 'utf8',
+];
+```
+
+**NOTES:**
+- Yii won't create the database for you, this has to be done manually before you can access it.
+- Check and edit the other files in the `config/` directory to customize your application as required.
+- Refer to the README in the `tests` directory for information specific to basic application tests.
+
+
+TESTING
+-------
+
+Tests are located in `tests` directory. They are developed with [Codeception PHP Testing Framework](http://codeception.com/).
+By default there are 3 test suites:
+
+- `unit`
+- `functional`
+- `acceptance`
+
+Tests can be executed by running
+
+```
+vendor/bin/codecept run
+```
+
+The command above will execute unit and functional tests. Unit tests are testing the system components, while functional
+tests are for testing user interaction. Acceptance tests are disabled by default as they require additional setup since
+they perform testing in real browser. 
+
+
+### Running  acceptance tests
+
+To execute acceptance tests do the following:  
+
+1. Rename `tests/acceptance.suite.yml.example` to `tests/acceptance.suite.yml` to enable suite configuration
+
+2. Replace `codeception/base` package in `composer.json` with `codeception/codeception` to install full featured
+   version of Codeception
+
+3. Update dependencies with Composer 
+
+    ```
+    composer update  
+    ```
+
+4. Download [Selenium Server](http://www.seleniumhq.org/download/) and launch it:
+
+    ```
+    java -jar ~/selenium-server-standalone-x.xx.x.jar
+    ```
+
+    In case of using Selenium Server 3.0 with Firefox browser since v48 or Google Chrome since v53 you must download [GeckoDriver](https://github.com/mozilla/geckodriver/releases) or [ChromeDriver](https://sites.google.com/a/chromium.org/chromedriver/downloads) and launch Selenium with it:
+
+    ```
+    # for Firefox
+    java -jar -Dwebdriver.gecko.driver=~/geckodriver ~/selenium-server-standalone-3.xx.x.jar
+    
+    # for Google Chrome
+    java -jar -Dwebdriver.chrome.driver=~/chromedriver ~/selenium-server-standalone-3.xx.x.jar
+    ``` 
+    
+    As an alternative way you can use already configured Docker container with older versions of Selenium and Firefox:
+    
+    ```
+    docker run --net=host selenium/standalone-firefox:2.53.0
+    ```
+
+5. (Optional) Create `yii2basic_test` database and update it by applying migrations if you have them.
+
+   ```
+   tests/bin/yii migrate
+   ```
+
+   The database configuration can be found at `config/test_db.php`.
+
+
+6. Start web server:
+
+    ```
+    tests/bin/yii serve
+    ```
+
+7. Now you can run all available tests
+
+   ```
+   # run all available tests
+   vendor/bin/codecept run
+
+   # run acceptance tests
+   vendor/bin/codecept run acceptance
+
+   # run only unit and functional tests
+   vendor/bin/codecept run unit,functional
+   ```
+
+### Code coverage support
+
+By default, code coverage is disabled in `codeception.yml` configuration file, you should uncomment needed rows to be able
+to collect code coverage. You can run your tests and collect coverage with the following command:
+
+```
+#collect coverage for all tests
+vendor/bin/codecept run --coverage --coverage-html --coverage-xml
+
+#collect coverage only for unit tests
+vendor/bin/codecept run unit --coverage --coverage-html --coverage-xml
+
+#collect coverage for unit and functional tests
+vendor/bin/codecept run functional,unit --coverage --coverage-html --coverage-xml
+```
+
+You can see code coverage output under the `tests/_output` directory.

+ 92 - 0
Vagrantfile

@@ -0,0 +1,92 @@
+require 'yaml'
+require 'fileutils'
+
+required_plugins_installed = nil
+required_plugins = %w( vagrant-hostmanager vagrant-vbguest )
+required_plugins.each do |plugin|
+  unless Vagrant.has_plugin? plugin
+    system "vagrant plugin install #{plugin}"
+    required_plugins_installed = true
+  end
+end
+
+# IF plugin[s] was just installed - restart required
+if required_plugins_installed
+  # Get CLI command[s] and call again
+  system 'vagrant' + ARGV.to_s.gsub(/\[\"|\", \"|\"\]/, ' ')
+  exit
+end
+
+domains = {
+  app: 'yii2basic.test'
+}
+
+vagrantfile_dir_path = File.dirname(__FILE__)
+
+config = {
+  local: vagrantfile_dir_path + '/vagrant/config/vagrant-local.yml',
+  example: vagrantfile_dir_path + '/vagrant/config/vagrant-local.example.yml'
+}
+
+# copy config from example if local config not exists
+FileUtils.cp config[:example], config[:local] unless File.exist?(config[:local])
+# read config
+options = YAML.load_file config[:local]
+
+# check github token
+if options['github_token'].nil? || options['github_token'].to_s.length != 40
+  puts "You must place REAL GitHub token into configuration:\n/yii2-app-basic/vagrant/config/vagrant-local.yml"
+  exit
+end
+
+# vagrant configurate
+Vagrant.configure(2) do |config|
+  # select the box
+  config.vm.box = 'bento/ubuntu-18.04'
+
+  # should we ask about box updates?
+  config.vm.box_check_update = options['box_check_update']
+
+  config.vm.provider 'virtualbox' do |vb|
+    # machine cpus count
+    vb.cpus = options['cpus']
+    # machine memory size
+    vb.memory = options['memory']
+    # machine name (for VirtualBox UI)
+    vb.name = options['machine_name']
+  end
+
+  # machine name (for vagrant console)
+  config.vm.define options['machine_name']
+
+  # machine name (for guest machine console)
+  config.vm.hostname = options['machine_name']
+
+  # network settings
+  config.vm.network 'private_network', ip: options['ip']
+
+  # sync: folder 'yii2-app-advanced' (host machine) -> folder '/app' (guest machine)
+  config.vm.synced_folder './', '/app', owner: 'vagrant', group: 'vagrant'
+
+  # disable folder '/vagrant' (guest machine)
+  config.vm.synced_folder '.', '/vagrant', disabled: true
+
+  # hosts settings (host machine)
+  config.vm.provision :hostmanager
+  config.hostmanager.enabled            = true
+  config.hostmanager.manage_host        = true
+  config.hostmanager.ignore_private_ip  = false
+  config.hostmanager.include_offline    = true
+  config.hostmanager.aliases            = domains.values
+
+  # quick fix for failed guest additions installations
+  # config.vbguest.auto_update = false
+
+  # provisioners
+  config.vm.provision 'shell', path: './vagrant/provision/once-as-root.sh', args: [options['timezone'], options['ip']]
+  config.vm.provision 'shell', path: './vagrant/provision/once-as-vagrant.sh', args: [options['github_token']], privileged: false
+  config.vm.provision 'shell', path: './vagrant/provision/always-as-root.sh', run: 'always'
+
+  # post-install message (vagrant console)
+  config.vm.post_up_message = "App URL: http://#{domains[:app]}"
+end

+ 29 - 0
assets/AdminLteAsset.php

@@ -0,0 +1,29 @@
+<?php
+namespace app\assets;
+
+use rmrevin\yii\fontawesome\NpmFreeAssetBundle;
+use yii\bootstrap4\BootstrapPluginAsset;
+use yii\web\AssetBundle;
+use yii\web\JqueryAsset;
+
+class AdminLteAsset extends AssetBundle
+{
+
+    public $sourcePath = '@vendor/almasaeed2010/adminlte/dist';
+
+    public $css = [
+        '//fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic',
+        'css/adminlte.min.css',
+    ];
+
+    public $js = [
+        'js/adminlte.min.js'
+    ];
+
+    public $depends = [
+        JqueryAsset::class,
+        BootstrapPluginAsset::class,
+        NpmFreeAssetBundle::class,
+        //JquerySlimScroll::class
+    ];
+}

+ 31 - 0
assets/AppAsset.php

@@ -0,0 +1,31 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace app\assets;
+
+use yii\web\AssetBundle;
+
+/**
+ * Main application asset bundle.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class AppAsset extends AssetBundle
+{
+    public $basePath = '@webroot';
+    public $baseUrl = '@web';
+    public $css = [
+        'css/site.css',
+    ];
+    public $js = [
+    ];
+    public $depends = [
+        'yii\web\YiiAsset',
+        'yii\bootstrap4\BootstrapAsset',
+    ];
+}

+ 27 - 0
codeception.yml

@@ -0,0 +1,27 @@
+actor: Tester
+bootstrap: _bootstrap.php
+paths:
+    tests: tests
+    log: tests/_output
+    data: tests/_data
+    helpers: tests/_support
+settings:
+    memory_limit: 1024M
+    colors: true
+modules:
+    config:
+        Yii2:
+            configFile: 'config/test.php'
+
+# To enable code coverage:
+#coverage:
+#    #c3_url: http://localhost:8080/index-test.php/
+#    enabled: true
+#    #remote: true
+#    #remote_config: '../codeception.yml'
+#    whitelist:
+#        include:
+#            - models/*
+#            - controllers/*
+#            - commands/*
+#            - mail/*

+ 34 - 0
commands/HelloController.php

@@ -0,0 +1,34 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace app\commands;
+
+use yii\console\Controller;
+use yii\console\ExitCode;
+
+/**
+ * This command echoes the first argument that you have entered.
+ *
+ * This command is provided as an example for you to learn how to create console commands.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class HelloController extends Controller
+{
+    /**
+     * This command echoes what you have entered as the message.
+     * @param string $message the message to be echoed.
+     * @return int Exit code
+     */
+    public function actionIndex($message = 'hello world')
+    {
+        echo $message . "\n";
+
+        return ExitCode::OK;
+    }
+}

+ 64 - 0
components/GlobalAccessBehavior.php

@@ -0,0 +1,64 @@
+<?php
+
+namespace app\components;
+
+use Yii;
+use yii\base\Behavior;
+use yii\base\Controller;
+use yii\filters\AccessControl;
+
+/**
+ * Class GlobalAccessBehavior
+ * @package common\behaviors
+ */
+class GlobalAccessBehavior extends Behavior
+{
+
+    /**
+     * @var array
+     * @see \yii\filters\AccessControl::rules
+     */
+    public $rules = [];
+
+    /**
+     * @var string
+     */
+    public $accessControlFilter = AccessControl::class;
+
+    /**
+     * @var callable a callback that will be called if the access should be denied
+     * to the current user. If not set, [[denyAccess()]] will be called.
+     *
+     * The signature of the callback should be as follows:
+     *
+     * ~~~
+     * function ($rule, $action)
+     * ~~~
+     *
+     * where `$rule` is the rule that denies the user, and `$action` is the current [[Action|action]] object.
+     * `$rule` can be `null` if access is denied because none of the rules matched.
+     */
+    public $denyCallback;
+
+    /**
+     * @return array
+     */
+    public function events()
+    {
+        return [
+            Controller::EVENT_BEFORE_ACTION => 'beforeAction'
+        ];
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function beforeAction()
+    {
+        Yii::$app->controller->attachBehavior('access', [
+            'class' => $this->accessControlFilter,
+            'denyCallback' => $this->denyCallback,
+            'rules' => $this->rules
+        ]);
+    }
+}

+ 18 - 0
components/TranslationEventHandler.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace app\components;
+
+use Yii;
+use yii\i18n\MissingTranslationEvent;
+
+class TranslationEventHandler
+{
+    public static function handleMissingTranslation(MissingTranslationEvent $event)
+    {
+        Yii::$app->i18n->translations['user'] = [
+            'class' => 'yii\i18n\PhpMessageSource',
+            'basePath' => '@vendor/dektrium/yii2-user/messages',
+        ];
+        $event->translatedMessage = Yii::t($event->category, $event->message);
+    }
+}

+ 30 - 0
components/User.php

@@ -0,0 +1,30 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: mxuser
+ * Date: 29.05.18
+ * Time: 17:06
+ */
+
+namespace app\components;
+
+
+use Yii;
+use yii\web\User as BaseUser;
+
+/**
+ * @property \app\models\user\User $identity
+ * */
+class User extends BaseUser
+{
+    public function can($permissionName, $params = [], $allowCaching = true)
+    {
+        if (Yii::$app->user->isGuest) {
+            return false;
+        }
+        if ($this->identity->type == 'admin') {
+            return true;
+        }
+        return $permissionName == $this->identity->type;
+    }
+}

+ 76 - 0
composer.json

@@ -0,0 +1,76 @@
+{
+    "name": "yiisoft/yii2-app-basic",
+    "description": "Yii 2 Basic Project Template",
+    "keywords": ["yii2", "framework", "basic", "project template"],
+    "homepage": "http://www.yiiframework.com/",
+    "type": "project",
+    "license": "BSD-3-Clause",
+    "support": {
+        "issues": "https://github.com/yiisoft/yii2/issues?state=open",
+        "forum": "http://www.yiiframework.com/forum/",
+        "wiki": "http://www.yiiframework.com/wiki/",
+        "irc": "irc://irc.freenode.net/yii",
+        "source": "https://github.com/yiisoft/yii2"
+    },
+    "minimum-stability": "stable",
+    "require": {
+        "php": ">=7.3",
+        "yiisoft/yii2": "~2.0.14",
+        "yiisoft/yii2-bootstrap4": "^2.0",
+        "yiisoft/yii2-swiftmailer": "~2.0.0 || ~2.1.0",
+        "almasaeed2010/adminlte": "^3.0",
+        "rmrevin/yii2-fontawesome": "^3.4",
+        "kartik-v/yii2-widget-select2": "dev-master",
+        "vlucas/phpdotenv": "^5.3",
+        "dektrium/yii2-user": "^0.9.14"
+    },
+    "require-dev": {
+        "yiisoft/yii2-debug": "~2.1.0",
+        "yiisoft/yii2-gii": "~2.1.0",
+        "yiisoft/yii2-faker": "~2.0.0",
+        "codeception/codeception": "^4.0",
+        "codeception/verify": "~0.5.0 || ~1.1.0",
+        "codeception/specify": "~0.4.6",
+        "symfony/browser-kit": ">=2.7 <=4.2.4",
+        "codeception/module-filesystem": "^1.0.0",
+        "codeception/module-yii2": "^1.0.0",
+        "codeception/module-asserts": "^1.0.0"
+    },
+    "config": {
+        "process-timeout": 1800,
+        "fxp-asset": {
+            "enabled": false
+        }
+    },
+    "scripts": {
+        "post-install-cmd": [
+            "yii\\composer\\Installer::postInstall"
+        ],
+        "post-create-project-cmd": [
+            "yii\\composer\\Installer::postCreateProject",
+            "yii\\composer\\Installer::postInstall"
+        ]
+    },
+    "extra": {
+        "yii\\composer\\Installer::postCreateProject": {
+            "setPermission": [
+                {
+                    "runtime": "0777",
+                    "web/assets": "0777",
+                    "yii": "0755"
+                }
+            ]
+        },
+        "yii\\composer\\Installer::postInstall": {
+            "generateCookieValidationKey": [
+                "config/web.php"
+            ]
+        }
+    },
+    "repositories": [
+        {
+            "type": "composer",
+            "url": "https://asset-packagist.org"
+        }
+    ]
+}

File diff suppressed because it is too large
+ 6210 - 0
composer.lock


+ 71 - 0
config/console.php

@@ -0,0 +1,71 @@
+<?php
+
+$params = require __DIR__ . '/params.php';
+$db = require __DIR__ . '/db.php';
+
+$config = [
+    'id' => 'basic-console',
+    'basePath' => dirname(__DIR__),
+    'bootstrap' => ['log'],
+    'controllerNamespace' => 'app\commands',
+    'aliases' => [
+        '@bower' => '@vendor/bower-asset',
+        '@npm'   => '@vendor/npm-asset',
+        '@tests' => '@app/tests',
+    ],
+    'components' => [
+        'cache' => [
+            'class' => 'yii\caching\FileCache',
+        ],
+        'log' => [
+            'targets' => [
+                [
+                    'class' => 'yii\log\FileTarget',
+                    'levels' => ['error', 'warning'],
+                ],
+            ],
+        ],
+        'mailer' => [
+            'class' => 'yii\swiftmailer\Mailer',
+            // send all mails to a file by default. You have to set
+            // 'useFileTransport' to false and configure a transport
+            // for the mailer to send real emails.
+            //'useFileTransport' => false,
+            'transport' => [
+                'class' => 'Swift_SmtpTransport',
+                'host' => $_ENV['SMTP_HOST'],
+                'username' => $_ENV['SMTP_LOGIN'],
+                'password' => $_ENV['SMTP_PASS'],
+                'port' => $_ENV['SMTP_PORT'],
+                'encryption' => $_ENV['SMTP_ENC'],
+            ],
+        ],
+        'db' => $db,
+    ],
+    'modules' => [
+        'user' => [
+            'class' => 'dektrium\user\Module',
+            'enableRegistration' => false,
+            'enablePasswordRecovery' => false,
+            'enableConfirmation' => false,
+        ],
+    ],
+    'params' => $params,
+    /*
+    'controllerMap' => [
+        'fixture' => [ // Fixture generation command line.
+            'class' => 'yii\faker\FixtureController',
+        ],
+    ],
+    */
+];
+
+if (YII_ENV_DEV) {
+    // configuration adjustments for 'dev' environment
+    $config['bootstrap'][] = 'gii';
+    $config['modules']['gii'] = [
+        'class' => 'yii\gii\Module',
+    ];
+}
+
+return $config;

+ 15 - 0
config/db.php

@@ -0,0 +1,15 @@
+<?php
+
+return [
+    'class' => 'yii\db\Connection',
+    'dsn' => 'mysql:host=' . $_ENV['DB_HOST'] . ';dbname=' . $_ENV['DB_NAME'],
+    'username' => $_ENV['DB_USER'],
+    'password' => $_ENV['DB_PASS'],
+    'tablePrefix' => $_ENV['DB_TBL_PREFIX'],
+    'charset' => 'utf8',
+
+    // Schema cache options (for production environment)
+    //'enableSchemaCache' => true,
+    //'schemaCacheDuration' => 60,
+    //'schemaCache' => 'cache',
+];

+ 7 - 0
config/params.php

@@ -0,0 +1,7 @@
+<?php
+
+return [
+    'adminEmail' => 'admin@example.com',
+    'senderEmail' => 'noreply@example.com',
+    'senderName' => 'Example.com mailer',
+];

+ 42 - 0
config/test.php

@@ -0,0 +1,42 @@
+<?php
+$params = require __DIR__ . '/params.php';
+$db = require __DIR__ . '/test_db.php';
+
+/**
+ * Application configuration shared by all test types
+ */
+return [
+    'id' => 'basic-tests',
+    'basePath' => dirname(__DIR__),
+    'aliases' => [
+        '@bower' => '@vendor/bower-asset',
+        '@npm'   => '@vendor/npm-asset',
+    ],
+    'language' => 'en-US',
+    'components' => [
+        'db' => $db,
+        'mailer' => [
+            'useFileTransport' => true,
+        ],
+        'assetManager' => [
+            'basePath' => __DIR__ . '/../web/assets',
+        ],
+        'urlManager' => [
+            'showScriptName' => true,
+        ],
+        'user' => [
+            'identityClass' => 'app\models\User',
+        ],
+        'request' => [
+            'cookieValidationKey' => 'test',
+            'enableCsrfValidation' => false,
+            // but if you absolutely need it set cookie domain to localhost
+            /*
+            'csrfCookie' => [
+                'domain' => 'localhost',
+            ],
+            */
+        ],
+    ],
+    'params' => $params,
+];

+ 6 - 0
config/test_db.php

@@ -0,0 +1,6 @@
+<?php
+$db = require __DIR__ . '/db.php';
+// test database! Important not to run tests on production or development databases
+$db['dsn'] = 'mysql:host=localhost;dbname=yii2basic_test';
+
+return $db;

+ 224 - 0
config/web.php

@@ -0,0 +1,224 @@
+<?php
+
+$params = require __DIR__ . '/params.php';
+$db = require __DIR__ . '/db.php';
+
+$config = [
+    'id' => 'basic',
+    'name'=>'Antares-2000',
+    'basePath' => dirname(__DIR__),
+    'bootstrap' => ['log'],
+    'language' => 'ru-RU',
+    //'defaultRoute' => 'main/index',
+    'aliases' => [
+        '@bower' => '@vendor/bower-asset',
+        '@npm'   => '@vendor/npm-asset',
+    ],
+    'components' => [
+        'request' => [
+            // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
+            'cookieValidationKey' => '9F0UJBZKPPoUsJAKuAh9hpvyWdaF1IIb',
+        ],
+        'cache' => [
+            'class' => 'yii\caching\FileCache',
+        ],
+        'user' => [
+            'class' => 'app\components\User',
+            'loginUrl' => ['login'],
+            //'returnUrl' => ['home'],
+            'identityClass' => 'app\models\user\User',
+        ],
+        'errorHandler' => [
+            'errorAction' => 'site/error',
+        ],
+        'mailer' => [
+            'class' => 'yii\swiftmailer\Mailer',
+            // send all mails to a file by default. You have to set
+            // 'useFileTransport' to false and configure a transport
+            // for the mailer to send real emails.
+            //'useFileTransport' => false,
+            'transport' => [
+                'class' => 'Swift_SmtpTransport',
+                'host' => $_ENV['SMTP_HOST'],
+                'username' => $_ENV['SMTP_LOGIN'],
+                'password' => $_ENV['SMTP_PASS'],
+                'port' => $_ENV['SMTP_PORT'],
+                'encryption' => $_ENV['SMTP_ENC'],
+            ],
+        ],
+        'log' => [
+            'traceLevel' => YII_DEBUG ? 3 : 0,
+            'targets' => [
+                [
+                    'class' => 'yii\log\FileTarget',
+                    'levels' => ['error', 'warning'],
+                ],
+            ],
+        ],
+        'db' => $db,
+        'urlManager' => [
+            'enablePrettyUrl' => true,
+            'showScriptName' => false,
+            'enableStrictParsing' => true,
+            'rules' => [
+                '/' => 'site/index',
+                'pricing/<action:[\w\-]+>' => 'pricing/<action>',
+                //'user/admin/<action:\w+>' => 'user/admin/<action>',
+                'user/<controller:[\w\-]+>/<action:[\w\-]+>' => 'user/<controller>/<action>',
+                /*'<controller>/<action>' => '<controller>/<action>',
+                '<controller:\w+>/<action:\w+>' => '<controller>/<action>',
+                '<controller:\w+>/<action:\w+>/<id:\d+>' => '<controller>/<action>',*/
+                //'login' => 'user/security/login',
+                '<alias:logout|login>' => 'user/security/<alias>',
+                //'<alias:logout|login>' => 'user/security/<alias>',
+
+            ],
+        ],
+        'i18n' => [
+            'translations' => [
+                'app' => [
+                    'class' => 'yii\i18n\PhpMessageSource',
+                    'basePath' => '@app/messages',
+                    'sourceLanguage' => 'en',
+                    'fileMap' => [
+                        'app' => 'app.php',
+                    ],
+                ],
+                'user' => [
+                    'class' => 'yii\i18n\PhpMessageSource',
+                    'basePath' => '@app/messages',
+                    'on missingTranslation' => ['app\components\TranslationEventHandler', 'handleMissingTranslation']
+                ],
+            ],
+        ],
+        'view' => [
+            'theme' => [
+                'pathMap' => [
+                    '@dektrium/user/views' => '@app/views/user'
+                ],
+            ],
+        ],
+        'assetManager' => [
+            'bundles' => [
+                'yii\bootstrap\BootstrapPluginAsset' => [
+                    'js'=>[]
+                ],
+                'yii\bootstrap\BootstrapAsset' => [
+                    'css' => [],
+                ],
+            ],
+        ],
+    ],
+    'modules' => [
+        'user' => [
+            'class' => 'dektrium\user\Module',
+            'enableRegistration' => false,
+            'enablePasswordRecovery' => false,
+            'enableConfirmation' => false,
+            'adminPermission' => 'admin',
+            //'admins' => ['ptenchik0'],
+            'rememberFor' => 86400,
+            /*'modelMap' => [
+                //'RecoveryForm' => 'app\models\security\RecoveryForm',
+                //'RegistrationForm' => 'app\models\security\RegistrationForm',
+                'User' => 'app\models\user\User',
+                'UserSearch' => 'app\models\search\Users',
+                'LoginForm' => 'app\models\security\LoginForm',
+                //'Profile' => 'app\models\user\Profile',
+                //'SettingsForm' => 'app\models\user\SettingsForm',
+            ],*/
+        ],
+    ],
+    'params' => $params,
+    'on beforeAction' => function ($event) {
+        if (Yii::$app->user->isGuest) Yii::$app->layout = 'guest';
+    },
+    'as globalAccess' => [
+        'class' => app\components\GlobalAccessBehavior::class,
+        'rules' => [
+            [
+                'actions' => ['error'],
+                'allow' => true,
+                'roles' => ["?","@"],
+            ],
+            [
+                'actions' => ['login'],
+                'allow' => true,
+                'roles' => ['?'],
+            ],
+            [
+                'actions' => ['logout'],
+                'allow' => true,
+                'roles' => ['@'],
+            ],
+            [
+                'controllers' => ['user/admin'],
+                'allow' => true,
+                'roles' => ['admin'],
+            ],
+            [
+                'controllers' => ['user/admin'],
+                'allow' => false,
+            ],
+            [
+                'allow' => true,
+                'roles' => ['client', 'admin'],
+            ],
+//            [
+//                'controllers' => ['sign-in'],
+//                'allow' => true,
+//                'roles' => ['?'],
+//                'actions' => ['login'],
+//            ],
+//            [
+//                'controllers' => ['sign-in'],
+//                'allow' => true,
+//                'roles' => ['@'],
+//                'actions' => ['logout'],
+//            ],
+//            [
+//                'controllers' => ['site'],
+//                'allow' => true,
+//                'roles' => ['?', '@'],
+//                'actions' => ['error'],
+//            ],
+//            [
+//                'controllers' => ['debug/default'],
+//                'allow' => true,
+//                'roles' => ['?'],
+//            ],
+//            [
+//                'controllers' => ['user'],
+//                'allow' => true,
+//                'roles' => ['administrator'],
+//            ],
+//            [
+//                'controllers' => ['user'],
+//                'allow' => false,
+//            ],
+//            [
+//                'allow' => true,
+//                'roles' => ['manager', 'administrator'],
+//            ],
+        ],
+    ],
+];
+
+if (YII_ENV_DEV) {
+    // configuration adjustments for 'dev' environment
+    $config['bootstrap'][] = 'debug';
+    $config['modules']['debug'] = [
+        'class' => 'yii\debug\Module',
+        // uncomment the following to add your IP if you are not connecting from localhost.
+        //'allowedIPs' => ['127.0.0.1', '::1'],
+    ];
+
+    $config['bootstrap'][] = 'gii';
+    $config['modules']['gii'] = [
+        'class' => 'yii\gii\Module',
+        // uncomment the following to add your IP if you are not connecting from localhost.
+        //'allowedIPs' => ['127.0.0.1', '::1'],
+    ];
+}
+
+return $config;

+ 16 - 0
controllers/PricingController.php

@@ -0,0 +1,16 @@
+<?php
+
+
+namespace app\controllers;
+
+
+use yii\web\Controller;
+
+class PricingController extends Controller
+{
+
+    public function actionIndex(){
+        return $this->render('index');
+    }
+
+}

+ 128 - 0
controllers/SiteController.php

@@ -0,0 +1,128 @@
+<?php
+
+namespace app\controllers;
+
+use Yii;
+use yii\filters\AccessControl;
+use yii\web\Controller;
+use yii\web\Response;
+use yii\filters\VerbFilter;
+use app\models\LoginForm;
+use app\models\ContactForm;
+
+class SiteController extends Controller
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function behaviors()
+    {
+        return [
+            /*'access' => [
+                'class' => AccessControl::className(),
+                'only' => ['logout'],
+                'rules' => [
+                    [
+                        'actions' => ['logout'],
+                        'allow' => true,
+                        'roles' => ['@'],
+                    ],
+                ],
+            ],*/
+            'verbs' => [
+                'class' => VerbFilter::className(),
+                'actions' => [
+                    'logout' => ['post'],
+                ],
+            ],
+        ];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function actions()
+    {
+        return [
+            'error' => [
+                'class' => 'yii\web\ErrorAction',
+            ],
+            'captcha' => [
+                'class' => 'yii\captcha\CaptchaAction',
+                'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null,
+            ],
+        ];
+    }
+
+    /**
+     * Displays homepage.
+     *
+     * @return string
+     */
+    public function actionIndex()
+    {
+        return $this->render('index');
+    }
+
+    /**
+     * Login action.
+     *
+     * @return Response|string
+     */
+    public function actionLogin()
+    {
+        if (!Yii::$app->user->isGuest) {
+            return $this->goHome();
+        }
+
+        $model = new LoginForm();
+        if ($model->load(Yii::$app->request->post()) && $model->login()) {
+            return $this->goBack();
+        }
+
+        $model->password = '';
+        return $this->render('login', [
+            'model' => $model,
+        ]);
+    }
+
+    /**
+     * Logout action.
+     *
+     * @return Response
+     */
+    public function actionLogout()
+    {
+        Yii::$app->user->logout();
+
+        return $this->goHome();
+    }
+
+    /**
+     * Displays contact page.
+     *
+     * @return Response|string
+     */
+    public function actionContact()
+    {
+        $model = new ContactForm();
+        if ($model->load(Yii::$app->request->post()) && $model->contact(Yii::$app->params['adminEmail'])) {
+            Yii::$app->session->setFlash('contactFormSubmitted');
+
+            return $this->refresh();
+        }
+        return $this->render('contact', [
+            'model' => $model,
+        ]);
+    }
+
+    /**
+     * Displays about page.
+     *
+     * @return string
+     */
+    public function actionAbout()
+    {
+        return $this->render('about');
+    }
+}

+ 9 - 0
docker-compose.yml

@@ -0,0 +1,9 @@
+version: '2'
+services:
+  php:
+    image: yiisoftware/yii2-php:7.4-apache
+    volumes:
+      - ~/.composer-docker/cache:/root/.composer/cache:delegated
+      - ./:/app:delegated
+    ports:
+      - '8000:80'

+ 22 - 0
mail/layouts/html.php

@@ -0,0 +1,22 @@
+<?php
+use yii\helpers\Html;
+
+/* @var $this \yii\web\View view component instance */
+/* @var $message \yii\mail\MessageInterface the message being composed */
+/* @var $content string main view render result */
+?>
+<?php $this->beginPage() ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+    <meta http-equiv="Content-Type" content="text/html; charset=<?= Yii::$app->charset ?>" />
+    <title><?= Html::encode($this->title) ?></title>
+    <?php $this->head() ?>
+</head>
+<body>
+    <?php $this->beginBody() ?>
+    <?= $content ?>
+    <?php $this->endBody() ?>
+</body>
+</html>
+<?php $this->endPage() ?>

+ 4 - 0
messages/ru/user.php

@@ -0,0 +1,4 @@
+<?php
+return [
+
+];

+ 25 - 0
migrations/m210602_124525_user_type_field.php

@@ -0,0 +1,25 @@
+<?php
+
+use yii\db\Migration;
+
+/**
+ * Class m210602_124525_user_type_field
+ */
+class m210602_124525_user_type_field extends Migration
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function safeUp()
+    {
+        $this->addColumn('{{user}}', 'type', $this->string(255)->notNull());
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function safeDown()
+    {
+        $this->dropColumn('{{user}}', 'type');
+    }
+}

+ 65 - 0
models/ContactForm.php

@@ -0,0 +1,65 @@
+<?php
+
+namespace app\models;
+
+use Yii;
+use yii\base\Model;
+
+/**
+ * ContactForm is the model behind the contact form.
+ */
+class ContactForm extends Model
+{
+    public $name;
+    public $email;
+    public $subject;
+    public $body;
+    public $verifyCode;
+
+
+    /**
+     * @return array the validation rules.
+     */
+    public function rules()
+    {
+        return [
+            // name, email, subject and body are required
+            [['name', 'email', 'subject', 'body'], 'required'],
+            // email has to be a valid email address
+            ['email', 'email'],
+            // verifyCode needs to be entered correctly
+            ['verifyCode', 'captcha'],
+        ];
+    }
+
+    /**
+     * @return array customized attribute labels
+     */
+    public function attributeLabels()
+    {
+        return [
+            'verifyCode' => 'Verification Code',
+        ];
+    }
+
+    /**
+     * Sends an email to the specified email address using the information collected by this model.
+     * @param string $email the target email address
+     * @return bool whether the model passes validation
+     */
+    public function contact($email)
+    {
+        if ($this->validate()) {
+            Yii::$app->mailer->compose()
+                ->setTo($email)
+                ->setFrom([Yii::$app->params['senderEmail'] => Yii::$app->params['senderName']])
+                ->setReplyTo([$this->email => $this->name])
+                ->setSubject($this->subject)
+                ->setTextBody($this->body)
+                ->send();
+
+            return true;
+        }
+        return false;
+    }
+}

+ 81 - 0
models/LoginForm.php

@@ -0,0 +1,81 @@
+<?php
+
+namespace app\models;
+
+use Yii;
+use yii\base\Model;
+
+/**
+ * LoginForm is the model behind the login form.
+ *
+ * @property-read User|null $user This property is read-only.
+ *
+ */
+class LoginForm extends Model
+{
+    public $username;
+    public $password;
+    public $rememberMe = true;
+
+    private $_user = false;
+
+
+    /**
+     * @return array the validation rules.
+     */
+    public function rules()
+    {
+        return [
+            // username and password are both required
+            [['username', 'password'], 'required'],
+            // rememberMe must be a boolean value
+            ['rememberMe', 'boolean'],
+            // password is validated by validatePassword()
+            ['password', 'validatePassword'],
+        ];
+    }
+
+    /**
+     * Validates the password.
+     * This method serves as the inline validation for password.
+     *
+     * @param string $attribute the attribute currently being validated
+     * @param array $params the additional name-value pairs given in the rule
+     */
+    public function validatePassword($attribute, $params)
+    {
+        if (!$this->hasErrors()) {
+            $user = $this->getUser();
+
+            if (!$user || !$user->validatePassword($this->password)) {
+                $this->addError($attribute, 'Incorrect username or password.');
+            }
+        }
+    }
+
+    /**
+     * Logs in a user using the provided username and password.
+     * @return bool whether the user is logged in successfully
+     */
+    public function login()
+    {
+        if ($this->validate()) {
+            return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600*24*30 : 0);
+        }
+        return false;
+    }
+
+    /**
+     * Finds user by [[username]]
+     *
+     * @return User|null
+     */
+    public function getUser()
+    {
+        if ($this->_user === false) {
+            $this->_user = User::findByUsername($this->username);
+        }
+
+        return $this->_user;
+    }
+}

+ 104 - 0
models/User.php

@@ -0,0 +1,104 @@
+<?php
+
+namespace app\models;
+
+class User extends \yii\base\BaseObject implements \yii\web\IdentityInterface
+{
+    public $id;
+    public $username;
+    public $password;
+    public $authKey;
+    public $accessToken;
+
+    private static $users = [
+        '100' => [
+            'id' => '100',
+            'username' => 'admin',
+            'password' => 'admin',
+            'authKey' => 'test100key',
+            'accessToken' => '100-token',
+        ],
+        '101' => [
+            'id' => '101',
+            'username' => 'demo',
+            'password' => 'demo',
+            'authKey' => 'test101key',
+            'accessToken' => '101-token',
+        ],
+    ];
+
+
+    /**
+     * {@inheritdoc}
+     */
+    public static function findIdentity($id)
+    {
+        return isset(self::$users[$id]) ? new static(self::$users[$id]) : null;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public static function findIdentityByAccessToken($token, $type = null)
+    {
+        foreach (self::$users as $user) {
+            if ($user['accessToken'] === $token) {
+                return new static($user);
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Finds user by username
+     *
+     * @param string $username
+     * @return static|null
+     */
+    public static function findByUsername($username)
+    {
+        foreach (self::$users as $user) {
+            if (strcasecmp($user['username'], $username) === 0) {
+                return new static($user);
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getId()
+    {
+        return $this->id;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthKey()
+    {
+        return $this->authKey;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function validateAuthKey($authKey)
+    {
+        return $this->authKey === $authKey;
+    }
+
+    /**
+     * Validates password
+     *
+     * @param string $password password to validate
+     * @return bool if password provided is valid for current user
+     */
+    public function validatePassword($password)
+    {
+        return $this->password === $password;
+    }
+}

+ 32 - 0
models/user/User.php

@@ -0,0 +1,32 @@
+<?php
+
+namespace app\models\user;
+
+use dektrium\user\helpers\Password;
+use dektrium\user\models\Token;
+
+use Exception;
+use RuntimeException;
+use Yii;
+use yii\helpers\Html;
+use yii\helpers\Url;
+
+use dektrium\user\models\User as BaseUser;
+
+
+
+/**
+ *
+ * @property Organizations $organization
+ */
+class User extends BaseUser
+{
+    public static function typeNames()
+    {
+        return [
+            'admin' => t('Адміністратор майданчика'),
+        ];
+    }
+
+}
+

+ 162 - 0
requirements.php

@@ -0,0 +1,162 @@
+<?php
+/**
+ * Application requirement checker script.
+ *
+ * In order to run this script use the following console command:
+ * php requirements.php
+ *
+ * In order to run this script from the web, you should copy it to the web root.
+ * If you are using Linux you can create a hard link instead, using the following command:
+ * ln ../requirements.php requirements.php
+ */
+
+// you may need to adjust this path to the correct Yii framework path
+// uncomment and adjust the following line if Yii is not located at the default path
+//$frameworkPath = dirname(__FILE__) . '/vendor/yiisoft/yii2';
+
+
+if (!isset($frameworkPath)) {
+    $searchPaths = array(
+        dirname(__FILE__) . '/vendor/yiisoft/yii2',
+        dirname(__FILE__) . '/../vendor/yiisoft/yii2',
+    );
+    foreach ($searchPaths as $path) {
+        if (is_dir($path)) {
+            $frameworkPath = $path;
+            break;
+        }
+    }
+}
+
+if (!isset($frameworkPath) || !is_dir($frameworkPath)) {
+    $message = "<h1>Error</h1>\n\n"
+        . "<p><strong>The path to yii framework seems to be incorrect.</strong></p>\n"
+        . '<p>You need to install Yii framework via composer or adjust the framework path in file <abbr title="' . __FILE__ . '">' . basename(__FILE__) . "</abbr>.</p>\n"
+        . '<p>Please refer to the <abbr title="' . dirname(__FILE__) . "/README.md\">README</abbr> on how to install Yii.</p>\n";
+
+    if (!empty($_SERVER['argv'])) {
+        // do not print HTML when used in console mode
+        echo strip_tags($message);
+    } else {
+        echo $message;
+    }
+    exit(1);
+}
+
+require_once($frameworkPath . '/requirements/YiiRequirementChecker.php');
+$requirementsChecker = new YiiRequirementChecker();
+
+$gdMemo = $imagickMemo = 'Either GD PHP extension with FreeType support or ImageMagick PHP extension with PNG support is required for image CAPTCHA.';
+$gdOK = $imagickOK = false;
+
+if (extension_loaded('imagick')) {
+    $imagick = new Imagick();
+    $imagickFormats = $imagick->queryFormats('PNG');
+    if (in_array('PNG', $imagickFormats)) {
+        $imagickOK = true;
+    } else {
+        $imagickMemo = 'Imagick extension should be installed with PNG support in order to be used for image CAPTCHA.';
+    }
+}
+
+if (extension_loaded('gd')) {
+    $gdInfo = gd_info();
+    if (!empty($gdInfo['FreeType Support'])) {
+        $gdOK = true;
+    } else {
+        $gdMemo = 'GD extension should be installed with FreeType support in order to be used for image CAPTCHA.';
+    }
+}
+
+/**
+ * Adjust requirements according to your application specifics.
+ */
+$requirements = array(
+    // Database :
+    array(
+        'name' => 'PDO extension',
+        'mandatory' => true,
+        'condition' => extension_loaded('pdo'),
+        'by' => 'All DB-related classes',
+    ),
+    array(
+        'name' => 'PDO SQLite extension',
+        'mandatory' => false,
+        'condition' => extension_loaded('pdo_sqlite'),
+        'by' => 'All DB-related classes',
+        'memo' => 'Required for SQLite database.',
+    ),
+    array(
+        'name' => 'PDO MySQL extension',
+        'mandatory' => false,
+        'condition' => extension_loaded('pdo_mysql'),
+        'by' => 'All DB-related classes',
+        'memo' => 'Required for MySQL database.',
+    ),
+    array(
+        'name' => 'PDO PostgreSQL extension',
+        'mandatory' => false,
+        'condition' => extension_loaded('pdo_pgsql'),
+        'by' => 'All DB-related classes',
+        'memo' => 'Required for PostgreSQL database.',
+    ),
+    // Cache :
+    array(
+        'name' => 'Memcache extension',
+        'mandatory' => false,
+        'condition' => extension_loaded('memcache') || extension_loaded('memcached'),
+        'by' => '<a href="http://www.yiiframework.com/doc-2.0/yii-caching-memcache.html">MemCache</a>',
+        'memo' => extension_loaded('memcached') ? 'To use memcached set <a href="http://www.yiiframework.com/doc-2.0/yii-caching-memcache.html#$useMemcached-detail">MemCache::useMemcached</a> to <code>true</code>.' : ''
+    ),
+    // CAPTCHA:
+    array(
+        'name' => 'GD PHP extension with FreeType support',
+        'mandatory' => false,
+        'condition' => $gdOK,
+        'by' => '<a href="http://www.yiiframework.com/doc-2.0/yii-captcha-captcha.html">Captcha</a>',
+        'memo' => $gdMemo,
+    ),
+    array(
+        'name' => 'ImageMagick PHP extension with PNG support',
+        'mandatory' => false,
+        'condition' => $imagickOK,
+        'by' => '<a href="http://www.yiiframework.com/doc-2.0/yii-captcha-captcha.html">Captcha</a>',
+        'memo' => $imagickMemo,
+    ),
+    // PHP ini :
+    'phpExposePhp' => array(
+        'name' => 'Expose PHP',
+        'mandatory' => false,
+        'condition' => $requirementsChecker->checkPhpIniOff("expose_php"),
+        'by' => 'Security reasons',
+        'memo' => '"expose_php" should be disabled at php.ini',
+    ),
+    'phpAllowUrlInclude' => array(
+        'name' => 'PHP allow url include',
+        'mandatory' => false,
+        'condition' => $requirementsChecker->checkPhpIniOff("allow_url_include"),
+        'by' => 'Security reasons',
+        'memo' => '"allow_url_include" should be disabled at php.ini',
+    ),
+    'phpSmtp' => array(
+        'name' => 'PHP mail SMTP',
+        'mandatory' => false,
+        'condition' => strlen(ini_get('SMTP')) > 0,
+        'by' => 'Email sending',
+        'memo' => 'PHP mail SMTP server required',
+    ),
+);
+
+// OPcache check
+if (!version_compare(phpversion(), '5.5', '>=')) {
+    $requirements[] = array(
+        'name' => 'APC extension',
+        'mandatory' => false,
+        'condition' => extension_loaded('apc'),
+        'by' => '<a href="http://www.yiiframework.com/doc-2.0/yii-caching-apccache.html">ApcCache</a>',
+    );
+}
+
+$result = $requirementsChecker->checkYii()->check($requirements)->getResult();
+$requirementsChecker->render();
+exit($result['summary']['errors'] === 0 ? 0 : 1);

+ 2 - 0
runtime/.gitignore

@@ -0,0 +1,2 @@
+*
+!.gitignore

+ 6 - 0
tests/_bootstrap.php

@@ -0,0 +1,6 @@
+<?php
+define('YII_ENV', 'test');
+defined('YII_DEBUG') or define('YII_DEBUG', true);
+
+require_once __DIR__ . '/../vendor/yiisoft/yii2/Yii.php';
+require __DIR__ .'/../vendor/autoload.php';

+ 1 - 0
tests/_data/.gitkeep

@@ -0,0 +1 @@
+

+ 2 - 0
tests/_output/.gitignore

@@ -0,0 +1,2 @@
+*
+!.gitignore

+ 26 - 0
tests/_support/AcceptanceTester.php

@@ -0,0 +1,26 @@
+<?php
+
+
+/**
+ * Inherited Methods
+ * @method void wantToTest($text)
+ * @method void wantTo($text)
+ * @method void execute($callable)
+ * @method void expectTo($prediction)
+ * @method void expect($prediction)
+ * @method void amGoingTo($argumentation)
+ * @method void am($role)
+ * @method void lookForwardTo($achieveValue)
+ * @method void comment($description)
+ * @method \Codeception\Lib\Friend haveFriend($name, $actorClass = NULL)
+ *
+ * @SuppressWarnings(PHPMD)
+*/
+class AcceptanceTester extends \Codeception\Actor
+{
+    use _generated\AcceptanceTesterActions;
+
+   /**
+    * Define custom actions here
+    */
+}

+ 23 - 0
tests/_support/FunctionalTester.php

@@ -0,0 +1,23 @@
+<?php
+
+
+/**
+ * Inherited Methods
+ * @method void wantToTest($text)
+ * @method void wantTo($text)
+ * @method void execute($callable)
+ * @method void expectTo($prediction)
+ * @method void expect($prediction)
+ * @method void amGoingTo($argumentation)
+ * @method void am($role)
+ * @method void lookForwardTo($achieveValue)
+ * @method void comment($description)
+ * @method \Codeception\Lib\Friend haveFriend($name, $actorClass = NULL)
+ *
+ * @SuppressWarnings(PHPMD)
+*/
+class FunctionalTester extends \Codeception\Actor
+{
+    use _generated\FunctionalTesterActions;
+
+}

+ 26 - 0
tests/_support/UnitTester.php

@@ -0,0 +1,26 @@
+<?php
+
+
+/**
+ * Inherited Methods
+ * @method void wantToTest($text)
+ * @method void wantTo($text)
+ * @method void execute($callable)
+ * @method void expectTo($prediction)
+ * @method void expect($prediction)
+ * @method void amGoingTo($argumentation)
+ * @method void am($role)
+ * @method void lookForwardTo($achieveValue)
+ * @method void comment($description)
+ * @method \Codeception\Lib\Friend haveFriend($name, $actorClass = NULL)
+ *
+ * @SuppressWarnings(PHPMD)
+*/
+class UnitTester extends \Codeception\Actor
+{
+    use _generated\UnitTesterActions;
+
+   /**
+    * Define custom actions here
+    */
+}

+ 10 - 0
tests/acceptance.suite.yml.example

@@ -0,0 +1,10 @@
+class_name: AcceptanceTester
+modules:
+    enabled:
+        - WebDriver:
+            url: http://127.0.0.1:8080/
+            browser: firefox
+        - Yii2:
+            part: orm
+            entryScript: index-test.php
+            cleanup: false

+ 12 - 0
tests/acceptance/AboutCest.php

@@ -0,0 +1,12 @@
+<?php
+
+use yii\helpers\Url;
+
+class AboutCest
+{
+    public function ensureThatAboutWorks(AcceptanceTester $I)
+    {
+        $I->amOnPage(Url::toRoute('/site/about'));
+        $I->see('About', 'h1');
+    }
+}

+ 34 - 0
tests/acceptance/ContactCest.php

@@ -0,0 +1,34 @@
+<?php
+
+use yii\helpers\Url;
+
+class ContactCest
+{
+    public function _before(\AcceptanceTester $I)
+    {
+        $I->amOnPage(Url::toRoute('/site/contact'));
+    }
+    
+    public function contactPageWorks(AcceptanceTester $I)
+    {
+        $I->wantTo('ensure that contact page works');
+        $I->see('Contact', 'h1');
+    }
+
+    public function contactFormCanBeSubmitted(AcceptanceTester $I)
+    {
+        $I->amGoingTo('submit contact form with correct data');
+        $I->fillField('#contactform-name', 'tester');
+        $I->fillField('#contactform-email', 'tester@example.com');
+        $I->fillField('#contactform-subject', 'test subject');
+        $I->fillField('#contactform-body', 'test content');
+        $I->fillField('#contactform-verifycode', 'testme');
+
+        $I->click('contact-button');
+        
+        $I->wait(2); // wait for button to be clicked
+
+        $I->dontSeeElement('#contact-form');
+        $I->see('Thank you for contacting us. We will respond to you as soon as possible.');
+    }
+}

+ 18 - 0
tests/acceptance/HomeCest.php

@@ -0,0 +1,18 @@
+<?php
+
+use yii\helpers\Url;
+
+class HomeCest
+{
+    public function ensureThatHomePageWorks(AcceptanceTester $I)
+    {
+        $I->amOnPage(Url::toRoute('/site/index'));        
+        $I->see('My Company');
+        
+        $I->seeLink('About');
+        $I->click('About');
+        $I->wait(2); // wait for page to be opened
+        
+        $I->see('This is the About page.');
+    }
+}

+ 21 - 0
tests/acceptance/LoginCest.php

@@ -0,0 +1,21 @@
+<?php
+
+use yii\helpers\Url;
+
+class LoginCest
+{
+    public function ensureThatLoginWorks(AcceptanceTester $I)
+    {
+        $I->amOnPage(Url::toRoute('/site/login'));
+        $I->see('Login', 'h1');
+
+        $I->amGoingTo('try to login with correct credentials');
+        $I->fillField('input[name="LoginForm[username]"]', 'admin');
+        $I->fillField('input[name="LoginForm[password]"]', 'admin');
+        $I->click('login-button');
+        $I->wait(2); // wait for button to be clicked
+
+        $I->expectTo('see user info');
+        $I->see('Logout');
+    }
+}

+ 1 - 0
tests/acceptance/_bootstrap.php

@@ -0,0 +1 @@
+<?php

+ 29 - 0
tests/bin/yii

@@ -0,0 +1,29 @@
+#!/usr/bin/env php
+<?php
+/**
+ * Yii console bootstrap file.
+ *
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+defined('YII_DEBUG') or define('YII_DEBUG', true);
+defined('YII_ENV') or define('YII_ENV', 'test');
+
+require __DIR__ . '/../../vendor/autoload.php';
+require __DIR__ . '/../../vendor/yiisoft/yii2/Yii.php';
+
+$config = yii\helpers\ArrayHelper::merge(
+    require __DIR__ . '/../../config/console.php',
+    [
+        'components' => [
+            'db' => require __DIR__ . '/../../config/test_db.php'
+        ]
+    ]
+);
+
+
+$application = new yii\console\Application($config);
+$exitCode = $application->run();
+exit($exitCode);

+ 20 - 0
tests/bin/yii.bat

@@ -0,0 +1,20 @@
+@echo off
+
+rem -------------------------------------------------------------
+rem  Yii command line bootstrap script for Windows.
+rem
+rem  @author Qiang Xue <qiang.xue@gmail.com>
+rem  @link http://www.yiiframework.com/
+rem  @copyright Copyright (c) 2008 Yii Software LLC
+rem  @license http://www.yiiframework.com/license/
+rem -------------------------------------------------------------
+
+@setlocal
+
+set YII_PATH=%~dp0
+
+if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe
+
+"%PHP_COMMAND%" "%YII_PATH%yii" %*
+
+@endlocal

+ 13 - 0
tests/functional.suite.yml

@@ -0,0 +1,13 @@
+# Codeception Test Suite Configuration
+
+# suite for functional (integration) tests.
+# emulate web requests and make application process them.
+# (tip: better to use with frameworks).
+
+# RUN `build` COMMAND AFTER ADDING/REMOVING MODULES.
+#basic/web/index.php
+class_name: FunctionalTester
+modules:
+    enabled:
+      - Filesystem
+      - Yii2

+ 57 - 0
tests/functional/ContactFormCest.php

@@ -0,0 +1,57 @@
+<?php
+
+class ContactFormCest 
+{
+    public function _before(\FunctionalTester $I)
+    {
+        $I->amOnPage(['site/contact']);
+    }
+
+    public function openContactPage(\FunctionalTester $I)
+    {
+        $I->see('Contact', 'h1');        
+    }
+
+    public function submitEmptyForm(\FunctionalTester $I)
+    {
+        $I->submitForm('#contact-form', []);
+        $I->expectTo('see validations errors');
+        $I->see('Contact', 'h1');
+        $I->see('Name cannot be blank');
+        $I->see('Email cannot be blank');
+        $I->see('Subject cannot be blank');
+        $I->see('Body cannot be blank');
+        $I->see('The verification code is incorrect');
+    }
+
+    public function submitFormWithIncorrectEmail(\FunctionalTester $I)
+    {
+        $I->submitForm('#contact-form', [
+            'ContactForm[name]' => 'tester',
+            'ContactForm[email]' => 'tester.email',
+            'ContactForm[subject]' => 'test subject',
+            'ContactForm[body]' => 'test content',
+            'ContactForm[verifyCode]' => 'testme',
+        ]);
+        $I->expectTo('see that email address is wrong');
+        $I->dontSee('Name cannot be blank', '.help-inline');
+        $I->see('Email is not a valid email address.');
+        $I->dontSee('Subject cannot be blank', '.help-inline');
+        $I->dontSee('Body cannot be blank', '.help-inline');
+        $I->dontSee('The verification code is incorrect', '.help-inline');        
+    }
+
+    public function submitFormSuccessfully(\FunctionalTester $I)
+    {
+        $I->submitForm('#contact-form', [
+            'ContactForm[name]' => 'tester',
+            'ContactForm[email]' => 'tester@example.com',
+            'ContactForm[subject]' => 'test subject',
+            'ContactForm[body]' => 'test content',
+            'ContactForm[verifyCode]' => 'testme',
+        ]);
+        $I->seeEmailIsSent();
+        $I->dontSeeElement('#contact-form');
+        $I->see('Thank you for contacting us. We will respond to you as soon as possible.');        
+    }
+}

+ 59 - 0
tests/functional/LoginFormCest.php

@@ -0,0 +1,59 @@
+<?php
+
+class LoginFormCest
+{
+    public function _before(\FunctionalTester $I)
+    {
+        $I->amOnRoute('site/login');
+    }
+
+    public function openLoginPage(\FunctionalTester $I)
+    {
+        $I->see('Login', 'h1');
+
+    }
+
+    // demonstrates `amLoggedInAs` method
+    public function internalLoginById(\FunctionalTester $I)
+    {
+        $I->amLoggedInAs(100);
+        $I->amOnPage('/');
+        $I->see('Logout (admin)');
+    }
+
+    // demonstrates `amLoggedInAs` method
+    public function internalLoginByInstance(\FunctionalTester $I)
+    {
+        $I->amLoggedInAs(\app\models\User::findByUsername('admin'));
+        $I->amOnPage('/');
+        $I->see('Logout (admin)');
+    }
+
+    public function loginWithEmptyCredentials(\FunctionalTester $I)
+    {
+        $I->submitForm('#login-form', []);
+        $I->expectTo('see validations errors');
+        $I->see('Username cannot be blank.');
+        $I->see('Password cannot be blank.');
+    }
+
+    public function loginWithWrongCredentials(\FunctionalTester $I)
+    {
+        $I->submitForm('#login-form', [
+            'LoginForm[username]' => 'admin',
+            'LoginForm[password]' => 'wrong',
+        ]);
+        $I->expectTo('see validations errors');
+        $I->see('Incorrect username or password.');
+    }
+
+    public function loginSuccessfully(\FunctionalTester $I)
+    {
+        $I->submitForm('#login-form', [
+            'LoginForm[username]' => 'admin',
+            'LoginForm[password]' => 'admin',
+        ]);
+        $I->see('Logout (admin)');
+        $I->dontSeeElement('form#login-form');              
+    }
+}

+ 1 - 0
tests/functional/_bootstrap.php

@@ -0,0 +1 @@
+<?php

+ 11 - 0
tests/unit.suite.yml

@@ -0,0 +1,11 @@
+# Codeception Test Suite Configuration
+
+# suite for unit (internal) tests.
+# RUN `build` COMMAND AFTER ADDING/REMOVING MODULES.
+
+class_name: UnitTester
+modules:
+    enabled:
+      - Asserts
+      - Yii2:
+            part: [orm, email, fixtures]

+ 3 - 0
tests/unit/_bootstrap.php

@@ -0,0 +1,3 @@
+<?php
+
+// add unit testing specific bootstrap code here

+ 41 - 0
tests/unit/models/ContactFormTest.php

@@ -0,0 +1,41 @@
+<?php
+
+namespace tests\unit\models;
+
+use app\models\ContactForm;
+use yii\mail\MessageInterface;
+
+class ContactFormTest extends \Codeception\Test\Unit
+{
+    /**
+     * @var \UnitTester
+     */
+    public $tester;
+
+    public function testEmailIsSentOnContact()
+    {
+        $model = new ContactForm();
+
+        $model->attributes = [
+            'name' => 'Tester',
+            'email' => 'tester@example.com',
+            'subject' => 'very important letter subject',
+            'body' => 'body of current message',
+            'verifyCode' => 'testme',
+        ];
+
+        expect_that($model->contact('admin@example.com'));
+
+        // using Yii2 module actions to check email was sent
+        $this->tester->seeEmailIsSent();
+
+        /** @var MessageInterface $emailMessage */
+        $emailMessage = $this->tester->grabLastSentEmail();
+        expect('valid email is sent', $emailMessage)->isInstanceOf('yii\mail\MessageInterface');
+        expect($emailMessage->getTo())->hasKey('admin@example.com');
+        expect($emailMessage->getFrom())->hasKey('noreply@example.com');
+        expect($emailMessage->getReplyTo())->hasKey('tester@example.com');
+        expect($emailMessage->getSubject())->equals('very important letter subject');
+        expect($emailMessage->toString())->stringContainsString('body of current message');
+    }
+}

+ 51 - 0
tests/unit/models/LoginFormTest.php

@@ -0,0 +1,51 @@
+<?php
+
+namespace tests\unit\models;
+
+use app\models\LoginForm;
+
+class LoginFormTest extends \Codeception\Test\Unit
+{
+    private $model;
+
+    protected function _after()
+    {
+        \Yii::$app->user->logout();
+    }
+
+    public function testLoginNoUser()
+    {
+        $this->model = new LoginForm([
+            'username' => 'not_existing_username',
+            'password' => 'not_existing_password',
+        ]);
+
+        expect_not($this->model->login());
+        expect_that(\Yii::$app->user->isGuest);
+    }
+
+    public function testLoginWrongPassword()
+    {
+        $this->model = new LoginForm([
+            'username' => 'demo',
+            'password' => 'wrong_password',
+        ]);
+
+        expect_not($this->model->login());
+        expect_that(\Yii::$app->user->isGuest);
+        expect($this->model->errors)->hasKey('password');
+    }
+
+    public function testLoginCorrect()
+    {
+        $this->model = new LoginForm([
+            'username' => 'demo',
+            'password' => 'demo',
+        ]);
+
+        expect_that($this->model->login());
+        expect_not(\Yii::$app->user->isGuest);
+        expect($this->model->errors)->hasntKey('password');
+    }
+
+}

+ 44 - 0
tests/unit/models/UserTest.php

@@ -0,0 +1,44 @@
+<?php
+
+namespace tests\unit\models;
+
+use app\models\User;
+
+class UserTest extends \Codeception\Test\Unit
+{
+    public function testFindUserById()
+    {
+        expect_that($user = User::findIdentity(100));
+        expect($user->username)->equals('admin');
+
+        expect_not(User::findIdentity(999));
+    }
+
+    public function testFindUserByAccessToken()
+    {
+        expect_that($user = User::findIdentityByAccessToken('100-token'));
+        expect($user->username)->equals('admin');
+
+        expect_not(User::findIdentityByAccessToken('non-existing'));        
+    }
+
+    public function testFindUserByUsername()
+    {
+        expect_that($user = User::findByUsername('admin'));
+        expect_not(User::findByUsername('not-admin'));
+    }
+
+    /**
+     * @depends testFindUserByUsername
+     */
+    public function testValidateUser($user)
+    {
+        $user = User::findByUsername('admin');
+        expect_that($user->validateAuthKey('test100key'));
+        expect_not($user->validateAuthKey('test102key'));
+
+        expect_that($user->validatePassword('admin'));
+        expect_not($user->validatePassword('123456'));        
+    }
+
+}

+ 2 - 0
vagrant/config/.gitignore

@@ -0,0 +1,2 @@
+# local configuration
+vagrant-local.yml

+ 22 - 0
vagrant/config/vagrant-local.example.yml

@@ -0,0 +1,22 @@
+# Your personal GitHub token
+github_token: <your-personal-github-token>
+# Read more: https://github.com/blog/1509-personal-api-tokens
+# You can generate it here: https://github.com/settings/tokens
+
+# Guest OS timezone
+timezone: Europe/London
+
+# Are we need check box updates for every 'vagrant up'?
+box_check_update: false
+
+# Virtual machine name
+machine_name: yii2basic
+
+# Virtual machine IP
+ip: 192.168.83.137
+
+# Virtual machine CPU cores number
+cpus: 1
+
+# Virtual machine RAM
+memory: 1024

+ 38 - 0
vagrant/nginx/app.conf

@@ -0,0 +1,38 @@
+server {
+   charset utf-8;
+   client_max_body_size 128M;
+   sendfile off;
+
+   listen 80; ## listen for ipv4
+   #listen [::]:80 default_server ipv6only=on; ## listen for ipv6
+
+   server_name yii2basic.test;
+   root        /app/web/;
+   index       index.php;
+
+   access_log  /app/vagrant/nginx/log/yii2basic.access.log;
+   error_log   /app/vagrant/nginx/log/yii2basic.error.log;
+
+   location / {
+       # Redirect everything that isn't a real file to index.php
+       try_files $uri $uri/ /index.php$is_args$args;
+   }
+
+   # uncomment to avoid processing of calls to non-existing static files by Yii
+   #location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ {
+   #    try_files $uri =404;
+   #}
+   #error_page 404 /404.html;
+
+   location ~ \.php$ {
+       include fastcgi_params;
+       fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+       #fastcgi_pass   127.0.0.1:9000;
+       fastcgi_pass unix:/var/run/php/php7.2-fpm.sock;
+       try_files $uri =404;
+   }
+
+   location ~ /\.(ht|svn|git) {
+       deny all;
+   }
+}

+ 3 - 0
vagrant/nginx/log/.gitignore

@@ -0,0 +1,3 @@
+#nginx logs
+yii2basic.access.log
+yii2basic.error.log

+ 18 - 0
vagrant/provision/always-as-root.sh

@@ -0,0 +1,18 @@
+#!/usr/bin/env bash
+
+#== Bash helpers ==
+
+function info {
+  echo " "
+  echo "--> $1"
+  echo " "
+}
+
+#== Provision script ==
+
+info "Provision-script user: `whoami`"
+
+info "Restart web-stack"
+service php7.2-fpm restart
+service nginx restart
+service mysql restart

+ 79 - 0
vagrant/provision/once-as-root.sh

@@ -0,0 +1,79 @@
+#!/usr/bin/env bash
+
+#== Import script args ==
+
+timezone=$(echo "$1")
+readonly IP=$2
+
+#== Bash helpers ==
+
+function info {
+  echo " "
+  echo "--> $1"
+  echo " "
+}
+
+#== Provision script ==
+
+info "Provision-script user: `whoami`"
+
+export DEBIAN_FRONTEND=noninteractive
+
+info "Configure timezone"
+timedatectl set-timezone ${timezone} --no-ask-password
+
+info "Add the VM IP to the list of allowed IPs"
+awk -v ip=$IP -f /app/vagrant/provision/provision.awk /app/config/web.php
+
+info "Prepare root password for MySQL"
+debconf-set-selections <<< 'mariadb-server mysql-server/root_password password'
+debconf-set-selections <<< 'mariadb-server mysql-server/root_password_again password'
+echo "Done!"
+
+info "Update OS software"
+apt-get update
+apt-get upgrade -y
+
+info "Install additional software"
+apt-get install -y php7.2-curl php7.2-cli php7.2-intl php7.2-mysqlnd php7.2-gd php7.2-fpm php7.2-mbstring php7.2-xml unzip nginx mariadb-server-10.1 php.xdebug
+
+info "Configure MySQL"
+sed -i 's/.*bind-address.*/bind-address = 0.0.0.0/' /etc/mysql/mariadb.conf.d/50-server.cnf
+mysql <<< "CREATE USER 'root'@'%' IDENTIFIED BY ''"
+mysql <<< "GRANT ALL PRIVILEGES ON *.* TO 'root'@'%'"
+mysql <<< "DROP USER 'root'@'localhost'"
+mysql <<< 'FLUSH PRIVILEGES'
+echo "Done!"
+
+info "Configure PHP-FPM"
+sed -i 's/user = www-data/user = vagrant/g' /etc/php/7.2/fpm/pool.d/www.conf
+sed -i 's/group = www-data/group = vagrant/g' /etc/php/7.2/fpm/pool.d/www.conf
+sed -i 's/owner = www-data/owner = vagrant/g' /etc/php/7.2/fpm/pool.d/www.conf
+cat << EOF > /etc/php/7.2/mods-available/xdebug.ini
+zend_extension=xdebug.so
+xdebug.remote_enable=1
+xdebug.remote_connect_back=1
+xdebug.remote_port=9000
+xdebug.remote_autostart=1
+EOF
+echo "Done!"
+
+info "Configure NGINX"
+sed -i 's/user www-data/user vagrant/g' /etc/nginx/nginx.conf
+echo "Done!"
+
+info "Enabling site configuration"
+ln -s /app/vagrant/nginx/app.conf /etc/nginx/sites-enabled/app.conf
+echo "Done!"
+
+info "Removing default site configuration"
+rm /etc/nginx/sites-enabled/default
+echo "Done!"
+
+info "Initailize databases for MySQL"
+mysql <<< 'CREATE DATABASE yii2basic'
+mysql <<< 'CREATE DATABASE yii2basic_test'
+echo "Done!"
+
+info "Install composer"
+curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

+ 31 - 0
vagrant/provision/once-as-vagrant.sh

@@ -0,0 +1,31 @@
+#!/usr/bin/env bash
+
+#== Import script args ==
+
+github_token=$(echo "$1")
+
+#== Bash helpers ==
+
+function info {
+  echo " "
+  echo "--> $1"
+  echo " "
+}
+
+#== Provision script ==
+
+info "Provision-script user: `whoami`"
+
+info "Configure composer"
+composer config --global github-oauth.github.com ${github_token}
+echo "Done!"
+
+info "Install project dependencies"
+cd /app
+composer --no-progress --prefer-dist install
+
+info "Create bash-alias 'app' for vagrant user"
+echo 'alias app="cd /app"' | tee /home/vagrant/.bash_aliases
+
+info "Enabling colorized prompt for guest console"
+sed -i "s/#force_color_prompt=yes/force_color_prompt=yes/" /home/vagrant/.bashrc

+ 50 - 0
vagrant/provision/provision.awk

@@ -0,0 +1,50 @@
+###
+# Modifying Yii2's files for Vagrant VM
+#
+# @author HA3IK <golubha3ik@gmail.com>
+# @version 1.0.0
+
+BEGIN {
+    print "AWK BEGINs its work:"
+    IGNORECASE = 1
+
+    # Correct IP - wildcard last octet
+    match(ip, /(([0-9]+\.)+)/, arr)
+    ip = arr[1] "*"
+}
+# BODY
+{
+    # Check if it's the same file
+    if (FILENAME != isFile["same"]){
+        msg = "- Work with: " FILENAME
+        # Close a previous file
+        close(isFile["same"])
+        # Delete previous data
+        delete isFile
+        # Save current file
+        isFile["same"] = FILENAME
+        # Define array index for the file
+        switch (FILENAME){
+        case /config\/web\.php$/:
+            isFile["IsConfWeb"] = 1
+            msg = msg " - add allowed IP: " ip
+            break
+        }
+        # Print the concatenated message for the file
+        print msg
+    }
+
+    # IF config/web.php
+    if (isFile["IsConfWeb"]){
+        # IF line has "allowedIPs" and doesn't has our IP
+        if (match($0, "allowedIPs") && !match($0, ip)){
+            match($0, /([^\]]+)(.+)/, arr)
+            $0 = sprintf("%s, '%s'%s", arr[1], ip, arr[2])
+        }
+        # Rewrite the file
+        print $0 > FILENAME
+    }
+}
+END {
+    print "AWK ENDs its work."
+}

+ 42 - 0
views/layouts/guest.php

@@ -0,0 +1,42 @@
+<?php
+
+/* @var $this \yii\web\View */
+/* @var $content string */
+
+use app\assets\AdminLteAsset;
+use app\widgets\Alert;
+use yii\helpers\Html;
+
+AdminLteAsset::register($this);
+
+$bodyClass = $this->params['body-class'] ?? null;
+Html::addCssClass($bodyClass, ['hold-transition']);
+
+?>
+<?php $this->beginPage() ?>
+<!DOCTYPE html>
+<html lang="<?= Yii::$app->language ?>">
+<head>
+    <meta charset="<?= Yii::$app->charset ?>">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <?php $this->registerCsrfMetaTags() ?>
+    <title><?= Html::encode($this->title) ?></title>
+    <?php $this->head() ?>
+</head>
+<?= Html::tag('body', '', $bodyClass); ?>
+<?php $this->beginBody() ?>
+
+<main>
+    <?= Alert::widget() ?>
+    <?= $content ?>
+</main>
+
+<footer class="small text-muted text-center">
+    <p class="pull-left">&copy; <?= Yii::$app->name; ?> <?= date('Y') ?></p>
+</footer>
+
+<?php $this->endBody() ?>
+</body>
+</html>
+<?php $this->endPage() ?>

+ 224 - 0
views/layouts/main.php

@@ -0,0 +1,224 @@
+<?php
+
+/* @var $this \yii\web\View */
+/* @var $content string */
+
+use app\widgets\Alert;
+use yii\helpers\Html;
+use yii\bootstrap4\Nav;
+use yii\bootstrap4\NavBar;
+use rmrevin\yii\fontawesome\FAR;
+use rmrevin\yii\fontawesome\FAS;
+use yii\widgets\Breadcrumbs;
+use app\widgets\MainSidebarMenu;
+use app\assets\AdminLteAsset;
+
+//AppAsset::register($this);
+AdminLteAsset::register($this);
+
+$bodyClass = $this->params['body-class'] ?? null;
+Html::addCssClass($bodyClass, ['hold-transition', 'sidebar-mini', 'layout-fixed', 'layout-navbar-fixed']);
+
+?>
+<?php $this->beginPage() ?>
+<!DOCTYPE html>
+<html lang="<?= Yii::$app->language ?>">
+<head>
+    <meta charset="<?= Yii::$app->charset ?>">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <?php $this->registerCsrfMetaTags() ?>
+    <title><?= Html::encode($this->title) ?></title>
+    <?php $this->head() ?>
+</head>
+<?= Html::tag('body', '', $bodyClass); ?>
+<?php $this->beginBody() ?>
+<!-- Site wrapper -->
+<div class="wrapper">
+    <!-- Navbar -->
+    <?php
+    NavBar::begin([
+        'brandUrl' => Yii::$app->homeUrl,
+        'togglerOptions' => ['data-widget' => 'pushmenu', 'type' => 'link', 'widget'=>'nav-link'],
+        'renderInnerContainer' => false,
+        'options' => [
+            'class' => 'main-header navbar navbar-expand navbar-light',
+        ],
+    ]);
+
+    echo Nav::widget([
+        'options' => ['class' => 'navbar-nav'],
+        'items' => [
+            ['label' => '<i class="fas fa-bars"></i>', 'linkOptions' => ['data-widget' => 'pushmenu'], 'encode' => false],
+        ],
+    ]);
+
+    $menuItems = [
+        [
+            'label' => FAS::icon('users', ['class' => ['nav-icon']]) . ' Пользователи',
+            'raw' => true,
+            'url' => ['/user/admin/index'],
+            'active' => Yii::$app->controller->module->id == 'user',
+            'visible' => Yii::$app->user->can('admin'),
+        ],
+    ];
+    if (Yii::$app->user->isGuest) {
+        $menuItems[] = ['label' => 'Login', 'url' => ['/site/login']];
+    } else {
+        $menuItems[] = '<li class="nav-item">'
+            . Html::beginForm(['/logout'], 'post')
+            . Html::submitButton(
+                'Выйти (' . Yii::$app->user->identity->username . ')',
+                ['class' => 'btn btn-link logout']
+            )
+            . Html::endForm()
+            . '</li>';
+    };
+    $menuItems[] = ['label' => 'Home2', 'url' => ['/site/index'], 'linkOptions' => ['data-widget' => 'control-sidebar', 'class' => 'd-none']];
+    echo Nav::widget([
+        'options' => ['class' => 'navbar-nav ml-auto'],
+        'encodeLabels' => false,
+        'items' => $menuItems,
+    ]);
+    NavBar::end();
+    ?>
+    <!-- /.navbar -->
+
+    <!-- Main Sidebar Container -->
+    <aside class="main-sidebar sidebar-dark-success elevation-4">
+        <!-- Brand Logo -->
+        <a href="<?= Yii::$app->homeUrl; ?>" class="brand-link navbar-dark text-center text-uppercase">
+            <!--<img src="../dist/img/AdminLTELogo.png" alt="AdminLTE Logo" class="brand-image img-circle elevation-3" style="opacity: .8">-->
+            <?= Html::img('@web/img/logo.png', ['alt' => Yii::$app->name, 'style' => 'width: 30%;opacity:.8;', 'class'=>'d-none brand-image img-circle elevation-3']); ?>
+            <span class="brand-text font-weight-light"><?= Yii::$app->name; ?></span>
+        </a>
+
+        <!-- Sidebar -->
+        <div class="sidebar">
+            <!-- Sidebar user panel (optional) -->
+            <div class="user-panel mt-3 pb-3 mb-3 d-flex">
+                <div class="image d-none">
+                    <!--<img src="../dist/img/user2-160x160.jpg" class="img-circle elevation-2" alt="User Image">-->
+                    <?= Html::img('@web/img/logo.png', ['alt' => 'User Image', 'style' => '', 'class'=>'d-none img-circle elevation-2']); ?>
+                </div>
+                <div class="info d-none">
+                    <a href="#" class="d-block">Alexander Pierce</a>
+                </div>
+            </div>
+
+            <!-- Sidebar Menu -->
+            <nav class="mt-2">
+                <?php
+                echo MainSidebarMenu::widget([
+                     'options' => [
+                         'class' => 'nav nav-pills nav-sidebar flex-column nav-child-indent',
+                         'data' => [
+                             'widget' => 'treeview',
+                             'accordion' => 'false'
+                         ],
+                         'role' => 'menu',
+                     ],
+                     'encodeLabels' => false,
+                     //'activateParents' => true,
+                     'items' => [
+                         [
+                             'icon' => FAS::icon('home', ['class' => ['nav-icon']]),
+                             'label' => 'Главная',
+                             'url' => Yii::$app->homeUrl,
+                             'active' => Yii::$app->controller->id === 'site',
+                         ],
+                         [
+                             'icon' => FAS::icon('hand-holding-usd', ['class' => ['nav-icon']]),
+                             'label' => 'Пополнить счет',
+                             'url' => Yii::$app->homeUrl,
+                             //'active' => Yii::$app->controller->id === 'site',
+                         ],
+                         [
+                             'icon' => FAS::icon('money-check-alt', ['class' => ['nav-icon']]),
+                             'label' => 'Тарифы',
+                             'url' => ['/pricing/index'],
+                             'active' => Yii::$app->controller->id === 'pricing',
+                         ],
+                         [
+                             'icon' => FAS::icon('history', ['class' => ['nav-icon']]),
+                             'label' => 'История платежей',
+                             'url' => Yii::$app->homeUrl,
+                         ],
+                         [
+                             'icon' => FAS::icon('user-alt', ['class' => ['nav-icon']]),
+                             'label' => 'Профайл',
+                             'url' => Yii::$app->homeUrl,//['/site/index'],
+                         ],
+                         [
+                             'icon' => FAS::icon('sign-out-alt', ['class' => ['nav-icon']]),
+                             'label' => 'Выход',
+                             'url' => ['/logout'],
+                             'template' => Html::beginForm(['/logout'], 'post')
+                                 . '<a href="#" onclick="this.parentNode.submit();" class="nav-link">{icon}&nbsp;<p>{label}</p></a>'
+                                 . Html::endForm()
+                         ]
+                     ],
+                 ]);
+                ?>
+            </nav>
+            <!-- /.sidebar-menu -->
+        </div>
+        <!-- /.sidebar -->
+    </aside>
+
+    <!-- Content Wrapper. Contains page content -->
+    <div class="content-wrapper">
+        <!-- Content Header (Page header) -->
+        <div class="content-header">
+            <div class="container-fluid">
+                <div class="row mb-2">
+                    <div class="col-sm-6">
+                        <h1><?= Html::encode($this->title); ?></h1>
+                    </div>
+                    <div class="col-sm-6 align-self-end">
+                        <?= Breadcrumbs::widget([
+                            'homeLink' => [
+                                'label' =>  FAS::icon('home', ['class' => ['nav-icon']]),
+                                'url' => Yii::$app->homeUrl,
+                                'class' => 'text-secondary',
+                                'encode' => false,
+                            ],
+                            'tag' => 'ol',
+                            'options' => [
+                                'class' => 'breadcrumb text-sm float-sm-right'
+                            ],
+                            'itemTemplate' => "<li class=\"breadcrumb-item\">{link}</li>\n",
+                            'activeItemTemplate' => "<li class=\"breadcrumb-item active\">{link}</li>\n",
+                            'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [],
+                        ]) ?>
+                    </div>
+                </div>
+            </div><!-- /.container-fluid -->
+        </div>
+        <!-- /.content-header -->
+
+        <!-- Main content -->
+        <div class="content">
+
+            <div class="container-fluid">
+                <?= Alert::widget() ?>
+                <?= $content ?>
+            </div>
+
+        </div>
+        <!-- /.content -->
+    </div>
+    <!-- /.content-wrapper -->
+
+    <footer class="main-footer text-sm">
+        <div class="float-right d-none d-sm-block">
+            Розробка: <a href="http://reactlogic.com.ua/" target="_blank">ReactLogic</a>
+        </div>
+        <strong>Copyright &copy; <?= date('Y') ?> <?= Html::encode(Yii::$app->name) ?>.</strong> All rights reserved.
+    </footer>
+</div>
+
+<?php $this->endBody() ?>
+</body>
+</html>
+<?php $this->endPage() ?>

+ 65 - 0
views/pricing/index.php

@@ -0,0 +1,65 @@
+<section class="content">
+    <div class="row align-items-center">
+        <div class="col">
+            <div class="card card-default">
+                <div class="card-header text-uppercase text-center">
+                    <h3 class="card-title float-none">Базовый</h3>
+                </div>
+                <div class="card-body">
+                    <p>
+                        Многие думают, что Lorem Ipsum - взятый с потолка псевдо-латинский набор слов, но это не совсем так. Его корни уходят в один фрагмент классической латыни 45 года н.э., то есть более двух тысячелетий назад.
+                    </p>
+                    <ul>
+                        <li>Многие программы электронной вёрстки</li>
+                    </ul>
+                </div>
+                <!-- /.card-body -->
+                <div class="card-footer text-center">
+                    <button type="button" class="btn btn-default btn-block">Заказать</button>
+                </div>
+            </div>
+        </div>
+        <div class="col">
+            <div class="card bg-gradient-success">
+                <div class="card-header text-uppercase text-center">
+                    <h3 class="card-title text-bold float-none">Стандарт</h3>
+                </div>
+                <div class="card-body">
+                    <p>
+                        Многие думают, что <strong>Lorem Ipsum</strong> - взятый с потолка псевдо-латинский набор слов, но это не совсем так. Его корни уходят в один фрагмент классической латыни 45 года н.э., то есть более двух тысячелетий назад.
+                    </p>
+                    <ul>
+                        <li>Давно выяснено, что при оценке дизайна</li>
+                        <li><strong>Lorem Ipsum</strong> используют потому</li>
+                        <li>а также реальное распределение букв</li>
+                        <li>Lorem Ipsum используют потому</li>
+                        <li>Давно выяснено, что при оценке дизайна</li>
+                    </ul>
+                </div>
+                <!-- /.card-body -->
+                <div class="card-footer text-center">
+                    <button type="button" class="btn btn-outline-light btn-lg btn-block text-uppercase">Заказать</button>
+                </div>
+            </div>
+        </div>
+        <div class="col">
+            <div class="card card-default">
+                <div class="card-header text-uppercase text-center">
+                    <h3 class="card-title float-none">Продвинутый</h3>
+                </div>
+                <div class="card-body">
+                    <p>
+                        Многие думают, что Lorem Ipsum - взятый с потолка псевдо-латинский набор слов, но это не совсем так. Его корни уходят в один фрагмент классической латыни 45 года н.э., то есть более двух тысячелетий назад.
+                    </p>
+                    <ul>
+                        <li>Многие программы электронной вёрстки</li>
+                    </ul>
+                </div>
+                <!-- /.card-body -->
+                <div class="card-footer text-center">
+                    <button type="button" class="btn btn-default btn-block">Заказать</button>
+                </div>
+            </div>
+        </div>
+    </div>
+</section>

+ 18 - 0
views/site/about.php

@@ -0,0 +1,18 @@
+<?php
+
+/* @var $this yii\web\View */
+
+use yii\helpers\Html;
+
+$this->title = 'About';
+$this->params['breadcrumbs'][] = $this->title;
+?>
+<div class="site-about">
+    <h1><?= Html::encode($this->title) ?></h1>
+
+    <p>
+        This is the About page. You may modify the following file to customize its content:
+    </p>
+
+    <code><?= __FILE__ ?></code>
+</div>

+ 68 - 0
views/site/contact.php

@@ -0,0 +1,68 @@
+<?php
+
+/* @var $this yii\web\View */
+/* @var $form yii\bootstrap4\ActiveForm */
+/* @var $model app\models\ContactForm */
+
+use yii\helpers\Html;
+use yii\bootstrap4\ActiveForm;
+use yii\captcha\Captcha;
+
+$this->title = 'Contact';
+$this->params['breadcrumbs'][] = $this->title;
+?>
+<div class="site-contact">
+    <h1><?= Html::encode($this->title) ?></h1>
+
+    <?php if (Yii::$app->session->hasFlash('contactFormSubmitted')): ?>
+
+        <div class="alert alert-success">
+            Thank you for contacting us. We will respond to you as soon as possible.
+        </div>
+
+        <p>
+            Note that if you turn on the Yii debugger, you should be able
+            to view the mail message on the mail panel of the debugger.
+            <?php if (Yii::$app->mailer->useFileTransport): ?>
+                Because the application is in development mode, the email is not sent but saved as
+                a file under <code><?= Yii::getAlias(Yii::$app->mailer->fileTransportPath) ?></code>.
+                Please configure the <code>useFileTransport</code> property of the <code>mail</code>
+                application component to be false to enable email sending.
+            <?php endif; ?>
+        </p>
+
+    <?php else: ?>
+
+        <p>
+            If you have business inquiries or other questions, please fill out the following form to contact us.
+            Thank you.
+        </p>
+
+        <div class="row">
+            <div class="col-lg-5">
+
+                <?php $form = ActiveForm::begin(['id' => 'contact-form']); ?>
+
+                    <?= $form->field($model, 'name')->textInput(['autofocus' => true]) ?>
+
+                    <?= $form->field($model, 'email') ?>
+
+                    <?= $form->field($model, 'subject') ?>
+
+                    <?= $form->field($model, 'body')->textarea(['rows' => 6]) ?>
+
+                    <?= $form->field($model, 'verifyCode')->widget(Captcha::className(), [
+                        'template' => '<div class="row"><div class="col-lg-3">{image}</div><div class="col-lg-6">{input}</div></div>',
+                    ]) ?>
+
+                    <div class="form-group">
+                        <?= Html::submitButton('Submit', ['class' => 'btn btn-primary', 'name' => 'contact-button']) ?>
+                    </div>
+
+                <?php ActiveForm::end(); ?>
+
+            </div>
+        </div>
+
+    <?php endif; ?>
+</div>

+ 27 - 0
views/site/error.php

@@ -0,0 +1,27 @@
+<?php
+
+/* @var $this yii\web\View */
+/* @var $name string */
+/* @var $message string */
+/* @var $exception Exception */
+
+use yii\helpers\Html;
+
+$this->title = $name;
+?>
+<div class="site-error">
+
+    <h1><?= Html::encode($this->title) ?></h1>
+
+    <div class="alert alert-danger">
+        <?= nl2br(Html::encode($message)) ?>
+    </div>
+
+    <p>
+        The above error occurred while the Web server was processing your request.
+    </p>
+    <p>
+        Please contact us if you think this is a server error. Thank you.
+    </p>
+
+</div>

+ 53 - 0
views/site/index.php

@@ -0,0 +1,53 @@
+<?php
+
+/* @var $this yii\web\View */
+
+$this->title = 'My Yii Application';
+?>
+<div class="site-index">
+
+    <div class="jumbotron">
+        <h1>Congratulations!</h1>
+
+        <p class="lead">You have successfully created your Yii-powered application.</p>
+
+        <p><a class="btn btn-lg btn-success" href="http://www.yiiframework.com">Get started with Yii</a></p>
+    </div>
+
+    <div class="body-content">
+
+        <div class="row">
+            <div class="col-lg-4">
+                <h2>Heading</h2>
+
+                <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
+                    dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
+                    ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
+                    fugiat nulla pariatur.</p>
+
+                <p><a class="btn btn-default" href="http://www.yiiframework.com/doc/">Yii Documentation &raquo;</a></p>
+            </div>
+            <div class="col-lg-4">
+                <h2>Heading</h2>
+
+                <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
+                    dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
+                    ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
+                    fugiat nulla pariatur.</p>
+
+                <p><a class="btn btn-default" href="http://www.yiiframework.com/forum/">Yii Forum &raquo;</a></p>
+            </div>
+            <div class="col-lg-4">
+                <h2>Heading</h2>
+
+                <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
+                    dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
+                    ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
+                    fugiat nulla pariatur.</p>
+
+                <p><a class="btn btn-default" href="http://www.yiiframework.com/extensions/">Yii Extensions &raquo;</a></p>
+            </div>
+        </div>
+
+    </div>
+</div>

+ 47 - 0
views/site/login.php

@@ -0,0 +1,47 @@
+<?php
+
+/* @var $this yii\web\View */
+/* @var $form yii\bootstrap4\ActiveForm */
+/* @var $model app\models\LoginForm */
+
+use yii\helpers\Html;
+use yii\bootstrap4\ActiveForm;
+
+$this->title = 'Login';
+$this->params['breadcrumbs'][] = $this->title;
+?>
+<div class="site-login">
+    <h1><?= Html::encode($this->title) ?></h1>
+
+    <p>Please fill out the following fields to login:</p>
+
+    <?php $form = ActiveForm::begin([
+        'id' => 'login-form',
+        'layout' => 'horizontal',
+        'fieldConfig' => [
+            'template' => "{label}\n<div class=\"col-lg-3\">{input}</div>\n<div class=\"col-lg-8\">{error}</div>",
+            'labelOptions' => ['class' => 'col-lg-1 control-label'],
+        ],
+    ]); ?>
+
+        <?= $form->field($model, 'username')->textInput(['autofocus' => true]) ?>
+
+        <?= $form->field($model, 'password')->passwordInput() ?>
+
+        <?= $form->field($model, 'rememberMe')->checkbox([
+            'template' => "<div class=\"col-lg-offset-1 col-lg-3\">{input} {label}</div>\n<div class=\"col-lg-8\">{error}</div>",
+        ]) ?>
+
+        <div class="form-group">
+            <div class="col-lg-offset-1 col-lg-11">
+                <?= Html::submitButton('Login', ['class' => 'btn btn-primary', 'name' => 'login-button']) ?>
+            </div>
+        </div>
+
+    <?php ActiveForm::end(); ?>
+
+    <div class="col-lg-offset-1" style="color:#999;">
+        You may login with <strong>admin/admin</strong> or <strong>demo/demo</strong>.<br>
+        To modify the username/password, please check out the code <code>app\models\User::$users</code>.
+    </div>
+</div>

+ 32 - 0
views/user/_alert.php

@@ -0,0 +1,32 @@
+<?php
+
+/*
+ * This file is part of the Dektrium project.
+ *
+ * (c) Dektrium project <http://github.com/dektrium>
+ *
+ * For the full copyright and license information, please view the LICENSE.md
+ * file that was distributed with this source code.
+ */
+
+use yii\bootstrap\Alert;
+
+/**
+ * @var dektrium\user\Module $module
+ */
+?>
+
+<?php if ($module->enableFlashMessages): ?>
+    <div class="row">
+        <div class="col-xs-12">
+            <?php foreach (Yii::$app->session->getAllFlashes() as $type => $message): ?>
+                <?php if (in_array($type, ['success', 'danger', 'warning', 'info'])): ?>
+                    <?= Alert::widget([
+                        'options' => ['class' => 'alert-dismissible alert-' . $type],
+                        'body' => $message
+                    ]) ?>
+                <?php endif ?>
+            <?php endforeach ?>
+        </div>
+    </div>
+<?php endif ?>

+ 44 - 0
views/user/admin/_account.php

@@ -0,0 +1,44 @@
+<?php
+
+/*
+ * This file is part of the Dektrium project
+ *
+ * (c) Dektrium project <http://github.com/dektrium>
+ *
+ * For the full copyright and license information, please view the LICENSE.md
+ * file that was distributed with this source code.
+ */
+
+use yii\bootstrap4\ActiveForm;
+use yii\helpers\Html;
+
+/**
+ * @var yii\web\View $this
+ * @var dektrium\user\models\User $user
+ */
+?>
+
+<?php $this->beginContent('@dektrium/user/views/admin/update.php', ['user' => $user]) ?>
+
+<?php $form = ActiveForm::begin([
+    'layout' => 'horizontal',
+    'enableAjaxValidation' => true,
+    'enableClientValidation' => false,
+    'fieldConfig' => [
+        'horizontalCssClasses' => [
+            'wrapper' => 'col-sm-9',
+        ],
+    ],
+]); ?>
+
+<?= $this->render('_user', ['form' => $form, 'user' => $user]) ?>
+
+<div class="form-group">
+    <div class="col-lg-offset-3 col-lg-9">
+        <?= Html::submitButton(Yii::t('user', 'Update'), ['class' => 'btn btn-block btn-success']) ?>
+    </div>
+</div>
+
+<?php ActiveForm::end(); ?>
+
+<?php $this->endContent() ?>

+ 31 - 0
views/user/admin/_assignments.php

@@ -0,0 +1,31 @@
+<?php
+
+/*
+ * This file is part of the Dektrium project
+ *
+ * (c) Dektrium project <http://github.com/dektrium>
+ *
+ * For the full copyright and license information, please view the LICENSE.md
+ * file that was distributed with this source code.
+ */
+
+use dektrium\rbac\widgets\Assignments;
+
+/**
+ * @var yii\web\View $this
+ * @var dektrium\user\models\User $user
+ */
+?>
+
+<?php $this->beginContent('@dektrium/user/views/admin/update.php', ['user' => $user]) ?>
+
+<?= yii\bootstrap\Alert::widget([
+    'options' => [
+        'class' => 'alert-info alert-dismissible',
+    ],
+    'body' => Yii::t('user', 'You can assign multiple roles or permissions to user by using the form below'),
+]) ?>
+
+<?= Assignments::widget(['userId' => $user->id]) ?>
+
+<?php $this->endContent() ?>

+ 53 - 0
views/user/admin/_info.php

@@ -0,0 +1,53 @@
+<?php
+
+/*
+ * This file is part of the Dektrium project
+ *
+ * (c) Dektrium project <http://github.com/dektrium>
+ *
+ * For the full copyright and license information, please view the LICENSE.md
+ * file that was distributed with this source code.
+ */
+
+/**
+ * @var yii\web\View $this
+ * @var dektrium\user\models\User $user
+ */
+?>
+
+<?php $this->beginContent('@dektrium/user/views/admin/update.php', ['user' => $user]) ?>
+
+<table class="table">
+    <tr>
+        <td><strong><?= Yii::t('user', 'Registration time') ?>:</strong></td>
+        <td><?= Yii::t('user', '{0, date, MMMM dd, YYYY HH:mm}', [$user->created_at]) ?></td>
+    </tr>
+    <?php if ($user->registration_ip !== null): ?>
+        <tr>
+            <td><strong><?= Yii::t('user', 'Registration IP') ?>:</strong></td>
+            <td><?= $user->registration_ip ?></td>
+        </tr>
+    <?php endif ?>
+    <tr>
+        <td><strong><?= Yii::t('user', 'Confirmation status') ?>:</strong></td>
+        <?php if ($user->isConfirmed): ?>
+            <td class="text-success">
+                <?= Yii::t('user', 'Confirmed at {0, date, MMMM dd, YYYY HH:mm}', [$user->confirmed_at]) ?>
+            </td>
+        <?php else: ?>
+            <td class="text-danger"><?= Yii::t('user', 'Unconfirmed') ?></td>
+        <?php endif ?>
+    </tr>
+    <tr>
+        <td><strong><?= Yii::t('user', 'Block status') ?>:</strong></td>
+        <?php if ($user->isBlocked): ?>
+            <td class="text-danger">
+                <?= Yii::t('user', 'Blocked at {0, date, MMMM dd, YYYY HH:mm}', [$user->blocked_at]) ?>
+            </td>
+        <?php else: ?>
+            <td class="text-success"><?= Yii::t('user', 'Not blocked') ?></td>
+        <?php endif ?>
+    </tr>
+</table>
+
+<?php $this->endContent() ?>

+ 68 - 0
views/user/admin/_menu.php

@@ -0,0 +1,68 @@
+<?php
+
+/*
+ * This file is part of the Dektrium project
+ *
+ * (c) Dektrium project <http://github.com/dektrium>
+ *
+ * For the full copyright and license information, please view the LICENSE.md
+ * file that was distributed with this source code.
+ */
+
+use yii\bootstrap4\Nav;
+
+?>
+
+<?= Nav::widget([
+    'options' => [
+        'class' => 'nav-tabs',
+        //'style' => 'margin-bottom: 15px',
+    ],
+    'items' => [
+        [
+            'label' => Yii::t('user', 'Users'),
+            'url' => ['/user/admin/index'],
+        ],
+        [
+            'label' => Yii::t('user', 'Roles'),
+            'url' => ['/rbac/role/index'],
+            'visible' => isset(Yii::$app->extensions['dektrium/yii2-rbac']),
+        ],
+        [
+            'label' => Yii::t('user', 'Permissions'),
+            'url' => ['/rbac/permission/index'],
+            'visible' => isset(Yii::$app->extensions['dektrium/yii2-rbac']),
+        ],
+        [
+            'label' => \Yii::t('user', 'Rules'),
+            'url'   => ['/rbac/rule/index'],
+            'visible' => isset(Yii::$app->extensions['dektrium/yii2-rbac']),
+        ],
+        [
+            'label' => Yii::t('user', 'Create'),
+            'options' => ['class' => 'nav-item has-treeview'],
+            'url' => '#',
+            'items' => [
+                [
+                    'label' => Yii::t('user', 'New user'),
+                    'url' => ['/user/admin/create'],
+                ],
+                [
+                    'label' => Yii::t('user', 'New role'),
+                    'url' => ['/rbac/role/create'],
+                    'visible' => isset(Yii::$app->extensions['dektrium/yii2-rbac']),
+                ],
+                [
+                    'label' => Yii::t('user', 'New permission'),
+                    'url' => ['/rbac/permission/create'],
+                    'visible' => isset(Yii::$app->extensions['dektrium/yii2-rbac']),
+                ],
+                [
+                    'label' => \Yii::t('user', 'New rule'),
+                    'url'   => ['/rbac/rule/create'],
+                    'visible' => isset(Yii::$app->extensions['dektrium/yii2-rbac']),
+                ]
+            ],
+        ],
+    ],
+]) ?>

+ 50 - 0
views/user/admin/_profile.php

@@ -0,0 +1,50 @@
+<?php
+
+/*
+ * This file is part of the Dektrium project
+ *
+ * (c) Dektrium project <http://github.com/dektrium>
+ *
+ * For the full copyright and license information, please view the LICENSE.md
+ * file that was distributed with this source code.
+ */
+
+use yii\bootstrap4\ActiveForm;
+use yii\helpers\Html;
+
+/**
+ * @var yii\web\View $this
+ * @var dektrium\user\models\User $user
+ * @var dektrium\user\models\Profile $profile
+ */
+?>
+
+<?php $this->beginContent('@dektrium/user/views/admin/update.php', ['user' => $user]) ?>
+
+<?php $form = ActiveForm::begin([
+    'layout' => 'horizontal',
+    'enableAjaxValidation' => true,
+    'enableClientValidation' => false,
+    'fieldConfig' => [
+        'horizontalCssClasses' => [
+            'wrapper' => 'col-sm-9',
+        ],
+    ],
+]); ?>
+
+<?= $form->field($profile, 'name') ?>
+<?= $form->field($profile, 'public_email') ?>
+<?= $form->field($profile, 'website') ?>
+<?= $form->field($profile, 'location') ?>
+<?= $form->field($profile, 'gravatar_email') ?>
+<?= $form->field($profile, 'bio')->textarea() ?>
+
+<div class="form-group">
+    <div class="col-lg-offset-3 col-lg-9">
+        <?= Html::submitButton(Yii::t('user', 'Update'), ['class' => 'btn btn-block btn-success']) ?>
+    </div>
+</div>
+
+<?php ActiveForm::end(); ?>
+
+<?php $this->endContent() ?>

+ 20 - 0
views/user/admin/_user.php

@@ -0,0 +1,20 @@
+<?php
+
+/*
+ * This file is part of the Dektrium project.
+ *
+ * (c) Dektrium project <http://github.com/dektrium>
+ *
+ * For the full copyright and license information, please view the LICENSE.md
+ * file that was distributed with this source code.
+ */
+
+/**
+ * @var yii\widgets\ActiveForm $form
+ * @var dektrium\user\models\User $user
+ */
+?>
+
+<?= $form->field($user, 'email')->textInput(['maxlength' => 255]) ?>
+<?= $form->field($user, 'username')->textInput(['maxlength' => 255]) ?>
+<?= $form->field($user, 'password')->passwordInput() ?>

+ 83 - 0
views/user/admin/create.php

@@ -0,0 +1,83 @@
+<?php
+
+/*
+ * This file is part of the Dektrium project.
+ *
+ * (c) Dektrium project <http://github.com/dektrium>
+ *
+ * For the full copyright and license information, please view the LICENSE.md
+ * file that was distributed with this source code.
+ */
+
+use yii\bootstrap4\ActiveForm;
+use yii\bootstrap4\Nav;
+use yii\helpers\Html;
+
+/**
+ * @var yii\web\View $this
+ * @var dektrium\user\models\User $user
+ */
+
+$this->title = Yii::t('user', 'Create a user account');
+$this->params['breadcrumbs'][] = ['label' => Yii::t('user', 'Users'), 'url' => ['index']];
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+<?= $this->render('/_alert', ['module' => Yii::$app->getModule('user'),]) ?>
+
+<?= $this->render('_menu') ?>
+
+<div class="row">
+    <div class="col-md-3">
+        <div class="panel panel-default">
+            <div class="panel-body">
+                <?= Nav::widget([
+                    'options' => [
+                        'class' => 'nav-pills nav-stacked',
+                    ],
+                    'items' => [
+                        ['label' => Yii::t('user', 'Account details'), 'url' => ['/user/admin/create']],
+                        ['label' => Yii::t('user', 'Profile details'), 'options' => [
+                            'class' => 'disabled',
+                            'onclick' => 'return false;',
+                        ]],
+                        ['label' => Yii::t('user', 'Information'), 'options' => [
+                            'class' => 'disabled',
+                            'onclick' => 'return false;',
+                        ]],
+                    ],
+                ]) ?>
+            </div>
+        </div>
+    </div>
+    <div class="col-md-9">
+        <div class="panel panel-default">
+            <div class="panel-body">
+                <div class="alert alert-info">
+                    <?= Yii::t('user', 'Credentials will be sent to the user by email') ?>.
+                    <?= Yii::t('user', 'A password will be generated automatically if not provided') ?>.
+                </div>
+                <?php $form = ActiveForm::begin([
+                    'layout' => 'horizontal',
+                    'enableAjaxValidation' => true,
+                    'enableClientValidation' => false,
+                    'fieldConfig' => [
+                        'horizontalCssClasses' => [
+                            'wrapper' => 'col-sm-9',
+                        ],
+                    ],
+                ]); ?>
+
+                <?= $this->render('_user', ['form' => $form, 'user' => $user]) ?>
+
+                <div class="form-group">
+                    <div class="col-lg-offset-3 col-lg-9">
+                        <?= Html::submitButton(Yii::t('user', 'Save'), ['class' => 'btn btn-block btn-success']) ?>
+                    </div>
+                </div>
+
+                <?php ActiveForm::end(); ?>
+            </div>
+        </div>
+    </div>
+</div>

+ 142 - 0
views/user/admin/index.php

@@ -0,0 +1,142 @@
+<?php
+
+/*
+ * This file is part of the Dektrium project.
+ *
+ * (c) Dektrium project <http://github.com/dektrium>
+ *
+ * For the full copyright and license information, please view the LICENSE.md
+ * file that was distributed with this source code.
+ */
+
+use yii\grid\GridView;
+use yii\helpers\Html;
+use yii\helpers\Url;
+use yii\web\View;
+use yii\widgets\Pjax;
+
+
+/**
+ * @var \yii\web\View $this
+ * @var \yii\data\ActiveDataProvider $dataProvider
+ * @var \dektrium\user\models\UserSearch $searchModel
+ */
+
+$this->title = Yii::t('user', 'Manage users');
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+<?= $this->render('/_alert', ['module' => Yii::$app->getModule('user')]) ?>
+
+<?= $this->render('/admin/_menu') ?>
+
+<?php Pjax::begin() ?>
+
+<?= GridView::widget([
+    'dataProvider' => $dataProvider,
+    'filterModel'  => $searchModel,
+    'layout'       => "{items}\n{pager}",
+    'columns' => [
+        [
+            'attribute' => 'id',
+            'headerOptions' => ['style' => 'width:90px;'], # 90px is sufficient for 5-digit user ids
+        ],
+        'username',
+        'email:email',
+        [
+            'attribute' => 'registration_ip',
+            'value' => function ($model) {
+                return $model->registration_ip == null
+                    ? '<span class="not-set">' . Yii::t('user', '(not set)') . '</span>'
+                    : $model->registration_ip;
+            },
+            'format' => 'html',
+        ],
+        [
+            'attribute' => 'created_at',
+            'value' => function ($model) {
+                if (extension_loaded('intl')) {
+                    return Yii::t('user', '{0, date, MMMM dd, YYYY HH:mm}', [$model->created_at]);
+                } else {
+                    return date('Y-m-d G:i:s', $model->created_at);
+                }
+            },
+        ],
+
+        [
+          'attribute' => 'last_login_at',
+          'value' => function ($model) {
+            if (!$model->last_login_at || $model->last_login_at == 0) {
+                return Yii::t('user', 'Never');
+            } else if (extension_loaded('intl')) {
+                return Yii::t('user', '{0, date, MMMM dd, YYYY HH:mm}', [$model->last_login_at]);
+            } else {
+                return date('Y-m-d G:i:s', $model->last_login_at);
+            }
+          },
+        ],
+        [
+            'header' => Yii::t('user', 'Confirmation'),
+            'value' => function ($model) {
+                if ($model->isConfirmed) {
+                    return '<div class="text-center">
+                                <span class="text-success">' . Yii::t('user', 'Confirmed') . '</span>
+                            </div>';
+                } else {
+                    return Html::a(Yii::t('user', 'Confirm'), ['confirm', 'id' => $model->id], [
+                        'class' => 'btn btn-xs btn-success btn-block',
+                        'data-method' => 'post',
+                        'data-confirm' => Yii::t('user', 'Are you sure you want to confirm this user?'),
+                    ]);
+                }
+            },
+            'format' => 'raw',
+            'visible' => Yii::$app->getModule('user')->enableConfirmation,
+        ],
+        [
+            'header' => Yii::t('user', 'Block status'),
+            'value' => function ($model) {
+                if ($model->isBlocked) {
+                    return Html::a(Yii::t('user', 'Unblock'), ['block', 'id' => $model->id], [
+                        'class' => 'btn btn-xs btn-success btn-block',
+                        'data-method' => 'post',
+                        'data-confirm' => Yii::t('user', 'Are you sure you want to unblock this user?'),
+                    ]);
+                } else {
+                    return Html::a(Yii::t('user', 'Block'), ['block', 'id' => $model->id], [
+                        'class' => 'btn btn-xs btn-danger btn-block',
+                        'data-method' => 'post',
+                        'data-confirm' => Yii::t('user', 'Are you sure you want to block this user?'),
+                    ]);
+                }
+            },
+            'format' => 'raw',
+        ],
+        [
+            'class' => 'yii\grid\ActionColumn',
+            'contentOptions' => ['style'=>'white-space:nowrap;'],
+            'template' => '{switch} {resend_password} {update} {delete}',
+            'buttons' => [
+                'resend_password' => function ($url, $model, $key) {
+                    if (\Yii::$app->user->identity->isAdmin && !$model->isAdmin) {
+                        return '
+                    <a data-method="POST" data-confirm="' . Yii::t('user', 'Are you sure?') . '" href="' . Url::to(['resend-password', 'id' => $model->id]) . '">
+                    <span title="' . Yii::t('user', 'Generate and send new password to user') . '" class="glyphicon glyphicon-envelope">
+                    </span> </a>';
+                    }
+                },
+                'switch' => function ($url, $model) {
+                    if(\Yii::$app->user->identity->isAdmin && $model->id != Yii::$app->user->id && Yii::$app->getModule('user')->enableImpersonateUser) {
+                        return Html::a('<span class="glyphicon glyphicon-user"></span>', ['/user/admin/switch', 'id' => $model->id], [
+                            'title' => Yii::t('user', 'Become this user'),
+                            'data-confirm' => Yii::t('user', 'Are you sure you want to switch to this user for the rest of this Session?'),
+                            'data-method' => 'POST',
+                        ]);
+                    }
+                }
+            ]
+        ],
+    ],
+]); ?>
+
+<?php Pjax::end() ?>

+ 104 - 0
views/user/admin/update.php

@@ -0,0 +1,104 @@
+<?php
+
+/*
+ * This file is part of the Dektrium project.
+ *
+ * (c) Dektrium project <http://github.com/dektrium>
+ *
+ * For the full copyright and license information, please view the LICENSE.md
+ * file that was distributed with this source code.
+ */
+
+use yii\bootstrap4\Nav;
+
+/**
+ * @var \yii\web\View $this
+ * @var \dektrium\user\models\User $user
+ * @var string $content
+ */
+
+$this->title = Yii::t('user', 'Update user account');
+$this->params['breadcrumbs'][] = ['label' => Yii::t('user', 'Users'), 'url' => ['index']];
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+<?= $this->render('/_alert', ['module' => Yii::$app->getModule('user')]) ?>
+
+<?= $this->render('_menu') ?>
+
+<div class="row">
+    <div class="col-md-3">
+        <div class="panel panel-default">
+            <div class="panel-body">
+                <?= Nav::widget([
+                    'options' => [
+                        'class' => 'nav-pills nav-stacked',
+                    ],
+                    'items' => [
+                        [
+                            'label' => Yii::t('user', 'Account details'),
+                            'url' => ['/user/admin/update', 'id' => $user->id]
+                        ],
+                        [
+                            'label' => Yii::t('user', 'Profile details'),
+                            'url' => ['/user/admin/update-profile', 'id' => $user->id]
+                        ],
+                        ['label' => Yii::t('user', 'Information'), 'url' => ['/user/admin/info', 'id' => $user->id]],
+                        [
+                            'label' => Yii::t('user', 'Assignments'),
+                            'url' => ['/user/admin/assignments', 'id' => $user->id],
+                            'visible' => isset(Yii::$app->extensions['dektrium/yii2-rbac']),
+                        ],
+                        '<hr>',
+                        [
+                            'label' => Yii::t('user', 'Confirm'),
+                            'url' => ['/user/admin/confirm', 'id' => $user->id],
+                            'visible' => !$user->isConfirmed,
+                            'linkOptions' => [
+                                'class' => 'text-success',
+                                'data-method' => 'post',
+                                'data-confirm' => Yii::t('user', 'Are you sure you want to confirm this user?'),
+                            ],
+                        ],
+                        [
+                            'label' => Yii::t('user', 'Block'),
+                            'url' => ['/user/admin/block', 'id' => $user->id],
+                            'visible' => !$user->isBlocked,
+                            'linkOptions' => [
+                                'class' => 'text-danger',
+                                'data-method' => 'post',
+                                'data-confirm' => Yii::t('user', 'Are you sure you want to block this user?'),
+                            ],
+                        ],
+                        [
+                            'label' => Yii::t('user', 'Unblock'),
+                            'url' => ['/user/admin/block', 'id' => $user->id],
+                            'visible' => $user->isBlocked,
+                            'linkOptions' => [
+                                'class' => 'text-success',
+                                'data-method' => 'post',
+                                'data-confirm' => Yii::t('user', 'Are you sure you want to unblock this user?'),
+                            ],
+                        ],
+                        [
+                            'label' => Yii::t('user', 'Delete'),
+                            'url' => ['/user/admin/delete', 'id' => $user->id],
+                            'linkOptions' => [
+                                'class' => 'text-danger',
+                                'data-method' => 'post',
+                                'data-confirm' => Yii::t('user', 'Are you sure you want to delete this user?'),
+                            ],
+                        ],
+                    ],
+                ]) ?>
+            </div>
+        </div>
+    </div>
+    <div class="col-md-9">
+        <div class="panel panel-default">
+            <div class="panel-body">
+                <?= $content ?>
+            </div>
+        </div>
+    </div>
+</div>

+ 110 - 0
views/user/security/login.php

@@ -0,0 +1,110 @@
+<?php
+
+/*
+ * This file is part of the Dektrium project.
+ *
+ * (c) Dektrium project <http://github.com/dektrium>
+ *
+ * For the full copyright and license information, please view the LICENSE.md
+ * file that was distributed with this source code.
+ */
+
+use dektrium\user\widgets\Connect;
+use dektrium\user\models\LoginForm;
+use yii\helpers\Html;
+use yii\widgets\ActiveForm;
+
+/**
+ * @var yii\web\View $this
+ * @var dektrium\user\models\LoginForm $model
+ * @var dektrium\user\Module $module
+ */
+
+$this->title = Yii::t('user', 'Sign in');
+$this->params['breadcrumbs'][] = $this->title;
+$this->params['body-class']['class'] = 'login-page';
+?>
+
+
+<div class="login-box">
+    <div class="login-logo">
+        <?= Html::encode($this->title) ?>
+    </div>
+    <!-- /.login-logo -->
+    <div class="card">
+        <div class="card-body login-card-body">
+
+            <?= $this->render('/_alert', ['module' => Yii::$app->getModule('user')]) ?>
+
+            <?php $form = ActiveForm::begin([
+                'id' => 'login-form',
+                'enableAjaxValidation' => true,
+                'enableClientValidation' => false,
+                'validateOnBlur' => false,
+                'validateOnType' => false,
+                'validateOnChange' => false,
+            ]) ?>
+
+            <?php if ($module->debug): ?>
+                <?= $form->field($model, 'login', [
+                    'inputOptions' => [
+                        'autofocus' => 'autofocus',
+                        'class' => 'form-control',
+                        'tabindex' => '1']])->dropDownList(LoginForm::loginList());
+                ?>
+
+            <?php else: ?>
+
+                <?= $form->field($model, 'login', ['inputOptions' => ['autofocus' => 'autofocus', 'class' => 'form-control', 'tabindex' => '1']]); ?>
+
+            <?php endif ?>
+
+            <?php if ($module->debug): ?>
+                <div class="alert alert-warning">
+                    <?= Yii::t('user', 'Password is not necessary because the module is in DEBUG mode.'); ?>
+                </div>
+            <?php else: ?>
+                <?= $form->field(
+                    $model,
+                    'password',
+                    ['inputOptions' => ['class' => 'form-control', 'tabindex' => '2']])
+                    ->passwordInput()
+                    ->label(
+                        Yii::t('user', 'Password')
+                        . ($module->enablePasswordRecovery ?
+                            ' (' . Html::a(
+                                Yii::t('user', 'Forgot password?'),
+                                ['/user/recovery/request'],
+                                ['tabindex' => '5']
+                            )
+                            . ')' : '')
+                    ) ?>
+            <?php endif ?>
+
+            <?= $form->field($model, 'rememberMe')->checkbox(['tabindex' => '3']) ?>
+
+            <?= Html::submitButton(
+                Yii::t('user', 'Sign in'),
+                ['class' => 'btn btn-primary btn-block', 'tabindex' => '4']
+            ) ?>
+
+            <?php ActiveForm::end(); ?>
+
+            <?php if ($module->enableConfirmation): ?>
+                <p class="text-center">
+                    <?= Html::a(Yii::t('user', 'Didn\'t receive confirmation message?'), ['/user/registration/resend']) ?>
+                </p>
+            <?php endif ?>
+            <?php if ($module->enableRegistration): ?>
+                <p class="text-center">
+                    <?= Html::a(Yii::t('user', 'Don\'t have an account? Sign up!'), ['/user/registration/register']) ?>
+                </p>
+            <?php endif ?>
+            <?= Connect::widget([
+                'baseAuthUrl' => ['/user/security/auth'],
+            ]) ?>
+
+        </div>
+        <!-- /.login-card-body -->
+    </div>
+</div>

+ 4 - 0
web/.htaccess

@@ -0,0 +1,4 @@
+RewriteEngine on
+RewriteCond %{REQUEST_FILENAME} !-d
+RewriteCond %{REQUEST_FILENAME} !-f
+RewriteRule . index.php [L]

+ 2 - 0
web/assets/.gitignore

@@ -0,0 +1,2 @@
+*
+!.gitignore

+ 115 - 0
web/css/site.css

@@ -0,0 +1,115 @@
+html,
+body {
+    height: 100%;
+}
+
+.wrap {
+    min-height: 100%;
+    height: auto;
+    margin: 0 auto -60px;
+    padding: 0 0 60px;
+}
+
+.wrap > .container {
+    padding: 70px 15px 20px;
+}
+
+.footer {
+    height: 60px;
+    background-color: #f5f5f5;
+    border-top: 1px solid #ddd;
+    padding-top: 20px;
+}
+
+.jumbotron {
+    text-align: center;
+    background-color: transparent;
+}
+
+.jumbotron .btn {
+    font-size: 21px;
+    padding: 14px 24px;
+}
+
+.not-set {
+    color: #c55;
+    font-style: italic;
+}
+
+/* add sorting icons to gridview sort links */
+a.asc:after, a.desc:after {
+    position: relative;
+    top: 1px;
+    display: inline-block;
+    font-family: 'Glyphicons Halflings';
+    font-style: normal;
+    font-weight: normal;
+    line-height: 1;
+    padding-left: 5px;
+}
+
+a.asc:after {
+    content: /*"\e113"*/ "\e151";
+}
+
+a.desc:after {
+    content: /*"\e114"*/ "\e152";
+}
+
+.sort-numerical a.asc:after {
+    content: "\e153";
+}
+
+.sort-numerical a.desc:after {
+    content: "\e154";
+}
+
+.sort-ordinal a.asc:after {
+    content: "\e155";
+}
+
+.sort-ordinal a.desc:after {
+    content: "\e156";
+}
+
+.grid-view th {
+    white-space: nowrap;
+}
+
+.hint-block {
+    display: block;
+    margin-top: 5px;
+    color: #999;
+}
+
+.error-summary {
+    color: #a94442;
+    background: #fdf7f7;
+    border-left: 3px solid #eed3d7;
+    padding: 10px 20px;
+    margin: 0 0 15px 0;
+}
+
+/* align the logout "link" (button in form) of the navbar */
+.nav li > form > button.logout {
+    padding: 15px;
+    border: none;
+}
+
+@media(max-width:767px) {
+    .nav li > form > button.logout {
+        display:block;
+        text-align: left;
+        width: 100%;
+        padding: 10px 15px;
+    }
+}
+
+.nav > li > form > button.logout:focus,
+.nav > li > form > button.logout:hover {
+    text-decoration: none;
+}
+
+.nav > li > form > button.logout:focus {
+    outline: none;
+}

BIN
web/favicon.ico


+ 16 - 0
web/index-test.php

@@ -0,0 +1,16 @@
+<?php
+
+// NOTE: Make sure this file is not accessible when deployed to production
+if (!in_array(@$_SERVER['REMOTE_ADDR'], ['127.0.0.1', '::1'])) {
+    die('You are not allowed to access this file.');
+}
+
+defined('YII_DEBUG') or define('YII_DEBUG', true);
+defined('YII_ENV') or define('YII_ENV', 'test');
+
+require __DIR__ . '/../vendor/autoload.php';
+require __DIR__ . '/../vendor/yiisoft/yii2/Yii.php';
+
+$config = require __DIR__ . '/../config/test.php';
+
+(new yii\web\Application($config))->run();

+ 17 - 0
web/index.php

@@ -0,0 +1,17 @@
+<?php
+
+// comment out the following two lines when deployed to production
+defined('YII_DEBUG') or define('YII_DEBUG', true);
+defined('YII_ENV') or define('YII_ENV', 'dev');
+
+require __DIR__ . '/../vendor/autoload.php';
+
+/*(new \Dotenv\Dotenv(__DIR__ . '/../'))->load();*/
+$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/../');
+$dotenv->load();
+
+require __DIR__ . '/../vendor/yiisoft/yii2/Yii.php';
+
+$config = require __DIR__ . '/../config/web.php';
+
+(new yii\web\Application($config))->run();

+ 2 - 0
web/robots.txt

@@ -0,0 +1,2 @@
+User-agent: *
+Disallow:

+ 75 - 0
widgets/Alert.php

@@ -0,0 +1,75 @@
+<?php
+namespace app\widgets;
+
+use Yii;
+
+/**
+ * Alert widget renders a message from session flash. All flash messages are displayed
+ * in the sequence they were assigned using setFlash. You can set message as following:
+ *
+ * ```php
+ * Yii::$app->session->setFlash('error', 'This is the message');
+ * Yii::$app->session->setFlash('success', 'This is the message');
+ * Yii::$app->session->setFlash('info', 'This is the message');
+ * ```
+ *
+ * Multiple messages could be set as follows:
+ *
+ * ```php
+ * Yii::$app->session->setFlash('error', ['Error 1', 'Error 2']);
+ * ```
+ *
+ * @author Kartik Visweswaran <kartikv2@gmail.com>
+ * @author Alexander Makarov <sam@rmcreative.ru>
+ */
+class Alert extends \yii\bootstrap4\Widget
+{
+    /**
+     * @var array the alert types configuration for the flash messages.
+     * This array is setup as $key => $value, where:
+     * - key: the name of the session flash variable
+     * - value: the bootstrap alert type (i.e. danger, success, info, warning)
+     */
+    public $alertTypes = [
+        'error'   => 'alert-danger',
+        'danger'  => 'alert-danger',
+        'success' => 'alert-success',
+        'info'    => 'alert-info',
+        'warning' => 'alert-warning'
+    ];
+    /**
+     * @var array the options for rendering the close button tag.
+     * Array will be passed to [[\yii\bootstrap\Alert::closeButton]].
+     */
+    public $closeButton = [];
+
+
+    /**
+     * {@inheritdoc}
+     */
+    public function run()
+    {
+        $session = Yii::$app->session;
+        $flashes = $session->getAllFlashes();
+        $appendClass = isset($this->options['class']) ? ' ' . $this->options['class'] : '';
+
+        foreach ($flashes as $type => $flash) {
+            if (!isset($this->alertTypes[$type])) {
+                continue;
+            }
+
+            foreach ((array) $flash as $i => $message) {
+                echo \yii\bootstrap\Alert::widget([
+                    'body' => $message,
+                    'closeButton' => $this->closeButton,
+                    'options' => array_merge($this->options, [
+                        'id' => $this->getId() . '-' . $type . '-' . $i,
+                        'class' => $this->alertTypes[$type] . $appendClass,
+                    ]),
+                ]);
+            }
+
+            $session->removeFlash($type);
+        }
+    }
+}

+ 172 - 0
widgets/MainSidebarMenu.php

@@ -0,0 +1,172 @@
+<?php
+
+namespace app\widgets;
+
+use yii\helpers\ArrayHelper;
+use yii\helpers\Html;
+use yii\helpers\Url;
+
+/**
+ * Menu displays a multi-level sidebar menu according to AdminLTE 3 design.
+ *
+ * The main property of Menu is [[items]], which specifies the possible items in the menu.
+ * A menu item can contain sub-items which specify the sub-menu under that menu item.
+ *
+ * Menu checks the current route and request parameters to toggle certain menu items
+ * with active state.
+ *
+ * The rendered HTML complies with the AdminLTE 3 design for the main sidebar component.
+ * See [AdminLTE 3 Docs](https://adminlte.io/docs/3.0/components/main-sidebar.html).
+ *
+ * @package backend\components\widgets
+ * @author Eugine Terentev <eugine@terentev.net>
+ * @author Victor Gonzalez <victor@vgr.cl>
+ */
+class MainSidebarMenu extends \yii\widgets\Menu
+{
+    /**
+     * @var string the template used to render the body of a menu which is a link.
+     *
+     * In this template, the token `{url}` will be replaced with the corresponding link URL;
+     * while `{label}` will be replaced with the link text.
+     *
+     * Additionally, to comply with AdminLTE 3 design, the following tags were added:
+     *
+     *  - `{icon}`: an icon shown before a label.
+     *  - `{dropdown-caret}`: the dropdown caret icon.
+     *  - `{badge}`: a badge shown between the label and the `{dropdown-caret}`.
+     *
+     * This property will be overridden by the `template` option set in individual menu items via [[items]].
+     */
+    public $linkTemplate = '<a href="{url}" class="nav-link">{icon}&nbsp;<p>{label}{dropdown-caret}{badge}</p></a>';
+
+    /**
+     * @var string the template used to render the body of a menu which is a link.
+     *
+     * In this template, the token `{url}` will be replaced with the corresponding link URL;
+     * while `{label}` will be replaced with the link text.
+     *
+     * Additionally, to comply with AdminLTE 3 design, the following tags were added:
+     *
+     *  - `{icon}`: replaced with an icon shown before a label.
+     *  - `{dropdown-caret}`: replaced with the dropdown caret icon.
+     *  - `{badge}`: a badge shown between the label and the `{dropdown-caret}`.
+     *
+     * This property will be overridden by the `template` option set in individual menu items via [[items]],
+     * but this template should not be changed to preserve AdminLte 3 compatibility.
+     */
+    public $linkTemplateActive = '<a href="{url}" class="nav-link active">{icon}&nbsp;<p>{label}{dropdown-caret}{badge}</p></a>';
+
+    /**
+     * @var string the template used to render the body of a menu which is NOT a link.
+     * In this template, the token `{label}` will be replaced with the label of the menu item,
+     * while `{icon}` will be repplaced by an icon image
+     * This property will be overridden by the `template` option set in individual menu items via [[items]].
+     */
+    public $labelTemplate = "{icon}\n{label}\n{badge}";
+
+    /**
+     * @var string the template used to render a list of sub-menus.
+     * In this template, the token `{items}` will be replaced with the rendered sub-menu items.
+     */
+    public $submenuTemplate = '<ul class="nav nav-treeview">{items}</ul>';
+
+    /**
+     * @var string the badge element HTML tag.
+     */
+    public $badgeTag = 'span';
+
+    /**
+     * @var string the badge element base CSS classes.
+     */
+    public $badgeClass = 'badge right';
+
+    /**
+     * @var string the badge element background CSS class.
+     */
+    public $badgeBgClass;
+
+    /**
+     * @var string the dropdown caret icon.
+     * @see $linkTemplate, $linkTemplateActive
+     */
+    public $dropdownCaret = '<i class="fas fa-angle-left right"></i>';
+
+    /**
+     * @var array array list of the HTML attributes for the menu's container tag..
+     */
+    public $options;
+
+    /**
+     * @var array list of HTML attributes shared by all menu [[items]]. If any individual menu item
+     * specifies its `options`, it will be merged with this property before being used to generate the HTML
+     * attributes for the menu item tag. The following special options are recognized:
+     *
+     * - tag: string, defaults to "li", the tag name of the item container tags.
+     *   Set to false to disable container tag.
+     *   See also [[\yii\helpers\Html::tag()]].
+     *
+     * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+     */
+    public $itemOptions = ['class' => ['nav-item']];
+
+    /**
+     * @var bool whether to activate parent menu items when one of the corresponding child menu items is active.
+     * The activated parent menu items will also have its CSS classes appended with [[activeCssClass]].
+     */
+    public $activateParents = true;
+
+    /**
+     * @var string the CSS class to be appended to the active menu item.
+     */
+    public $activeCssClass = 'menu-open';
+
+    /**
+     * @inheritdoc
+     */
+    protected function renderItem($item)
+    {
+        // check if item is active and set special template
+        $linkTemplate = $this->linkTemplate;
+        if ($item['active']) {
+            $linkTemplate = $this->linkTemplateActive;
+        }
+
+        // set badge if any
+        $item['badgeOptions'] = isset($item['badgeOptions']) ? $item['badgeOptions'] : [];
+        if (!ArrayHelper::getValue($item, 'badgeOptions.class')) {
+            $bg = isset($item['badgeBgClass']) ? $item['badgeBgClass'] : $this->badgeBgClass;
+            $item['badgeOptions']['class'] = $this->badgeClass . ' ' . $bg;
+        }
+
+        // add dropdown caret if item has childrens
+        if (isset($item['items']) && !isset($item['dropdown-caret'])) {
+            $item['dropdown-caret'] = $this->dropdownCaret;
+        }
+
+        if (isset($item['url'])) {
+            // item uses an url, so uses the `$linkTemplate` variable
+            $template = ArrayHelper::getValue($item, 'template', $linkTemplate);
+
+            return strtr($template, [
+                '{badge}' => isset($item['badge']) ? Html::tag('span', $item['badge'], $item['badgeOptions']) : '',
+                '{icon}' => isset($item['icon']) ? $item['icon'] : '',
+                '{dropdown-caret}' => isset($item['dropdown-caret']) ? $item['dropdown-caret'] : '',
+                '{url}' => Url::to($item['url']),
+                '{label}' => $item['label'],
+            ]);
+        } else {
+            // just a plain text item
+            $template = ArrayHelper::getValue($item, 'template', $this->labelTemplate);
+
+            return strtr($template, [
+                '{badge}' => isset($item['badge'])
+                    ? Html::tag('small', $item['badge'], $item['badgeOptions'])
+                    : '',
+                '{icon}' => isset($item['icon']) ? $item['icon'] : '',
+                '{dropdown-caret}' => isset($item['dropdown-caret']) ? $item['dropdown-caret'] : '',
+                '{label}' => $item['label'],
+            ]);
+        }
+    }
+}

+ 25 - 0
yii

@@ -0,0 +1,25 @@
+#!/usr/bin/env php
+<?php
+/**
+ * Yii console bootstrap file.
+ *
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+defined('YII_DEBUG') or define('YII_DEBUG', true);
+defined('YII_ENV') or define('YII_ENV', 'dev');
+
+require __DIR__ . '/vendor/autoload.php';
+
+$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
+$dotenv->load();
+
+require __DIR__ . '/vendor/yiisoft/yii2/Yii.php';
+
+$config = require __DIR__ . '/config/console.php';
+
+$application = new yii\console\Application($config);
+$exitCode = $application->run();
+exit($exitCode);

+ 20 - 0
yii.bat

@@ -0,0 +1,20 @@
+@echo off
+
+rem -------------------------------------------------------------
+rem  Yii command line bootstrap script for Windows.
+rem
+rem  @author Qiang Xue <qiang.xue@gmail.com>
+rem  @link http://www.yiiframework.com/
+rem  @copyright Copyright (c) 2008 Yii Software LLC
+rem  @license http://www.yiiframework.com/license/
+rem -------------------------------------------------------------
+
+@setlocal
+
+set YII_PATH=%~dp0
+
+if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe
+
+"%PHP_COMMAND%" "%YII_PATH%yii" %*
+
+@endlocal