diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index 3d8d32c25..ea75bda5f 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -3,5 +3,5 @@ Contributing to Yii2
 
 - [Report an issue](https://github.com/yiisoft/yii2/blob/master/docs/internals/report-an-issue.md)
 - [Translate documentation or messages](https://github.com/yiisoft/yii2/blob/master/docs/internals/translation-workflow.md)
-- [Give us feedback or start a design discussion](http://www.yiiframework.com/forum/index.php/forum/42-general-discussions-for-yii-20/)
+- [Give us feedback or start a design discussion](https://www.yiiframework.com/forum/index.php/forum/42-general-discussions-for-yii-20/)
 - [Contribute to the core code or fix bugs](https://github.com/yiisoft/yii2/blob/master/docs/internals/git-workflow.md)
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index c0c1cacbc..692239301 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1,3 +1,4 @@
 # These are supported funding model platforms
 
 open_collective: yiisoft
+tidelift: "packagist/yiisoft/yii2-app-basic"
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
index 52ec8692f..a2ee92439 100644
--- a/.github/ISSUE_TEMPLATE.md
+++ b/.github/ISSUE_TEMPLATE.md
@@ -9,6 +9,6 @@
 
 | Q                | A
 | ---------------- | ---
-| Yii vesion       |
+| Yii version       |
 | PHP version      |
 | Operating system |
diff --git a/.github/SECURITY.md b/.github/SECURITY.md
new file mode 100644
index 000000000..405acca4e
--- /dev/null
+++ b/.github/SECURITY.md
@@ -0,0 +1,6 @@
+# Security Policy
+
+Please use the [security issue form](https://www.yiiframework.com/security) to report to us any security issue you find in Yii.
+DO NOT use the issue tracker or discuss it in the public forum as it will cause more damage than help.
+
+Please note that as a non-commercial OpenSource project we are not able to pay bounties at the moment.
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 000000000..a5beac653
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,83 @@
+on:
+  - pull_request
+  - push
+
+name: build
+
+jobs:
+  tests:
+    name: PHP ${{ matrix.php }} - ${{ matrix.os }}
+
+    env:
+      extensions: dom, json, gd, imagick
+      key: cache-v4
+
+    runs-on: ${{ matrix.os }}
+
+    strategy:
+      matrix:
+        os:
+          - ubuntu-latest
+          - windows-latest
+
+        php:
+          - "7.4"
+          - "8.0"
+          - "8.1"
+          - "8.2"
+          - "8.3"
+          - "8.4"
+          
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v5
+
+      - name: Setup cache environment
+        id: cache-env
+        uses: shivammathur/cache-extensions@v1
+        with:
+          php-version: ${{ matrix.php }}
+          extensions: ${{ env.extensions }}
+          key: ${{ env.key }}
+
+      - name: Cache extensions
+        uses: actions/cache@v4
+        with:
+          path: ${{ steps.cache-env.outputs.dir }}
+          key: ${{ steps.cache-env.outputs.key }}
+          restore-keys: ${{ steps.cache-env.outputs.key }}
+
+      - name: Install PHP with extensions
+        uses: shivammathur/setup-php@v2
+        with:
+          php-version: ${{ matrix.php }}
+          extensions: ${{ env.extensions }}
+          ini-values: date.timezone='UTC'
+          
+      - name: Determine composer cache directory on Linux
+        if: matrix.os == 'ubuntu-latest'
+        run: |
+            echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV
+
+      - name: Determine composer cache directory on Windows
+        if: matrix.os == 'windows-latest'
+        run: |
+            echo "COMPOSER_CACHE_DIR=~\AppData\Local\Composer" >> $GITHUB_ENV
+
+      - name: Cache dependencies installed with composer
+        uses: actions/cache@v4
+        with:
+          path: ${{ steps.cache-env.outputs.dir }}
+          key: php${{ matrix.php }}-composer-${{ matrix.dependencies }}-${{ hashFiles('**/composer.json') }}
+          restore-keys: |
+            php${{ matrix.php }}-composer-${{ matrix.dependencies }}-
+
+      - name: Install dependencies with composer php PHP [5.6 - 8.0]
+        run: composer update --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi
+
+      - name: Run tests with codeception
+        run: |
+          sed -i "s/'cookieValidationKey' => ''/'cookieValidationKey' => 'testkey'/" config/web.php
+          php -S 127.0.0.1:8080 -t public > ./runtime/yii.log 2>&1 &
+          vendor/bin/codecept run
+        shell: bash
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 18e63d91a..000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,45 +0,0 @@
-language: php
-
-php:
-  - 5.4
-  - 5.5
-  - 5.6
-  - 7.0
-  - 7.1
-#  - hhvm
-
-# faster builds on new travis setup not using sudo
-sudo: false
-
-# cache vendor dirs
-cache:
-  directories:
-    - $HOME/.composer/cache
-
-services:
-  - mysql
-
-before_install:
-  - mysql -e 'CREATE DATABASE IF NOT EXISTS yii2_basic_tests;'
-
-install:
-  - travis_retry composer self-update && composer --version
-  - travis_retry composer update --dev --prefer-dist --no-interaction
-  # install php extensions
-  - |
-    if (php --version | grep -i HipHop > /dev/null); then
-      echo "Skipping imagick and gmagick tests on HHVM"
-    else
-      pear config-set preferred_state beta
-      printf "\n" | pecl install imagick
-      # gmagick is not installed on travis currently
-      #printf "\n" | pecl install gmagick
-    fi
-# setup application:
-  - |
-    sed -i "s/'cookieValidationKey' => ''/'cookieValidationKey' => 'testkey'/" config/web.php
-
-script:
-  - |
-    php -S localhost:8080 -t web > /dev/null 2>&1 &
-    vendor/bin/codecept run
diff --git a/README.md b/README.md
index 287a6ff87..981a7f9d9 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
     <br>
 </p>
 
-Yii 2 Basic Project Template is a skeleton [Yii 2](http://www.yiiframework.com/) application best for
+Yii 2 Basic Project Template is a skeleton [Yii 2](https://www.yiiframework.com/) application best for
 rapidly creating small projects.
 
 The template contains the basic features including user login/logout and a contact page.
@@ -15,7 +15,7 @@ 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 Status](https://travis-ci.org/yiisoft/yii2-app-basic.svg?branch=master)](https://travis-ci.org/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
 -------------------
@@ -37,7 +37,7 @@ DIRECTORY STRUCTURE
 REQUIREMENTS
 ------------
 
-The minimum requirement by this project template that your Web server supports PHP 5.4.0.
+The minimum requirement by this project template that your Web server supports PHP 7.4.
 
 
 INSTALLATION
@@ -45,8 +45,8 @@ 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).
+If you do not have [Composer](https://getcomposer.org/), you may install it by following the instructions
+at [getcomposer.org](https://getcomposer.org/doc/00-intro.md#installation-nix).
 
 You can then install this project template using the following command:
 
@@ -63,7 +63,7 @@ http://localhost/basic/web/
 
 ### Install from an Archive File
 
-Extract the archive file downloaded from [yiiframework.com](http://www.yiiframework.com/download/) to
+Extract the archive file downloaded from [yiiframework.com](https://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:
@@ -131,8 +131,8 @@ return [
 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:
+Tests are located in `tests` directory. They are developed with [Codeception PHP Testing Framework](https://codeception.com/).
+By default, there are 3 test suites:
 
 - `unit`
 - `functional`
@@ -155,7 +155,7 @@ 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
+2. Replace `codeception/base` package in `composer.json` with `codeception/codeception` to install full-featured
    version of Codeception
 
 3. Update dependencies with Composer 
@@ -164,7 +164,7 @@ To execute acceptance tests do the following:
     composer update  
     ```
 
-4. Download [Selenium Server](http://www.seleniumhq.org/download/) and launch it:
+4. Download [Selenium Server](https://www.seleniumhq.org/download/) and launch it:
 
     ```
     java -jar ~/selenium-server-standalone-x.xx.x.jar
@@ -186,7 +186,7 @@ To execute acceptance tests do the following:
     docker run --net=host selenium/standalone-firefox:2.53.0
     ```
 
-5. (Optional) Create `yii2_basic_tests` database and update it by applying migrations if you have them.
+5. (Optional) Create `yii2basic_test` database and update it by applying migrations if you have them.
 
    ```
    tests/bin/yii migrate
@@ -221,13 +221,13 @@ to collect code coverage. You can run your tests and collect coverage with the f
 
 ```
 #collect coverage for all tests
-vendor/bin/codecept run -- --coverage-html --coverage-xml
+vendor/bin/codecept run --coverage --coverage-html --coverage-xml
 
 #collect coverage only for unit tests
-vendor/bin/codecept run unit -- --coverage-html --coverage-xml
+vendor/bin/codecept run unit --coverage --coverage-html --coverage-xml
 
 #collect coverage for unit and functional tests
-vendor/bin/codecept run functional,unit -- --coverage-html --coverage-xml
+vendor/bin/codecept run functional,unit --coverage --coverage-html --coverage-xml
 ```
 
 You can see code coverage output under the `tests/_output` directory.
diff --git a/Vagrantfile b/Vagrantfile
index 44fd76c6d..258aea601 100644
--- a/Vagrantfile
+++ b/Vagrantfile
@@ -1,9 +1,20 @@
 require 'yaml'
 require 'fileutils'
 
+required_plugins_installed = nil
 required_plugins = %w( vagrant-hostmanager vagrant-vbguest )
 required_plugins.each do |plugin|
-    exec "vagrant plugin install #{plugin}" unless Vagrant.has_plugin? 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 = {
@@ -31,7 +42,7 @@ end
 # vagrant configurate
 Vagrant.configure(2) do |config|
   # select the box
-  config.vm.box = 'bento/ubuntu-16.04'
+  config.vm.box = 'bento/ubuntu-18.04'
 
   # should we ask about box updates?
   config.vm.box_check_update = options['box_check_update']
@@ -72,7 +83,7 @@ Vagrant.configure(2) do |config|
   # config.vbguest.auto_update = false
 
   # provisioners
-  config.vm.provision 'shell', path: './vagrant/provision/once-as-root.sh', args: [options['timezone']]
+  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'
 
diff --git a/assets/AppAsset.php b/assets/AppAsset.php
index 47932b165..3d40487ae 100644
--- a/assets/AppAsset.php
+++ b/assets/AppAsset.php
@@ -1,8 +1,8 @@
 <?php
 /**
- * @link http://www.yiiframework.com/
+ * @link https://www.yiiframework.com/
  * @copyright Copyright (c) 2008 Yii Software LLC
- * @license http://www.yiiframework.com/license/
+ * @license https://www.yiiframework.com/license/
  */
 
 namespace app\assets;
@@ -26,6 +26,6 @@ class AppAsset extends AssetBundle
     ];
     public $depends = [
         'yii\web\YiiAsset',
-        'yii\bootstrap\BootstrapAsset',
+        'yii\bootstrap5\BootstrapAsset'
     ];
 }
diff --git a/codeception.yml b/codeception.yml
index a95affe04..dd8febcff 100644
--- a/codeception.yml
+++ b/codeception.yml
@@ -1,11 +1,11 @@
 actor: Tester
+bootstrap: _bootstrap.php
 paths:
     tests: tests
-    log: tests/_output
+    output: tests/_output
     data: tests/_data
     helpers: tests/_support
 settings:
-    bootstrap: _bootstrap.php
     memory_limit: 1024M
     colors: true
 modules:
@@ -25,12 +25,3 @@ modules:
 #            - controllers/*
 #            - commands/*
 #            - mail/*
-#    blacklist:
-#        include:
-#            - assets/*
-#            - config/*
-#            - runtime/*
-#            - vendor/*
-#            - views/*
-#            - web/*
-#            - tests/*
diff --git a/commands/HelloController.php b/commands/HelloController.php
index 20066c40c..9ec914d3c 100644
--- a/commands/HelloController.php
+++ b/commands/HelloController.php
@@ -1,8 +1,8 @@
 <?php
 /**
- * @link http://www.yiiframework.com/
+ * @link https://www.yiiframework.com/
  * @copyright Copyright (c) 2008 Yii Software LLC
- * @license http://www.yiiframework.com/license/
+ * @license https://www.yiiframework.com/license/
  */
 
 namespace app\commands;
diff --git a/composer.json b/composer.json
index 8ffc76db4..4b6cab88c 100644
--- a/composer.json
+++ b/composer.json
@@ -2,34 +2,38 @@
     "name": "yiisoft/yii2-app-basic",
     "description": "Yii 2 Basic Project Template",
     "keywords": ["yii2", "framework", "basic", "project template"],
-    "homepage": "http://www.yiiframework.com/",
+    "homepage": "https://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",
+        "forum": "https://www.yiiframework.com/forum/",
+        "wiki": "https://www.yiiframework.com/wiki/",
+        "irc": "ircs://irc.libera.chat:6697/yii",
         "source": "https://github.com/yiisoft/yii2"
     },
     "minimum-stability": "dev",
     "require": {
-        "php": ">=5.4.0",
-        "yiisoft/yii2": "~2.0.14",
-        "yiisoft/yii2-bootstrap": "~2.0.0",
-        "yiisoft/yii2-swiftmailer": "~2.0.0 || ~2.1.0"
+        "php": ">=7.4.0",
+        "yiisoft/yii2": "~2.0.45",
+        "yiisoft/yii2-bootstrap5": "~2.0.2",
+        "yiisoft/yii2-symfonymailer": "~2.0.3"
     },
     "require-dev": {
         "yiisoft/yii2-debug": "~2.1.0",
-        "yiisoft/yii2-gii": "~2.1.0",
+        "yiisoft/yii2-gii": "~2.2.0",
         "yiisoft/yii2-faker": "~2.0.0",
-
-        "codeception/base": "~2.3.0",
-        "codeception/verify": "~0.4.0",
-        "codeception/specify": "~0.4.6",
-        "symfony/browser-kit": ">=2.7 <=4.2.4"
+        "codeception/codeception": "^5.0.0 || ^4.0",
+        "codeception/lib-innerbrowser": "^4.0 || ^3.0 || ^1.1",
+        "codeception/module-asserts": "^3.0 || ^1.1",
+        "codeception/module-yii2": "^1.1",
+        "codeception/module-filesystem": "^3.0 || ^2.0 || ^1.1",
+        "codeception/verify": "^3.0 || ^2.2"
     },
     "config": {
+        "allow-plugins": {
+            "yiisoft/yii2-composer" : true
+        },
         "process-timeout": 1800,
         "fxp-asset": {
             "enabled": false
diff --git a/config/__autocomplete.php b/config/__autocomplete.php
new file mode 100644
index 000000000..d99dea50e
--- /dev/null
+++ b/config/__autocomplete.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * This class only exists here for IDE (PHPStorm/Netbeans/...) autocompletion.
+ * This file is never included anywhere.
+ * Adjust this file to match classes configured in your application config, to enable IDE autocompletion for custom components.
+ * Example: A property phpdoc can be added in `__Application` class as `@property \vendor\package\Rollbar|__Rollbar $rollbar` and adding a class in this file
+ * ```php
+ * // @property of \vendor\package\Rollbar goes here
+ * class __Rollbar {
+ * }
+ * ```
+ */
+class Yii {
+    /**
+     * @var \yii\web\Application|\yii\console\Application|__Application
+     */
+    public static $app;
+}
+
+/**
+ * @property yii\rbac\DbManager $authManager 
+ * @property \yii\web\User|__WebUser $user
+ * 
+ */
+class __Application {
+}
+
+/**
+ * @property app\models\User $identity
+ */
+class __WebUser {
+}
diff --git a/config/console.php b/config/console.php
index 299656afc..ca429affa 100644
--- a/config/console.php
+++ b/config/console.php
@@ -43,6 +43,14 @@
     $config['modules']['gii'] = [
         'class' => 'yii\gii\Module',
     ];
+    // configuration adjustments for 'dev' environment
+    // requires version `2.1.21` of yii2-debug module
+    $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'],
+    ];
 }
 
 return $config;
diff --git a/config/test.php b/config/test.php
index f95bc031e..317bc1cb6 100644
--- a/config/test.php
+++ b/config/test.php
@@ -16,7 +16,11 @@
     'components' => [
         'db' => $db,
         'mailer' => [
+            'class' => \yii\symfonymailer\Mailer::class,
+            'viewPath' => '@app/mail',
+            // send all mails to a file by default.
             'useFileTransport' => true,
+            'messageClass' => 'yii\symfonymailer\Message'
         ],
         'assetManager' => [
             'basePath' => __DIR__ . '/../web/assets',
diff --git a/config/test_db.php b/config/test_db.php
index 5d213e135..f4290e0b0 100644
--- a/config/test_db.php
+++ b/config/test_db.php
@@ -1,6 +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=yii2_basic_tests';
+$db['dsn'] = 'mysql:host=localhost;dbname=yii2basic_test';
 
 return $db;
diff --git a/config/web.php b/config/web.php
index 3c03de8c5..8b9dd3f94 100644
--- a/config/web.php
+++ b/config/web.php
@@ -27,10 +27,9 @@
             '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.
+            'class' => \yii\symfonymailer\Mailer::class,
+            'viewPath' => '@app/mail',
+            // send all mails to a file by default.
             'useFileTransport' => true,
         ],
         'log' => [
diff --git a/controllers/SiteController.php b/controllers/SiteController.php
index 6e8a85d10..67c3f50f8 100644
--- a/controllers/SiteController.php
+++ b/controllers/SiteController.php
@@ -19,7 +19,7 @@ public function behaviors()
     {
         return [
             'access' => [
-                'class' => AccessControl::className(),
+                'class' => AccessControl::class,
                 'only' => ['logout'],
                 'rules' => [
                     [
@@ -30,7 +30,7 @@ public function behaviors()
                 ],
             ],
             'verbs' => [
-                'class' => VerbFilter::className(),
+                'class' => VerbFilter::class,
                 'actions' => [
                     'logout' => ['post'],
                 ],
diff --git a/docker-compose.yml b/docker-compose.yml
index c3800ff53..86be3bd0d 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,9 +1,9 @@
 version: '2'
 services:
   php:
-    image: yiisoftware/yii2-php:7.1-apache
+    image: yiisoftware/yii2-php:7.4-apache
     volumes:
       - ~/.composer-docker/cache:/root/.composer/cache:delegated
       - ./:/app:delegated
     ports:
-      - '8000:80'
\ No newline at end of file
+      - '8000:80'
diff --git a/mail/layouts/html.php b/mail/layouts/html.php
index bddbc6129..95732cdfa 100644
--- a/mail/layouts/html.php
+++ b/mail/layouts/html.php
@@ -1,9 +1,9 @@
 <?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 */
+/** @var \yii\web\View $this view component instance */
+/** @var \yii\mail\MessageInterface $message the message being composed */
+/** @var string $content 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">
diff --git a/mail/layouts/text.php b/mail/layouts/text.php
new file mode 100644
index 000000000..c3768640b
--- /dev/null
+++ b/mail/layouts/text.php
@@ -0,0 +1,13 @@
+<?php
+
+/** 
+ * @var yii\web\View $this view component instance
+ * @var yii\mail\BaseMessage $message the message being composed
+ * @var string $content main view render result
+ */
+
+$this->beginPage();
+$this->beginBody();
+echo $content;
+$this->endBody();
+$this->endPage();
diff --git a/models/LoginForm.php b/models/LoginForm.php
index cc6af264c..dce15ccf7 100644
--- a/models/LoginForm.php
+++ b/models/LoginForm.php
@@ -8,7 +8,7 @@
 /**
  * LoginForm is the model behind the login form.
  *
- * @property User|null $user This property is read-only.
+ * @property-read User|null $user
  *
  */
 class LoginForm extends Model
diff --git a/requirements.php b/requirements.php
index 6cf322eb0..c1a6bf680 100644
--- a/requirements.php
+++ b/requirements.php
@@ -105,22 +105,22 @@
         '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>.' : ''
+        'by' => '<a href="https://www.yiiframework.com/doc-2.0/yii-caching-memcache.html">MemCache</a>',
+        'memo' => extension_loaded('memcached') ? 'To use memcached set <a href="https://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>',
+        'by' => '<a href="https://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>',
+        'by' => '<a href="https://www.yiiframework.com/doc-2.0/yii-captcha-captcha.html">Captcha</a>',
         'memo' => $imagickMemo,
     ),
     // PHP ini :
@@ -153,7 +153,7 @@
         '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>',
+        'by' => '<a href="https://www.yiiframework.com/doc-2.0/yii-caching-apccache.html">ApcCache</a>',
     );
 }
 
diff --git a/tests/acceptance.suite.yml.example b/tests/acceptance.suite.yml.example
index 7bb4986fd..3821d4fe4 100644
--- a/tests/acceptance.suite.yml.example
+++ b/tests/acceptance.suite.yml.example
@@ -1,4 +1,4 @@
-class_name: AcceptanceTester
+actor: AcceptanceTester
 modules:
     enabled:
         - WebDriver:
diff --git a/tests/bin/yii b/tests/bin/yii
index 4923537fb..39df4013d 100755
--- a/tests/bin/yii
+++ b/tests/bin/yii
@@ -3,9 +3,9 @@
 /**
  * Yii console bootstrap file.
  *
- * @link http://www.yiiframework.com/
+ * @link https://www.yiiframework.com/
  * @copyright Copyright (c) 2008 Yii Software LLC
- * @license http://www.yiiframework.com/license/
+ * @license https://www.yiiframework.com/license/
  */
 
 defined('YII_DEBUG') or define('YII_DEBUG', true);
diff --git a/tests/bin/yii.bat b/tests/bin/yii.bat
index d516b3a19..ce14c92bc 100644
--- a/tests/bin/yii.bat
+++ b/tests/bin/yii.bat
@@ -4,9 +4,9 @@ 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  @link https://www.yiiframework.com/
 rem  @copyright Copyright (c) 2008 Yii Software LLC
-rem  @license http://www.yiiframework.com/license/
+rem  @license https://www.yiiframework.com/license/
 rem -------------------------------------------------------------
 
 @setlocal
diff --git a/tests/functional.suite.yml b/tests/functional.suite.yml
index 374c6df45..9d8cf149c 100644
--- a/tests/functional.suite.yml
+++ b/tests/functional.suite.yml
@@ -6,8 +6,9 @@
 
 # RUN `build` COMMAND AFTER ADDING/REMOVING MODULES.
 #basic/web/index.php
-class_name: FunctionalTester
+actor: FunctionalTester
 modules:
     enabled:
       - Filesystem
       - Yii2
+      - Asserts
diff --git a/tests/functional/ContactFormCest.php b/tests/functional/ContactFormCest.php
index ad8167865..d17ef52fd 100644
--- a/tests/functional/ContactFormCest.php
+++ b/tests/functional/ContactFormCest.php
@@ -4,7 +4,7 @@ class ContactFormCest
 {
     public function _before(\FunctionalTester $I)
     {
-        $I->amOnPage(['site/contact']);
+        $I->amOnRoute('site/contact');
     }
 
     public function openContactPage(\FunctionalTester $I)
diff --git a/tests/unit.suite.yml b/tests/unit.suite.yml
index d2e6622f8..c14e49c37 100644
--- a/tests/unit.suite.yml
+++ b/tests/unit.suite.yml
@@ -3,7 +3,7 @@
 # suite for unit (internal) tests.
 # RUN `build` COMMAND AFTER ADDING/REMOVING MODULES.
 
-class_name: UnitTester
+actor: UnitTester
 modules:
     enabled:
       - Asserts
diff --git a/tests/unit/models/ContactFormTest.php b/tests/unit/models/ContactFormTest.php
index 031d2bd4f..1bb1bb2d0 100644
--- a/tests/unit/models/ContactFormTest.php
+++ b/tests/unit/models/ContactFormTest.php
@@ -7,7 +7,6 @@
 
 class ContactFormTest extends \Codeception\Test\Unit
 {
-    private $model;
     /**
      * @var \UnitTester
      */
@@ -15,34 +14,28 @@ class ContactFormTest extends \Codeception\Test\Unit
 
     public function testEmailIsSentOnContact()
     {
-        /** @var ContactForm $model */
-        $this->model = $this->getMockBuilder('app\models\ContactForm')
-            ->setMethods(['validate'])
-            ->getMock();
+        $model = new ContactForm();
 
-        $this->model->expects($this->once())
-            ->method('validate')
-            ->willReturn(true);
-
-        $this->model->attributes = [
+        $model->attributes = [
             'name' => 'Tester',
             'email' => 'tester@example.com',
             'subject' => 'very important letter subject',
             'body' => 'body of current message',
+            'verifyCode' => 'testme',
         ];
 
-        expect_that($this->model->contact('admin@example.com'));
+        verify($model->contact('admin@example.com'))->notEmpty();
 
         // 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())->contains('body of current message');
+        verify($emailMessage)->instanceOf('yii\mail\MessageInterface');
+        verify($emailMessage->getTo())->arrayHasKey('admin@example.com');
+        verify($emailMessage->getFrom())->arrayHasKey('noreply@example.com');
+        verify($emailMessage->getReplyTo())->arrayHasKey('tester@example.com');
+        verify($emailMessage->getSubject())->equals('very important letter subject');
+        verify($emailMessage->toString())->stringContainsString('body of current message');
     }
 }
diff --git a/tests/unit/models/LoginFormTest.php b/tests/unit/models/LoginFormTest.php
index ecdb14512..3c1dcddaa 100644
--- a/tests/unit/models/LoginFormTest.php
+++ b/tests/unit/models/LoginFormTest.php
@@ -20,8 +20,8 @@ public function testLoginNoUser()
             'password' => 'not_existing_password',
         ]);
 
-        expect_not($this->model->login());
-        expect_that(\Yii::$app->user->isGuest);
+        verify($this->model->login())->false();
+        verify(\Yii::$app->user->isGuest)->true();
     }
 
     public function testLoginWrongPassword()
@@ -31,9 +31,9 @@ public function testLoginWrongPassword()
             'password' => 'wrong_password',
         ]);
 
-        expect_not($this->model->login());
-        expect_that(\Yii::$app->user->isGuest);
-        expect($this->model->errors)->hasKey('password');
+        verify($this->model->login())->false();
+        verify(\Yii::$app->user->isGuest)->true();
+        verify($this->model->errors)->arrayHasKey('password');
     }
 
     public function testLoginCorrect()
@@ -43,9 +43,9 @@ public function testLoginCorrect()
             'password' => 'demo',
         ]);
 
-        expect_that($this->model->login());
-        expect_not(\Yii::$app->user->isGuest);
-        expect($this->model->errors)->hasntKey('password');
+        verify($this->model->login())->true();
+        verify(\Yii::$app->user->isGuest)->false();
+        verify($this->model->errors)->arrayHasNotKey('password');
     }
 
 }
diff --git a/tests/unit/models/UserTest.php b/tests/unit/models/UserTest.php
index cb612785f..28986cbbd 100644
--- a/tests/unit/models/UserTest.php
+++ b/tests/unit/models/UserTest.php
@@ -8,37 +8,37 @@ class UserTest extends \Codeception\Test\Unit
 {
     public function testFindUserById()
     {
-        expect_that($user = User::findIdentity(100));
-        expect($user->username)->equals('admin');
+        verify($user = User::findIdentity(100))->notEmpty();
+        verify($user->username)->equals('admin');
 
-        expect_not(User::findIdentity(999));
+        verify(User::findIdentity(999))->empty();
     }
 
     public function testFindUserByAccessToken()
     {
-        expect_that($user = User::findIdentityByAccessToken('100-token'));
-        expect($user->username)->equals('admin');
+        verify($user = User::findIdentityByAccessToken('100-token'))->notEmpty();
+        verify($user->username)->equals('admin');
 
-        expect_not(User::findIdentityByAccessToken('non-existing'));        
+        verify(User::findIdentityByAccessToken('non-existing'))->empty();        
     }
 
     public function testFindUserByUsername()
     {
-        expect_that($user = User::findByUsername('admin'));
-        expect_not(User::findByUsername('not-admin'));
+        verify($user = User::findByUsername('admin'))->notEmpty();
+        verify(User::findByUsername('not-admin'))->empty();
     }
 
     /**
      * @depends testFindUserByUsername
      */
-    public function testValidateUser($user)
+    public function testValidateUser()
     {
         $user = User::findByUsername('admin');
-        expect_that($user->validateAuthKey('test100key'));
-        expect_not($user->validateAuthKey('test102key'));
+        verify($user->validateAuthKey('test100key'))->notEmpty();
+        verify($user->validateAuthKey('test102key'))->empty();
 
-        expect_that($user->validatePassword('admin'));
-        expect_not($user->validatePassword('123456'));        
+        verify($user->validatePassword('admin'))->notEmpty();
+        verify($user->validatePassword('123456'))->empty();        
     }
 
 }
diff --git a/tests/unit/widgets/AlertTest.php b/tests/unit/widgets/AlertTest.php
new file mode 100644
index 000000000..e9857a161
--- /dev/null
+++ b/tests/unit/widgets/AlertTest.php
@@ -0,0 +1,261 @@
+<?php
+
+namespace tests\unit\widgets;
+
+use app\widgets\Alert;
+use Yii;
+
+class AlertTest extends \Codeception\Test\Unit
+{
+    public function testSingleErrorMessage()
+    {
+        $message = 'This is an error message';
+
+        Yii::$app->session->setFlash('error', $message);
+
+        $renderingResult = Alert::widget();
+
+        verify($renderingResult)->stringContainsString($message);
+        verify($renderingResult)->stringContainsString('alert-danger');
+
+        verify($renderingResult)->stringNotContainsString('alert-success');
+        verify($renderingResult)->stringNotContainsString('alert-info');
+        verify($renderingResult)->stringNotContainsString('alert-warning');
+    }
+
+    public function testMultipleErrorMessages()
+    {
+        $firstMessage = 'This is the first error message';
+        $secondMessage = 'This is the second error message';
+
+        Yii::$app->session->setFlash('error', [$firstMessage, $secondMessage]);
+
+        $renderingResult = Alert::widget();
+
+        verify($renderingResult)->stringContainsString($firstMessage);
+        verify($renderingResult)->stringContainsString($secondMessage);
+        verify($renderingResult)->stringContainsString('alert-danger');
+
+        verify($renderingResult)->stringNotContainsString('alert-success');
+        verify($renderingResult)->stringNotContainsString('alert-info');
+        verify($renderingResult)->stringNotContainsString('alert-warning');
+    }
+
+    public function testSingleDangerMessage()
+    {
+        $message = 'This is a danger message';
+
+        Yii::$app->session->setFlash('danger', $message);
+
+        $renderingResult = Alert::widget();
+
+        verify($renderingResult)->stringContainsString($message);
+        verify($renderingResult)->stringContainsString('alert-danger');
+
+        verify($renderingResult)->stringNotContainsString('alert-success');
+        verify($renderingResult)->stringNotContainsString('alert-info');
+        verify($renderingResult)->stringNotContainsString('alert-warning');
+    }
+
+    public function testMultipleDangerMessages()
+    {
+        $firstMessage = 'This is the first danger message';
+        $secondMessage = 'This is the second danger message';
+
+        Yii::$app->session->setFlash('danger', [$firstMessage, $secondMessage]);
+
+        $renderingResult = Alert::widget();
+
+        verify($renderingResult)->stringContainsString($firstMessage);
+        verify($renderingResult)->stringContainsString($secondMessage);
+        verify($renderingResult)->stringContainsString('alert-danger');
+
+        verify($renderingResult)->stringNotContainsString('alert-success');
+        verify($renderingResult)->stringNotContainsString('alert-info');
+        verify($renderingResult)->stringNotContainsString('alert-warning');
+    }
+
+    public function testSingleSuccessMessage()
+    {
+        $message = 'This is a success message';
+
+        Yii::$app->session->setFlash('success', $message);
+
+        $renderingResult = Alert::widget();
+
+        verify($renderingResult)->stringContainsString($message);
+        verify($renderingResult)->stringContainsString('alert-success');
+
+        verify($renderingResult)->stringNotContainsString('alert-danger');
+        verify($renderingResult)->stringNotContainsString('alert-info');
+        verify($renderingResult)->stringNotContainsString('alert-warning');
+    }
+
+    public function testMultipleSuccessMessages()
+    {
+        $firstMessage = 'This is the first danger message';
+        $secondMessage = 'This is the second danger message';
+
+        Yii::$app->session->setFlash('success', [$firstMessage, $secondMessage]);
+
+        $renderingResult = Alert::widget();
+
+        verify($renderingResult)->stringContainsString($firstMessage);
+        verify($renderingResult)->stringContainsString($secondMessage);
+        verify($renderingResult)->stringContainsString('alert-success');
+
+        verify($renderingResult)->stringNotContainsString('alert-danger');
+        verify($renderingResult)->stringNotContainsString('alert-info');
+        verify($renderingResult)->stringNotContainsString('alert-warning');
+    }
+
+    public function testSingleInfoMessage()
+    {
+        $message = 'This is an info message';
+
+        Yii::$app->session->setFlash('info', $message);
+
+        $renderingResult = Alert::widget();
+
+        verify($renderingResult)->stringContainsString($message);
+        verify($renderingResult)->stringContainsString('alert-info');
+
+        verify($renderingResult)->stringNotContainsString('alert-danger');
+        verify($renderingResult)->stringNotContainsString('alert-success');
+        verify($renderingResult)->stringNotContainsString('alert-warning');
+    }
+
+    public function testMultipleInfoMessages()
+    {
+        $firstMessage = 'This is the first info message';
+        $secondMessage = 'This is the second info message';
+
+        Yii::$app->session->setFlash('info', [$firstMessage, $secondMessage]);
+
+        $renderingResult = Alert::widget();
+
+        verify($renderingResult)->stringContainsString($firstMessage);
+        verify($renderingResult)->stringContainsString($secondMessage);
+        verify($renderingResult)->stringContainsString('alert-info');
+
+        verify($renderingResult)->stringNotContainsString('alert-danger');
+        verify($renderingResult)->stringNotContainsString('alert-success');
+        verify($renderingResult)->stringNotContainsString('alert-warning');
+    }
+
+    public function testSingleWarningMessage()
+    {
+        $message = 'This is a warning message';
+
+        Yii::$app->session->setFlash('warning', $message);
+
+        $renderingResult = Alert::widget();
+
+        verify($renderingResult)->stringContainsString($message);
+        verify($renderingResult)->stringContainsString('alert-warning');
+
+        verify($renderingResult)->stringNotContainsString('alert-danger');
+        verify($renderingResult)->stringNotContainsString('alert-success');
+        verify($renderingResult)->stringNotContainsString('alert-info');
+    }
+
+    public function testMultipleWarningMessages()
+    {
+        $firstMessage = 'This is the first warning message';
+        $secondMessage = 'This is the second warning message';
+
+        Yii::$app->session->setFlash('warning', [$firstMessage, $secondMessage]);
+
+        $renderingResult = Alert::widget();
+
+        verify($renderingResult)->stringContainsString($firstMessage);
+        verify($renderingResult)->stringContainsString($secondMessage);
+        verify($renderingResult)->stringContainsString('alert-warning');
+
+        verify($renderingResult)->stringNotContainsString('alert-danger');
+        verify($renderingResult)->stringNotContainsString('alert-success');
+        verify($renderingResult)->stringNotContainsString('alert-info');
+    }
+
+    public function testSingleMixedMessages() {
+        $errorMessage = 'This is an error message';
+        $dangerMessage = 'This is a danger message';
+        $successMessage = 'This is a success message';
+        $infoMessage = 'This is a info message';
+        $warningMessage = 'This is a warning message';
+
+        Yii::$app->session->setFlash('error', $errorMessage);
+        Yii::$app->session->setFlash('danger', $dangerMessage);
+        Yii::$app->session->setFlash('success', $successMessage);
+        Yii::$app->session->setFlash('info', $infoMessage);
+        Yii::$app->session->setFlash('warning', $warningMessage);
+
+        $renderingResult = Alert::widget();
+
+        verify($renderingResult)->stringContainsString($errorMessage);
+        verify($renderingResult)->stringContainsString($dangerMessage);
+        verify($renderingResult)->stringContainsString($successMessage);
+        verify($renderingResult)->stringContainsString($infoMessage);
+        verify($renderingResult)->stringContainsString($warningMessage);
+
+        verify($renderingResult)->stringContainsString('alert-danger');
+        verify($renderingResult)->stringContainsString('alert-success');
+        verify($renderingResult)->stringContainsString('alert-info');
+        verify($renderingResult)->stringContainsString('alert-warning');
+    }
+
+    public function testMultipleMixedMessages() {
+        $firstErrorMessage = 'This is the first error message';
+        $secondErrorMessage = 'This is the second error message';
+        $firstDangerMessage = 'This is the first danger message';
+        $secondDangerMessage = 'This is the second';
+        $firstSuccessMessage = 'This is the first success message';
+        $secondSuccessMessage = 'This is the second success message';
+        $firstInfoMessage = 'This is the first info message';
+        $secondInfoMessage = 'This is the second info message';
+        $firstWarningMessage = 'This is the first warning message';
+        $secondWarningMessage = 'This is the second warning message';
+
+        Yii::$app->session->setFlash('error', [$firstErrorMessage, $secondErrorMessage]);
+        Yii::$app->session->setFlash('danger', [$firstDangerMessage, $secondDangerMessage]);
+        Yii::$app->session->setFlash('success', [$firstSuccessMessage, $secondSuccessMessage]);
+        Yii::$app->session->setFlash('info', [$firstInfoMessage, $secondInfoMessage]);
+        Yii::$app->session->setFlash('warning', [$firstWarningMessage, $secondWarningMessage]);
+
+        $renderingResult = Alert::widget();
+
+        verify($renderingResult)->stringContainsString($firstErrorMessage);
+        verify($renderingResult)->stringContainsString($secondErrorMessage);
+        verify($renderingResult)->stringContainsString($firstDangerMessage);
+        verify($renderingResult)->stringContainsString($secondDangerMessage);
+        verify($renderingResult)->stringContainsString($firstSuccessMessage);
+        verify($renderingResult)->stringContainsString($secondSuccessMessage);
+        verify($renderingResult)->stringContainsString($firstInfoMessage);
+        verify($renderingResult)->stringContainsString($secondInfoMessage);
+        verify($renderingResult)->stringContainsString($firstWarningMessage);
+        verify($renderingResult)->stringContainsString($secondWarningMessage);
+
+        verify($renderingResult)->stringContainsString('alert-danger');
+        verify($renderingResult)->stringContainsString('alert-success');
+        verify($renderingResult)->stringContainsString('alert-info');
+        verify($renderingResult)->stringContainsString('alert-warning');
+    }
+
+    public function testFlashIntegrity()
+    {
+        $errorMessage = 'This is an error message';
+        $unrelatedMessage = 'This is a message that is not related to the alert widget';
+
+        Yii::$app->session->setFlash('error', $errorMessage);
+        Yii::$app->session->setFlash('unrelated', $unrelatedMessage);
+
+        Alert::widget();
+
+        // Simulate redirect
+        Yii::$app->session->close();
+        Yii::$app->session->open();
+
+        verify(Yii::$app->session->getFlash('error'))->empty();
+        verify(Yii::$app->session->getFlash('unrelated'))->equals($unrelatedMessage);
+    }
+}
diff --git a/vagrant/nginx/app.conf b/vagrant/nginx/app.conf
index b22e66106..1bfc0d583 100644
--- a/vagrant/nginx/app.conf
+++ b/vagrant/nginx/app.conf
@@ -28,7 +28,7 @@ server {
        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.0-fpm.sock;
+       fastcgi_pass unix:/var/run/php/php7.2-fpm.sock;
        try_files $uri =404;
    }
 
diff --git a/vagrant/provision/always-as-root.sh b/vagrant/provision/always-as-root.sh
index e5d8e33e1..17fb58421 100644
--- a/vagrant/provision/always-as-root.sh
+++ b/vagrant/provision/always-as-root.sh
@@ -13,6 +13,6 @@ function info {
 info "Provision-script user: `whoami`"
 
 info "Restart web-stack"
-service php7.0-fpm restart
+service php7.2-fpm restart
 service nginx restart
-service mysql restart
\ No newline at end of file
+service mysql restart
diff --git a/vagrant/provision/once-as-root.sh b/vagrant/provision/once-as-root.sh
index 311d77b4c..1d2917016 100644
--- a/vagrant/provision/once-as-root.sh
+++ b/vagrant/provision/once-as-root.sh
@@ -3,6 +3,7 @@
 #== Import script args ==
 
 timezone=$(echo "$1")
+readonly IP=$2
 
 #== Bash helpers ==
 
@@ -21,9 +22,12 @@ 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-10.0 mysql-server/root_password password \"''\""
-debconf-set-selections <<< "mariadb-server-10.0 mysql-server/root_password_again password \"''\""
+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"
@@ -31,21 +35,21 @@ apt-get update
 apt-get upgrade -y
 
 info "Install additional software"
-apt-get install -y php7.0-curl php7.0-cli php7.0-intl php7.0-mysqlnd php7.0-gd php7.0-fpm php7.0-mbstring php7.0-xml unzip nginx mariadb-server-10.0 php.xdebug
+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 -uroot <<< "CREATE USER 'root'@'%' IDENTIFIED BY ''"
-mysql -uroot <<< "GRANT ALL PRIVILEGES ON *.* TO 'root'@'%'"
-mysql -uroot <<< "DROP USER 'root'@'localhost'"
-mysql -uroot <<< "FLUSH PRIVILEGES"
+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.0/fpm/pool.d/www.conf
-sed -i 's/group = www-data/group = vagrant/g' /etc/php/7.0/fpm/pool.d/www.conf
-sed -i 's/owner = www-data/owner = vagrant/g' /etc/php/7.0/fpm/pool.d/www.conf
-cat << EOF > /etc/php/7.0/mods-available/xdebug.ini
+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
@@ -66,10 +70,10 @@ info "Removing default site configuration"
 rm /etc/nginx/sites-enabled/default
 echo "Done!"
 
-info "Initailize databases for MySQL"
-mysql -uroot <<< "CREATE DATABASE yii2basic"
-mysql -uroot <<< "CREATE DATABASE yii2basic_test"
+info "Initialize 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
\ No newline at end of file
+curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
diff --git a/vagrant/provision/provision.awk b/vagrant/provision/provision.awk
new file mode 100644
index 000000000..bcf44d3be
--- /dev/null
+++ b/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."
+}
diff --git a/views/layouts/main.php b/views/layouts/main.php
index 87dfdcbc9..c204435c0 100644
--- a/views/layouts/main.php
+++ b/views/layouts/main.php
@@ -1,77 +1,79 @@
 <?php
 
-/* @var $this \yii\web\View */
-/* @var $content string */
+/** @var yii\web\View $this */
+/** @var string $content */
 
-use app\widgets\Alert;
-use yii\helpers\Html;
-use yii\bootstrap\Nav;
-use yii\bootstrap\NavBar;
-use yii\widgets\Breadcrumbs;
 use app\assets\AppAsset;
+use app\widgets\Alert;
+use yii\bootstrap5\Breadcrumbs;
+use yii\bootstrap5\Html;
+use yii\bootstrap5\Nav;
+use yii\bootstrap5\NavBar;
 
 AppAsset::register($this);
+
+$this->registerCsrfMetaTags();
+$this->registerMetaTag(['charset' => Yii::$app->charset], 'charset');
+$this->registerMetaTag(['name' => 'viewport', 'content' => 'width=device-width, initial-scale=1, shrink-to-fit=no']);
+$this->registerMetaTag(['name' => 'description', 'content' => $this->params['meta_description'] ?? '']);
+$this->registerMetaTag(['name' => 'keywords', 'content' => $this->params['meta_keywords'] ?? '']);
+$this->registerLinkTag(['rel' => 'icon', 'type' => 'image/x-icon', 'href' => Yii::getAlias('@web/favicon.ico')]);
 ?>
 <?php $this->beginPage() ?>
 <!DOCTYPE html>
-<html lang="<?= Yii::$app->language ?>">
+<html lang="<?= Yii::$app->language ?>" class="h-100">
 <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>
-<body>
+<body class="d-flex flex-column h-100">
 <?php $this->beginBody() ?>
 
-<div class="wrap">
+<header id="header">
     <?php
     NavBar::begin([
         'brandLabel' => Yii::$app->name,
         'brandUrl' => Yii::$app->homeUrl,
-        'options' => [
-            'class' => 'navbar-inverse navbar-fixed-top',
-        ],
+        'options' => ['class' => 'navbar-expand-md navbar-dark bg-dark fixed-top']
     ]);
     echo Nav::widget([
-        'options' => ['class' => 'navbar-nav navbar-right'],
+        'options' => ['class' => 'navbar-nav'],
         'items' => [
             ['label' => 'Home', 'url' => ['/site/index']],
             ['label' => 'About', 'url' => ['/site/about']],
             ['label' => 'Contact', 'url' => ['/site/contact']],
-            Yii::$app->user->isGuest ? (
-                ['label' => 'Login', 'url' => ['/site/login']]
-            ) : (
-                '<li>'
-                . Html::beginForm(['/site/logout'], 'post')
-                . Html::submitButton(
-                    'Logout (' . Yii::$app->user->identity->username . ')',
-                    ['class' => 'btn btn-link logout']
-                )
-                . Html::endForm()
-                . '</li>'
-            )
-        ],
+            Yii::$app->user->isGuest
+                ? ['label' => 'Login', 'url' => ['/site/login']]
+                : '<li class="nav-item">'
+                    . Html::beginForm(['/site/logout'])
+                    . Html::submitButton(
+                        'Logout (' . Yii::$app->user->identity->username . ')',
+                        ['class' => 'nav-link btn btn-link logout']
+                    )
+                    . Html::endForm()
+                    . '</li>'
+        ]
     ]);
     NavBar::end();
     ?>
+</header>
 
+<main id="main" class="flex-shrink-0" role="main">
     <div class="container">
-        <?= Breadcrumbs::widget([
-            'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [],
-        ]) ?>
+        <?php if (!empty($this->params['breadcrumbs'])): ?>
+            <?= Breadcrumbs::widget(['links' => $this->params['breadcrumbs']]) ?>
+        <?php endif ?>
         <?= Alert::widget() ?>
         <?= $content ?>
     </div>
-</div>
+</main>
 
-<footer class="footer">
+<footer id="footer" class="mt-auto py-3 bg-light">
     <div class="container">
-        <p class="pull-left">&copy; My Company <?= date('Y') ?></p>
-
-        <p class="pull-right"><?= Yii::powered() ?></p>
+        <div class="row text-muted">
+            <div class="col-md-6 text-center text-md-start">&copy; My Company <?= date('Y') ?></div>
+            <div class="col-md-6 text-center text-md-end"><?= Yii::powered() ?></div>
+        </div>
     </div>
 </footer>
 
diff --git a/views/site/about.php b/views/site/about.php
index 68d5cf3bd..ea006ecda 100644
--- a/views/site/about.php
+++ b/views/site/about.php
@@ -1,6 +1,6 @@
 <?php
 
-/* @var $this yii\web\View */
+/** @var yii\web\View $this */
 
 use yii\helpers\Html;
 
diff --git a/views/site/contact.php b/views/site/contact.php
index f4c1e74a2..597fabcb2 100644
--- a/views/site/contact.php
+++ b/views/site/contact.php
@@ -1,11 +1,11 @@
 <?php
 
-/* @var $this yii\web\View */
-/* @var $form yii\bootstrap\ActiveForm */
-/* @var $model app\models\ContactForm */
+/** @var yii\web\View $this */
+/** @var yii\bootstrap5\ActiveForm $form */
+/** @var app\models\ContactForm $model */
 
-use yii\helpers\Html;
-use yii\bootstrap\ActiveForm;
+use yii\bootstrap5\ActiveForm;
+use yii\bootstrap5\Html;
 use yii\captcha\Captcha;
 
 $this->title = 'Contact';
@@ -51,7 +51,7 @@
 
                     <?= $form->field($model, 'body')->textarea(['rows' => 6]) ?>
 
-                    <?= $form->field($model, 'verifyCode')->widget(Captcha::className(), [
+                    <?= $form->field($model, 'verifyCode')->widget(Captcha::class, [
                         'template' => '<div class="row"><div class="col-lg-3">{image}</div><div class="col-lg-6">{input}</div></div>',
                     ]) ?>
 
diff --git a/views/site/error.php b/views/site/error.php
index 0ba257465..176874796 100644
--- a/views/site/error.php
+++ b/views/site/error.php
@@ -1,9 +1,9 @@
 <?php
 
-/* @var $this yii\web\View */
-/* @var $name string */
-/* @var $message string */
-/* @var $exception Exception */
+/** @var yii\web\View $this */
+/** @var string $name */
+/** @var string $message */
+/** @var Exception $exception */
 
 use yii\helpers\Html;
 
diff --git a/views/site/index.php b/views/site/index.php
index f78061088..df5df7c3d 100644
--- a/views/site/index.php
+++ b/views/site/index.php
@@ -1,23 +1,23 @@
 <?php
 
-/* @var $this yii\web\View */
+/** @var yii\web\View $this */
 
 $this->title = 'My Yii Application';
 ?>
 <div class="site-index">
 
-    <div class="jumbotron">
-        <h1>Congratulations!</h1>
+    <div class="jumbotron text-center bg-transparent mt-5 mb-5">
+        <h1 class="display-4">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>
+        <p><a class="btn btn-lg btn-success" href="https://www.yiiframework.com">Get started with Yii</a></p>
     </div>
 
     <div class="body-content">
 
         <div class="row">
-            <div class="col-lg-4">
+            <div class="col-lg-4 mb-3">
                 <h2>Heading</h2>
 
                 <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
@@ -25,9 +25,9 @@
                     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>
+                <p><a class="btn btn-outline-secondary" href="https://www.yiiframework.com/doc/">Yii Documentation &raquo;</a></p>
             </div>
-            <div class="col-lg-4">
+            <div class="col-lg-4 mb-3">
                 <h2>Heading</h2>
 
                 <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
@@ -35,7 +35,7 @@
                     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>
+                <p><a class="btn btn-outline-secondary" href="https://www.yiiframework.com/forum/">Yii Forum &raquo;</a></p>
             </div>
             <div class="col-lg-4">
                 <h2>Heading</h2>
@@ -45,7 +45,7 @@
                     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>
+                <p><a class="btn btn-outline-secondary" href="https://www.yiiframework.com/extensions/">Yii Extensions &raquo;</a></p>
             </div>
         </div>
 
diff --git a/views/site/login.php b/views/site/login.php
index 0944d37e4..db003c01c 100644
--- a/views/site/login.php
+++ b/views/site/login.php
@@ -1,11 +1,12 @@
 <?php
 
-/* @var $this yii\web\View */
-/* @var $form yii\bootstrap\ActiveForm */
-/* @var $model app\models\LoginForm */
+/** @var yii\web\View $this */
+/** @var yii\bootstrap5\ActiveForm $form */
 
-use yii\helpers\Html;
-use yii\bootstrap\ActiveForm;
+/** @var app\models\LoginForm $model */
+
+use yii\bootstrap5\ActiveForm;
+use yii\bootstrap5\Html;
 
 $this->title = 'Login';
 $this->params['breadcrumbs'][] = $this->title;
@@ -15,33 +16,40 @@
 
     <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'],
-        ],
-    ]); ?>
+    <div class="row">
+        <div class="col-lg-5">
+
+            <?php $form = ActiveForm::begin([
+                'id' => 'login-form',
+                'fieldConfig' => [
+                    'template' => "{label}\n{input}\n{error}",
+                    'labelOptions' => ['class' => 'col-lg-1 col-form-label mr-lg-3'],
+                    'inputOptions' => ['class' => 'col-lg-3 form-control'],
+                    'errorOptions' => ['class' => 'col-lg-7 invalid-feedback'],
+                ],
+            ]); ?>
 
-        <?= $form->field($model, 'username')->textInput(['autofocus' => true]) ?>
+            <?= $form->field($model, 'username')->textInput(['autofocus' => true]) ?>
 
-        <?= $form->field($model, 'password')->passwordInput() ?>
+            <?= $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>",
-        ]) ?>
+            <?= $form->field($model, 'rememberMe')->checkbox([
+                'template' => "<div class=\"custom-control custom-checkbox\">{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 class="form-group">
+                <div>
+                    <?= Html::submitButton('Login', ['class' => 'btn btn-primary', 'name' => 'login-button']) ?>
+                </div>
             </div>
-        </div>
 
-    <?php ActiveForm::end(); ?>
+            <?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 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>
     </div>
 </div>
diff --git a/web/css/site.css b/web/css/site.css
index 2bfebd275..fb300b39e 100644
--- a/web/css/site.css
+++ b/web/css/site.css
@@ -1,34 +1,16 @@
-html,
-body {
-    height: 100%;
-}
-
-.wrap {
-    min-height: 100%;
-    height: auto;
-    margin: 0 auto -60px;
-    padding: 0 0 60px;
-}
-
-.wrap > .container {
+main > .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;
+    font-size: .9em;
+    height: 60px;
 }
 
-.jumbotron .btn {
-    font-size: 21px;
-    padding: 14px 24px;
+.footer > .container {
+    padding-right: 15px;
+    padding-left: 15px;
 }
 
 .not-set {
@@ -38,38 +20,24 @@ body {
 
 /* add sorting icons to gridview sort links */
 a.asc:after, a.desc:after {
-    position: relative;
-    top: 1px;
+    content: '';
+    left: 3px;
     display: inline-block;
-    font-family: 'Glyphicons Halflings';
-    font-style: normal;
-    font-weight: normal;
-    line-height: 1;
-    padding-left: 5px;
+    width: 0;
+    height: 0;
+    border: solid 5px transparent;
+    margin: 4px 4px 2px 4px;
+    background: transparent;
 }
 
 a.asc:after {
-    content: /*"\e113"*/ "\e151";
+    border-bottom: solid 7px #212529;
+    border-top-width: 0;
 }
 
 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";
+    border-top: solid 7px #212529;
+    border-bottom-width: 0;
 }
 
 .grid-view th {
@@ -92,8 +60,8 @@ a.desc:after {
 
 /* align the logout "link" (button in form) of the navbar */
 .nav li > form > button.logout {
-    padding: 15px;
-    border: none;
+    padding-top: 7px;
+    color: rgba(255, 255, 255, 0.5);
 }
 
 @media(max-width:767px) {
@@ -101,15 +69,20 @@ a.desc:after {
         display:block;
         text-align: left;
         width: 100%;
-        padding: 10px 15px;
+        padding: 10px 0;
     }
 }
 
 .nav > li > form > button.logout:focus,
 .nav > li > form > button.logout:hover {
     text-decoration: none;
+    color: rgba(255, 255, 255, 0.75);
 }
 
 .nav > li > form > button.logout:focus {
     outline: none;
 }
+
+.form-group {
+    margin-bottom: 1rem;
+}
\ No newline at end of file
diff --git a/widgets/Alert.php b/widgets/Alert.php
index ae6c1d85c..1302c1f29 100644
--- a/widgets/Alert.php
+++ b/widgets/Alert.php
@@ -1,4 +1,5 @@
 <?php
+
 namespace app\widgets;
 
 use Yii;
@@ -22,7 +23,7 @@
  * @author Kartik Visweswaran <kartikv2@gmail.com>
  * @author Alexander Makarov <sam@rmcreative.ru>
  */
-class Alert extends \yii\bootstrap\Widget
+class Alert extends \yii\bootstrap5\Widget
 {
     /**
      * @var array the alert types configuration for the flash messages.
@@ -50,16 +51,13 @@ class Alert extends \yii\bootstrap\Widget
     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_keys($this->alertTypes) as $type) {
+            $flash = $session->getFlash($type);
 
             foreach ((array) $flash as $i => $message) {
-                echo \yii\bootstrap\Alert::widget([
+                echo \yii\bootstrap5\Alert::widget([
                     'body' => $message,
                     'closeButton' => $this->closeButton,
                     'options' => array_merge($this->options, [
diff --git a/yii b/yii
index 2a2e8ede7..1d0008f5d 100755
--- a/yii
+++ b/yii
@@ -3,9 +3,9 @@
 /**
  * Yii console bootstrap file.
  *
- * @link http://www.yiiframework.com/
+ * @link https://www.yiiframework.com/
  * @copyright Copyright (c) 2008 Yii Software LLC
- * @license http://www.yiiframework.com/license/
+ * @license https://www.yiiframework.com/license/
  */
 
 defined('YII_DEBUG') or define('YII_DEBUG', true);
diff --git a/yii.bat b/yii.bat
index d516b3a19..ce14c92bc 100644
--- a/yii.bat
+++ b/yii.bat
@@ -4,9 +4,9 @@ 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  @link https://www.yiiframework.com/
 rem  @copyright Copyright (c) 2008 Yii Software LLC
-rem  @license http://www.yiiframework.com/license/
+rem  @license https://www.yiiframework.com/license/
 rem -------------------------------------------------------------
 
 @setlocal
