diff --git a/.bowerrc b/.bowerrc
index 1669168f2..a39b5b0fb 100644
--- a/.bowerrc
+++ b/.bowerrc
@@ -1,3 +1,3 @@
{
- "directory" : "vendor/bower"
+ "directory" : "vendor/bower-asset"
}
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
new file mode 100644
index 000000000..011c056bb
--- /dev/null
+++ b/.git-blame-ignore-revs
@@ -0,0 +1,8 @@
+# Reformat code te be PSR-2 compatible
+93cc82c4bf42cea403e1acaab201338bea304b6e
+# Convert to short syntax (array)
+332030325fbad38a64c5e60980d3b14b6434d6dd
+# Convert to short syntax
+b0fcdfab1aecaeb620f75a83f15aa02bc25765a0
+# Fix codestyle
+0f91f32ecff52ef479addb0a5013342d59fe0697
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 000000000..705fb391d
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,7 @@
+# Ignore all test and documentation for archive
+/.github export-ignore
+/.git-blame-ignore-revs export-ignore
+/.gitattributes export-ignore
+/.scrutinizer.yml export-ignore
+/.travis.yml export-ignore
+/docs export-ignore
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
new file mode 100644
index 000000000..ea75bda5f
--- /dev/null
+++ b/.github/CONTRIBUTING.md
@@ -0,0 +1,7 @@
+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](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
new file mode 100644
index 000000000..692239301
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +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
new file mode 100644
index 000000000..a2ee92439
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,14 @@
+### What steps will reproduce the problem?
+
+### What's expected?
+
+### What do you get instead?
+
+
+### Additional info
+
+| Q | A
+| ---------------- | ---
+| Yii version |
+| PHP version |
+| Operating system |
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 000000000..968a845de
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,7 @@
+| Q | A
+| ------------- | ---
+| Is bugfix? | yes/no
+| New feature? | yes/no
+| Breaks BC? | yes/no
+| Tests pass? | yes/no
+| Fixed issues | comma-separated list of tickets # fixed by the PR, if any
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/.gitignore b/.gitignore
index 45bf7bf41..05fb29d1b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,9 @@
# phpstorm project files
.idea
+# visual studio code project files
+.vscode
+
# netbeans project files
nbproject
@@ -25,3 +28,9 @@ composer.phar
phpunit.phar
# local phpunit config
/phpunit.xml
+
+tests/_output/*
+tests/_support/_generated
+
+#vagrant folder
+/.vagrant
\ No newline at end of file
diff --git a/LICENSE.md b/LICENSE.md
index e98f03df8..ee872b9ab 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -1,6 +1,3 @@
-The Yii framework is free software. It is released under the terms of
-the following BSD License.
-
Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com)
All rights reserved.
diff --git a/README.md b/README.md
index c5bec59a4..981a7f9d9 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,21 @@
-Yii 2 Basic Application Template
-================================
-
-Yii 2 Basic Application Template is a skeleton Yii 2 application best for
+
+
+
+
+
Yii 2 Basic Project Template
+
+
+
+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.
It includes all commonly used configurations that would allow you to focus on adding new
features to your application.
+[](https://packagist.org/packages/yiisoft/yii2-app-basic)
+[](https://packagist.org/packages/yiisoft/yii2-app-basic)
+[](https://github.com/yiisoft/yii2-app-basic/actions?query=workflow%3Abuild)
DIRECTORY STRUCTURE
-------------------
@@ -29,17 +37,44 @@ DIRECTORY STRUCTURE
REQUIREMENTS
------------
-The minimum requirement by this application 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
------------
+### Install via Composer
+
+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:
+
+~~~
+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
+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:
+
+```php
+'request' => [
+ // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
+ 'cookieValidationKey' => '',
+],
+```
+
You can then access the application through the following URL:
~~~
@@ -47,24 +82,27 @@ http://localhost/basic/web/
~~~
-### Install via Composer
+### Install with Docker
-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).
+Update your vendor packages
-You can then install this application template using the following command:
+ docker-compose run --rm php composer update --prefer-dist
+
+Run the installation triggers (creating cookie validation code)
-~~~
-php composer.phar global require "fxp/composer-asset-plugin:1.0.0"
-php composer.phar create-project --prefer-dist --stability=dev yiisoft/yii2-app-basic basic
-~~~
+ docker-compose run --rm php composer install
+
+Start the container
-Now you should be able to access the application through the following URL, assuming `basic` is the directory
-directly under the Web root.
+ docker-compose up -d
+
+You can then access the application through the following URL:
-~~~
-http://localhost/basic/web/
-~~~
+ 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
@@ -84,6 +122,112 @@ return [
];
```
-**NOTE:** Yii won't create the database for you, this has to be done manually before you can access it.
+**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](https://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](https://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
+```
-Also check and edit the other files in the `config/` directory to customize your application.
+You can see code coverage output under the `tests/_output` directory.
diff --git a/Vagrantfile b/Vagrantfile
new file mode 100644
index 000000000..258aea601
--- /dev/null
+++ b/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
diff --git a/assets/AppAsset.php b/assets/AppAsset.php
index 0e495a8f8..d9d4cc430 100644
--- a/assets/AppAsset.php
+++ b/assets/AppAsset.php
@@ -1,8 +1,9 @@
* @since 2.0
*/
@@ -24,6 +27,6 @@ class AppAsset extends AssetBundle
];
public $depends = [
'yii\web\YiiAsset',
- 'yii\bootstrap\BootstrapAsset',
+ 'yii\bootstrap5\BootstrapAsset'
];
}
diff --git a/codeception.yml b/codeception.yml
new file mode 100644
index 000000000..dd8febcff
--- /dev/null
+++ b/codeception.yml
@@ -0,0 +1,27 @@
+actor: Tester
+bootstrap: _bootstrap.php
+paths:
+ tests: tests
+ output: 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/*
diff --git a/commands/HelloController.php b/commands/HelloController.php
index 86ab8b853..28dba79cd 100644
--- a/commands/HelloController.php
+++ b/commands/HelloController.php
@@ -1,13 +1,15 @@
=5.4.0",
- "yiisoft/yii2": "*",
- "yiisoft/yii2-bootstrap": "*",
- "yiisoft/yii2-swiftmailer": "*"
+ "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-codeception": "*",
- "yiisoft/yii2-debug": "*",
- "yiisoft/yii2-gii": "*",
- "yiisoft/yii2-faker": "*"
+ "yiisoft/yii2-debug": "~2.1.0",
+ "yiisoft/yii2-gii": "~2.2.0",
+ "yiisoft/yii2-faker": "~2.0.0",
+ "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": {
- "process-timeout": 1800
+ "allow-plugins": {
+ "yiisoft/yii2-composer" : true
+ },
+ "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::postCreateProject",
+ "yii\\composer\\Installer::postInstall"
]
},
"extra": {
@@ -41,14 +56,18 @@
"web/assets": "0777",
"yii": "0755"
}
- ],
+ ]
+ },
+ "yii\\composer\\Installer::postInstall": {
"generateCookieValidationKey": [
"config/web.php"
]
- },
- "asset-installer-paths": {
- "npm-asset-library": "vendor/npm",
- "bower-asset-library": "vendor/bower"
}
- }
+ },
+ "repositories": [
+ {
+ "type": "composer",
+ "url": "https://asset-packagist.org/"
+ }
+ ]
}
diff --git a/config/__autocomplete.php b/config/__autocomplete.php
new file mode 100644
index 000000000..e5e1c00d7
--- /dev/null
+++ b/config/__autocomplete.php
@@ -0,0 +1,35 @@
+ 'basic-console',
'basePath' => dirname(__DIR__),
- 'bootstrap' => ['log', 'gii'],
+ 'bootstrap' => ['log'],
'controllerNamespace' => 'app\commands',
- 'modules' => [
- 'gii' => 'yii\gii\Module',
+ 'aliases' => [
+ '@bower' => '@vendor/bower-asset',
+ '@npm' => '@vendor/npm-asset',
+ '@tests' => '@app/tests',
],
'components' => [
'cache' => [
@@ -28,4 +28,29 @@
'db' => $db,
],
'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',
+ ];
+ // 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/db.php b/config/db.php
index c4c12529c..bc75e616f 100644
--- a/config/db.php
+++ b/config/db.php
@@ -6,4 +6,9 @@
'username' => 'root',
'password' => '',
'charset' => 'utf8',
+
+ // Schema cache options (for production environment)
+ //'enableSchemaCache' => true,
+ //'schemaCacheDuration' => 60,
+ //'schemaCache' => 'cache',
];
diff --git a/config/params.php b/config/params.php
index 6ebf2792b..981c621ac 100644
--- a/config/params.php
+++ b/config/params.php
@@ -2,4 +2,6 @@
return [
'adminEmail' => 'admin@example.com',
+ 'senderEmail' => 'noreply@example.com',
+ 'senderName' => 'Example.com mailer',
];
diff --git a/config/test.php b/config/test.php
new file mode 100644
index 000000000..fb5e2929e
--- /dev/null
+++ b/config/test.php
@@ -0,0 +1,47 @@
+ 'basic-tests',
+ 'basePath' => dirname(__DIR__),
+ 'aliases' => [
+ '@bower' => '@vendor/bower-asset',
+ '@npm' => '@vendor/npm-asset',
+ ],
+ 'language' => 'en-US',
+ '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',
+ ],
+ '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,
+];
diff --git a/config/test_db.php b/config/test_db.php
new file mode 100644
index 000000000..f10835fe1
--- /dev/null
+++ b/config/test_db.php
@@ -0,0 +1,7 @@
+ 'basic',
'basePath' => dirname(__DIR__),
'bootstrap' => ['log'],
+ '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
@@ -22,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' => [
@@ -37,7 +41,15 @@
],
],
],
- 'db' => require(__DIR__ . '/db.php'),
+ 'db' => $db,
+ /*
+ 'urlManager' => [
+ 'enablePrettyUrl' => true,
+ 'showScriptName' => false,
+ 'rules' => [
+ ],
+ ],
+ */
],
'params' => $params,
];
@@ -45,10 +57,18 @@
if (YII_ENV_DEV) {
// configuration adjustments for 'dev' environment
$config['bootstrap'][] = 'debug';
- $config['modules']['debug'] = 'yii\debug\Module';
+ $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'] = 'yii\gii\Module';
+ $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;
diff --git a/controllers/SiteController.php b/controllers/SiteController.php
index f95994160..67c3f50f8 100644
--- a/controllers/SiteController.php
+++ b/controllers/SiteController.php
@@ -5,17 +5,21 @@
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(),
+ 'class' => AccessControl::class,
'only' => ['logout'],
'rules' => [
[
@@ -26,7 +30,7 @@ public function behaviors()
],
],
'verbs' => [
- 'class' => VerbFilter::className(),
+ 'class' => VerbFilter::class,
'actions' => [
'logout' => ['post'],
],
@@ -34,6 +38,9 @@ public function behaviors()
];
}
+ /**
+ * {@inheritdoc}
+ */
public function actions()
{
return [
@@ -47,27 +54,43 @@ public function actions()
];
}
+ /**
+ * Displays homepage.
+ *
+ * @return string
+ */
public function actionIndex()
{
return $this->render('index');
}
+ /**
+ * Login action.
+ *
+ * @return Response|string
+ */
public function actionLogin()
{
- if (!\Yii::$app->user->isGuest) {
+ if (!Yii::$app->user->isGuest) {
return $this->goHome();
}
$model = new LoginForm();
if ($model->load(Yii::$app->request->post()) && $model->login()) {
return $this->goBack();
- } else {
- return $this->render('login', [
- 'model' => $model,
- ]);
}
+
+ $model->password = '';
+ return $this->render('login', [
+ 'model' => $model,
+ ]);
}
+ /**
+ * Logout action.
+ *
+ * @return Response
+ */
public function actionLogout()
{
Yii::$app->user->logout();
@@ -75,6 +98,11 @@ public function actionLogout()
return $this->goHome();
}
+ /**
+ * Displays contact page.
+ *
+ * @return Response|string
+ */
public function actionContact()
{
$model = new ContactForm();
@@ -82,13 +110,17 @@ public function actionContact()
Yii::$app->session->setFlash('contactFormSubmitted');
return $this->refresh();
- } else {
- return $this->render('contact', [
- 'model' => $model,
- ]);
}
+ return $this->render('contact', [
+ 'model' => $model,
+ ]);
}
+ /**
+ * Displays about page.
+ *
+ * @return string
+ */
public function actionAbout()
{
return $this->render('about');
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 000000000..86be3bd0d
--- /dev/null
+++ b/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'
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 @@
beginPage() ?>
diff --git a/mail/layouts/text.php b/mail/layouts/text.php
new file mode 100644
index 000000000..0873d7728
--- /dev/null
+++ b/mail/layouts/text.php
@@ -0,0 +1,13 @@
+beginPage();
+$this->beginBody();
+echo $content;
+$this->endBody();
+$this->endPage();
diff --git a/models/ContactForm.php b/models/ContactForm.php
index d4052ee93..f001d2192 100644
--- a/models/ContactForm.php
+++ b/models/ContactForm.php
@@ -16,6 +16,7 @@ class ContactForm extends Model
public $body;
public $verifyCode;
+
/**
* @return array the validation rules.
*/
@@ -43,22 +44,22 @@ public function attributeLabels()
/**
* Sends an email to the specified email address using the information collected by this model.
- * @param string $email the target email address
- * @return boolean whether the model passes validation
+ * @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([$this->email => $this->name])
+ ->setFrom([Yii::$app->params['senderEmail'] => Yii::$app->params['senderName']])
+ ->setReplyTo([$this->email => $this->name])
->setSubject($this->subject)
->setTextBody($this->body)
->send();
return true;
- } else {
- return false;
}
+ return false;
}
}
diff --git a/models/LoginForm.php b/models/LoginForm.php
index 7bd44d402..5304eecf4 100644
--- a/models/LoginForm.php
+++ b/models/LoginForm.php
@@ -7,6 +7,9 @@
/**
* LoginForm is the model behind the login form.
+ *
+ * @property-read User|null $user
+ *
*/
class LoginForm extends Model
{
@@ -52,15 +55,14 @@ public function validatePassword($attribute, $params)
/**
* Logs in a user using the provided username and password.
- * @return boolean whether the user is logged in successfully
+ * @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);
- } else {
- return false;
+ return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600 * 24 * 30 : 0);
}
+ return false;
}
/**
diff --git a/models/User.php b/models/User.php
index cbfb9fefc..2e3fb25ed 100644
--- a/models/User.php
+++ b/models/User.php
@@ -2,7 +2,7 @@
namespace app\models;
-class User extends \yii\base\Object implements \yii\web\IdentityInterface
+class User extends \yii\base\BaseObject implements \yii\web\IdentityInterface
{
public $id;
public $username;
@@ -27,8 +27,9 @@ class User extends \yii\base\Object implements \yii\web\IdentityInterface
],
];
+
/**
- * @inheritdoc
+ * {@inheritdoc}
*/
public static function findIdentity($id)
{
@@ -36,7 +37,7 @@ public static function findIdentity($id)
}
/**
- * @inheritdoc
+ * {@inheritdoc}
*/
public static function findIdentityByAccessToken($token, $type = null)
{
@@ -52,7 +53,7 @@ public static function findIdentityByAccessToken($token, $type = null)
/**
* Finds user by username
*
- * @param string $username
+ * @param string $username
* @return static|null
*/
public static function findByUsername($username)
@@ -67,7 +68,7 @@ public static function findByUsername($username)
}
/**
- * @inheritdoc
+ * {@inheritdoc}
*/
public function getId()
{
@@ -75,7 +76,7 @@ public function getId()
}
/**
- * @inheritdoc
+ * {@inheritdoc}
*/
public function getAuthKey()
{
@@ -83,7 +84,7 @@ public function getAuthKey()
}
/**
- * @inheritdoc
+ * {@inheritdoc}
*/
public function validateAuthKey($authKey)
{
@@ -93,8 +94,8 @@ public function validateAuthKey($authKey)
/**
* Validates password
*
- * @param string $password password to validate
- * @return boolean if password provided is valid for current user
+ * @param string $password password to validate
+ * @return bool if password provided is valid for current user
*/
public function validatePassword($password)
{
diff --git a/requirements.php b/requirements.php
index ee6b2cb53..db69a36ee 100644
--- a/requirements.php
+++ b/requirements.php
@@ -1,4 +1,5 @@
Error';
- echo 'The path to yii framework seems to be incorrect.
';
- echo 'You need to install Yii framework via composer or adjust the framework path in file ' . basename(__FILE__) . '.
';
- echo 'Please refer to the README on how to install Yii.
';
+if (!isset($frameworkPath) || !is_dir($frameworkPath)) {
+ $message = "Error
\n\n"
+ . "The path to yii framework seems to be incorrect.
\n"
+ . 'You need to install Yii framework via composer or adjust the framework path in file ' . basename(__FILE__) . ".
\n"
+ . 'Please refer to the README on how to install Yii.
\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');
@@ -82,28 +106,22 @@
'name' => 'Memcache extension',
'mandatory' => false,
'condition' => extension_loaded('memcache') || extension_loaded('memcached'),
- 'by' => 'MemCache',
- 'memo' => extension_loaded('memcached') ? 'To use memcached set MemCache::useMemcached to true.' : ''
- ),
- array(
- 'name' => 'APC extension',
- 'mandatory' => false,
- 'condition' => extension_loaded('apc'),
- 'by' => 'ApcCache',
+ 'by' => 'MemCache',
+ 'memo' => extension_loaded('memcached') ? 'To use memcached set MemCache::useMemcached to true.' : ''
),
// CAPTCHA:
array(
'name' => 'GD PHP extension with FreeType support',
'mandatory' => false,
'condition' => $gdOK,
- 'by' => 'Captcha',
+ 'by' => 'Captcha',
'memo' => $gdMemo,
),
array(
'name' => 'ImageMagick PHP extension with PNG support',
'mandatory' => false,
'condition' => $imagickOK,
- 'by' => 'Captcha',
+ 'by' => 'Captcha',
'memo' => $imagickMemo,
),
// PHP ini :
@@ -124,9 +142,22 @@
'phpSmtp' => array(
'name' => 'PHP mail SMTP',
'mandatory' => false,
- 'condition' => strlen(ini_get('SMTP'))>0,
+ 'condition' => strlen(ini_get('SMTP')) > 0,
'by' => 'Email sending',
'memo' => 'PHP mail SMTP server required',
),
);
-$requirementsChecker->checkYii()->check($requirements)->render();
+
+// OPcache check
+if (!version_compare(phpversion(), '5.5', '>=')) {
+ $requirements[] = array(
+ 'name' => 'APC extension',
+ 'mandatory' => false,
+ 'condition' => extension_loaded('apc'),
+ 'by' => 'ApcCache',
+ );
+}
+
+$result = $requirementsChecker->checkYii()->check($requirements)->getResult();
+$requirementsChecker->render();
+exit($result['summary']['errors'] === 0 ? 0 : 1);
diff --git a/tests/README.md b/tests/README.md
deleted file mode 100644
index 8134fb3a7..000000000
--- a/tests/README.md
+++ /dev/null
@@ -1,111 +0,0 @@
-This directory contains various tests for the basic application.
-
-Tests in `codeception` directory are developed with [Codeception PHP Testing Framework](http://codeception.com/).
-
-After creating the basic application, follow these steps to prepare for the tests:
-
-1. Install Codeception if it's not yet installed:
-
-```
-composer global require "codeception/codeception=2.0.*"
-composer global require "codeception/specify=*"
-composer global require "codeception/verify=*"
-```
-
-If you've never used Composer for global packages run `composer global status`. It should output:
-
-```
-Changed current directory to
-```
-
-Then add `/vendor/bin` to you `PATH` environment variable. Now we're able to use `codecept` from command
-line globally.
-
-2. Install faker extension by running the following from template root directory where `composer.json` is:
-
-```
-composer require --dev yiisoft/yii2-faker:*
-```
-
-3. Create `yii2_basic_tests` database and update it by applying migrations:
-
-```
-codeception/bin/yii migrate
-```
-
-4. Build the test suites:
-
-```
-codecept build
-```
-
-5. In order to be able to run acceptance tests you need to start a webserver. The simplest way is to use PHP built in
-webserver. In the `web` directory execute the following:
-
-```
-php -S localhost:8080
-```
-
-6. Now you can run the tests with the following commands:
-
-```
-# run all available tests
-codecept run
-# run acceptance tests
-codecept run acceptance
-# run functional tests
-codecept run functional
-# run unit tests
-codecept run unit
-```
-
-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
-codecept run --coverage-html --coverage-xml
-
-#collect coverage only for unit tests
-codecept run unit --coverage-html --coverage-xml
-
-#collect coverage for unit and functional tests
-codecept run functional,unit --coverage-html --coverage-xml
-```
-
-You can see code coverage output under the `tests/_output` directory.
-
-###Remote code coverage
-
-When you run your tests not in the same process where code coverage is collected, then you should uncomment `remote` option and its
-related options, to be able to collect code coverage correctly. To setup remote code coverage you should follow [instructions](http://codeception.com/docs/11-Codecoverage)
-from codeception site.
-
-1. install `Codeception c3` remote support `composer require "codeception/c3:*"`;
-
-2. copy `c3.php` file under your `web` directory;
-
-3. include `c3.php` file in your `index-test.php` file before application run, so it can catch needed requests.
-
-Configuration options that are used by remote code coverage:
-
-- c3_url: url pointing to entry script that includes `c3.php` file, so `Codeception` will be able to produce code coverage;
-- remote: whether to enable remote code coverage or not;
-- remote_config: path to the `codeception.yml` configuration file, from the directory where `c3.php` file is located. This is needed
- so that `Codeception` can create itself instance and collect code coverage correctly.
-
-By default `c3_url` and `remote_config` setup correctly, you only need to copy and include `c3.php` file in your `index-test.php`
-
-After that you should be able to collect code coverage from tests that run through `PhpBrowser` or `WebDriver` with same command
-as for other tests:
-
-```
-#collect coverage from remote
-codecept run acceptance --coverage-html --coverage-xml
-```
-
-Please refer to [Codeception tutorial](http://codeception.com/docs/01-Introduction) for
-more details about writing and running acceptance, functional and unit tests.
diff --git a/tests/_bootstrap.php b/tests/_bootstrap.php
new file mode 100644
index 000000000..4b8424f73
--- /dev/null
+++ b/tests/_bootstrap.php
@@ -0,0 +1,7 @@
+amOnPage(Url::toRoute('/site/about'));
+ $I->see('About', 'h1');
+ }
+}
diff --git a/tests/acceptance/ContactCest.php b/tests/acceptance/ContactCest.php
new file mode 100644
index 000000000..ac74fdb1f
--- /dev/null
+++ b/tests/acceptance/ContactCest.php
@@ -0,0 +1,34 @@
+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.');
+ }
+}
diff --git a/tests/acceptance/HomeCest.php b/tests/acceptance/HomeCest.php
new file mode 100644
index 000000000..98fb6b9f7
--- /dev/null
+++ b/tests/acceptance/HomeCest.php
@@ -0,0 +1,18 @@
+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.');
+ }
+}
diff --git a/tests/acceptance/LoginCest.php b/tests/acceptance/LoginCest.php
new file mode 100644
index 000000000..6f5cb2f38
--- /dev/null
+++ b/tests/acceptance/LoginCest.php
@@ -0,0 +1,21 @@
+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');
+ }
+}
diff --git a/tests/acceptance/_bootstrap.php b/tests/acceptance/_bootstrap.php
new file mode 100644
index 000000000..b3d9bbc7f
--- /dev/null
+++ b/tests/acceptance/_bootstrap.php
@@ -0,0 +1 @@
+ [
+ 'db' => require __DIR__ . '/../../config/test_db.php'
+ ]
+ ]
+);
+
+
+$application = new yii\console\Application($config);
+$exitCode = $application->run();
+exit($exitCode);
diff --git a/tests/codeception/bin/yii.bat b/tests/bin/yii.bat
similarity index 82%
rename from tests/codeception/bin/yii.bat
rename to tests/bin/yii.bat
index d516b3a19..ce14c92bc 100644
--- a/tests/codeception/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
-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/codeception.yml b/tests/codeception.yml
deleted file mode 100644
index b71ba31c1..000000000
--- a/tests/codeception.yml
+++ /dev/null
@@ -1,36 +0,0 @@
-actor: Tester
-#coverage:
-# #c3_url: http://localhost:8080/index-test.php/
-# enabled: true
-# #remote: true
-# #remote_config: '../tests/codeception.yml'
-# white_list:
-# include:
-# - ../models/*
-# - ../controllers/*
-# - ../commands/*
-# - ../mail/*
-# blacklist:
-# include:
-# - ../assets/*
-# - ../config/*
-# - ../runtime/*
-# - ../vendor/*
-# - ../views/*
-# - ../web/*
-# - ../tests/*
-paths:
- tests: codeception
- log: codeception/_output
- data: codeception/_data
- helpers: codeception/_support
-settings:
- bootstrap: _bootstrap.php
- suite_class: \PHPUnit_Framework_TestSuite
- memory_limit: 1024M
- log: true
- colors: true
-config:
- # the entry script URL (with host info) for functional and acceptance tests
- # PLEASE ADJUST IT TO THE ACTUAL ENTRY SCRIPT URL
- test_entry_url: http://localhost:8080/index-test.php
\ No newline at end of file
diff --git a/tests/codeception/.gitignore b/tests/codeception/.gitignore
deleted file mode 100644
index 985dbb423..000000000
--- a/tests/codeception/.gitignore
+++ /dev/null
@@ -1,4 +0,0 @@
-# these files are auto generated by codeception build
-/unit/UnitTester.php
-/functional/FunctionalTester.php
-/acceptance/AcceptanceTester.php
diff --git a/tests/codeception/_bootstrap.php b/tests/codeception/_bootstrap.php
deleted file mode 100644
index 755029ef3..000000000
--- a/tests/codeception/_bootstrap.php
+++ /dev/null
@@ -1,16 +0,0 @@
- $value) {
- $inputType = $field === 'body' ? 'textarea' : 'input';
- $this->actor->fillField($inputType . '[name="ContactForm[' . $field . ']"]', $value);
- }
- $this->actor->click('contact-button');
- }
-}
diff --git a/tests/codeception/_pages/LoginPage.php b/tests/codeception/_pages/LoginPage.php
deleted file mode 100644
index c3a2ef2dd..000000000
--- a/tests/codeception/_pages/LoginPage.php
+++ /dev/null
@@ -1,25 +0,0 @@
-actor->fillField('input[name="LoginForm[username]"]', $username);
- $this->actor->fillField('input[name="LoginForm[password]"]', $password);
- $this->actor->click('login-button');
- }
-}
diff --git a/tests/codeception/acceptance.suite.yml b/tests/codeception/acceptance.suite.yml
deleted file mode 100644
index 1781b0077..000000000
--- a/tests/codeception/acceptance.suite.yml
+++ /dev/null
@@ -1,27 +0,0 @@
-# Codeception Test Suite Configuration
-
-# suite for acceptance tests.
-# perform tests in browser using the Selenium-like tools.
-# powered by Mink (http://mink.behat.org).
-# (tip: that's what your customer will see).
-# (tip: test your ajax and javascript by one of Mink drivers).
-
-# RUN `build` COMMAND AFTER ADDING/REMOVING MODULES.
-
-class_name: AcceptanceTester
-modules:
- enabled:
- - PhpBrowser
-# you can use WebDriver instead of PhpBrowser to test javascript and ajax.
-# This will require you to install selenium. See http://codeception.com/docs/04-AcceptanceTests#Selenium
-# "restart" option is used by the WebDriver to start each time per test-file new session and cookies,
-# it is useful if you want to login in your app in each test.
-# - WebDriver
- config:
- PhpBrowser:
-# PLEASE ADJUST IT TO THE ACTUAL ENTRY POINT WITHOUT PATH INFO
- url: http://localhost:8080
-# WebDriver:
-# url: http://localhost:8080
-# browser: firefox
-# restart: true
diff --git a/tests/codeception/acceptance/AboutCept.php b/tests/codeception/acceptance/AboutCept.php
deleted file mode 100644
index 88a550698..000000000
--- a/tests/codeception/acceptance/AboutCept.php
+++ /dev/null
@@ -1,10 +0,0 @@
-wantTo('ensure that about works');
-AboutPage::openBy($I);
-$I->see('About', 'h1');
diff --git a/tests/codeception/acceptance/ContactCept.php b/tests/codeception/acceptance/ContactCept.php
deleted file mode 100644
index b51d1c085..000000000
--- a/tests/codeception/acceptance/ContactCept.php
+++ /dev/null
@@ -1,57 +0,0 @@
-wantTo('ensure that contact works');
-
-$contactPage = ContactPage::openBy($I);
-
-$I->see('Contact', 'h1');
-
-$I->amGoingTo('submit contact form with no data');
-$contactPage->submit([]);
-if (method_exists($I, 'wait')) {
- $I->wait(3); // only for selenium
-}
-$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');
-
-$I->amGoingTo('submit contact form with not correct email');
-$contactPage->submit([
- 'name' => 'tester',
- 'email' => 'tester.email',
- 'subject' => 'test subject',
- 'body' => 'test content',
- 'verifyCode' => 'testme',
-]);
-if (method_exists($I, 'wait')) {
- $I->wait(3); // only for selenium
-}
-$I->expectTo('see that email adress 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');
-
-$I->amGoingTo('submit contact form with correct data');
-$contactPage->submit([
- 'name' => 'tester',
- 'email' => 'tester@example.com',
- 'subject' => 'test subject',
- 'body' => 'test content',
- 'verifyCode' => 'testme',
-]);
-if (method_exists($I, 'wait')) {
- $I->wait(3); // only for selenium
-}
-$I->dontSeeElement('#contact-form');
-$I->see('Thank you for contacting us. We will respond to you as soon as possible.');
diff --git a/tests/codeception/acceptance/HomeCept.php b/tests/codeception/acceptance/HomeCept.php
deleted file mode 100644
index 1f9353595..000000000
--- a/tests/codeception/acceptance/HomeCept.php
+++ /dev/null
@@ -1,11 +0,0 @@
-wantTo('ensure that home page works');
-$I->amOnPage(Yii::$app->homeUrl);
-$I->see('My Company');
-$I->seeLink('About');
-$I->click('About');
-$I->see('This is the About page.');
diff --git a/tests/codeception/acceptance/LoginCept.php b/tests/codeception/acceptance/LoginCept.php
deleted file mode 100644
index 90d063583..000000000
--- a/tests/codeception/acceptance/LoginCept.php
+++ /dev/null
@@ -1,37 +0,0 @@
-wantTo('ensure that login works');
-
-$loginPage = LoginPage::openBy($I);
-
-$I->see('Login', 'h1');
-
-$I->amGoingTo('try to login with empty credentials');
-$loginPage->login('', '');
-if (method_exists($I, 'wait')) {
- $I->wait(3); // only for selenium
-}
-$I->expectTo('see validations errors');
-$I->see('Username cannot be blank.');
-$I->see('Password cannot be blank.');
-
-$I->amGoingTo('try to login with wrong credentials');
-$loginPage->login('admin', 'wrong');
-if (method_exists($I, 'wait')) {
- $I->wait(3); // only for selenium
-}
-$I->expectTo('see validations errors');
-$I->see('Incorrect username or password.');
-
-$I->amGoingTo('try to login with correct credentials');
-$loginPage->login('admin', 'admin');
-if (method_exists($I, 'wait')) {
- $I->wait(3); // only for selenium
-}
-$I->expectTo('see user info');
-$I->see('Logout (admin)');
diff --git a/tests/codeception/acceptance/_bootstrap.php b/tests/codeception/acceptance/_bootstrap.php
deleted file mode 100644
index 36f9f1d11..000000000
--- a/tests/codeception/acceptance/_bootstrap.php
+++ /dev/null
@@ -1,2 +0,0 @@
-run();
-exit($exitCode);
diff --git a/tests/codeception/config/acceptance.php b/tests/codeception/config/acceptance.php
deleted file mode 100644
index c688575b6..000000000
--- a/tests/codeception/config/acceptance.php
+++ /dev/null
@@ -1,11 +0,0 @@
- [
- 'fixture' => [
- 'class' => 'yii\faker\FixtureController',
- 'fixtureDataPath' => '@tests/codeception/fixtures',
- 'templatePath' => '@tests/codeception/templates',
- 'namespace' => 'tests\codeception\fixtures',
- ],
- ],
- 'components' => [
- 'db' => [
- 'dsn' => 'mysql:host=localhost;dbname=yii2_basic_tests',
- ],
- 'mailer' => [
- 'useFileTransport' => true,
- ],
- 'urlManager' => [
- 'showScriptName' => true,
- ],
- ],
-];
diff --git a/tests/codeception/config/functional.php b/tests/codeception/config/functional.php
deleted file mode 100644
index 6d22bd97b..000000000
--- a/tests/codeception/config/functional.php
+++ /dev/null
@@ -1,25 +0,0 @@
- [
- 'request' => [
- // it's not recommended to run functional tests with CSRF validation enabled
- 'enableCsrfValidation' => false,
- // but if you absolutely need it set cookie domain to localhost
- /*
- 'csrfCookie' => [
- 'domain' => 'localhost',
- ],
- */
- ],
- ],
- ]
-);
diff --git a/tests/codeception/config/unit.php b/tests/codeception/config/unit.php
deleted file mode 100644
index 5bab5eaec..000000000
--- a/tests/codeception/config/unit.php
+++ /dev/null
@@ -1,11 +0,0 @@
-wantTo('ensure that about works');
-AboutPage::openBy($I);
-$I->see('About', 'h1');
diff --git a/tests/codeception/functional/ContactCept.php b/tests/codeception/functional/ContactCept.php
deleted file mode 100644
index 074820a70..000000000
--- a/tests/codeception/functional/ContactCept.php
+++ /dev/null
@@ -1,48 +0,0 @@
-wantTo('ensure that contact works');
-
-$contactPage = ContactPage::openBy($I);
-
-$I->see('Contact', 'h1');
-
-$I->amGoingTo('submit contact form with no data');
-$contactPage->submit([]);
-$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');
-
-$I->amGoingTo('submit contact form with not correct email');
-$contactPage->submit([
- 'name' => 'tester',
- 'email' => 'tester.email',
- 'subject' => 'test subject',
- 'body' => 'test content',
- 'verifyCode' => 'testme',
-]);
-$I->expectTo('see that email adress 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');
-
-$I->amGoingTo('submit contact form with correct data');
-$contactPage->submit([
- 'name' => 'tester',
- 'email' => 'tester@example.com',
- 'subject' => 'test subject',
- 'body' => 'test content',
- 'verifyCode' => 'testme',
-]);
-$I->dontSeeElement('#contact-form');
-$I->see('Thank you for contacting us. We will respond to you as soon as possible.');
diff --git a/tests/codeception/functional/HomeCept.php b/tests/codeception/functional/HomeCept.php
deleted file mode 100644
index 94efb09cd..000000000
--- a/tests/codeception/functional/HomeCept.php
+++ /dev/null
@@ -1,11 +0,0 @@
-wantTo('ensure that home page works');
-$I->amOnPage(Yii::$app->homeUrl);
-$I->see('My Company');
-$I->seeLink('About');
-$I->click('About');
-$I->see('This is the About page.');
diff --git a/tests/codeception/functional/LoginCept.php b/tests/codeception/functional/LoginCept.php
deleted file mode 100644
index 66e760989..000000000
--- a/tests/codeception/functional/LoginCept.php
+++ /dev/null
@@ -1,28 +0,0 @@
-wantTo('ensure that login works');
-
-$loginPage = LoginPage::openBy($I);
-
-$I->see('Login', 'h1');
-
-$I->amGoingTo('try to login with empty credentials');
-$loginPage->login('', '');
-$I->expectTo('see validations errors');
-$I->see('Username cannot be blank.');
-$I->see('Password cannot be blank.');
-
-$I->amGoingTo('try to login with wrong credentials');
-$loginPage->login('admin', 'wrong');
-$I->expectTo('see validations errors');
-$I->see('Incorrect username or password.');
-
-$I->amGoingTo('try to login with correct credentials');
-$loginPage->login('admin', 'admin');
-$I->expectTo('see user info');
-$I->see('Logout (admin)');
diff --git a/tests/codeception/functional/_bootstrap.php b/tests/codeception/functional/_bootstrap.php
deleted file mode 100644
index 8aac0915b..000000000
--- a/tests/codeception/functional/_bootstrap.php
+++ /dev/null
@@ -1,2 +0,0 @@
-mailer->fileTransportCallback = function ($mailer, $message) {
- return 'testing_message.eml';
- };
- }
-
- protected function tearDown()
- {
- unlink($this->getMessageFile());
- parent::tearDown();
- }
-
- public function testContact()
- {
- $model = $this->getMock('app\models\ContactForm', ['validate']);
- $model->expects($this->once())->method('validate')->will($this->returnValue(true));
-
- $model->attributes = [
- 'name' => 'Tester',
- 'email' => 'tester@example.com',
- 'subject' => 'very important letter subject',
- 'body' => 'body of current message',
- ];
-
- $model->contact('admin@example.com');
-
- $this->specify('email should be send', function () {
- expect('email file should exist', file_exists($this->getMessageFile()))->true();
- });
-
- $this->specify('message should contain correct data', function () use ($model) {
- $emailMessage = file_get_contents($this->getMessageFile());
-
- expect('email should contain user name', $emailMessage)->contains($model->name);
- expect('email should contain sender email', $emailMessage)->contains($model->email);
- expect('email should contain subject', $emailMessage)->contains($model->subject);
- expect('email should contain body', $emailMessage)->contains($model->body);
- });
- }
-
- private function getMessageFile()
- {
- return Yii::getAlias(Yii::$app->mailer->fileTransportPath) . '/testing_message.eml';
- }
-
-}
diff --git a/tests/codeception/unit/models/LoginFormTest.php b/tests/codeception/unit/models/LoginFormTest.php
deleted file mode 100644
index c7f971a06..000000000
--- a/tests/codeception/unit/models/LoginFormTest.php
+++ /dev/null
@@ -1,61 +0,0 @@
-user->logout();
- parent::tearDown();
- }
-
- public function testLoginNoUser()
- {
- $model = new LoginForm([
- 'username' => 'not_existing_username',
- 'password' => 'not_existing_password',
- ]);
-
- $this->specify('user should not be able to login, when there is no identity', function () use ($model) {
- expect('model should not login user', $model->login())->false();
- expect('user should not be logged in', Yii::$app->user->isGuest)->true();
- });
- }
-
- public function testLoginWrongPassword()
- {
- $model = new LoginForm([
- 'username' => 'demo',
- 'password' => 'wrong_password',
- ]);
-
- $this->specify('user should not be able to login with wrong password', function () use ($model) {
- expect('model should not login user', $model->login())->false();
- expect('error message should be set', $model->errors)->hasKey('password');
- expect('user should not be logged in', Yii::$app->user->isGuest)->true();
- });
- }
-
- public function testLoginCorrect()
- {
- $model = new LoginForm([
- 'username' => 'demo',
- 'password' => 'demo',
- ]);
-
- $this->specify('user should be able to login with correct credentials', function () use ($model) {
- expect('model should login user', $model->login())->true();
- expect('error message should not be set', $model->errors)->hasntKey('password');
- expect('user should be logged in', Yii::$app->user->isGuest)->false();
- });
- }
-
-}
diff --git a/tests/codeception/unit/models/UserTest.php b/tests/codeception/unit/models/UserTest.php
deleted file mode 100644
index f4f4f4bb9..000000000
--- a/tests/codeception/unit/models/UserTest.php
+++ /dev/null
@@ -1,17 +0,0 @@
-loadFixtures(['user']);
- }
-
- // TODO add test methods here
-}
diff --git a/tests/codeception/unit/templates/fixtures/.gitkeep b/tests/codeception/unit/templates/fixtures/.gitkeep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/tests/codeception/functional.suite.yml b/tests/functional.suite.yml
similarity index 73%
rename from tests/codeception/functional.suite.yml
rename to tests/functional.suite.yml
index ad7164cb6..9d8cf149c 100644
--- a/tests/codeception/functional.suite.yml
+++ b/tests/functional.suite.yml
@@ -6,11 +6,9 @@
# RUN `build` COMMAND AFTER ADDING/REMOVING MODULES.
#basic/web/index.php
-class_name: FunctionalTester
+actor: FunctionalTester
modules:
enabled:
- Filesystem
- Yii2
- config:
- Yii2:
- configFile: 'codeception/config/functional.php'
+ - Asserts
diff --git a/tests/functional/ContactFormCest.php b/tests/functional/ContactFormCest.php
new file mode 100644
index 000000000..f1ccaf18f
--- /dev/null
+++ b/tests/functional/ContactFormCest.php
@@ -0,0 +1,57 @@
+amOnRoute('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.');
+ }
+}
diff --git a/tests/functional/LoginFormCest.php b/tests/functional/LoginFormCest.php
new file mode 100644
index 000000000..a1545a706
--- /dev/null
+++ b/tests/functional/LoginFormCest.php
@@ -0,0 +1,58 @@
+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');
+ }
+}
diff --git a/tests/functional/_bootstrap.php b/tests/functional/_bootstrap.php
new file mode 100644
index 000000000..b3d9bbc7f
--- /dev/null
+++ b/tests/functional/_bootstrap.php
@@ -0,0 +1 @@
+attributes = [
+ 'name' => 'Tester',
+ 'email' => 'tester@example.com',
+ 'subject' => 'very important letter subject',
+ 'body' => 'body of current message',
+ 'verifyCode' => 'testme',
+ ];
+
+ 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();
+ 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
new file mode 100644
index 000000000..6f96c2377
--- /dev/null
+++ b/tests/unit/models/LoginFormTest.php
@@ -0,0 +1,50 @@
+user->logout();
+ }
+
+ public function testLoginNoUser()
+ {
+ $this->model = new LoginForm([
+ 'username' => 'not_existing_username',
+ 'password' => 'not_existing_password',
+ ]);
+
+ verify($this->model->login())->false();
+ verify(\Yii::$app->user->isGuest)->true();
+ }
+
+ public function testLoginWrongPassword()
+ {
+ $this->model = new LoginForm([
+ 'username' => 'demo',
+ 'password' => 'wrong_password',
+ ]);
+
+ verify($this->model->login())->false();
+ verify(\Yii::$app->user->isGuest)->true();
+ verify($this->model->errors)->arrayHasKey('password');
+ }
+
+ public function testLoginCorrect()
+ {
+ $this->model = new LoginForm([
+ 'username' => 'demo',
+ 'password' => 'demo',
+ ]);
+
+ 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
new file mode 100644
index 000000000..3db9772a3
--- /dev/null
+++ b/tests/unit/models/UserTest.php
@@ -0,0 +1,43 @@
+notEmpty();
+ verify($user->username)->equals('admin');
+
+ verify(User::findIdentity(999))->empty();
+ }
+
+ public function testFindUserByAccessToken()
+ {
+ verify($user = User::findIdentityByAccessToken('100-token'))->notEmpty();
+ verify($user->username)->equals('admin');
+
+ verify(User::findIdentityByAccessToken('non-existing'))->empty();
+ }
+
+ public function testFindUserByUsername()
+ {
+ verify($user = User::findByUsername('admin'))->notEmpty();
+ verify(User::findByUsername('not-admin'))->empty();
+ }
+
+ /**
+ * @depends testFindUserByUsername
+ */
+ public function testValidateUser()
+ {
+ $user = User::findByUsername('admin');
+ verify($user->validateAuthKey('test100key'))->notEmpty();
+ verify($user->validateAuthKey('test102key'))->empty();
+
+ 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..af2db5d21
--- /dev/null
+++ b/tests/unit/widgets/AlertTest.php
@@ -0,0 +1,263 @@
+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/config/.gitignore b/vagrant/config/.gitignore
new file mode 100644
index 000000000..0685a56ca
--- /dev/null
+++ b/vagrant/config/.gitignore
@@ -0,0 +1,2 @@
+# local configuration
+vagrant-local.yml
\ No newline at end of file
diff --git a/vagrant/config/vagrant-local.example.yml b/vagrant/config/vagrant-local.example.yml
new file mode 100644
index 000000000..919aec0e8
--- /dev/null
+++ b/vagrant/config/vagrant-local.example.yml
@@ -0,0 +1,22 @@
+# Your personal GitHub token
+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
diff --git a/vagrant/nginx/app.conf b/vagrant/nginx/app.conf
new file mode 100644
index 000000000..1bfc0d583
--- /dev/null
+++ b/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;
+ }
+}
diff --git a/vagrant/nginx/log/.gitignore b/vagrant/nginx/log/.gitignore
new file mode 100644
index 000000000..c0fbfdd8d
--- /dev/null
+++ b/vagrant/nginx/log/.gitignore
@@ -0,0 +1,3 @@
+#nginx logs
+yii2basic.access.log
+yii2basic.error.log
\ No newline at end of file
diff --git a/vagrant/provision/always-as-root.sh b/vagrant/provision/always-as-root.sh
new file mode 100644
index 000000000..17fb58421
--- /dev/null
+++ b/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
diff --git a/vagrant/provision/once-as-root.sh b/vagrant/provision/once-as-root.sh
new file mode 100644
index 000000000..1d2917016
--- /dev/null
+++ b/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 "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
diff --git a/vagrant/provision/once-as-vagrant.sh b/vagrant/provision/once-as-vagrant.sh
new file mode 100644
index 000000000..8da47217c
--- /dev/null
+++ b/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
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
+# @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 d5bba3a04..c204435c0 100644
--- a/views/layouts/main.php
+++ b/views/layouts/main.php
@@ -1,67 +1,81 @@
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')]);
?>
beginPage() ?>
-
+
-
-
- = Html::csrfMetaTags() ?>
= Html::encode($this->title) ?>
head() ?>
-
-
+
beginBody() ?>
-
- 'My Company',
- 'brandUrl' => Yii::$app->homeUrl,
- 'options' => [
- 'class' => 'navbar-inverse navbar-fixed-top',
- ],
- ]);
- echo Nav::widget([
- 'options' => ['class' => 'navbar-nav navbar-right'],
- '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']] :
- ['label' => 'Logout (' . Yii::$app->user->identity->username . ')',
- 'url' => ['/site/logout'],
- 'linkOptions' => ['data-method' => 'post']],
- ],
- ]);
- NavBar::end();
- ?>
-
- = Breadcrumbs::widget([
- 'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [],
- ]) ?>
- = $content ?>
-
+
+
+
+
+ params['breadcrumbs'])): ?>
+ = Breadcrumbs::widget(['links' => $this->params['breadcrumbs']]) ?>
+
+ = Alert::widget() ?>
+ = $content ?>
+
-