init repo

This commit is contained in:
b1ek 2024-11-28 01:58:04 +10:00
commit 7694c40a68
Signed by: blek
GPG Key ID: A622C22C9BC616B2
80 changed files with 8375 additions and 0 deletions

3
.bowerrc Normal file
View File

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

33
.gitignore vendored Normal file
View 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
View 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.

92
Vagrantfile vendored Normal file
View File

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

31
assets/AppAsset.php Normal file
View 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
View 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/*

View 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;
}
}

74
composer.json Normal file
View File

@ -0,0 +1,74 @@
{
"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": ">=7.4.0",
"yiisoft/yii2": "~2.0.45",
"yiisoft/yii2-bootstrap5": "~2.0.2",
"yiisoft/yii2-symfonymailer": "~2.0.3"
},
"require-dev": {
"yiisoft/yii2-debug": "~2.1.0",
"yiisoft/yii2-gii": "~2.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",
"symfony/browser-kit": "^6.0 || >=2.7 <=4.2.4"
},
"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"
}
]
}

5420
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

33
config/__autocomplete.php Normal file
View 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
View 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
View 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
View 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
View 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
View 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;

74
config/web.php Normal file
View File

@ -0,0 +1,74 @@
<?php
$params = require __DIR__ . '/params.php';
$db = require __DIR__ . '/db.php';
$config = [
'id' => 'basic',
'basePath' => dirname(__DIR__),
'bootstrap' => ['log'],
'aliases' => [
'@bower' => '@vendor/bower-asset',
'@npm' => '@vendor/npm-asset',
],
'components' => [
'request' => [
// !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
'cookieValidationKey' => '0qNaulpEfs2NYedhkKChdCgJqb0hj6jP',
],
'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;

View File

@ -0,0 +1,104 @@
<?php
namespace app\controllers;
use app\models\Comment;
use app\models\CommentEditForm;
use app\models\CommentNewForm;
use Yii;
use yii\web\Controller;
use yii\filters\AccessControl;
use yii\filters\VerbFilter;
use yii\web\Request;
class CommentsController extends Controller
{
public function behaviors()
{
return [
'access' => [
'class' => AccessControl::class,
],
'verbs' => [
'class' => VerbFilter::class,
'actions' => [
'post' => ['POST'],
'edit' => ['POST'],
'delete' => ['DELETE']
]
]
];
}
public function actionPost(Request $request)
{
if (!$request->isPost) {
return false;
}
if (Yii::$app->user->id === null) {
return false;
}
$data = $request->post();
$form = new CommentNewForm();
$form->load([ ...$data, 'user_id' => Yii::$app->user->id ]);
if ($form->validate()) {
if ($form->create()) {
return true;
}
}
return false;
}
public function actionEdit(Request $request)
{
if (!$request->isPost) {
return false;
}
if (Yii::$app->user->id === null) {
return false;
}
$data = $request->post();
$form = new CommentEditForm();
$form->load([ ...$data, 'user_id' => Yii::$app->user->id ]);
if ($form->validate()) {
if ($form->edit()) {
return true;
}
}
return false;
}
public function actionDelete(Request $request)
{
/*
Можно конечно поспорить что в этом месте лучше
в модель это запихнуть, но мне кажется что это
какой-то абсурд для такого маленького кол-ва
данных
*/
if (!$request->isDelete) {
return false;
}
if (Yii::$app->user->id === null) {
return false;
}
if ($request->getQueryParam('id') === null) {
return false;
}
$comment = Comment::findOne([ 'id' => $request->getQueryParam('id') ]);
if ($comment === null) {
return false;
}
if ($comment->user_id !== Yii::$app->user->id) {
return false;
}
return $comment->delete();
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace app\controllers;
use app\models\UserSearch;
use Yii;
use yii\filters\AccessControl;
use yii\filters\VerbFilter;
use yii\web\Controller;
use yii\web\Request;
class UserSearchController extends Controller
{
public function behaviors()
{
return [
'access' => [
'class' => AccessControl::class,
'only' => ['search'],
'rules' => [
[
'actions' => ['search'],
'allow' => true,
'roles' => ['userListViewer']
]
]
],
'verbs' => [
'class' => VerbFilter::class,
'actions' => [
'search' => ['POST'],
]
]
];
}
public function actionSearch(Request $request)
{
if (!$request->isPost) {
return false;
}
if (Yii::$app->user->id === null) {
return false;
}
$search = new UserSearch();
$provider = $search->search($request->post());
return $provider->query->all();
}
}

