initial commit
This commit is contained in:
commit
a3189eb4a1
33
.gitignore
vendored
Normal file
33
.gitignore
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
# phpstorm project files
|
||||
.idea
|
||||
|
||||
# netbeans project files
|
||||
nbproject
|
||||
|
||||
# zend studio for eclipse project files
|
||||
.buildpath
|
||||
.project
|
||||
.settings
|
||||
|
||||
# windows thumbnail cache
|
||||
Thumbs.db
|
||||
|
||||
# composer vendor dir
|
||||
/vendor
|
||||
|
||||
# composer itself is not needed
|
||||
composer.phar
|
||||
|
||||
# Mac DS_Store Files
|
||||
.DS_Store
|
||||
|
||||
# phpunit itself is not needed
|
||||
phpunit.phar
|
||||
# local phpunit config
|
||||
/phpunit.xml
|
||||
|
||||
tests/_output/*
|
||||
tests/_support/_generated
|
||||
|
||||
#vagrant folder
|
||||
/.vagrant
|
29
LICENSE.md
Normal file
29
LICENSE.md
Normal file
@ -0,0 +1,29 @@
|
||||
Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com)
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Yii Software LLC nor the names of its
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
233
README.md
Normal file
233
README.md
Normal file
@ -0,0 +1,233 @@
|
||||
<p align="center">
|
||||
<a href="https://github.com/yiisoft" target="_blank">
|
||||
<img src="https://avatars0.githubusercontent.com/u/993323" height="100px">
|
||||
</a>
|
||||
<h1 align="center">Yii 2 Basic Project Template</h1>
|
||||
<br>
|
||||
</p>
|
||||
|
||||
Yii 2 Basic Project Template is a skeleton [Yii 2](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
|
||||
-------------------
|
||||
|
||||
assets/ contains assets definition
|
||||
commands/ contains console commands (controllers)
|
||||
config/ contains application configurations
|
||||
controllers/ contains Web controller classes
|
||||
mail/ contains view files for e-mails
|
||||
models/ contains model classes
|
||||
runtime/ contains files generated during runtime
|
||||
tests/ contains various tests for the basic application
|
||||
vendor/ contains dependent 3rd-party packages
|
||||
views/ contains view files for the Web application
|
||||
web/ contains the entry script and Web resources
|
||||
|
||||
|
||||
|
||||
REQUIREMENTS
|
||||
------------
|
||||
|
||||
The minimum requirement by this project template that your Web server supports PHP 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](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' => '<secret random string goes here>',
|
||||
],
|
||||
```
|
||||
|
||||
You can then access the application through the following URL:
|
||||
|
||||
~~~
|
||||
http://localhost/basic/web/
|
||||
~~~
|
||||
|
||||
|
||||
### Install with Docker
|
||||
|
||||
Update your vendor packages
|
||||
|
||||
docker-compose run --rm php composer update --prefer-dist
|
||||
|
||||
Run the installation triggers (creating cookie validation code)
|
||||
|
||||
docker-compose run --rm php composer install
|
||||
|
||||
Start the container
|
||||
|
||||
docker-compose up -d
|
||||
|
||||
You can then access the application through the following URL:
|
||||
|
||||
http://127.0.0.1:8000
|
||||
|
||||
**NOTES:**
|
||||
- Minimum required Docker engine version `17.04` for development (see [Performance tuning for volume mounts](https://docs.docker.com/docker-for-mac/osxfs-caching/))
|
||||
- The default configuration uses a host-volume in your home directory `.docker-composer` for composer caches
|
||||
|
||||
|
||||
CONFIGURATION
|
||||
-------------
|
||||
|
||||
### Database
|
||||
|
||||
Edit the file `config/db.php` with real data, for example:
|
||||
|
||||
```php
|
||||
return [
|
||||
'class' => 'yii\db\Connection',
|
||||
'dsn' => 'mysql:host=localhost;dbname=yii2basic',
|
||||
'username' => 'root',
|
||||
'password' => '1234',
|
||||
'charset' => 'utf8',
|
||||
];
|
||||
```
|
||||
|
||||
**NOTES:**
|
||||
- Yii won't create the database for you, this has to be done manually before you can access it.
|
||||
- Check and edit the other files in the `config/` directory to customize your application as required.
|
||||
- Refer to the README in the `tests` directory for information specific to basic application tests.
|
||||
|
||||
|
||||
TESTING
|
||||
-------
|
||||
|
||||
Tests are located in `tests` directory. They are developed with [Codeception PHP Testing Framework](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
|
||||
```
|
||||
|
||||
You can see code coverage output under the `tests/_output` directory.
|
41
TASK.md
Normal file
41
TASK.md
Normal file
@ -0,0 +1,41 @@
|
||||
# Описание задачи: Реализация списка задач с возможностью изменения статуса выполнения
|
||||
|
||||
## Цель
|
||||
|
||||
Создать веб-интерфейс для отображения списка задач с возможностью помечать задачи как выполненные или невыполненные. Изменения статуса задачи должны отправляться на сервер асинхронно (AJAX) и сохраняться в кеше.
|
||||
|
||||
## Основные требования
|
||||
|
||||
> К редактированию доступны только файлы `models/Task.php` и `views/task/index.php`.
|
||||
|
||||
- Задача содержит поля:
|
||||
- `id` — уникальный идентификатор задачи.
|
||||
- `order` — порядок сортировки.
|
||||
- `title` — название задачи.
|
||||
- `checked` — булево значение, указывающее, выполнена задача или нет.
|
||||
- Изначальные данные хранятся в константе `INIT_DATA`.
|
||||
- Данные загружаются и сохраняются в кеш приложения (`Yii::$app->cache`).
|
||||
- Необходимо реализовать методы:
|
||||
- `search($params)`
|
||||
- `save()` — сохраняет текущий объект задачи в кеш.
|
||||
- `findOne($id)` — возвращает задачу по идентификатору.
|
||||
- Валидация поля `checked` как булевого значения.
|
||||
- Определены метки атрибутов для отображения в интерфейсе.
|
||||
|
||||
## Представление (`index.php`)
|
||||
|
||||
- Отображает список задач в виде таблицы с помощью `GridView`.
|
||||
- Колонки таблицы:
|
||||
- `id` — идентификатор задачи.
|
||||
- `title` — название задачи.
|
||||
- `checked` — чекбокс, отражающий статус выполнения задачи.
|
||||
- При изменении состояния чекбокса отправляется AJAX POST-запрос на `\app\controllers\TaskController::actionSetState`.
|
||||
- В случае ошибки запроса состояние чекбокса восстанавливается.
|
||||
|
||||
## Технические детали
|
||||
|
||||
- Данные задач не хранятся в базе, а кэшируются в памяти приложения.
|
||||
- Сортировка задач происходит по полю `order` в порядке возрастания.
|
||||
- AJAX-запросы позволяют обновлять статус задачи без перезагрузки страницы.
|
||||
- Валидация и сохранение данных реализованы в модели `Task`.
|
||||
- Представление использует стандартные компоненты Yii2 (`GridView`, `ArrayDataProvider`).
|
31
assets/AppAsset.php
Normal file
31
assets/AppAsset.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
/**
|
||||
* @link https://www.yiiframework.com/
|
||||
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||
* @license https://www.yiiframework.com/license/
|
||||
*/
|
||||
|
||||
namespace app\assets;
|
||||
|
||||
use yii\web\AssetBundle;
|
||||
|
||||
/**
|
||||
* Main application asset bundle.
|
||||
*
|
||||
* @author Qiang Xue <qiang.xue@gmail.com>
|
||||
* @since 2.0
|
||||
*/
|
||||
class AppAsset extends AssetBundle
|
||||
{
|
||||
public $basePath = '@webroot';
|
||||
public $baseUrl = '@web';
|
||||
public $css = [
|
||||
'css/site.css',
|
||||
];
|
||||
public $js = [
|
||||
];
|
||||
public $depends = [
|
||||
'yii\web\YiiAsset',
|
||||
'yii\bootstrap5\BootstrapAsset'
|
||||
];
|
||||
}
|
27
codeception.yml
Normal file
27
codeception.yml
Normal file
@ -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/*
|
34
commands/HelloController.php
Normal file
34
commands/HelloController.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
/**
|
||||
* @link https://www.yiiframework.com/
|
||||
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||
* @license https://www.yiiframework.com/license/
|
||||
*/
|
||||
|
||||
namespace app\commands;
|
||||
|
||||
use yii\console\Controller;
|
||||
use yii\console\ExitCode;
|
||||
|
||||
/**
|
||||
* This command echoes the first argument that you have entered.
|
||||
*
|
||||
* This command is provided as an example for you to learn how to create console commands.
|
||||
*
|
||||
* @author Qiang Xue <qiang.xue@gmail.com>
|
||||
* @since 2.0
|
||||
*/
|
||||
class HelloController extends Controller
|
||||
{
|
||||
/**
|
||||
* This command echoes what you have entered as the message.
|
||||
* @param string $message the message to be echoed.
|
||||
* @return int Exit code
|
||||
*/
|
||||
public function actionIndex($message = 'hello world')
|
||||
{
|
||||
echo $message . "\n";
|
||||
|
||||
return ExitCode::OK;
|
||||
}
|
||||
}
|
75
composer.json
Normal file
75
composer.json
Normal file
@ -0,0 +1,75 @@
|
||||
{
|
||||
"name": "yiisoft/yii2-app-basic",
|
||||
"description": "Yii 2 Basic Project Template",
|
||||
"keywords": ["yii2", "framework", "basic", "project template"],
|
||||
"homepage": "https://www.yiiframework.com/",
|
||||
"type": "project",
|
||||
"license": "BSD-3-Clause",
|
||||
"support": {
|
||||
"issues": "https://github.com/yiisoft/yii2/issues?state=open",
|
||||
"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": "stable",
|
||||
"require": {
|
||||
"php": ">=8.2.0",
|
||||
"yiisoft/yii2": "~2.0.45",
|
||||
"yiisoft/yii2-bootstrap5": "~2.0.2",
|
||||
"yiisoft/yii2-symfonymailer": "~2.0.3",
|
||||
"yiisoft/yii2-httpclient": "^2.0",
|
||||
"ext-zip": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"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": {
|
||||
"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::postInstall"
|
||||
]
|
||||
},
|
||||
"extra": {
|
||||
"yii\\composer\\Installer::postCreateProject": {
|
||||
"setPermission": [
|
||||
{
|
||||
"runtime": "0777",
|
||||
"web/assets": "0777",
|
||||
"yii": "0755"
|
||||
}
|
||||
]
|
||||
},
|
||||
"yii\\composer\\Installer::postInstall": {
|
||||
"generateCookieValidationKey": [
|
||||
"config/web.php"
|
||||
]
|
||||
}
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"type": "composer",
|
||||
"url": "https://asset-packagist.org"
|
||||
}
|
||||
]
|
||||
}
|
5602
composer.lock
generated
Normal file
5602
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
33
config/__autocomplete.php
Normal file
33
config/__autocomplete.php
Normal file
@ -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 {
|
||||
}
|
56
config/console.php
Normal file
56
config/console.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
$params = require __DIR__ . '/params.php';
|
||||
$db = require __DIR__ . '/db.php';
|
||||
|
||||
$config = [
|
||||
'id' => 'basic-console',
|
||||
'basePath' => dirname(__DIR__),
|
||||
'bootstrap' => ['log'],
|
||||
'controllerNamespace' => 'app\commands',
|
||||
'aliases' => [
|
||||
'@bower' => '@vendor/bower-asset',
|
||||
'@npm' => '@vendor/npm-asset',
|
||||
'@tests' => '@app/tests',
|
||||
],
|
||||
'components' => [
|
||||
'cache' => [
|
||||
'class' => 'yii\caching\FileCache',
|
||||
],
|
||||
'log' => [
|
||||
'targets' => [
|
||||
[
|
||||
'class' => 'yii\log\FileTarget',
|
||||
'levels' => ['error', 'warning'],
|
||||
],
|
||||
],
|
||||
],
|
||||
'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;
|
14
config/db.php
Normal file
14
config/db.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'class' => 'yii\db\Connection',
|
||||
'dsn' => 'mysql:host=localhost;dbname=yii2basic',
|
||||
'username' => 'root',
|
||||
'password' => '',
|
||||
'charset' => 'utf8',
|
||||
|
||||
// Schema cache options (for production environment)
|
||||
//'enableSchemaCache' => true,
|
||||
//'schemaCacheDuration' => 60,
|
||||
//'schemaCache' => 'cache',
|
||||
];
|
7
config/params.php
Normal file
7
config/params.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'adminEmail' => 'admin@example.com',
|
||||
'senderEmail' => 'noreply@example.com',
|
||||
'senderName' => 'Example.com mailer',
|
||||
];
|
46
config/test.php
Normal file
46
config/test.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
$params = require __DIR__ . '/params.php';
|
||||
$db = require __DIR__ . '/test_db.php';
|
||||
|
||||
/**
|
||||
* Application configuration shared by all test types
|
||||
*/
|
||||
return [
|
||||
'id' => 'basic-tests',
|
||||
'basePath' => dirname(__DIR__),
|
||||
'aliases' => [
|
||||
'@bower' => '@vendor/bower-asset',
|
||||
'@npm' => '@vendor/npm-asset',
|
||||
],
|
||||
'language' => 'en-US',
|
||||
'components' => [
|
||||
'db' => $db,
|
||||
'mailer' => [
|
||||
'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,
|
||||
];
|
6
config/test_db.php
Normal file
6
config/test_db.php
Normal file
@ -0,0 +1,6 @@
|
||||
<?php
|
||||
$db = require __DIR__ . '/db.php';
|
||||
// test database! Important not to run tests on production or development databases
|
||||
$db['dsn'] = 'mysql:host=localhost;dbname=yii2basic_test';
|
||||
|
||||
return $db;
|
73
config/web.php
Normal file
73
config/web.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
$params = require __DIR__ . '/params.php';
|
||||
$db = require __DIR__ . '/db.php';
|
||||
|
||||
$config = [
|
||||
'id' => 'basic',
|
||||
'basePath' => dirname(__DIR__),
|
||||
'bootstrap' => ['log'],
|
||||
'language' => 'ru-RU',
|
||||
'aliases' => [
|
||||
'@bower' => '@vendor/bower-asset',
|
||||
'@npm' => '@vendor/npm-asset',
|
||||
],
|
||||
'components' => [
|
||||
'request' => [
|
||||
// !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
|
||||
'cookieValidationKey' => 'Uu-5e7RXOqiaV9x21bVKx0Od8muNGBKh',
|
||||
],
|
||||
'cache' => [
|
||||
'class' => 'yii\caching\FileCache',
|
||||
],
|
||||
'user' => [
|
||||
'identityClass' => 'app\models\User',
|
||||
'enableAutoLogin' => true,
|
||||
],
|
||||
'errorHandler' => [
|
||||
'errorAction' => 'site/error',
|
||||
],
|
||||
'mailer' => [
|
||||
'class' => \yii\symfonymailer\Mailer::class,
|
||||
'viewPath' => '@app/mail',
|
||||
// send all mails to a file by default.
|
||||
'useFileTransport' => true,
|
||||
],
|
||||
'log' => [
|
||||
'traceLevel' => YII_DEBUG ? 3 : 0,
|
||||
'targets' => [
|
||||
[
|
||||
'class' => 'yii\log\FileTarget',
|
||||
'levels' => ['error', 'warning'],
|
||||
],
|
||||
],
|
||||
],
|
||||
'db' => $db,
|
||||
'urlManager' => [
|
||||
'enablePrettyUrl' => true,
|
||||
'showScriptName' => false,
|
||||
'rules' => [
|
||||
],
|
||||
],
|
||||
],
|
||||
'params' => $params,
|
||||
];
|
||||
|
||||
if (YII_ENV_DEV) {
|
||||
// configuration adjustments for 'dev' environment
|
||||
$config['bootstrap'][] = 'debug';
|
||||
$config['modules']['debug'] = [
|
||||
'class' => 'yii\debug\Module',
|
||||
// uncomment the following to add your IP if you are not connecting from localhost.
|
||||
//'allowedIPs' => ['127.0.0.1', '::1'],
|
||||
];
|
||||
|
||||
$config['bootstrap'][] = 'gii';
|
||||
$config['modules']['gii'] = [
|
||||
'class' => 'yii\gii\Module',
|
||||
// uncomment the following to add your IP if you are not connecting from localhost.
|
||||
//'allowedIPs' => ['127.0.0.1', '::1'],
|
||||
];
|
||||
}
|
||||
|
||||
return $config;
|
128
controllers/SiteController.php
Normal file
128
controllers/SiteController.php
Normal file
@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
namespace app\controllers;
|
||||
|
||||
use Yii;
|
||||
use yii\filters\AccessControl;
|
||||
use yii\web\Controller;
|
||||
use yii\web\Response;
|
||||
use yii\filters\VerbFilter;
|
||||
use app\models\LoginForm;
|
||||
use app\models\ContactForm;
|
||||
|
||||
class SiteController extends Controller
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function behaviors()
|
||||
{
|
||||
return [
|
||||
'access' => [
|
||||
'class' => AccessControl::class,
|
||||
'only' => ['logout'],
|
||||
'rules' => [
|
||||
[
|
||||
'actions' => ['logout'],
|
||||
'allow' => true,
|
||||
'roles' => ['@'],
|
||||
],
|
||||
],
|
||||
],
|
||||
'verbs' => [
|
||||
'class' => VerbFilter::class,
|
||||
'actions' => [
|
||||
'logout' => ['post'],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function actions()
|
||||
{
|
||||
return [
|
||||
'error' => [
|
||||
'class' => 'yii\web\ErrorAction',
|
||||
],
|
||||
'captcha' => [
|
||||
'class' => 'yii\captcha\CaptchaAction',
|
||||
'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays homepage.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function actionIndex()
|
||||
{
|
||||
return $this->render('index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Login action.
|
||||
*
|
||||
* @return Response|string
|
||||
*/
|
||||
public function actionLogin()
|
||||
{
|
||||
if (!Yii::$app->user->isGuest) {
|
||||
return $this->goHome();
|
||||
}
|
||||
|
||||
$model = new LoginForm();
|
||||
if ($model->load(Yii::$app->request->post()) && $model->login()) {
|
||||
return $this->goBack();
|
||||
}
|
||||
|
||||
$model->password = '';
|
||||
return $this->render('login', [
|
||||
'model' => $model,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout action.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function actionLogout()
|
||||
{
|
||||
Yii::$app->user->logout();
|
||||
|
||||
return $this->goHome();
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays contact page.
|
||||
*
|
||||
* @return Response|string
|
||||
*/
|
||||
public function actionContact()
|
||||
{
|
||||
$model = new ContactForm();
|
||||
if ($model->load(Yii::$app->request->post()) && $model->contact(Yii::$app->params['adminEmail'])) {
|
||||
Yii::$app->session->setFlash('contactFormSubmitted');
|
||||
|
||||
return $this->refresh();
|
||||
}
|
||||
return $this->render('contact', [
|
||||
'model' => $model,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays about page.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function actionAbout()
|
||||
{
|
||||
return $this->render('about');
|
||||
}
|
||||
}
|
32
controllers/SubmitController.php
Normal file
32
controllers/SubmitController.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace app\controllers;
|
||||
|
||||
use app\models\SubmitForm;
|
||||
use app\services\TaskSubmitService;
|
||||
use Yii;
|
||||
use yii\web\Controller;
|
||||
|
||||
class SubmitController extends Controller
|
||||
{
|
||||
public function actionIndex()
|
||||
{
|
||||
$model = new SubmitForm();
|
||||
$result = null;
|
||||
|
||||
if ($model->load(Yii::$app->request->post()) && $model->validate()) {
|
||||
try {
|
||||
$service = new TaskSubmitService($model->access_code);
|
||||
$result = $service->submit();
|
||||
} catch (\Exception $e) {
|
||||
Yii::$app->session->setFlash('error', 'Ошибка: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render('index', [
|
||||
'model' => $model,
|
||||
'result' => $result,
|
||||
]);
|
||||
}
|
||||
}
|
30
controllers/TaskController.php
Normal file
30
controllers/TaskController.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace app\controllers;
|
||||
|
||||
|
||||
use app\models\Task;
|
||||
use Yii;
|
||||
use yii\web\BadRequestHttpException;
|
||||
use yii\web\Controller;
|
||||
|
||||
class TaskController extends Controller
|
||||
{
|
||||
public function actionIndex()
|
||||
{
|
||||
$task = new Task();
|
||||
$dataProvider = $task->search(Yii::$app->request->queryParams);
|
||||
return $this->render('index', ['dataProvider' => $dataProvider]);
|
||||
}
|
||||
|
||||
public function actionSetState($id)
|
||||
{
|
||||
$model = Task::findOne($id);
|
||||
if($model && $model->load(Yii::$app->request->post(), '')) {
|
||||
$model->save();
|
||||
return true;
|
||||
}
|
||||
throw new BadRequestHttpException();
|
||||
}
|
||||
}
|
8
docker-compose.yml
Normal file
8
docker-compose.yml
Normal file
@ -0,0 +1,8 @@
|
||||
services:
|
||||
php:
|
||||
image: yiisoftware/yii2-php:8.2-fpm-nginx
|
||||
volumes:
|
||||
- ~/.composer-docker/cache:/root/.composer/cache:delegated
|
||||
- ./:/app:delegated
|
||||
ports:
|
||||
- '8000:80'
|
22
mail/layouts/html.php
Normal file
22
mail/layouts/html.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
use yii\helpers\Html;
|
||||
|
||||
/** @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">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=<?= Yii::$app->charset ?>" />
|
||||
<title><?= Html::encode($this->title) ?></title>
|
||||
<?php $this->head() ?>
|
||||
</head>
|
||||
<body>
|
||||
<?php $this->beginBody() ?>
|
||||
<?= $content ?>
|
||||
<?php $this->endBody() ?>
|
||||
</body>
|
||||
</html>
|
||||
<?php $this->endPage() ?>
|
13
mail/layouts/text.php
Normal file
13
mail/layouts/text.php
Normal file
@ -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();
|
65
models/ContactForm.php
Normal file
65
models/ContactForm.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace app\models;
|
||||
|
||||
use Yii;
|
||||
use yii\base\Model;
|
||||
|
||||
/**
|
||||
* ContactForm is the model behind the contact form.
|
||||
*/
|
||||
class ContactForm extends Model
|
||||
{
|
||||
public $name;
|
||||
public $email;
|
||||
public $subject;
|
||||
public $body;
|
||||
public $verifyCode;
|
||||
|
||||
|
||||
/**
|
||||
* @return array the validation rules.
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
// name, email, subject and body are required
|
||||
[['name', 'email', 'subject', 'body'], 'required'],
|
||||
// email has to be a valid email address
|
||||
['email', 'email'],
|
||||
// verifyCode needs to be entered correctly
|
||||
['verifyCode', 'captcha'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array customized attribute labels
|
||||
*/
|
||||
public function attributeLabels()
|
||||
{
|
||||
return [
|
||||
'verifyCode' => 'Verification Code',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an email to the specified email address using the information collected by this model.
|
||||
* @param string $email the target email address
|
||||
* @return bool whether the model passes validation
|
||||
*/
|
||||
public function contact($email)
|
||||
{
|
||||
if ($this->validate()) {
|
||||
Yii::$app->mailer->compose()
|
||||
->setTo($email)
|
||||
->setFrom([Yii::$app->params['senderEmail'] => Yii::$app->params['senderName']])
|
||||
->setReplyTo([$this->email => $this->name])
|
||||
->setSubject($this->subject)
|
||||
->setTextBody($this->body)
|
||||
->send();
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
81
models/LoginForm.php
Normal file
81
models/LoginForm.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace app\models;
|
||||
|
||||
use Yii;
|
||||
use yii\base\Model;
|
||||
|
||||
/**
|
||||
* LoginForm is the model behind the login form.
|
||||
*
|
||||
* @property-read User|null $user
|
||||
*
|
||||
*/
|
||||
class LoginForm extends Model
|
||||
{
|
||||
public $username;
|
||||
public $password;
|
||||
public $rememberMe = true;
|
||||
|
||||
private $_user = false;
|
||||
|
||||
|
||||
/**
|
||||
* @return array the validation rules.
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
// username and password are both required
|
||||
[['username', 'password'], 'required'],
|
||||
// rememberMe must be a boolean value
|
||||
['rememberMe', 'boolean'],
|
||||
// password is validated by validatePassword()
|
||||
['password', 'validatePassword'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the password.
|
||||
* This method serves as the inline validation for password.
|
||||
*
|
||||
* @param string $attribute the attribute currently being validated
|
||||
* @param array $params the additional name-value pairs given in the rule
|
||||
*/
|
||||
public function validatePassword($attribute, $params)
|
||||
{
|
||||
if (!$this->hasErrors()) {
|
||||
$user = $this->getUser();
|
||||
|
||||
if (!$user || !$user->validatePassword($this->password)) {
|
||||
$this->addError($attribute, 'Incorrect username or password.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs in a user using the provided username and password.
|
||||
* @return bool whether the user is logged in successfully
|
||||
*/
|
||||
public function login()
|
||||
{
|
||||
if ($this->validate()) {
|
||||
return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600*24*30 : 0);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds user by [[username]]
|
||||
*
|
||||
* @return User|null
|
||||
*/
|
||||
public function getUser()
|
||||
{
|
||||
if ($this->_user === false) {
|
||||
$this->_user = User::findByUsername($this->username);
|
||||
}
|
||||
|
||||
return $this->_user;
|
||||
}
|
||||
}
|
25
models/SubmitForm.php
Normal file
25
models/SubmitForm.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace app\models;
|
||||
|
||||
use yii\base\Model;
|
||||
|
||||
class SubmitForm extends Model
|
||||
{
|
||||
public string $access_code = '';
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
['access_code', 'required'],
|
||||
['access_code', 'string', 'max' => 10],
|
||||
];
|
||||
}
|
||||
|
||||
public function attributeLabels()
|
||||
{
|
||||
return [
|
||||
'access_code' => 'Код доступа'
|
||||
];
|
||||
}
|
||||
}
|
33
models/Task.php
Normal file
33
models/Task.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace app\models;
|
||||
|
||||
use yii\base\Model;
|
||||
|
||||
class Task extends Model
|
||||
{
|
||||
public $id;
|
||||
public $order;
|
||||
public $title;
|
||||
public $checked;
|
||||
|
||||
private const INIT_DATA = [
|
||||
['id' => 4, 'order' => 2, 'title' => 'Сделать тестовое задание', 'checked' => false],
|
||||
['id' => 2, 'order' => 1, 'title' => 'Написать документацию', 'checked' => true],
|
||||
['id' => 1, 'order' => 3, 'title' => 'Отправить на проверку', 'checked' => false],
|
||||
];
|
||||
|
||||
|
||||
public function search($params) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function save()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function findOne($id) {
|
||||
return false;
|
||||
}
|
||||
}
|
104
models/User.php
Normal file
104
models/User.php
Normal file
@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
namespace app\models;
|
||||
|
||||
class User extends \yii\base\BaseObject implements \yii\web\IdentityInterface
|
||||
{
|
||||
public $id;
|
||||
public $username;
|
||||
public $password;
|
||||
public $authKey;
|
||||
public $accessToken;
|
||||
|
||||
private static $users = [
|
||||
'100' => [
|
||||
'id' => '100',
|
||||
'username' => 'admin',
|
||||
'password' => 'admin',
|
||||
'authKey' => 'test100key',
|
||||
'accessToken' => '100-token',
|
||||
],
|
||||
'101' => [
|
||||
'id' => '101',
|
||||
'username' => 'demo',
|
||||
'password' => 'demo',
|
||||
'authKey' => 'test101key',
|
||||
'accessToken' => '101-token',
|
||||
],
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function findIdentity($id)
|
||||
{
|
||||
return isset(self::$users[$id]) ? new static(self::$users[$id]) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function findIdentityByAccessToken($token, $type = null)
|
||||
{
|
||||
foreach (self::$users as $user) {
|
||||
if ($user['accessToken'] === $token) {
|
||||
return new static($user);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds user by username
|
||||
*
|
||||
* @param string $username
|
||||
* @return static|null
|
||||
*/
|
||||
public static function findByUsername($username)
|
||||
{
|
||||
foreach (self::$users as $user) {
|
||||
if (strcasecmp($user['username'], $username) === 0) {
|
||||
return new static($user);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAuthKey()
|
||||
{
|
||||
return $this->authKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateAuthKey($authKey)
|
||||
{
|
||||
return $this->authKey === $authKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates password
|
||||
*
|
||||
* @param string $password password to validate
|
||||
* @return bool if password provided is valid for current user
|
||||
*/
|
||||
public function validatePassword($password)
|
||||
{
|
||||
return $this->password === $password;
|
||||
}
|
||||
}
|
162
requirements.php
Normal file
162
requirements.php
Normal file
@ -0,0 +1,162 @@
|
||||
<?php
|
||||
/**
|
||||
* Application requirement checker script.
|
||||
*
|
||||
* In order to run this script use the following console command:
|
||||
* php requirements.php
|
||||
*
|
||||
* In order to run this script from the web, you should copy it to the web root.
|
||||
* If you are using Linux you can create a hard link instead, using the following command:
|
||||
* ln ../requirements.php requirements.php
|
||||
*/
|
||||
|
||||
// you may need to adjust this path to the correct Yii framework path
|
||||
// uncomment and adjust the following line if Yii is not located at the default path
|
||||
//$frameworkPath = dirname(__FILE__) . '/vendor/yiisoft/yii2';
|
||||
|
||||
|
||||
if (!isset($frameworkPath)) {
|
||||
$searchPaths = array(
|
||||
dirname(__FILE__) . '/vendor/yiisoft/yii2',
|
||||
dirname(__FILE__) . '/../vendor/yiisoft/yii2',
|
||||
);
|
||||
foreach ($searchPaths as $path) {
|
||||
if (is_dir($path)) {
|
||||
$frameworkPath = $path;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($frameworkPath) || !is_dir($frameworkPath)) {
|
||||
$message = "<h1>Error</h1>\n\n"
|
||||
. "<p><strong>The path to yii framework seems to be incorrect.</strong></p>\n"
|
||||
. '<p>You need to install Yii framework via composer or adjust the framework path in file <abbr title="' . __FILE__ . '">' . basename(__FILE__) . "</abbr>.</p>\n"
|
||||
. '<p>Please refer to the <abbr title="' . dirname(__FILE__) . "/README.md\">README</abbr> on how to install Yii.</p>\n";
|
||||
|
||||
if (!empty($_SERVER['argv'])) {
|
||||
// do not print HTML when used in console mode
|
||||
echo strip_tags($message);
|
||||
} else {
|
||||
echo $message;
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
|
||||
require_once($frameworkPath . '/requirements/YiiRequirementChecker.php');
|
||||
$requirementsChecker = new YiiRequirementChecker();
|
||||
|
||||
$gdMemo = $imagickMemo = 'Either GD PHP extension with FreeType support or ImageMagick PHP extension with PNG support is required for image CAPTCHA.';
|
||||
$gdOK = $imagickOK = false;
|
||||
|
||||
if (extension_loaded('imagick')) {
|
||||
$imagick = new Imagick();
|
||||
$imagickFormats = $imagick->queryFormats('PNG');
|
||||
if (in_array('PNG', $imagickFormats)) {
|
||||
$imagickOK = true;
|
||||
} else {
|
||||
$imagickMemo = 'Imagick extension should be installed with PNG support in order to be used for image CAPTCHA.';
|
||||
}
|
||||
}
|
||||
|
||||
if (extension_loaded('gd')) {
|
||||
$gdInfo = gd_info();
|
||||
if (!empty($gdInfo['FreeType Support'])) {
|
||||
$gdOK = true;
|
||||
} else {
|
||||
$gdMemo = 'GD extension should be installed with FreeType support in order to be used for image CAPTCHA.';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust requirements according to your application specifics.
|
||||
*/
|
||||
$requirements = array(
|
||||
// Database :
|
||||
array(
|
||||
'name' => 'PDO extension',
|
||||
'mandatory' => true,
|
||||
'condition' => extension_loaded('pdo'),
|
||||
'by' => 'All DB-related classes',
|
||||
),
|
||||
array(
|
||||
'name' => 'PDO SQLite extension',
|
||||
'mandatory' => false,
|
||||
'condition' => extension_loaded('pdo_sqlite'),
|
||||
'by' => 'All DB-related classes',
|
||||
'memo' => 'Required for SQLite database.',
|
||||
),
|
||||
array(
|
||||
'name' => 'PDO MySQL extension',
|
||||
'mandatory' => false,
|
||||
'condition' => extension_loaded('pdo_mysql'),
|
||||
'by' => 'All DB-related classes',
|
||||
'memo' => 'Required for MySQL database.',
|
||||
),
|
||||
array(
|
||||
'name' => 'PDO PostgreSQL extension',
|
||||
'mandatory' => false,
|
||||
'condition' => extension_loaded('pdo_pgsql'),
|
||||
'by' => 'All DB-related classes',
|
||||
'memo' => 'Required for PostgreSQL database.',
|
||||
),
|
||||
// Cache :
|
||||
array(
|
||||
'name' => 'Memcache extension',
|
||||
'mandatory' => false,
|
||||
'condition' => extension_loaded('memcache') || extension_loaded('memcached'),
|
||||
'by' => '<a href="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="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="https://www.yiiframework.com/doc-2.0/yii-captcha-captcha.html">Captcha</a>',
|
||||
'memo' => $imagickMemo,
|
||||
),
|
||||
// PHP ini :
|
||||
'phpExposePhp' => array(
|
||||
'name' => 'Expose PHP',
|
||||
'mandatory' => false,
|
||||
'condition' => $requirementsChecker->checkPhpIniOff("expose_php"),
|
||||
'by' => 'Security reasons',
|
||||
'memo' => '"expose_php" should be disabled at php.ini',
|
||||
),
|
||||
'phpAllowUrlInclude' => array(
|
||||
'name' => 'PHP allow url include',
|
||||
'mandatory' => false,
|
||||
'condition' => $requirementsChecker->checkPhpIniOff("allow_url_include"),
|
||||
'by' => 'Security reasons',
|
||||
'memo' => '"allow_url_include" should be disabled at php.ini',
|
||||
),
|
||||
'phpSmtp' => array(
|
||||
'name' => 'PHP mail SMTP',
|
||||
'mandatory' => false,
|
||||
'condition' => strlen(ini_get('SMTP')) > 0,
|
||||
'by' => 'Email sending',
|
||||
'memo' => 'PHP mail SMTP server required',
|
||||
),
|
||||
);
|
||||
|
||||
// OPcache check
|
||||
if (!version_compare(phpversion(), '5.5', '>=')) {
|
||||
$requirements[] = array(
|
||||
'name' => 'APC extension',
|
||||
'mandatory' => false,
|
||||
'condition' => extension_loaded('apc'),
|
||||
'by' => '<a href="https://www.yiiframework.com/doc-2.0/yii-caching-apccache.html">ApcCache</a>',
|
||||
);
|
||||
}
|
||||
|
||||
$result = $requirementsChecker->checkYii()->check($requirements)->getResult();
|
||||
$requirementsChecker->render();
|
||||
exit($result['summary']['errors'] === 0 ? 0 : 1);
|
2
runtime/.gitignore
vendored
Normal file
2
runtime/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
82
services/TaskSubmitService.php
Normal file
82
services/TaskSubmitService.php
Normal file
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace app\services;
|
||||
|
||||
use Yii;
|
||||
use yii\httpclient\Client;
|
||||
|
||||
class TaskSubmitService
|
||||
{
|
||||
private string $accessCode;
|
||||
private string $endpoint = 'https://pulse.vladimirdrobnitsa.online/applicants/submit-test';
|
||||
// private string $endpoint = 'http://pulse.local/applicants/submit-test';
|
||||
|
||||
// Пути к файлам относительно корня приложения
|
||||
private array $files = [
|
||||
'models/Task.php',
|
||||
'views/task/index.php',
|
||||
];
|
||||
|
||||
public function __construct(string $accessCode)
|
||||
{
|
||||
$this->accessCode = $accessCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Создаёт ZIP-архив с файлами и возвращает путь к архиву
|
||||
* @return string путь к архиву
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function createZipArchive(): string
|
||||
{
|
||||
$zipPath = Yii::getAlias('@runtime/task_files_' . uniqid() . '.zip');
|
||||
|
||||
$zip = new \ZipArchive();
|
||||
if ($zip->open($zipPath, \ZipArchive::CREATE) !== true) {
|
||||
throw new \Exception('Не удалось создать архив');
|
||||
}
|
||||
|
||||
foreach ($this->files as $file) {
|
||||
$fullPath = Yii::getAlias('@app/' . $file);
|
||||
if (!file_exists($fullPath)) {
|
||||
throw new \Exception("Файл не найден: $fullPath");
|
||||
}
|
||||
// Добавляем файл в архив, сохраняя структуру
|
||||
$zip->addFile($fullPath, $file);
|
||||
}
|
||||
|
||||
$zip->close();
|
||||
|
||||
return $zipPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Отправляет архив на внешний API
|
||||
* @return array|null ответ сервера или null при ошибке
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function submit(): ?array
|
||||
{
|
||||
$zipPath = $this->createZipArchive();
|
||||
|
||||
$client = new Client();
|
||||
|
||||
$response = $client->createRequest()
|
||||
->setMethod('POST')
|
||||
->setUrl($this->endpoint)
|
||||
->addFile('archive', $zipPath)
|
||||
->setData(['access_code' => $this->accessCode])
|
||||
->send();
|
||||
|
||||
// Удаляем временный архив
|
||||
@unlink($zipPath);
|
||||
Yii::warning($response);
|
||||
|
||||
if ($response->isOk) {
|
||||
return $response->data;
|
||||
}
|
||||
|
||||
Yii::error('Ошибка отправки архива: ' . $response->content, __METHOD__);
|
||||
return null;
|
||||
}
|
||||
}
|
6
tests/_bootstrap.php
Normal file
6
tests/_bootstrap.php
Normal file
@ -0,0 +1,6 @@
|
||||
<?php
|
||||
define('YII_ENV', 'test');
|
||||
defined('YII_DEBUG') or define('YII_DEBUG', true);
|
||||
|
||||
require_once __DIR__ . '/../vendor/yiisoft/yii2/Yii.php';
|
||||
require __DIR__ .'/../vendor/autoload.php';
|
1
tests/_data/.gitkeep
Normal file
1
tests/_data/.gitkeep
Normal file
@ -0,0 +1 @@
|
||||
|
2
tests/_output/.gitignore
vendored
Normal file
2
tests/_output/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
26
tests/_support/AcceptanceTester.php
Normal file
26
tests/_support/AcceptanceTester.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
|
||||
/**
|
||||
* Inherited Methods
|
||||
* @method void wantToTest($text)
|
||||
* @method void wantTo($text)
|
||||
* @method void execute($callable)
|
||||
* @method void expectTo($prediction)
|
||||
* @method void expect($prediction)
|
||||
* @method void amGoingTo($argumentation)
|
||||
* @method void am($role)
|
||||
* @method void lookForwardTo($achieveValue)
|
||||
* @method void comment($description)
|
||||
* @method \Codeception\Lib\Friend haveFriend($name, $actorClass = NULL)
|
||||
*
|
||||
* @SuppressWarnings(PHPMD)
|
||||
*/
|
||||
class AcceptanceTester extends \Codeception\Actor
|
||||
{
|
||||
use _generated\AcceptanceTesterActions;
|
||||
|
||||
/**
|
||||
* Define custom actions here
|
||||
*/
|
||||
}
|
23
tests/_support/FunctionalTester.php
Normal file
23
tests/_support/FunctionalTester.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
|
||||
/**
|
||||
* Inherited Methods
|
||||
* @method void wantToTest($text)
|
||||
* @method void wantTo($text)
|
||||
* @method void execute($callable)
|
||||
* @method void expectTo($prediction)
|
||||
* @method void expect($prediction)
|
||||
* @method void amGoingTo($argumentation)
|
||||
* @method void am($role)
|
||||
* @method void lookForwardTo($achieveValue)
|
||||
* @method void comment($description)
|
||||
* @method \Codeception\Lib\Friend haveFriend($name, $actorClass = NULL)
|
||||
*
|
||||
* @SuppressWarnings(PHPMD)
|
||||
*/
|
||||
class FunctionalTester extends \Codeception\Actor
|
||||
{
|
||||
use _generated\FunctionalTesterActions;
|
||||
|
||||
}
|
26
tests/_support/UnitTester.php
Normal file
26
tests/_support/UnitTester.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
|
||||
/**
|
||||
* Inherited Methods
|
||||
* @method void wantToTest($text)
|
||||
* @method void wantTo($text)
|
||||
* @method void execute($callable)
|
||||
* @method void expectTo($prediction)
|
||||
* @method void expect($prediction)
|
||||
* @method void amGoingTo($argumentation)
|
||||
* @method void am($role)
|
||||
* @method void lookForwardTo($achieveValue)
|
||||
* @method void comment($description)
|
||||
* @method \Codeception\Lib\Friend haveFriend($name, $actorClass = NULL)
|
||||
*
|
||||
* @SuppressWarnings(PHPMD)
|
||||
*/
|
||||
class UnitTester extends \Codeception\Actor
|
||||
{
|
||||
use _generated\UnitTesterActions;
|
||||
|
||||
/**
|
||||
* Define custom actions here
|
||||
*/
|
||||
}
|
10
tests/acceptance.suite.yml.example
Normal file
10
tests/acceptance.suite.yml.example
Normal file
@ -0,0 +1,10 @@
|
||||
actor: AcceptanceTester
|
||||
modules:
|
||||
enabled:
|
||||
- WebDriver:
|
||||
url: http://127.0.0.1:8080/
|
||||
browser: firefox
|
||||
- Yii2:
|
||||
part: orm
|
||||
entryScript: index-test.php
|
||||
cleanup: false
|
12
tests/acceptance/AboutCest.php
Normal file
12
tests/acceptance/AboutCest.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
use yii\helpers\Url;
|
||||
|
||||
class AboutCest
|
||||
{
|
||||
public function ensureThatAboutWorks(AcceptanceTester $I)
|
||||
{
|
||||
$I->amOnPage(Url::toRoute('/site/about'));
|
||||
$I->see('About', 'h1');
|
||||
}
|
||||
}
|
34
tests/acceptance/ContactCest.php
Normal file
34
tests/acceptance/ContactCest.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use yii\helpers\Url;
|
||||
|
||||
class ContactCest
|
||||
{
|
||||
public function _before(\AcceptanceTester $I)
|
||||
{
|
||||
$I->amOnPage(Url::toRoute('/site/contact'));
|
||||
}
|
||||
|
||||
public function contactPageWorks(AcceptanceTester $I)
|
||||
{
|
||||
$I->wantTo('ensure that contact page works');
|
||||
$I->see('Contact', 'h1');
|
||||
}
|
||||
|
||||
public function contactFormCanBeSubmitted(AcceptanceTester $I)
|
||||
{
|
||||
$I->amGoingTo('submit contact form with correct data');
|
||||
$I->fillField('#contactform-name', 'tester');
|
||||
$I->fillField('#contactform-email', 'tester@example.com');
|
||||
$I->fillField('#contactform-subject', 'test subject');
|
||||
$I->fillField('#contactform-body', 'test content');
|
||||
$I->fillField('#contactform-verifycode', 'testme');
|
||||
|
||||
$I->click('contact-button');
|
||||
|
||||
$I->wait(2); // wait for button to be clicked
|
||||
|
||||
$I->dontSeeElement('#contact-form');
|
||||
$I->see('Thank you for contacting us. We will respond to you as soon as possible.');
|
||||
}
|
||||
}
|
18
tests/acceptance/HomeCest.php
Normal file
18
tests/acceptance/HomeCest.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
use yii\helpers\Url;
|
||||
|
||||
class HomeCest
|
||||
{
|
||||
public function ensureThatHomePageWorks(AcceptanceTester $I)
|
||||
{
|
||||
$I->amOnPage(Url::toRoute('/site/index'));
|
||||
$I->see('My Company');
|
||||
|
||||
$I->seeLink('About');
|
||||
$I->click('About');
|
||||
$I->wait(2); // wait for page to be opened
|
||||
|
||||
$I->see('This is the About page.');
|
||||
}
|
||||
}
|
21
tests/acceptance/LoginCest.php
Normal file
21
tests/acceptance/LoginCest.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
use yii\helpers\Url;
|
||||
|
||||
class LoginCest
|
||||
{
|
||||
public function ensureThatLoginWorks(AcceptanceTester $I)
|
||||
{
|
||||
$I->amOnPage(Url::toRoute('/site/login'));
|
||||
$I->see('Login', 'h1');
|
||||
|
||||
$I->amGoingTo('try to login with correct credentials');
|
||||
$I->fillField('input[name="LoginForm[username]"]', 'admin');
|
||||
$I->fillField('input[name="LoginForm[password]"]', 'admin');
|
||||
$I->click('login-button');
|
||||
$I->wait(2); // wait for button to be clicked
|
||||
|
||||
$I->expectTo('see user info');
|
||||
$I->see('Logout');
|
||||
}
|
||||
}
|
1
tests/acceptance/_bootstrap.php
Normal file
1
tests/acceptance/_bootstrap.php
Normal file
@ -0,0 +1 @@
|
||||
<?php
|
29
tests/bin/yii
Normal file
29
tests/bin/yii
Normal file
@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/**
|
||||
* Yii console bootstrap file.
|
||||
*
|
||||
* @link https://www.yiiframework.com/
|
||||
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||
* @license https://www.yiiframework.com/license/
|
||||
*/
|
||||
|
||||
defined('YII_DEBUG') or define('YII_DEBUG', true);
|
||||
defined('YII_ENV') or define('YII_ENV', 'test');
|
||||
|
||||
require __DIR__ . '/../../vendor/autoload.php';
|
||||
require __DIR__ . '/../../vendor/yiisoft/yii2/Yii.php';
|
||||
|
||||
$config = yii\helpers\ArrayHelper::merge(
|
||||
require __DIR__ . '/../../config/console.php',
|
||||
[
|
||||
'components' => [
|
||||
'db' => require __DIR__ . '/../../config/test_db.php'
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
$application = new yii\console\Application($config);
|
||||
$exitCode = $application->run();
|
||||
exit($exitCode);
|
20
tests/bin/yii.bat
Normal file
20
tests/bin/yii.bat
Normal file
@ -0,0 +1,20 @@
|
||||
@echo off
|
||||
|
||||
rem -------------------------------------------------------------
|
||||
rem Yii command line bootstrap script for Windows.
|
||||
rem
|
||||
rem @author Qiang Xue <qiang.xue@gmail.com>
|
||||
rem @link https://www.yiiframework.com/
|
||||
rem @copyright Copyright (c) 2008 Yii Software LLC
|
||||
rem @license https://www.yiiframework.com/license/
|
||||
rem -------------------------------------------------------------
|
||||
|
||||
@setlocal
|
||||
|
||||
set YII_PATH=%~dp0
|
||||
|
||||
if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe
|
||||
|
||||
"%PHP_COMMAND%" "%YII_PATH%yii" %*
|
||||
|
||||
@endlocal
|
14
tests/functional.suite.yml
Normal file
14
tests/functional.suite.yml
Normal file
@ -0,0 +1,14 @@
|
||||
# Codeception Test Suite Configuration
|
||||
|
||||
# suite for functional (integration) tests.
|
||||
# emulate web requests and make application process them.
|
||||
# (tip: better to use with frameworks).
|
||||
|
||||
# RUN `build` COMMAND AFTER ADDING/REMOVING MODULES.
|
||||
#basic/web/index.php
|
||||
actor: FunctionalTester
|
||||
modules:
|
||||
enabled:
|
||||
- Filesystem
|
||||
- Yii2
|
||||
- Asserts
|
57
tests/functional/ContactFormCest.php
Normal file
57
tests/functional/ContactFormCest.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
class ContactFormCest
|
||||
{
|
||||
public function _before(\FunctionalTester $I)
|
||||
{
|
||||
$I->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.');
|
||||
}
|
||||
}
|
59
tests/functional/LoginFormCest.php
Normal file
59
tests/functional/LoginFormCest.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
class LoginFormCest
|
||||
{
|
||||
public function _before(\FunctionalTester $I)
|
||||
{
|
||||
$I->amOnRoute('site/login');
|
||||
}
|
||||
|
||||
public function openLoginPage(\FunctionalTester $I)
|
||||
{
|
||||
$I->see('Login', 'h1');
|
||||
|
||||
}
|
||||
|
||||
// demonstrates `amLoggedInAs` method
|
||||
public function internalLoginById(\FunctionalTester $I)
|
||||
{
|
||||
$I->amLoggedInAs(100);
|
||||
$I->amOnPage('/');
|
||||
$I->see('Logout (admin)');
|
||||
}
|
||||
|
||||
// demonstrates `amLoggedInAs` method
|
||||
public function internalLoginByInstance(\FunctionalTester $I)
|
||||
{
|
||||
$I->amLoggedInAs(\app\models\User::findByUsername('admin'));
|
||||
$I->amOnPage('/');
|
||||
$I->see('Logout (admin)');
|
||||
}
|
||||
|
||||
public function loginWithEmptyCredentials(\FunctionalTester $I)
|
||||
{
|
||||
$I->submitForm('#login-form', []);
|
||||
$I->expectTo('see validations errors');
|
||||
$I->see('Username cannot be blank.');
|
||||
$I->see('Password cannot be blank.');
|
||||
}
|
||||
|
||||
public function loginWithWrongCredentials(\FunctionalTester $I)
|
||||
{
|
||||
$I->submitForm('#login-form', [
|
||||
'LoginForm[username]' => 'admin',
|
||||
'LoginForm[password]' => 'wrong',
|
||||
]);
|
||||
$I->expectTo('see validations errors');
|
||||
$I->see('Incorrect username or password.');
|
||||
}
|
||||
|
||||
public function loginSuccessfully(\FunctionalTester $I)
|
||||
{
|
||||
$I->submitForm('#login-form', [
|
||||
'LoginForm[username]' => 'admin',
|
||||
'LoginForm[password]' => 'admin',
|
||||
]);
|
||||
$I->see('Logout (admin)');
|
||||
$I->dontSeeElement('form#login-form');
|
||||
}
|
||||
}
|
1
tests/functional/_bootstrap.php
Normal file
1
tests/functional/_bootstrap.php
Normal file
@ -0,0 +1 @@
|
||||
<?php
|
11
tests/unit.suite.yml
Normal file
11
tests/unit.suite.yml
Normal file
@ -0,0 +1,11 @@
|
||||
# Codeception Test Suite Configuration
|
||||
|
||||
# suite for unit (internal) tests.
|
||||
# RUN `build` COMMAND AFTER ADDING/REMOVING MODULES.
|
||||
|
||||
actor: UnitTester
|
||||
modules:
|
||||
enabled:
|
||||
- Asserts
|
||||
- Yii2:
|
||||
part: [orm, email, fixtures]
|
3
tests/unit/_bootstrap.php
Normal file
3
tests/unit/_bootstrap.php
Normal file
@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
// add unit testing specific bootstrap code here
|
41
tests/unit/models/ContactFormTest.php
Normal file
41
tests/unit/models/ContactFormTest.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace tests\unit\models;
|
||||
|
||||
use app\models\ContactForm;
|
||||
use yii\mail\MessageInterface;
|
||||
|
||||
class ContactFormTest extends \Codeception\Test\Unit
|
||||
{
|
||||
/**
|
||||
* @var \UnitTester
|
||||
*/
|
||||
public $tester;
|
||||
|
||||
public function testEmailIsSentOnContact()
|
||||
{
|
||||
$model = new ContactForm();
|
||||
|
||||
$model->attributes = [
|
||||
'name' => 'Tester',
|
||||
'email' => 'tester@example.com',
|
||||
'subject' => 'very important letter subject',
|
||||
'body' => 'body of current message',
|
||||
'verifyCode' => 'testme',
|
||||
];
|
||||
|
||||
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');
|
||||
}
|
||||
}
|
51
tests/unit/models/LoginFormTest.php
Normal file
51
tests/unit/models/LoginFormTest.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace tests\unit\models;
|
||||
|
||||
use app\models\LoginForm;
|
||||
|
||||
class LoginFormTest extends \Codeception\Test\Unit
|
||||
{
|
||||
private $model;
|
||||
|
||||
protected function _after()
|
||||
{
|
||||
\Yii::$app->user->logout();
|
||||
}
|
||||
|
||||
public function testLoginNoUser()
|
||||
{
|
||||
$this->model = new LoginForm([
|
||||
'username' => 'not_existing_username',
|
||||
'password' => 'not_existing_password',
|
||||
]);
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
}
|
44
tests/unit/models/UserTest.php
Normal file
44
tests/unit/models/UserTest.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace tests\unit\models;
|
||||
|
||||
use app\models\User;
|
||||
|
||||
class UserTest extends \Codeception\Test\Unit
|
||||
{
|
||||
public function testFindUserById()
|
||||
{
|
||||
verify($user = User::findIdentity(100))->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();
|
||||
}
|
||||
|
||||
}
|
261
tests/unit/widgets/AlertTest.php
Normal file
261
tests/unit/widgets/AlertTest.php
Normal file
@ -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);
|
||||
}
|
||||
}
|
72
views/layouts/main.php
Normal file
72
views/layouts/main.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
/** @var yii\web\View $this */
|
||||
/** @var string $content */
|
||||
|
||||
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 ?>" class="h-100">
|
||||
<head>
|
||||
<title><?= Html::encode($this->title) ?></title>
|
||||
<?php $this->head() ?>
|
||||
</head>
|
||||
<body class="d-flex flex-column h-100">
|
||||
<?php $this->beginBody() ?>
|
||||
|
||||
<header id="header">
|
||||
<?php
|
||||
NavBar::begin([
|
||||
'brandLabel' => 'Тестовое задание',
|
||||
'brandUrl' => Yii::$app->homeUrl,
|
||||
'options' => ['class' => 'navbar-expand-md navbar-dark bg-dark fixed-top']
|
||||
]);
|
||||
echo Nav::widget([
|
||||
'options' => ['class' => 'navbar-nav'],
|
||||
'items' => [
|
||||
['label' => 'Выполнение', 'url' => ['/task/index']],
|
||||
['label' => 'Отправка', 'url' => ['/submit/index']],
|
||||
]
|
||||
]);
|
||||
NavBar::end();
|
||||
?>
|
||||
</header>
|
||||
|
||||
<main id="main" class="flex-shrink-0" role="main">
|
||||
<div class="container">
|
||||
<?php if (!empty($this->params['breadcrumbs'])): ?>
|
||||
<?= Breadcrumbs::widget(['links' => $this->params['breadcrumbs']]) ?>
|
||||
<?php endif ?>
|
||||
<?= Alert::widget() ?>
|
||||
<?= $content ?>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer id="footer" class="mt-auto py-3 bg-light">
|
||||
<div class="container">
|
||||
<div class="row text-muted">
|
||||
<div class="col-md-6 text-center text-md-start">© My Company <?= date('Y') ?></div>
|
||||
<div class="col-md-6 text-center text-md-end"><?= Yii::powered() ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<?php $this->endBody() ?>
|
||||
</body>
|
||||
</html>
|
||||
<?php $this->endPage() ?>
|
91
views/site/index.php
Normal file
91
views/site/index.php
Normal file
@ -0,0 +1,91 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Описание задачи: Реализация списка задач</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
max-width: 800px;
|
||||
margin: 2rem auto;
|
||||
padding: 0 1rem;
|
||||
color: #333;
|
||||
}
|
||||
h1, h2 {
|
||||
color: #2c3e50;
|
||||
}
|
||||
ul {
|
||||
margin-top: 0;
|
||||
}
|
||||
blockquote {
|
||||
border-left: 4px solid #ccc;
|
||||
padding-left: 1rem;
|
||||
color: #666;
|
||||
margin: 1rem 0;
|
||||
font-style: italic;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
code {
|
||||
background-color: #f4f4f4;
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: 3px;
|
||||
font-family: Consolas, monospace;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Описание задачи: Реализация списка задач с возможностью изменения статуса выполнения</h1>
|
||||
|
||||
<h2>Цель</h2>
|
||||
<p>Создать веб-интерфейс для отображения списка задач с возможностью помечать задачи как выполненные или невыполненные. Изменения статуса задачи должны отправляться на сервер асинхронно (AJAX) и сохраняться в кеше.</p>
|
||||
|
||||
<h2>Основные требования</h2>
|
||||
<blockquote>К редактированию доступны только файлы <code>models/Task.php</code> и <code>views/task/index.php</code>.</blockquote>
|
||||
|
||||
<ul>
|
||||
<li>Задача содержит поля:
|
||||
<ul>
|
||||
<li><code>id</code> — уникальный идентификатор задачи.</li>
|
||||
<li><code>order</code> — порядок сортировки.</li>
|
||||
<li><code>title</code> — название задачи.</li>
|
||||
<li><code>checked</code> — булево значение, указывающее, выполнена задача или нет.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Изначальные данные хранятся в константе <code>INIT_DATA</code>.</li>
|
||||
<li>Данные загружаются и сохраняются в кеш приложения (<code>Yii::$app->cache</code>).</li>
|
||||
<li>Необходимо реализовать методы:
|
||||
<ul>
|
||||
<li><code>search($params)</code></li>
|
||||
<li><code>save()</code> — сохраняет текущий объект задачи в кеш.</li>
|
||||
<li><code>findOne($id)</code> — возвращает задачу по идентификатору.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Валидация поля <code>checked</code> как булевого значения.</li>
|
||||
<li>Определены метки атрибутов для отображения в интерфейсе.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Представление (<code>index.php</code>)</h2>
|
||||
<ul>
|
||||
<li>Отображает список задач в виде таблицы с помощью <code>GridView</code>.</li>
|
||||
<li>Колонки таблицы:
|
||||
<ul>
|
||||
<li><code>id</code> — идентификатор задачи.</li>
|
||||
<li><code>title</code> — название задачи.</li>
|
||||
<li><code>checked</code> — чекбокс, отражающий статус выполнения задачи.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>При изменении состояния чекбокса отправляется AJAX POST-запрос на <code>\app\controllers\TaskController::actionSetState</code>.</li>
|
||||
<li>В случае ошибки запроса состояние чекбокса восстанавливается.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Технические детали</h2>
|
||||
<ul>
|
||||
<li>Данные задач не хранятся в базе, а кэшируются в памяти приложения.</li>
|
||||
<li>Сортировка задач происходит по полю <code>order</code> в порядке возрастания.</li>
|
||||
<li>AJAX-запросы позволяют обновлять статус задачи без перезагрузки страницы.</li>
|
||||
<li>Валидация и сохранение данных реализованы в модели <code>Task</code>.</li>
|
||||
<li>Представление использует стандартные компоненты Yii2 (<code>GridView</code>, <code>ArrayDataProvider</code>).</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
42
views/submit/index.php
Normal file
42
views/submit/index.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
use yii\helpers\Html;
|
||||
use yii\widgets\ActiveForm;
|
||||
|
||||
/* @var $this yii\web\View */
|
||||
/* @var $model app\models\SubmitForm */
|
||||
/* @var $result array|null */
|
||||
|
||||
$this->title = 'Отправка задачи';
|
||||
?>
|
||||
|
||||
<h1><?= Html::encode($this->title) ?></h1>
|
||||
|
||||
<?php if (Yii::$app->session->hasFlash('error')): ?>
|
||||
<div class="alert alert-danger">
|
||||
<?= Yii::$app->session->getFlash('error') ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($result !== null): ?>
|
||||
<?php if(key_exists('success', $result) && key_exists('message', $result)) : ?>
|
||||
<div class="alert <?=$result['success'] ? 'alert-success' : 'alert-danger'?>">
|
||||
<h4>Результат отправки:</h4>
|
||||
<pre><?= htmlspecialchars(print_r($result['message'], true)) ?></pre>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="alert alert-danger">
|
||||
'Неизвестная ошибка'
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php $form = ActiveForm::begin(); ?>
|
||||
|
||||
<?= $form->field($model, 'access_code')->textInput(['maxlength' => true]) ?>
|
||||
|
||||
<div class="form-group">
|
||||
<?= Html::submitButton('Отправить', ['class' => 'btn btn-primary']) ?>
|
||||
</div>
|
||||
|
||||
<?php ActiveForm::end(); ?>
|
21
views/task/index.php
Normal file
21
views/task/index.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
use yii\helpers\Url;
|
||||
|
||||
|
||||
$this->title = 'Список задач';
|
||||
|
||||
$setStateUrl = Url::to(['task/set-state']);
|
||||
|
||||
$js = <<<JS
|
||||
// Обработчик клика по чекбоксу
|
||||
|
||||
JS;
|
||||
|
||||
$this->registerJs($js);
|
||||
|
||||
?>
|
||||
|
||||
<h1><?= \yii\helpers\Html::encode($this->title) ?></h1>
|
||||
|
||||
<?= 'Табличные данные' ?>
|
4
web/.htaccess
Normal file
4
web/.htaccess
Normal file
@ -0,0 +1,4 @@
|
||||
RewriteEngine on
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteRule . index.php [L]
|
2
web/assets/.gitignore
vendored
Normal file
2
web/assets/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
88
web/css/site.css
Normal file
88
web/css/site.css
Normal file
@ -0,0 +1,88 @@
|
||||
main > .container {
|
||||
padding: 70px 15px 20px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
background-color: #f5f5f5;
|
||||
font-size: .9em;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.footer > .container {
|
||||
padding-right: 15px;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.not-set {
|
||||
color: #c55;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* add sorting icons to gridview sort links */
|
||||
a.asc:after, a.desc:after {
|
||||
content: '';
|
||||
left: 3px;
|
||||
display: inline-block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border: solid 5px transparent;
|
||||
margin: 4px 4px 2px 4px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
a.asc:after {
|
||||
border-bottom: solid 7px #212529;
|
||||
border-top-width: 0;
|
||||
}
|
||||
|
||||
a.desc:after {
|
||||
border-top: solid 7px #212529;
|
||||
border-bottom-width: 0;
|
||||
}
|
||||
|
||||
.grid-view th {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.hint-block {
|
||||
display: block;
|
||||
margin-top: 5px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.error-summary {
|
||||
color: #a94442;
|
||||
background: #fdf7f7;
|
||||
border-left: 3px solid #eed3d7;
|
||||
padding: 10px 20px;
|
||||
margin: 0 0 15px 0;
|
||||
}
|
||||
|
||||
/* align the logout "link" (button in form) of the navbar */
|
||||
.nav li > form > button.logout {
|
||||
padding-top: 7px;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
@media(max-width:767px) {
|
||||
.nav li > form > button.logout {
|
||||
display:block;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
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;
|
||||
}
|
BIN
web/favicon.ico
Normal file
BIN
web/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 318 B |
16
web/index-test.php
Normal file
16
web/index-test.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
// NOTE: Make sure this file is not accessible when deployed to production
|
||||
if (!in_array(@$_SERVER['REMOTE_ADDR'], ['127.0.0.1', '::1'])) {
|
||||
die('You are not allowed to access this file.');
|
||||
}
|
||||
|
||||
defined('YII_DEBUG') or define('YII_DEBUG', true);
|
||||
defined('YII_ENV') or define('YII_ENV', 'test');
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
require __DIR__ . '/../vendor/yiisoft/yii2/Yii.php';
|
||||
|
||||
$config = require __DIR__ . '/../config/test.php';
|
||||
|
||||
(new yii\web\Application($config))->run();
|
12
web/index.php
Normal file
12
web/index.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
// comment out the following two lines when deployed to production
|
||||
defined('YII_DEBUG') or define('YII_DEBUG', true);
|
||||
defined('YII_ENV') or define('YII_ENV', 'dev');
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
require __DIR__ . '/../vendor/yiisoft/yii2/Yii.php';
|
||||
|
||||
$config = require __DIR__ . '/../config/web.php';
|
||||
|
||||
(new yii\web\Application($config))->run();
|
2
web/robots.txt
Normal file
2
web/robots.txt
Normal file
@ -0,0 +1,2 @@
|
||||
User-agent: *
|
||||
Disallow:
|
73
widgets/Alert.php
Normal file
73
widgets/Alert.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace app\widgets;
|
||||
|
||||
use Yii;
|
||||
|
||||
/**
|
||||
* Alert widget renders a message from session flash. All flash messages are displayed
|
||||
* in the sequence they were assigned using setFlash. You can set message as following:
|
||||
*
|
||||
* ```php
|
||||
* Yii::$app->session->setFlash('error', 'This is the message');
|
||||
* Yii::$app->session->setFlash('success', 'This is the message');
|
||||
* Yii::$app->session->setFlash('info', 'This is the message');
|
||||
* ```
|
||||
*
|
||||
* Multiple messages could be set as follows:
|
||||
*
|
||||
* ```php
|
||||
* Yii::$app->session->setFlash('error', ['Error 1', 'Error 2']);
|
||||
* ```
|
||||
*
|
||||
* @author Kartik Visweswaran <kartikv2@gmail.com>
|
||||
* @author Alexander Makarov <sam@rmcreative.ru>
|
||||
*/
|
||||
class Alert extends \yii\bootstrap5\Widget
|
||||
{
|
||||
/**
|
||||
* @var array the alert types configuration for the flash messages.
|
||||
* This array is setup as $key => $value, where:
|
||||
* - key: the name of the session flash variable
|
||||
* - value: the bootstrap alert type (i.e. danger, success, info, warning)
|
||||
*/
|
||||
public $alertTypes = [
|
||||
'error' => 'alert-danger',
|
||||
'danger' => 'alert-danger',
|
||||
'success' => 'alert-success',
|
||||
'info' => 'alert-info',
|
||||
'warning' => 'alert-warning'
|
||||
];
|
||||
/**
|
||||
* @var array the options for rendering the close button tag.
|
||||
* Array will be passed to [[\yii\bootstrap\Alert::closeButton]].
|
||||
*/
|
||||
public $closeButton = [];
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
$session = Yii::$app->session;
|
||||
$appendClass = isset($this->options['class']) ? ' ' . $this->options['class'] : '';
|
||||
|
||||
foreach (array_keys($this->alertTypes) as $type) {
|
||||
$flash = $session->getFlash($type);
|
||||
|
||||
foreach ((array) $flash as $i => $message) {
|
||||
echo \yii\bootstrap5\Alert::widget([
|
||||
'body' => $message,
|
||||
'closeButton' => $this->closeButton,
|
||||
'options' => array_merge($this->options, [
|
||||
'id' => $this->getId() . '-' . $type . '-' . $i,
|
||||
'class' => $this->alertTypes[$type] . $appendClass,
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
$session->removeFlash($type);
|
||||
}
|
||||
}
|
||||
}
|
21
yii
Normal file
21
yii
Normal file
@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/**
|
||||
* Yii console bootstrap file.
|
||||
*
|
||||
* @link https://www.yiiframework.com/
|
||||
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||
* @license https://www.yiiframework.com/license/
|
||||
*/
|
||||
|
||||
defined('YII_DEBUG') or define('YII_DEBUG', true);
|
||||
defined('YII_ENV') or define('YII_ENV', 'dev');
|
||||
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
require __DIR__ . '/vendor/yiisoft/yii2/Yii.php';
|
||||
|
||||
$config = require __DIR__ . '/config/console.php';
|
||||
|
||||
$application = new yii\console\Application($config);
|
||||
$exitCode = $application->run();
|
||||
exit($exitCode);
|
20
yii.bat
Normal file
20
yii.bat
Normal file
@ -0,0 +1,20 @@
|
||||
@echo off
|
||||
|
||||
rem -------------------------------------------------------------
|
||||
rem Yii command line bootstrap script for Windows.
|
||||
rem
|
||||
rem @author Qiang Xue <qiang.xue@gmail.com>
|
||||
rem @link https://www.yiiframework.com/
|
||||
rem @copyright Copyright (c) 2008 Yii Software LLC
|
||||
rem @license https://www.yiiframework.com/license/
|
||||
rem -------------------------------------------------------------
|
||||
|
||||
@setlocal
|
||||
|
||||
set YII_PATH=%~dp0
|
||||
|
||||
if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe
|
||||
|
||||
"%PHP_COMMAND%" "%YII_PATH%yii" %*
|
||||
|
||||
@endlocal
|
Loading…
x
Reference in New Issue
Block a user