11
docker-compose.yml Normal file
View File

@ -0,0 +1,11 @@
services:
php:
image: yiisoftware/yii2-php:8.3-fpm-24.3.0-nginx
volumes:
- ~/.composer-docker/cache:/root/.composer/cache:delegated
- ./:/app:delegated
ports:
- '8000:80'
environment:
PHP_ENABLE_XDEBUG: 1
XDEBUG_CONFIG: 'client_host=host.docker.internal client_port=9003'

22
mail/layouts/html.php Normal file
View 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
View 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();

39
models/Blogs.php Normal file
View File

@ -0,0 +1,39 @@
<?php
namespace app\models;
class Blogs extends \yii\db\ActiveRecord
{
public $id;
public $user_id;
public $company_id;
/**
* {@inheritDoc}
*/
public function rules()
{
return [
[['id'], 'required'],
[['id'], 'integer'],
];
}
public function getUser()
{
return $this->hasOne(User::class, [ 'id' => 'user_id' ]);
}
public function getCompany()
{
return $this->hasOne(Companies::class, [ 'id' => 'company_id' ]);
}
/**
* {@inheritDoc}
*/
public function tableName()
{
return "blogs";
}
}

48
models/Comment.php Normal file
View File

@ -0,0 +1,48 @@
<?php
namespace app\models;
class Comment extends \yii\db\ActiveRecord
{
public $id;
public $user_id;
public $material_id;
public $content;
/**
* {@inheritDoc}
*/
public function rules()
{
return [
[['id', 'user_id', 'material_id', 'content'], 'required'],
[['id', 'user_id', 'material_id'], 'integer'],
[['content'], 'string'],
];
}
/**
* {@inheritDoc}
*/
public function getUser()
{
return $this->hasOne(User::class, [ 'id' => 'user_id' ]);
}
/**
* {@inheritDoc}
*/
public function getMaterial()
{
return $this->hasOne(Material::class, [ 'id' => 'material_id' ]);
}
/**
* {@inheritDoc}
*/
public function tableName()
{
return "comments";
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace app\models;
class CommentEditForm extends \yii\base\Model
{
public $id;
public $user_id;
public $content;
public function rules()
{
return [
[['content', 'id', 'user_id'], 'required'],
[['content'], 'string'],
[['id'], 'integer']
];
}
public function edit()
{
$comment = Comment::findOne([ 'id' => $this->id ]);
if ($comment === null) {
return false;
}
if ($comment->user_id !== $this->user_id) {
return false;
}
$comment->content = $this->content;
return $comment->save();
}
}

28
models/CommentNewForm.php Normal file
View File

@ -0,0 +1,28 @@
<?php
namespace app\models;
class CommentNewForm extends \yii\base\Model
{
public $message;
public $user_id;
public $material_id;
/**
* {@inheritDoc}
*/
public function rules() {
return [
[['message'], 'string'],
[['user_id', 'material_id'], 'integer'],
[['message', 'user_id', 'material_id'], 'required'],
];
}
public function create()
{
$comment = new Comment();
$comment->user_id = $this->user_id;
$comment->material_id = $this->material_id;
return $comment->save();
}
}

31
models/Companies.php Normal file
View File

@ -0,0 +1,31 @@
<?php
namespace app\models;
class Companies extends \yii\db\ActiveRecord
{
public $id;
public $title;
public $website;
public $address;
/**
* {@inheritDoc}
*/
public function rules()
{
return [
[['id', 'title', 'website', 'address'], 'required'],
[['title', 'website', 'address'], 'string'],
[['id'], 'integer'],
];
}
/**
* {@inheritDoc}
*/
public function tableName()
{
return "companies";
}
}

31
models/Material.php Normal file
View File

@ -0,0 +1,31 @@
<?php
namespace app\models;
class Material extends \yii\db\ActiveRecord
{
public $id;
public $title;
public $content;
public $blog_id;
/**
* {@inheritDoc}
*/
public function rules()
{
return [
[['id', 'title', 'content', 'blog_id'], 'required'],
[['title', 'content', 'blog_id'], 'string'],
[['id'], 'integer'],
];
}
/**
* {@inheritDoc}
*/
public function tableName()
{
return "materials";
}
}

38
models/Subscriptions.php Normal file
View File

@ -0,0 +1,38 @@
<?php
namespace app\models;
class Subscriptions extends \yii\db\ActiveRecord
{
public $user_id;
public $company_id;
/**
* {@inheritDoc}
*/
public function rules()
{
return [
[['user_id'], 'required'],
[['id'], 'integer'],
];
}
public function getUser()
{
return $this->hasOne(User::class, [ 'id' => 'user_id' ]);
}
public function getCompany()
{
return $this->hasOne(Companies::class, [ 'id' => 'company_id' ]);
}
/**
* {@inheritDoc}
*/
public function tableName()
{
return "subscriptions";
}
}

127
models/User.php Normal file
View File

@ -0,0 +1,127 @@
<?php
namespace app\models;
use Yii;
class User extends \yii\db\ActiveRecord implements \yii\web\IdentityInterface
{
public $id;
public $name;
public $login;
/// Path to a file stored in an imaginatory S3 bucket
public $avatar;
public $phone;
public $website;
public $passHash;
public $accessToken;
/**
* {@inheritDoc}
*/
public function rules()
{
return [
[['id', 'name', 'login', 'phone', 'passHash'], 'required'],
[['name', 'login', 'avatar', 'phone', 'website', 'passHash'], 'string'],
[['id'], 'integer'],
];
}
/**
* {@inheritDoc}
*/
public function attributeLabels()
{
return [
'id' => 'ID',
'name' => 'Имя',
'login' => 'Логин',
'phone' => 'Телефон',
'website' => 'Личный сайт',
'accessToken' => '',
'passHash' => 'Пароль',
];
}
/**
* {@inheritDoc}
*/
public function fields()
{
return [
'id', 'name', 'login', 'avatar', 'phone', 'website', 'accessToken'
];
}
/**
* {@inheritDoc}
*/
public static function tableName()
{
return "user";
}
/**
* {@inheritdoc}
*/
public static function findIdentity($id)
{
return self::findOne([ 'id' => $id ]);
}
/**
* {@inheritdoc}
*/
public static function findIdentityByAccessToken($token, $type = null)
{
return self::findOne([ 'accessToken' => $token ]);
}
/**
* Finds user by username
*
* @param string $username
* @return static|null
*/
public static function findByUsername($username)
{
return self::findOne([ 'login' => $username ]);
}
/**
* {@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 Yii::$app->security->validatePassword($password, $this->passHash);
}
}

101
models/UserSearch.php Normal file
View File

@ -0,0 +1,101 @@
<?php
namespace app\models;
use Yii;
use yii\data\ActiveDataProvider;
class UserSearch extends User
{
public $name;
public $sort_comment_count;
public $sort_subscribers_count;
public $sort_post_count;
/**
* {@inheritDoc}
*/
public function rules()
{
return [
[['name'], 'string'],
[['sort_comment_count', 'sort_subscribers_count', 'sort_post_count'], 'in', 'range' => [ 'desc', 'asc' ]],
[['sort_comment_count', 'sort_subscribers_count', 'sort_post_count'], 'onlyOneSort'],
[['page'], 'integer'],
[['page'], 'required'],
];
}
public function onlyOneSort($attribute, $params)
{
$attributes = ['sort_comment_count', 'sort_subscribers_count', 'sort_post_count'];
$oneSet = false;
foreach ($attributes as $attribute) {
if (isset($this->$attribute)) {
if ($oneSet) {
$this->addError($attribute, Yii::t('validation', 'only one sort is allowed'));
return false;
}
$oneSet = true;
}
}
}
public function search($params)
{
$query = User::find();
$query->limit(100);
$query->offset(((int) $this->page) * 100);
/**
* Впринципе можно наверное и просто query вернуть но это
* на всякий чтобы можно было через какую нибудь вьюшку отобразить
*/
$provider = new ActiveDataProvider([
'query' => $query
]);
/**
* Запрос скорее всего кривой, но это по большей части псевдокод
* так что надеюсь простите если оно выглядит нелепо
*/
if (!($this->load($params)) && $this->validate()) {
return $provider;
}
if ($this->name) {
$query->andFilterWhere(['like', 'name', $this->name]);
}
if ($this->sort_comment_count === true) {
$query->join('outer', 'comments', [ 'user.comment_id' => 'comments.id' ]);
$query->orderBy([ 'COUNT(comment.id)' => $this->sort_comment_count ]);
}
if ($this->sort_post_count === true) {
$query->join('outer', 'posts', [ 'posts.user_id' => 'user.id' ]);
$query->orderBy([ 'COUNT(posts.id)' => $this->sort_post_count ]);
}
if ($this->sort_subscribers_count === true) {
/**
* Тут бы я вместо под запроса поставил как два джоина прежде чем пускать в прожакшн
* но у меня сейчас недостаточно мозговых сил чтобы так сильно продумывать
*/
$query->join('outer', 'blogs', [ 'blogs.user_id' => 'user.id' ]);
$query->orderBy([ 'IF(blogs.id, SELECT COUNT(*) FROM subscriptions s WHERE s.blog_id = blogs.id)' => $this->sort_subscribers_count ]);
}
if (
$this->sort_comment_count === true
|| $this->sort_post_count === true
|| $this->sort_subscribers_count === true
) {
$query->groupBy('user.id');
}
}
}

162
requirements.php Normal file
View 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
View File

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

6
tests/_bootstrap.php Normal file
View 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
View File

@ -0,0 +1 @@

2
tests/_output/.gitignore vendored Normal file
View File

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

View 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
*/
}

View 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;
}

View 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
*/
}

View 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

View 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');
}
}

View 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.');
}
}

View 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.');
}
}

View 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');
}
}

View File

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

29
tests/bin/yii Executable file
View 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
View 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

View 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

View 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.');
}
}

View 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');
}
}

View File

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

11
tests/unit.suite.yml Normal file
View 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]

View File

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

View 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');
}
}

View 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');
}
}

View 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();
}
}

View 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);
}
}

2
vagrant/config/.gitignore vendored Normal file
View File

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

View File

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

38
vagrant/nginx/app.conf Normal file
View File

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

3
vagrant/nginx/log/.gitignore vendored Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

83
views/layouts/main.php Normal file
View File

@ -0,0 +1,83 @@
<?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' => Yii::$app->name,
'brandUrl' => Yii::$app->homeUrl,
'options' => ['class' => 'navbar-expand-md navbar-dark bg-dark fixed-top']
]);
echo Nav::widget([
'options' => ['class' => 'navbar-nav'],
'items' => [
['label' => 'Home', 'url' => ['/site/index']],
['label' => 'About', 'url' => ['/site/about']],
['label' => 'Contact', 'url' => ['/site/contact']],
Yii::$app->user->isGuest
? ['label' => 'Login', 'url' => ['/site/login']]
: '<li class="nav-item">'
. Html::beginForm(['/site/logout'])
. Html::submitButton(
'Logout (' . Yii::$app->user->identity->username . ')',
['class' => 'nav-link btn btn-link logout']
)
. Html::endForm()
. '</li>'
]
]);
NavBar::end();
?>
</header>
<main id="main" class="flex-shrink-0" role="main">
<div class="container">
<?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">&copy; 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() ?>

18
views/site/about.php Normal file
View File

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

68
views/site/contact.php Normal file
View File

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

27
views/site/error.php Normal file
View File

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

53
views/site/index.php Normal file
View File

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

55
views/site/login.php Normal file
View File

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

4
web/.htaccess Normal file
View 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
View File

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

88
web/css/site.css Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 B

16
web/index-test.php Normal file
View 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
View 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
View File

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

73
widgets/Alert.php Normal file
View 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 Executable file
View 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
View 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