Merge pull request 'schema' (#3) from init into main

Reviewed-on: #3
This commit is contained in:
mironov 2024-07-04 06:44:14 +03:00
commit 53bdc30f19
64 changed files with 3126 additions and 5700 deletions

View File

@ -82,5 +82,6 @@ backend: [admin-example.webeffector.ru](https://admin-example.webeffector.ru)
Test data for stage/dev:
- Администратор admin@web.local:admin
- Супервайзор supervisor@web.local:supervisor
- Менеджер manager@web.local:manager
- Пользователь user@web.local:user
- Пользователь customer@web.local:customer

View File

@ -10,14 +10,13 @@ return [
'id' => 'app-backend',
'basePath' => dirname(__DIR__),
'controllerNamespace' => 'backend\controllers',
'bootstrap' => ['log'],
'modules' => [],
'bootstrap' => ['log', 'schema'],
'components' => [
'request' => [
'csrfParam' => '_csrf-backend',
],
'user' => [
'identityClass' => 'common\models\User',
'identityClass' => common\components\user\models\User::class,
'enableAutoLogin' => true,
'identityCookie' => ['name' => '_identity-backend', 'httpOnly' => true],
],
@ -37,14 +36,20 @@ return [
'errorHandler' => [
'errorAction' => 'site/error',
],
/*
'urlManager' => [
'enablePrettyUrl' => true,
'showScriptName' => false,
'rules' => [
],
],
*/
],
'modules' => [
'user' => [
'class' => backend\modules\user\Module::class,
],
'schema' => [
'class' => backend\modules\schema\Module::class,
],
],
'params' => $params,
];

View File

@ -0,0 +1,47 @@
<?php
namespace backend\modules\schema;
use Yii;
use yii\web\Application;
use yii\i18n\PhpMessageSource;
use yii\base\BootstrapInterface;
class Module extends yii\base\Module implements BootstrapInterface
{
public $controllerNamespace = 'backend\modules\schema\controllers';
public function init()
{
parent::init();
$this->registerTranslations();
}
public function registerTranslations()
{
Yii::$app->i18n->translations['schema*'] = [
'class' => PhpMessageSource::class,
'sourceLanguage' => 'ru-RU',
'basePath' => '@backend/modules/schema/messages',
'fileMap' => [
'schema' => 'schema.php',
],
];
}
public static function t($category, $message, $params = [], $language = null): string
{
return Yii::t('schema' . $category, $message, $params, $language);
}
public function bootstrap($app)
{
if ($app instanceof Application) {
$app->getUrlManager()->addRules([
['class' => 'yii\web\UrlRule', 'pattern' => $this->id, 'route' => $this->id . '/default/index'],
['class' => 'yii\web\UrlRule', 'pattern' => $this->id . '<id:\w+>', 'route' => $this->id . '/default/view'],
['class' => 'yii\web\UrlRule', 'pattern' => $this->id . 'default/<action:[\w\-]+>', 'route' => $this->id . '/<controller>/<action>'],
], false);
}
}
}

View File

@ -0,0 +1,30 @@
# Модуль Schema
## Подключение модуля
'modules' => [
'schema' => [
'class' => backend\modules\schema\Module::class,
],
]
Прописываем nameSpace в сгенерированном файле для подключения к проекту
namespace backend\modules\schema\migrations;
Добавляем миграции в настройки проекта
'controllerMap' => [
'migrate' => [
'class' => yii\console\controllers\MigrateController::class,
'migrationNamespaces' => [
'backend\modules\schema\migrations',
],
],
],
docker-compose run --rm php ./yii migrate
Добавление миграций
docker-compose run --rm php ./yii migrate/create --migrationPath=@backend/modules/schema/migrations create_schema_table

View File

@ -0,0 +1,108 @@
<?php
namespace backend\modules\schema\controllers;
use Yii;
use yii\web\Response;
use yii\web\Controller;
use yii\filters\AccessControl;
use common\helpers\UserHelper;
use yii\web\NotFoundHttpException;
use common\components\schema\models\Schema;
use common\components\schema\models\search\SchemaSearch;
class DefaultController extends Controller
{
public function behaviors(): array
{
return [
'access' => [
'class' => AccessControl::class,
'denyCallback' => function () {
return $this->goHome();
},
'rules' => [
[
'allow' => true,
'roles' => [UserHelper::ROLE_SUPERVISOR],
],
],
],
];
}
public function actionIndex(): string
{
$searchModel = new SchemaSearch();
$params = Yii::$app->request->queryParams;
$dataProvider = $searchModel->search($params);
return $this->render('index', [
'dataProvider' => $dataProvider,
'searchModel' => $searchModel,
]);
}
public function actionCreate()
{
$model = new Schema();
if ($model->load(Yii::$app->request->post())) {
$model->save();
} else {
if ($model->getErrors()) {
Yii::$app->session->setFlash('error', json_encode($model->getErrors(), JSON_UNESCAPED_UNICODE));
}
}
return $this->render('create', [
'model' => $model,
]);
}
public function actionUpdate($id)
{
$model = $this->findModel($id);
if ($model->load(Yii::$app->request->post()) && $model->save()) {
Yii::$app->session->setFlash('success', Yii::t('schema', 'Сохранено'));
return $this->redirect('index');
} else {
if ($model->getErrors()) {
Yii::$app->session->setFlash('error', json_encode($model->getErrors(), JSON_UNESCAPED_UNICODE));
}
}
return $this->render('update', [
'model' => $model,
]);
}
public function actionDelete($id): Response
{
$model = $this->findModel($id);
$message = Yii::t('schema', 'Удалена');
if ($model->delete()) {
Yii::$app->session->setFlash('success', $message);
}
if ($model->getErrors()) {
Yii::$app->session->setFlash('error', json_encode($model->getErrors(), JSON_UNESCAPED_UNICODE));
}
return $this->redirect(['index']);
}
protected function findModel($id)
{
$model = Schema::find()
->where(['id' => $id])
->one();
if ($model !== null) {
return $model;
} else {
throw new NotFoundHttpException(Yii::t('app/error', 'The requested page does not exist.'));
}
}
}

View File

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

View File

@ -0,0 +1,30 @@
<?php
use yii\web\View;
use yii\helpers\Html;
use yii\widgets\ActiveForm;
use common\components\schema\models\schema;
/**
* @var $this View
* @var $form ActiveForm
* @var $model Schema
*/
?>
<div class="row">
<?php $form = ActiveForm::begin(); ?>
<div class="col-lg-12 mb-3">
<?= $form->field($model, 'name')->textarea(['maxlength' => 255]) ?>
</div>
<div class="row">
<div class="col-md-12 col-lg-6 mb-3">
<?= $form->field($model, 'type')->dropDownList(Schema::getTypeList()) ?>
</div>
</div>
<div class="col-xs-12">
<?= Html::submitButton($model->isNewRecord ? Yii::t('schema', 'Добавить') : Yii::t('schema', 'Обновить'), ['class' => 'btn btn-outline-primary btn-sm']) ?>
</div>
<?php ActiveForm::end(); ?>
</div>

View File

@ -0,0 +1,27 @@
<?php
use yii\web\View;
use yii\helpers\Html;
use common\components\schema\models\schema;
/**
* @var $this View
* @var $model Schema
*/
$this->title = Yii::t('schema', 'Добавить');
$this->params['breadcrumbs'][] = ['label' => Yii::t('schema', 'Schema'), 'url' => ['index']];
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="row">
<div class="col-xs-12">
<h1><?= Html::encode($this->title) ?></h1>
<?= $this->render('_form', [
'model' => $model,
]) ?>
</div>
</div>

View File

@ -0,0 +1,97 @@
<?php
use yii\helpers\Url;
use yii\helpers\Html;
use yii\grid\GridView;
use yii\widgets\ActiveForm;
use common\components\schema\models\Schema;
use common\components\schema\models\search\SchemaSearch;
/**
* @var $this yii\web\View
* @var $dataProvider yii\data\ActiveDataProvider
* @var $searchModel Schema
*/
$this->title = Yii::t('schema', 'Стоп-слова');
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="row">
<div class="col-xs-12">
<h1><?= Html::encode($this->title) ?></h1>
<div class="table">
<?= Html::a(Yii::t('schema', 'Добавить стоп-слово'), Url::to(['create']), ['class' => 'btn btn-outline-primary btn-sm']) ?>
</div>
<?php $form = ActiveForm::begin(['id' => 'schema-form', 'method' => 'get']); ?>
<div class="row">
<div class="col-xs-12 col-md-4 col-lg-3">
<?= $form->field($searchModel, 'name')->textInput() ?>
</div>
<div class="col-xs-12 col-md-4 col-lg-3">
<?= $form->field($searchModel, 'type')->dropDownList(schema::getTypeList(), ['prompt' => Yii::t('schema', 'Все')]) ?>
</div>
<div class="col-xs-12 col-md-4 col-lg-3">
<?= $form->field($searchModel, 'created_at')->widget(DatePicker::class, [
'language' => 'ru',
'containerOptions' => ['class' => 'form-group'],
'options' => ['class' => 'form-control'],
'dateFormat' => 'php:d.m.Y',
])->label() ?>
</div>
</div>
<br/>
<div class="row">
<div class="col-xs-12 col-md-6 col-lg-4">
<?= Html::submitButton(Yii::t('schema', 'Фильтр'), ['class' => 'btn btn-sm btn-success']) ?>
<?= Html::a(Yii::t('schema', 'Отчистить фильтр'), Url::to(['/bad-word']), ['class' => 'btn btn-outline-dark btn-sm']) ?>
</div>
</div>
<?php ActiveForm::end(); ?>
<br/>
<div class="table-responsive">
<?= GridView::widget([
'tableOptions' => ['class' => 'table table-striped table-bordered'],
'dataProvider' => $dataProvider,
'pager' => [
'firstPageLabel' => Yii::t('schema', 'В начало'),
'lastPageLabel' => Yii::t('schema', 'В конец'),
],
'columns' => [
['class' => yii\grid\SerialColumn::class],
'name',
[
'attribute' => 'type',
'value' => function ($model) {
return schema::getTypeList($model->type);
},
],
'created_at:date',
[
'class' => yii\grid\ActionColumn::class,
'template' => '{update}',
'buttons' => [
'update' => function ($url, $model, $key) {
return Html::a(Yii::t('schema', '<span class="fa" aria-hidden="true">{icon}</span>', [
'icon' => HtmlHelper::ICON_UPDATE,
]), $url);
},
],
],
[
// todo ajax refactor
'class' => yii\grid\ActionColumn::class,
'template' => '{delete}',
'buttons' => [
'delete' => function ($url, $model, $key) {
return Html::a(Yii::t('schema', '<span class="fa" aria-hidden="true">{icon}</span>', [
'icon' => HtmlHelper::ICON_DELETE,
]), $url, ['id' => "delete-bad-word-$model->id"]);
},
],
],
],
]) ?>
</div>
</div>
</div>

View File

@ -0,0 +1,24 @@
<?php
use yii\helpers\Html;
use common\components\schema\models\Schema;
/**
* @var $this yii\web\View
* @var $model Schema
*/
$this->title = Yii::t('schema', 'Обновить') . ": " . Yii::t('schema', 'стоп-слово') . "#$model->id";
$this->params['breadcrumbs'][] = ['label' => Yii::t('schema', 'Стоп-слова'), 'url' => ['index']];
$this->params['breadcrumbs'][] = $model->id;
?>
<div class="row">
<div class="col-xs-12">
<h1><?= Html::encode($this->title) ?></h1>
<?= $this->render('_form', [
'model' => $model,
]) ?>
</div>
</div>

View File

@ -0,0 +1,47 @@
<?php
namespace backend\modules\user;
use Yii;
use yii\web\Application;
use yii\i18n\PhpMessageSource;
use yii\base\BootstrapInterface;
class Module extends yii\base\Module implements BootstrapInterface
{
public $controllerNamespace = 'backend\modules\user\controllers';
public function init()
{
parent::init();
$this->registerTranslations();
}
public function registerTranslations()
{
Yii::$app->i18n->translations['user*'] = [
'class' => PhpMessageSource::class,
'sourceLanguage' => 'ru-RU',
'basePath' => '@backend/modules/user/messages',
'fileMap' => [
'user' => 'user.php',
],
];
}
public static function t($category, $message, $params = [], $language = null): string
{
return Yii::t('user' . $category, $message, $params, $language);
}
public function bootstrap($app)
{
if ($app instanceof Application) {
$app->getUrlManager()->addRules([
['class' => 'yii\web\UrlRule', 'pattern' => $this->id, 'route' => $this->id . '/default/index'],
['class' => 'yii\web\UrlRule', 'pattern' => $this->id . '<id:\w+>', 'route' => $this->id . '/default/view'],
['class' => 'yii\web\UrlRule', 'pattern' => $this->id . '<controller:[\w\-]+>/<action:[\w\-]+>', 'route' => $this->id . '/<controller>/<action>'],
], false);
}
}
}

View File

@ -0,0 +1,62 @@
# Модуль User
## Подключение модуля
'modules' => [
'user' => [
'class' => backend\modules\user\Module::class,
],
]
Прописываем nameSpace в сгенерированном файле для подключения к проекту
namespace backend\modules\user\migrations;
Добавляем миграции в настройки проекта
'controllerMap' => [
'migrate' => [
'class' => yii\console\controllers\MigrateController::class,
'migrationNamespaces' => [
'backend\modules\user\migrations',
],
],
],
docker-compose run --rm php ./yii migrate --migrationPath=@yii/rbac/migrations
docker-compose run --rm php ./yii migrate
Добавление миграций
docker-compose run --rm php ./yii migrate/create --migrationPath=@backend/modules/user/migrations create_users_table
##Roles
user_customer:
- customer (заказчик);
user_employee:
- author (автор/исполнитель);
- technic (ТЗ-мейкер);
- auditor (аудитор);
- manager (контент-менеджер);
user_admin:
- supervisor (супервайзер);
- admin (администратор);
test data:
- admin@content.local:admin
- supervisor@content.local:supervisor
- customer@content.local:customer
- technic@content.local:technic
- author@content.local:author
- auditor@content.local:auditor
- manager@content.local:manager

View File

@ -0,0 +1,174 @@
<?php
namespace backend\modules\user\controllers;
use Yii;
use yii\helpers\Url;
use yii\web\Response;
use yii\web\Controller;
use common\helpers\UserHelper;
use yii\filters\AccessControl;
use yii\web\NotFoundHttpException;
use common\components\user\models\User;
use frontend\models\PasswordResetRequestForm;
use common\components\user\models\search\UserSearch;
class DefaultController extends Controller
{
public function behaviors(): array
{
return [
'access' => [
'class' => AccessControl::class,
'denyCallback' => function () {
return $this->goHome();
},
'rules' => [
[
'allow' => true,
'roles' => [UserHelper::ROLE_SUPERVISOR],
],
],
],
];
}
public function actionIndex(): string
{
$searchModel = new UserSearch();
$params = Yii::$app->request->queryParams;
$dataProvider = $searchModel->search($params);
return $this->render('index', [
'dataProvider' => $dataProvider,
'searchModel' => $searchModel,
]);
}
public function actionCreate(): string|Response
{
$auth = Yii::$app->authManager;
$model = new User();
$model->setScenario(User::SCENARIO_USER_CREATE);
if ($model->load(Yii::$app->request->post())) {
$model->generateAuthKey();
$model->setPassword(Yii::$app->security->generateRandomKey(Yii::$app->params['user.passwordMinLength']));
$model->type = match ($model->role) {
UserHelper::ROLE_AUDITOR, UserHelper::ROLE_MANAGER, UserHelper::ROLE_TECHNIC, UserHelper::ROLE_AUTHOR => UserHelper::TYPE_EMPLOYEE,
UserHelper::ROLE_ADMIN, UserHelper::ROLE_SUPERVISOR => UserHelper::TYPE_ADMIN,
default => UserHelper::TYPE_CUSTOMER,
};
if ($model->save()) {
$userRole = $auth->getRole($model->role);
$auth->assign($userRole, $model->id);
Yii::$app->session->setFlash('success', Yii::t('user', 'Создан'));
return $this->redirect(['update', 'id' => $model->id]);
} else {
if ($model->getErrors()) {
Yii::$app->session->setFlash('error', json_encode($model->getErrors(), JSON_UNESCAPED_UNICODE));
}
}
}
return $this->render('create', [
'model' => $model,
]);
}
public function actionUpdate($id): Response|string
{
$model = $this->findModel($id);
$userType = $model->typeName;
$model->$userType = $model->meta;
if ($model->load(Yii::$app->request->post()) && $model->save()) {
if ($model->role) {
$model->setRole($model->role);
$model->meta->load($model->$userType, '');
$model->meta->save();
Yii::$app->session->setFlash('success', Yii::t('user', 'Пользватель {email} обновлён', [
'email' => $model->email,
]));
return $this->redirect(['index']);
}
Yii::$app->session->setFlash('error', Yii::t('user', 'Роль не указана'));
return $this->render('update', [
'model' => $model,
]);
} else {
if ($model->getErrors()) Yii::$app->session->setFlash('error', json_encode($model->getErrors(), JSON_UNESCAPED_UNICODE));
return $this->render('update', [
'model' => $model,
]);
}
}
public function actionBan($id): Response
{
$model = $this->findModel($id);
$model->status = UserHelper::STATUS_BLOCKED_MANUAL;
if ($model->save()) {
Yii::$app->session->setFlash('success', Yii::t('user', '{email} заблокирован', [
'email' => $model->email,
]));
}
if ($model->getErrors()) {
Yii::$app->session->setFlash('error', json_encode($model->getErrors(), JSON_UNESCAPED_UNICODE));
}
return $this->redirect(['index']);
}
public function actionResetPassword($id): Response
{
$model = $this->findModel($id);
if ($model->status == UserHelper::STATUS_ACTIVE || $model->status == UserHelper::STATUS_NEW || $model->status == UserHelper::STATUS_TEST) {
$user = new PasswordResetRequestForm();
$user->email = $model->email;
if ($user->validate()) {
if ($token = $user->sendEmail()) {
$message = Yii::t('user', 'Пароль сброшен для {email}: {link}', [
'email' => $model->email,
'link' => Yii::$app->params['webUrl'] . Url::to(['/reset-password', 'token' => $token]),
]);
Yii::$app->telegram->sendMessage(Yii::$app->params['telegram']['accountExpirationChatId'], $message);
Yii::$app->session->setFlash('success', $message);
} else {
Yii::$app->session->setFlash('error', 'Нельзя отправить письмо на {email}', [
'email' => $model->email,
]);
}
}
} else {
Yii::$app->session->setFlash('error', Yii::t('user', 'Пользователь не должен быть забанен'));
}
return $this->redirect(['/user']);
}
protected function findModel($id): array|User
{
$model = User::find()
->select([User::tableName() . '.*', 'auth_assignment.item_name as role'])
->where([User::tableName() . '.id' => $id])
->leftJoin('auth_assignment', 'auth_assignment.user_id = ' . User::tableName() . '.id')
->one();
if ($model !== null) {
return $model;
} else {
throw new NotFoundHttpException(Yii::t('app/error', 'The requested page does not exist.'));
}
}
}

View File

@ -0,0 +1,73 @@
<?php
return [
//users
'ID' => 'Номер',
'Пользователь' => 'Пользователь',
'Пароль' => 'Пароль',
'Email' => 'Email',
'Роль' => 'Роль',
'Роли' => 'Роли',
'Имя' => 'Имя',
'Фамилия' => 'Фамилия',
'Отчество' => 'Отчество',
'Полное имя' => 'Полное имя',
'День рождения' => 'День рождения',
'Аватар' => 'Аватар',
'Локация' => 'Локация',
'Телефон' => 'Телефон',
'Телеграм' => 'Телеграм',
'Ник' => 'Ник',
'Соцсеть' => 'Соцсеть',
'Попыток тестирования' => 'Попыток тестирования',
'Результат тестирования' => 'Результат тестирования',
'Дата тестирования' => 'Дата тестирования',
'Аккаунт подтверждён' => 'Аккаунт подтверждён',
'Заблокирован' => 'Заблокирован',
//userStatus
'Забанен вручную' => 'Забанен вручную',
'Забанен автоматически' => 'Забанен автоматически',
'Новый' => 'Новый',
'Подтверждён' => 'Подтверждён',
'Прошёл тест' => 'Прошёл тест',
'Активный' => 'Активный',
// event
'Новый аккаунт {role} создан: {email}' => 'Новый аккаунт {role} создан: {email}',
'Ошибка при создании пользователя: {error}' => 'Ошибка при создании пользователя: {error}',
// userType
'Администратор' => 'Администратор',
'Заказчик' => 'Заказчик',
'Исполнитель' => 'Исполнитель',
//userRole
'Супервайзер' => 'Супервайзер',
'ТЗ мейкер' => 'ТЗ мейкер',
'Аудитор' => 'Аудитор',
'Контент менеджер' => 'Контент менеджер',
// user
'Все' => 'Все',
'Фильтр' => 'Фильтр',
'Добавить пользователя' => 'Добавить пользователя',
'Сбросить фильтр' => 'Сбросить фильтр',
'В начало' => 'В начало',
'В конец' => 'В конец',
'Сброс пароля' => 'Сброс пароля',
'{email} заблокирован' => '{email} заблокирован',
'Пользватель {email} обновлён' => 'Пользватель {email} обновлён',
'Создан' => 'Создан',
'Пользователь не должен быть забанен' => 'Пользователь не должен быть забанен',
'Нельзя отправить письмо на {email}' => 'Нельзя отправить письмо на {email}',
'Пароль сброшен для {email}: {link}' => 'Пароль сброшен для {email}: {link}',
'Выбери роль' => 'Выбери роль',
'Добавить' => 'Добавить',
'Обновить' => 'Обновить',
'Обновление {email}' => 'Обновление {email}',
'Добавление' => 'Добавление',
'Роль не указана' => 'Роль не указана',
'Статус изменён: {email}' => 'Статус изменён: {email}',
];

View File

@ -0,0 +1,33 @@
<?php
namespace backend\modules\user\migrations;
use yii\db\Migration;
class m240226_083100_modify_user_table extends Migration
{
public string $userTable = '{{%user}}';
public function safeUp(): bool
{
$table = $this->db->schema->getTableSchema($this->userTable);
if ($table) {
if (!isset($table->columns['type'])) {
$this->addColumn($this->userTable, 'type', $this->smallInteger()->notNull()->after('status'));
}
return true;
}
return false;
}
public function safeDown(): bool
{
if (YII_ENV_PROD) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,69 @@
<?php
namespace backend\modules\user\migrations;
use yii\db\Migration;
/**
* Handles the creation of table `{{%user_employee}}`.
*/
class m240226_083103_create_employee_table extends Migration
{
public string $employeeTable = '{{%user_employee}}';
public string $userTable = '{{%user}}';
public function safeUp(): bool
{
$tableOptions = null;
if ($this->db->driverName === 'mysql') {
$tableOptions = 'CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE=InnoDB';
}
if ($this->db->schema->getTableSchema($this->userTable)) {
if ($this->db->schema->getTableSchema($this->employeeTable) === null) {
$this->createTable($this->employeeTable, [
'id' => $this->primaryKey(),
'user_id' => $this->integer()->notNull(),
'first_name' => $this->string()->notNull(),
'last_name' => $this->string()->null(),
'middle_name' => $this->string()->null(),
'full_name' => $this->string(),
'birthday' => $this->integer(),
'avatar' => $this->string(),
'phone' => $this->string(),
'telegram' => $this->string(),
'location' => $this->string(),
'verified_at' => $this->integer(),
'blocked_at' => $this->integer(),
], $tableOptions);
$this->addForeignKey('fk-employee-user', $this->employeeTable, 'user_id', $this->userTable, 'id', 'CASCADE', 'NO ACTION');
$this->createIndex('idx-user_id', $this->employeeTable, 'user_id');
$this->createIndex('idx-full_name', $this->employeeTable, 'full_name');
}
return true;
}
return false;
}
public function safeDown(): bool
{
if (YII_ENV_PROD) {
return false;
}
$table = $this->db->schema->getTableSchema($this->employeeTable);
if ($table != null) {
if (isset($table->foreignKeys) && array_key_exists('fk-employee-user', $table->foreignKeys) == true) {
$this->dropForeignKey('fk-employee-user', $this->employeeTable);
}
$this->dropTable($this->employeeTable);
}
return true;
}
}

View File

@ -0,0 +1,70 @@
<?php
namespace backend\modules\user\migrations;
use yii\db\Migration;
/**
* Handles the creation of table `{{%user_customer}}`.
*/
class m240226_083135_create_customer_table extends Migration
{
public string $customerTable = '{{%user_customer}}';
public string $userTable = '{{%user}}';
public function safeUp(): bool
{
$tableOptions = null;
if ($this->db->driverName === 'mysql') {
$tableOptions = 'CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE=InnoDB';
}
if ($this->db->schema->getTableSchema($this->userTable)) {
if ($this->db->schema->getTableSchema($this->customerTable) == null) {
$this->createTable($this->customerTable, [
'id' => $this->primaryKey(),
'user_id' => $this->integer()->notNull(),
'first_name' => $this->string()->notNull(),
'last_name' => $this->string()->null(),
'middle_name' => $this->string()->null(),
'full_name' => $this->string(),
'birthday' => $this->integer(),
'avatar' => $this->string(),
'phone' => $this->string(),
'telegram' => $this->string(),
'location' => $this->string(),
'verified_at' => $this->integer(),
'blocked_at' => $this->integer(),
], $tableOptions);
$this->addForeignKey('fk-customer-user', $this->customerTable, 'user_id', $this->userTable, 'id', 'CASCADE', 'NO ACTION');
$this->createIndex('idx-user_id', $this->customerTable, 'user_id');
$this->createIndex('idx-full_name', $this->customerTable, 'full_name');
}
return true;
}
return false;
}
public function safeDown(): bool
{
if (YII_ENV_PROD) {
return false;
}
$table = $this->db->schema->getTableSchema($this->customerTable);
if ($table != null) {
if (isset($table->foreignKeys) && array_key_exists('fk-customer-user', $table->foreignKeys) == true) {
$this->dropForeignKey('fk-customer-user', $this->customerTable);
}
$this->dropTable($this->customerTable);
}
return true;
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace backend\modules\user\migrations;
use yii\db\Migration;
/**
* Handles the creation of table `{{%user_admin}}`.
*/
class m240226_083203_create_admin_table extends Migration
{
public string $adminTable = '{{%user_admin}}';
public string $userTable = '{{%user}}';
public function safeUp(): bool
{
$tableOptions = null;
if ($this->db->driverName === 'mysql') {
$tableOptions = 'CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE=InnoDB';
}
if ($this->db->schema->getTableSchema($this->userTable)) {
if ($this->db->schema->getTableSchema($this->adminTable) == null) {
$this->createTable($this->adminTable, [
'id' => $this->primaryKey(),
'user_id' => $this->integer()->notNull(),
'first_name' => $this->string()->notNull(),
'last_name' => $this->string()->null(),
'middle_name' => $this->string()->null(),
'full_name' => $this->string(),
'birthday' => $this->integer(),
'avatar' => $this->string(),
'phone' => $this->string(),
'telegram' => $this->string(),
'location' => $this->string(),
], $tableOptions);
$this->addForeignKey('fk-admin-user', $this->adminTable, 'user_id', $this->userTable, 'id', 'CASCADE', 'NO ACTION');
$this->createIndex('idx-user_id', $this->adminTable, 'user_id');
$this->createIndex('idx-full_name', $this->adminTable, 'full_name');
}
return true;
}
return false;
}
public function safeDown(): bool
{
if (YII_ENV_PROD) {
return false;
}
$table = $this->db->schema->getTableSchema($this->adminTable);
if ($table != null) {
if (isset($table->foreignKeys) && array_key_exists('fk-admin-user', $table->foreignKeys) == true) {
$this->dropForeignKey('fk-admin-user', $this->adminTable);
}
$this->dropTable($this->adminTable);
}
return true;
}
}

View File

@ -0,0 +1,223 @@
<?php
namespace backend\modules\user\migrations;
use Yii;
use yii\db\Migration;
use common\helpers\UserHelper;
use common\components\user\models\User;
use common\components\user\models\UserAdmin;
use common\components\user\models\UserCustomer;
use common\components\user\models\UserEmployee;
class m240226_083212_create_users_roles extends Migration
{
public function safeUp(): bool
{
$auth = Yii::$app->authManager;
$dataUsers = [
UserHelper::TYPE_ADMIN => [
UserHelper::ROLE_ADMIN,
UserHelper::ROLE_SUPERVISOR,
],
UserHelper::TYPE_CUSTOMER => [
UserHelper::ROLE_CUSTOMER,
],
UserHelper::TYPE_EMPLOYEE => [
UserHelper::ROLE_MANAGER,
],
];
// create permission create_customer
if (!$auth->getPermission('create_customer')) {
$createCustomer = $auth->createPermission('create_customer');
$createCustomer->description = 'create_customer permission';
$auth->add($createCustomer);
echo "create_customer perm created\n";
} else {
$createCustomer = $auth->getPermission('create_customer');
}
// create permission update_customer
if (!$auth->getPermission('update_customer')) {
$updateCustomer = $auth->createPermission('update_customer');
$updateCustomer->description = 'update_customer permission';
$auth->add($updateCustomer);
echo "update_customer permission created\n";
} else {
$updateCustomer = $auth->getPermission('update_customer');
}
// create customer role
if (!$auth->getRole(UserHelper::ROLE_CUSTOMER)) {
$customer = $auth->createRole(UserHelper::ROLE_CUSTOMER);
$customer->description = UserHelper::ROLE_CUSTOMER;
$auth->add($customer);
$auth->addChild($customer, $updateCustomer);
echo UserHelper::ROLE_CUSTOMER . " role created\n";
} else {
$customer = $auth->getRole('customer');
}
// create permission create_supervisor
if (!$auth->getPermission('create_supervisor')) {
$createSupervisor = $auth->createPermission('create_supervisor');
$createSupervisor->description = 'create_supervisor permission';
$auth->add($createSupervisor);
echo "create_supervisor perm created\n";
} else {
$createSupervisor = $auth->getPermission('create_supervisor');
}
// create permission update_supervisor
if (!$auth->getPermission('update_supervisor')) {
$updateSupervisor = $auth->createPermission('update_supervisor');
$updateSupervisor->description = 'update_supervisor permission';
$auth->add($updateSupervisor);
echo "update_supervisor perm created\n";
} else {
$updateSupervisor = $auth->getPermission('update_supervisor');
}
// create permission create_admin
if (!$auth->getPermission('create_admin')) {
$createAdmin = $auth->createPermission('create_admin');
$createAdmin->description = 'create_admin permission';
$auth->add($createAdmin);
echo "create_admin perm created\n";
} else {
$createAdmin = $auth->getPermission('create_admin');
}
// create permission update_admin
if (!$auth->getPermission('update_admin')) {
$updateAdmin = $auth->createPermission('update_admin');
$updateAdmin->description = 'update_admin permission';
$auth->add($updateAdmin);
echo "update_admin perm created\n";
} else {
$updateAdmin = $auth->getPermission('update_admin');
}
// create manager role
foreach ($dataUsers[UserHelper::TYPE_EMPLOYEE] as $employee) {
if (!$auth->getPermission("create_$employee")) {
$createEmployee = $auth->createPermission("create_$employee");
$createEmployee->description = "create_$employee permission";
$auth->add($createEmployee);
echo "create_$employee perm created\n";
}
if (!$auth->getPermission("update_$employee")) {
$updateEmployee = $auth->createPermission("update_$employee");
$updateEmployee->description = "update_$employee permission";
$auth->add($updateEmployee);
echo "update_$employee perm created\n";
} else {
$updateEmployee = $auth->getPermission("update_$employee");
}
if (!$auth->getRole($employee)) {
$$employee = $auth->createRole($employee);
$$employee->description = $employee;
$auth->add($$employee);
$auth->addChild($$employee, $updateEmployee);
echo "$employee role created\n";
}
}
// create supervisor role
if (!$auth->getRole(UserHelper::ROLE_SUPERVISOR)) {
$supervisor = $auth->createRole(UserHelper::ROLE_SUPERVISOR);
$supervisor->description = UserHelper::ROLE_SUPERVISOR;
$auth->add($supervisor);
$auth->addChild($supervisor, $updateSupervisor);
$auth->addChild($supervisor, $createCustomer);
$auth->addChild($supervisor, $customer);
foreach ($dataUsers[UserHelper::TYPE_EMPLOYEE] as $employee) {
$auth->addChild($supervisor, $$employee);
$auth->addChild($supervisor, $auth->getPermission("create_$employee"));
}
echo UserHelper::ROLE_SUPERVISOR . " role created\n";
} else {
$supervisor = $auth->getRole(UserHelper::ROLE_SUPERVISOR);
}
// create admin role
if (!$auth->getRole(UserHelper::ROLE_ADMIN)) {
$admin = $auth->createRole(UserHelper::ROLE_ADMIN);
$admin->description = UserHelper::ROLE_ADMIN;
$auth->add($admin);
$auth->addChild($admin, $supervisor);
$auth->addChild($admin, $createSupervisor);
$auth->addChild($admin, $createAdmin);
$auth->addChild($admin, $updateAdmin);
echo UserHelper::ROLE_ADMIN . " role created\n";
}
if (YII_ENV_PROD) {
echo "Rbac is OK\n";
return true;
}
foreach ($dataUsers as $type => $users) {
foreach ($users as $role) {
self::createUser($role, $type, $auth);
}
}
echo "Rbac is OK\n";
return true;
}
public function safeDown(): bool
{
if (YII_ENV_PROD) {
return false;
}
$auth = Yii::$app->authManager;
$auth->removeAll();
User::deleteAll();
return true;
}
private function createUser($role, $type, $auth): void
{
if (!User::findOne(['email' => "$role@web.local"])) {
$model = new User();
$model->email = "$role@web.local";
$model->username = $role;
$model->status = UserHelper::STATUS_ACTIVE;
$model->type = $type;
$model->role = $role;
$model->setPassword($role);
$model->generateAuthKey();
if ($model->save()) {
$userMeta = new UserCustomer();
if ($model->type == UserHelper::TYPE_ADMIN) {
$userMeta = new UserAdmin();
} elseif ($model->type == UserHelper::TYPE_EMPLOYEE) {
$userMeta = new UserEmployee();
} elseif ($model->type == UserHelper::TYPE_EMPLOYEE) {
$userMeta = new UserEmployee();
}
$authRole = $auth->getRole($role);
$auth->assign($authRole, $model->getId());
$userMeta->first_name = $model->email;
$userMeta->link('user', $model);
echo "$role is created\n";
} else {
echo json_encode($model->getErrors(), JSON_UNESCAPED_UNICODE) . "\n";
echo "$role creation is fail\n";
}
}
}
}

View File

@ -0,0 +1,34 @@
<?php
use yii\web\View;
use yii\helpers\Html;
use yii\widgets\ActiveForm;
use common\helpers\UserHelper;
use common\components\user\models\User;
/**
* @var $this View
* @var $form ActiveForm
* @var $model User
*/
?>
<div class="row">
<?php $form = ActiveForm::begin(); ?>
<div class="col-xs-12 col-md-6 col-lg-3 mb-3">
<?= $form->field($model, 'email')->textInput(['maxlength' => 100]) ?>
</div>
<?php if ($model->isNewRecord) { ?>
<div class="col-xs-12 col-md-6 col-lg-3 mb-3">
<?= $form->field($model, 'role')->dropDownList(UserHelper::roleNameList(), ['prompt' => Yii::t('user', 'Выбери роль')]) ?>
</div>
<?php } else {
echo $this->render('userMeta', ['model' => $model, 'form' => $form]);
} ?>
<div class="col-xs-12">
<?= Html::submitButton($model->isNewRecord ? Yii::t('user', 'Добавить') : Yii::t('user', 'Обновить'), ['class' => 'btn btn-outline-primary btn-sm']) ?>
</div>
<?php ActiveForm::end(); ?>
</div>

View File

@ -0,0 +1,27 @@
<?php
use yii\web\View;
use yii\helpers\Html;
use common\components\user\models\User;
/**
* @var $this View
* @var $model User
*/
$this->title = Yii::t('user', 'Добавление');
$this->params['breadcrumbs'][] = ['label' => Yii::t('user', 'Пользователи'), 'url' => ['index']];
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="row">
<div class="col-xs-12">
<h1><?= Html::encode($this->title) ?></h1>
<?= $this->render('_form', [
'model' => $model,
]) ?>
</div>
</div>

View File

@ -0,0 +1,154 @@
<?php
use common\helpers\HtmlHelper;
use yii\helpers\Url;
use yii\helpers\Html;
use yii\grid\GridView;
use yii\widgets\ActiveForm;
use common\helpers\UserHelper;
use common\components\user\models\User;
use common\components\user\models\search\UserSearch;
/**
* @var $this yii\web\View
* @var $model User
* @var $dataProvider yii\data\ActiveDataProvider
* @var $searchModel UserSearch
*/
$this->title = Yii::t('user', 'Пользователи');
$this->params['breadcrumbs'][] = $this->title;
$script = <<< JS
//Активация/Деактивация пользователя
$("input[type=checkbox].status").on('change',function () {
let id = $(this).attr('data-id');
$.ajax({
type: "get",
url: "/user/default/activate",
data: {
id: id
}
}).done(function(response) {
if(response.status) {
$('#alert-error').hide();
$('#alert-success').html(response.message).fadeIn().delay(300).fadeOut();
if(response.activate) {
$('.reset-password-' + id).removeClass('d-none');
} else {
$('.reset-password-' + id).addClass('d-none');
}
} else {
$('#alert-success').hide();
$('#alert-error').html(response.message).fadeIn().delay(300).fadeOut();
}
});
});
JS;
$this->registerJs($script, yii\web\View::POS_END);
?>
<div class="row">
<div class="col-xs-12">
<h1><?= Html::encode($this->title) ?></h1>
<div class="table">
<?= Html::a(Yii::t('user', 'Добавить пользователя'), Url::to(['create']), ['class' => 'btn btn-outline-primary btn-sm']) ?>
</div>
<?php $form = ActiveForm::begin([
'id' => 'user-form',
'method' => 'get',
'action' => '/user',
]); ?>
<div class="row">
<div class="col-xs-12 col-md-4 col-lg-3">
<?= $form->field($searchModel, 'role')->dropDownList(UserHelper::roleNameList(), ['prompt' => Yii::t('user', 'Все')]) ?>
</div>
<div class="col-xs-12 col-md-4 col-lg-3">
<?= $form->field($searchModel, 'status')->dropDownList(UserHelper::statusNameList(), ['prompt' => Yii::t('user', 'Все')]) ?>
</div>
</div>
<br/>
<div class="row">
<div class="col-xs-12 col-md-6 col-lg-3">
<?= Html::submitButton(Yii::t('user', 'Фильтр'), ['class' => 'btn btn-sm btn-success']) ?>
<?= Html::a(Yii::t('user', 'Сбросить фильтр'), Url::to(['/user']), ['class' => 'btn btn-outline-dark btn-sm']) ?>
</div>
</div>
<?php ActiveForm::end(); ?>
<br/>
<div class="table-responsive">
<?= GridView::widget([
'tableOptions' => ['class' => 'table table-striped table-bordered'],
'dataProvider' => $dataProvider,
'pager' => [
'firstPageLabel' => Yii::t('user', 'В начало'),
'lastPageLabel' => Yii::t('user', 'В конец'),
],
'columns' => [
['class' => yii\grid\SerialColumn::class],
'email',
[
'attribute' => 'role',
'format' => 'raw',
'value' => function ($model) {
if ($model->role) {
return UserHelper::roleNameList($model->role);
} else {
return Yii::t('user', 'Роль не указана');
}
},
],
[
'attribute' => 'status',
'format' => 'raw',
'value' => function ($model) {
return UserHelper::statusNameList($model->status);
},
],
[
'class' => yii\grid\ActionColumn::class,
'template' => '{reset-password}',
'buttons' => [
'reset-password' => function ($url, $model, $key) {
$hidden = 'd-none';
if ($model->status == UserHelper::STATUS_ACTIVE) {
$hidden = '';
}
return Html::a(Yii::t('user', 'Сброс пароля'), $url, ['class' => "btn btn-sm btn-outline-secondary reset-password-{$model->id} $hidden", 'data-id' => $model->id]);
},
],
],
[
'class' => yii\grid\ActionColumn::class,
'template' => '{update}',
'buttons' => [
'update' => function ($url, $model, $key) {
return Html::a(Yii::t('user', '<span class="fa" aria-hidden="true">{icon}</span>', [
'icon' => HtmlHelper::ICON_UPDATE,
]), $url);
},
],
],
[
'class' => yii\grid\ActionColumn::class,
'template' => '{ban}',
'buttons' => [
'ban' => function ($url, $model, $key) {
if ($model->status != UserHelper::STATUS_BLOCKED_MANUAL && $model->status != UserHelper::STATUS_BLOCKED_AUTO) {
return Html::a(Yii::t('user', '<span class="fa" aria-hidden="true">{icon}</span>', [
'icon' => HtmlHelper::ICON_DELETE,
]), $url);
} else {
return '';
}
},
],
],
],
]); ?>
</div>
</div>
</div>

View File

@ -0,0 +1,26 @@
<?php
use yii\helpers\Html;
/**
* @var $this yii\web\View
* @var $model common\components\user\models\User
* @var $roles array
*/
$this->title = Yii::t('user', 'Обновление {email}', [
'email' => $model->email
]);
$this->params['breadcrumbs'][] = ['label' => Yii::t('user', 'Пользователи'), 'url' => ['index']];
$this->params['breadcrumbs'][] = $model->username;
?>
<div class="row">
<div class="col-xs-12">
<h1><?= Html::encode($this->title) ?></h1>
<?= $this->render('_form', [
'model' => $model,
]) ?>
</div>
</div>

View File

@ -0,0 +1,70 @@
<?php
use yii\web\View;
use yii\widgets\ActiveForm;
use common\helpers\UserHelper;
use common\components\user\models\User;
/**
* @var $this View
* @var $form ActiveForm
* @var $model User
*/
$attribute = '';
if ($model->type == UserHelper::TYPE_ADMIN) {
$attribute = 'admin';
?>
<div class="col-xs-12 col-md-8 col-lg-4 mb-3">
<?= $form->field($model, $attribute . '[birthday]')->textInput(['maxlength' => 100])->label(Yii::t('user', 'День рождения')) ?>
</div>
<?php }
if ($model->type == UserHelper::TYPE_EMPLOYEE) {
$attribute = 'employee';
?>
<div class="col-xs-12 col-md-8 col-lg-4 mb-3">
<?= $form->field($model, $attribute . '[birthday]')->textInput(['maxlength' => 100])->label(Yii::t('user', 'День рождения')) ?>
</div>
<div class="col-xs-12 col-md-8 col-lg-4 mb-3">
<?= $form->field($model, $attribute . '[about]')->textInput(['maxlength' => 255])->label(Yii::t('user', 'О себе')) ?>
</div>
<div class="col-xs-12 col-md-8 col-lg-4 mb-3">
<?= $form->field($model, $attribute . '[nick_name]')->textInput(['maxlength' => 100])->label(Yii::t('user', 'Ник')) ?>
</div>
<div class="col-xs-12 col-md-8 col-lg-4 mb-3">
<?= $form->field($model, $attribute . '[web]')->textInput(['maxlength' => 100])->label(Yii::t('user', 'Соц.сети')) ?>
</div>
<div class="col-xs-12 col-md-8 col-lg-4 mb-3">
<?= $form->field($model, $attribute . '[test_count]')->textInput(['maxlength' => 100])->label(Yii::t('user', 'Попытка тестирования')) ?>
</div>
<div class="col-xs-12 col-md-8 col-lg-4 mb-3">
<?= $form->field($model, $attribute . '[test_result]')->textInput(['maxlength' => 100])->label(Yii::t('user', 'Результат тестирования')) ?>
</div>
<?php }
if ($model->type == UserHelper::TYPE_CUSTOMER) {
$attribute = 'customer';
} ?>
<div class="row">
<div class="col-xs-6 col-md-2 mb-3">
<?= $form->field($model, $attribute . '[location]')->dropDownList(UserHelper::locationNames(), ['prompt' => Yii::t('user', '...')])->label(Yii::t('user', 'Код страны')) ?>
</div>
<div class="col-xs-6 col-md-2 mb-3">
<?= $form->field($model, $attribute . '[phone]')->textInput(['maxlength' => 100, 'placeholder' => '901 234 56 78'])->label(Yii::t('user', 'Телефон')) ?>
</div>
</div>
<div class="col-xs-12 col-md-8 col-lg-4 mb-3">
<?= $form->field($model, $attribute . '[last_name]')->textInput(['maxlength' => 100])->label(Yii::t('user', 'Фамилия')) ?>
</div>
<div class="col-xs-12 col-md-8 col-lg-4 mb-3">
<?= $form->field($model, $attribute . '[first_name]')->textInput(['maxlength' => 100])->label(Yii::t('user', 'Имя')) ?>
</div>
<div class="col-xs-12 col-md-8 col-lg-4 mb-3">
<?= $form->field($model, $attribute . '[middle_name]')->textInput(['maxlength' => 100])->label(Yii::t('user', 'Отчество')) ?>
</div>
<div class="col-xs-12 col-md-8 col-lg-4 mb-3">
<?= $form->field($model, $attribute . '[telegram]')->textInput(['maxlength' => 100])->label(Yii::t('user', 'Телеграм')) ?>
</div>

View File

@ -0,0 +1,68 @@
<?php
namespace common\components\schema\models;
use Yii;
use yii\db\ActiveRecord;
use common\helpers\TextHelper;
use yii\behaviors\TimestampBehavior;
/**
* This is the model class for table "{{%schema}}".
*
* @property int $id
* @property string|null $name
* @property int|null $created_at
* @property int|null $updated_at
*
* @property-read int|null|array $typeList
*/
class Schema extends ActiveRecord
{
const TYPE_ACCURATE = 1; // точное exact
const TYPE_SOFT = 2; // разбавочое dilute
public static function tableName(): string
{
return '{{%bad_word}}';
}
public function rules(): array
{
return [
[['created_at', 'updated_at'], 'integer'],
[['name'], 'string', 'max' => 255],
];
}
public function attributeLabels(): array
{
return [
'id' => Yii::t('schema', 'ID'),
'name' => Yii::t('schema', 'Название'),
'created_at' => Yii::t('schema', 'Создано'),
'updated_at' => Yii::t('schema', 'Изменено'),
];
}
public function behaviors(): array
{
return [
TimestampBehavior::class,
];
}
public static function getTypeList($type = null)
{
$typeList = [
self::TYPE_ACCURATE => Yii::t('schema', 'Точное'),
self::TYPE_SOFT => Yii::t('schema', 'Разбавочное'),
];
if ($type) {
return isset($typeList[$type]) ? $typeList[$type] : null;
}
return $typeList;
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace common\components\schema\models\search;
use yii\data\ActiveDataProvider;
use common\components\schema\models\Schema;
class SchemaSearch extends Schema
{
public function scenarios(): array
{
return Schema::scenarios();
}
public function rules(): array
{
return [
[['name', 'created_at'], 'string'],
[['name'], 'string', 'max' => 255],
];
}
public function search($params): ?ActiveDataProvider
{
$query = Schema::find();
$dataProvider = new ActiveDataProvider([
'query' => $query,
'sort' => [
'defaultOrder' => [
'created_at' => SORT_DESC,
'id' => SORT_DESC,
],
],
]);
$this->load($params);
if (!$this->validate()) {
return $dataProvider;
}
$query->andFilterWhere([
'like', 'name', $this->name,
]);
if ($this->created_at) {
$query->andFilterWhere([
'between', 'created_at', strtotime(date('d.m.Y', strtotime($this->created_at))), strtotime(date('d.m.Y', strtotime($this->created_at))) + 86400, // +1 days
]);
}
return $dataProvider;
}
}

View File

@ -0,0 +1,87 @@
<?php
namespace common\components\user\handlers;
use Yii;
use Exception;
use yii\base\Event;
use yii\web\Application;
use common\helpers\UserHelper;
use common\components\user\models\User;
use common\components\user\models\UserAdmin;
use common\components\user\models\UserCustomer;
use common\components\user\models\UserEmployee;
class UserCreateEventHandler
{
static public function run(Event $event)
{
if (Yii::$app instanceof Application) {
/** @var User $user */
$user = $event->sender;
if (!$user || !$user->email) {
try {
self::sendTelegram($user, self::errorMessage());
} catch (Exception $e) {
}
return;
}
$userMeta = new UserCustomer();
$auth = Yii::$app->authManager;
if ($user->type == UserHelper::TYPE_ADMIN) {
$userMeta = new UserAdmin();
} elseif ($user->type == UserHelper::TYPE_EMPLOYEE) {
$userMeta = new UserEmployee();
} elseif ($user->type == UserHelper::TYPE_EMPLOYEE) {
$userMeta = new UserEmployee();
}
$authRole = $auth->getRole($user->role);
$auth->assign($authRole, $user->getId());
$userMeta->first_name = $user->email;
$userMeta->link('user', $user);
$verifyLink = Yii::$app->urlManager->createAbsoluteUrl(['/verify-email', 'token' => $user->verification_token]);
if(!YII_ENV_PROD) {
Yii::$app->telegram->sendMessage(Yii::$app->params['telegram']['accountExpirationChatId'], "Ссылка для верификации (test): " . $verifyLink);
}
try {
self::sendTelegram($user, self::successMessage($user));
} catch (Exception $e) {
//todo mock
}
}
}
static private function sendEmail($email): bool
{
// todo mock
return true;
}
static private function sendTelegram(User $user, $message)
{
Yii::$app->telegram->sendMessage(Yii::$app->params['telegram']['accountExpirationChatId'], $message);
}
static private function errorMessage(): string
{
return Yii::t('user', 'Ошибка при создании пользователя');
}
static private function successMessage(User $user): string
{
return Yii::t('user', 'Новый аккаунт {role} создан: {email}', [
'role' => UserHelper::roleNameList($user->role),
'email' => $user->email,
]);
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace common\components\user\handlers;
use Yii;
use yii\base\Event;
use common\helpers\MailHelper;
use common\components\user\models\User;
class UserStatusChangeEventHandler
{
static public function run(Event $event)
{
/** @var User $user */
$user = $event->sender;
if (!$user || !$user->email) {
return;
}
self::sendTelegram($user);
// self::sendEmail($user->email);
}
static private function sendTelegram(User $user): bool
{
return Yii::$app->telegram->sendMessage(Yii::$app->params['telega']['accountExpirationChatId'], Yii::t('user', 'Статус изменён: {email}', [
'email' => $user->email,
]));
}
static private function sendEmail($email): bool
{
return MailHelper::sendMail(
$email,
null,
Yii::t('app', 'Статус изменён'),
[
'title' => Yii::t('app', 'Статус изменён'),
'body' => Yii::t('user', 'Ваш статус изменился, {link}', [
'link' => Yii::$app->params['webUrl'],
]),
]);
}
}

View File

@ -0,0 +1,362 @@
<?php
namespace common\components\user\models;
use Yii;
use yii\db\ActiveQuery;
use yii\db\ActiveRecord;
use common\helpers\UserHelper;
use yii\web\IdentityInterface;
use yii\base\NotSupportedException;
use yii\behaviors\AttributeBehavior;
use yii\behaviors\TimestampBehavior;
use yii\web\BadRequestHttpException;
/**
* @property integer $id
* @property string $username
* @property string $password_hash
* @property string $password_reset_token
* @property string $verification_token
* @property string $email
* @property string $auth_key
* @property integer $status
* @property integer $created_at
* @property integer $updated_at
* @property string $password write-only password
* @property integer $type
* @property string $role
* @property string $roles
* @property string $typeName (admin|employee|customer)
*
* @property UserAdmin $userAdmin
* @property UserCustomer $userCustomer
* @property UserEmployee $userEmployee
* @property UserAdmin|UserEmployee|UserCustomer $meta
* @property integer $quizCount
* @property void $quizFailed
*/
class User extends ActiveRecord implements IdentityInterface
{
const SCENARIO_USER_CREATE = 'user-create';
const SCENARIO_USER_ADMIN_UPDATE = 'user-admin-update';
const SCENARIO_USER_EMPLOYEE_UPDATE = 'user-employee-update';
const SCENARIO_USER_CUSTOMER_UPDATE = 'user-customer-update';
const EVENT_USER_CREATE = 'userCreateEvent';
const EVENT_USER_UPDATE = 'userUpdateEvent';
const EVENT_USER_STATUS_CHANGED = 'userStatusChanged';
const EVENT_USER_PASSWORD_CHANGED = 'userPasswordChanged';
public $agree;
public $role;
public $admin;
public $employee;
public $customer;
public $payment_bank;
public static function tableName(): string
{
return '{{%user}}';
}
public function rules(): array
{
return [
[['email'], 'unique', 'message' => Yii::t('app', 'Почта уже занята')],
[['username'], 'unique', 'message' => Yii::t('app', 'Логин уже занят')],
[['email'], 'required'],
[['role', 'email'], 'string'],
['status', 'in', 'range' => UserHelper::statusList()],
['type', 'in', 'range' => UserHelper::typeList()],
['role', 'in', 'range' => UserHelper::roleList()],
[['agree', 'payment_bank'], 'safe'],
['role', 'required', 'on' => self::SCENARIO_USER_CREATE],
[['employee', 'customer', 'admin'], 'safe'],
];
}
public function behaviors(): array
{
return [
TimestampBehavior::class,
[
'class' => AttributeBehavior::class,
'attributes' => [
ActiveRecord::EVENT_BEFORE_INSERT => 'username',
ActiveRecord::EVENT_BEFORE_UPDATE => 'username',
],
'value' => function ($event) {
return $this->email;
},
],
];
}
public function afterSave($insert, $changedAttributes)
{
if ($insert) {
if ($this->scenario === self::SCENARIO_USER_CREATE) {
$this->trigger(self::EVENT_USER_CREATE);
}
//todo add scenario user status change
}
parent::afterSave($insert, $changedAttributes);
}
public function scenarios()
{
$scenarios = parent::scenarios();
$scenarios[self::SCENARIO_USER_CREATE] = ['type', 'status', 'email', 'agree', 'role'];
$scenarios[self::SCENARIO_USER_ADMIN_UPDATE] = ['type', 'status', 'email', 'role', 'admin'];
$scenarios[self::SCENARIO_USER_EMPLOYEE_UPDATE] = ['type', 'status', 'email', 'role', 'employee'];
$scenarios[self::SCENARIO_USER_CUSTOMER_UPDATE] = ['type', 'status', 'email', 'role', 'customer'];
return $scenarios;
}
public function attributeLabels(): array
{
return [
'id' => Yii::t('user', 'ID'),
'email' => Yii::t('user', 'Email'),
'name' => Yii::t('user', 'Имя'),
'username' => Yii::t('user', 'Логин'),
'status' => Yii::t('user', 'Статус'),
'type' => Yii::t('user', 'Тип'),
'role' => Yii::t('user', 'Роль'),
'created_at' => Yii::t('user', 'Создан'),
'updated_at' => Yii::t('user', 'Обновлён'),
];
}
public function setRole($role): void
{
if (!isset($role)) {
throw new BadRequestHttpException();
}
$auth = Yii::$app->authManager;
$role = $auth->getRole($role);
if ($role) {
$auth->revokeAll($this->id);
$auth->assign($role, $this->id);
}
}
public function getRole()
{
$role = Yii::$app->authManager->getRolesByUser($this->id);
if ($role) {
return $role;
}
return false;
}
public function getTypeName(): string
{
return match ($this->type) {
UserHelper::TYPE_CUSTOMER => 'customer',
UserHelper::TYPE_EMPLOYEE => 'employee',
UserHelper::TYPE_ADMIN => 'admin',
};
}
public function getUserAdmin(): ActiveQuery
{
return $this->hasOne(UserAdmin::class, ['user_id' => 'id']);
}
public function getUserCustomer(): ActiveQuery
{
return $this->hasOne(UserCustomer::class, ['user_id' => 'id']);
}
public function getUserEmployee(): ActiveQuery
{
return $this->hasOne(UserEmployee::class, ['user_id' => 'id']);
}
public function getMeta(): UserAdmin|UserEmployee|UserCustomer
{
return match ($this->type) {
UserHelper::TYPE_ADMIN => $this->userAdmin,
UserHelper::TYPE_EMPLOYEE => $this->userEmployee,
default => $this->userCustomer,
};
}
static public function current(): User|IdentityInterface|null
{
return Yii::$app->user->identity;
}
public static function findIdentity($id): User|IdentityInterface|null
{
return static::findOne(['and',
['id' => $id],
['>=', 'status', UserHelper::STATUS_NEW],
]
);
}
public static function findIdentityByAccessToken($token, $type = null): ?IdentityInterface
{
//todo translate
throw new NotSupportedException('"findIdentityByAccessToken" is not implemented.');
}
public static function findByUsername(string $email)
{
$user = User::find()->where(['and',
['or',
['email' => $email],
['username' => $email],
],
['>=', 'status', UserHelper::STATUS_NEW],
])->one();
return $user ?? null;
}
public static function findByPasswordResetToken($token): array|ActiveRecord|null
{
if (!static::isPasswordResetTokenValid($token)) {
return null;
}
return static::find()->where(['and',
['password_reset_token' => $token],
['>=', 'status', UserHelper::STATUS_NEW],
])->one();
}
public static function findByVerificationToken($token): array|ActiveRecord|null
{
return static::find()->where(['and',
['verification_token' => $token],
['>=', 'status', UserHelper::STATUS_NEW],
])->one();
}
public static function isPasswordResetTokenValid($token): bool
{
if (empty($token)) {
return false;
}
$timestamp = (int)substr($token, strrpos($token, '_') + 1);
$expire = Yii::$app->params['user.passwordResetTokenExpire'];
return $timestamp + $expire >= time();
}
public function getId()
{
return $this->getPrimaryKey();
}
public function getAuthKey(): ?string
{
return $this->auth_key;
}
public function validatePhone(string $phone): bool
{
return true;
}
public function validateAuthKey($authKey): bool
{
return $this->getAuthKey() === $authKey;
}
public function validatePassword(string $password): bool
{
return Yii::$app->security->validatePassword($password, $this->password_hash);
}
public function setPassword(string $password)
{
$this->password_hash = Yii::$app->security->generatePasswordHash($password);
}
public function generateAuthKey()
{
$this->auth_key = Yii::$app->security->generateRandomString();
}
public function generatePasswordResetToken()
{
$this->password_reset_token = Yii::$app->security->generateRandomString() . '_' . time();
}
public function generateEmailVerificationToken()
{
$this->verification_token = Yii::$app->security->generateRandomString() . '_' . time();
}
public function removePasswordResetToken()
{
$this->password_reset_token = null;
}
public function quizStart($count, $time): void
{
if (UserHelper::TYPE_EMPLOYEE == $this->type) {
$user = UserEmployee::find()->where(['user_id' => $this->id])->one();
if ($user) {
$user->test_try_count = $count;
$user->test_at = $time;
$user->test_result = null;
$user->save(false);
}
}
}
public function quizFailed(): void
{
if (UserHelper::TYPE_EMPLOYEE == $this->type) {
$this->status = UserHelper::STATUS_BLOCKED_AUTO;
$this->save(false);
}
}
public function setTestResult(bool $result): void
{
if (UserHelper::TYPE_EMPLOYEE == $this->type) {
if ($result) {
$this->status = UserHelper::STATUS_TEST;
$this->save(false);
}
$this->meta->test_result = time();
$this->meta->save(false);
}
}
public function testAt(): int|null
{
if (UserHelper::TYPE_EMPLOYEE == $this->type) {
return $this->meta->test_at;
}
return null;
}
public function testTryCount(): int
{
if (UserHelper::TYPE_EMPLOYEE == $this->type) {
return $this->meta->test_try_count ?? 0;
}
return 0;
}
public function getTestResult(): int|null
{
if (UserHelper::TYPE_EMPLOYEE == $this->type) {
return $this->meta->test_result ?? null;
}
return null;
}
}

View File

@ -0,0 +1,86 @@
<?php
namespace common\components\user\models;
use Yii;
use yii\db\ActiveRecord;
use yii\behaviors\AttributeBehavior;
/**
* This is the model class for table "{{%user_admin}}".
*
* @property int $id
* @property int $user_id
* @property string $first_name
* @property string|null $last_name
* @property string|null $middle_name
* @property string|null $full_name
* @property int|null $birthday
* @property string|null $avatar
* @property string|null $location
* @property string|null $phone
* @property string|null $telegram
*
* @property User $user
*/
class UserAdmin extends ActiveRecord
{
public static function tableName(): string
{
return '{{%user_admin}}';
}
public function rules(): array
{
return [
[['user_id', 'first_name'], 'required'],
[['user_id', 'birthday'], 'integer'],
[['first_name', 'last_name', 'middle_name', 'full_name', 'avatar', 'location', 'phone', 'telegram'], 'string', 'max' => 255],
[['user_id'], 'exist', 'skipOnError' => true, 'targetClass' => User::class, 'targetAttribute' => ['user_id' => 'id']],
];
}
public function attributeLabels(): array
{
return [
'id' => Yii::t('user', 'ID'),
'user_id' => Yii::t('user', 'Пользователь'),
'first_name' => Yii::t('user', 'Имя'),
'last_name' => Yii::t('user', 'Фамилия'),
'middle_name' => Yii::t('user', 'Отчество'),
'full_name' => Yii::t('user', 'Полное имя'),
'birthday' => Yii::t('user', 'День рождения'),
'avatar' => Yii::t('user', 'Аватар'),
'location' => Yii::t('user', 'Локация'),
'phone' => Yii::t('user', 'Телефон'),
'telegram' => Yii::t('user', 'Телеграм'),
];
}
public function getUser(): \yii\db\ActiveQuery
{
return $this->hasOne(User::class, ['id' => 'user_id']);
}
public function getFullName(): string
{
return implode(' ', [$this->last_name, $this->first_name, $this->middle_name]);
}
// todo refactor
public function behaviors(): array
{
return [
[
'class' => AttributeBehavior::class,
'attributes' => [
ActiveRecord::EVENT_BEFORE_INSERT => 'full_name',
ActiveRecord::EVENT_BEFORE_UPDATE => 'full_name',
],
'value' => function ($event) {
return self::getFullName();
},
],
];
}
}

View File

@ -0,0 +1,82 @@
<?php
namespace common\components\user\models;
use Yii;
use yii\db\ActiveRecord;
use yii\behaviors\AttributeBehavior;
/**
* This is the model class for table "{{%user_customer}}".
*
* @property int $id
* @property int $user_id
* @property string $first_name
* @property string|null $last_name
* @property string|null $middle_name
* @property string|null $full_name
* @property string|null $telegram
* @property string|null $location
* @property string|null $phone
*
* @property User $user
*/
class UserCustomer extends ActiveRecord
{
public static function tableName(): string
{
return '{{%user_customer}}';
}
public function rules(): array
{
return [
[['user_id', 'first_name'], 'required'],
[['user_id'], 'integer'],
[['first_name', 'last_name', 'middle_name', 'full_name', 'telegram', 'location', 'phone'], 'string', 'max' => 255],
[['user_id'], 'exist', 'skipOnError' => true, 'targetClass' => User::class, 'targetAttribute' => ['user_id' => 'id']],
];
}
public function attributeLabels()
{
return [
'id' => Yii::t('user', 'ID'),
'user_id' => Yii::t('user', 'Пользователь'),
'first_name' => Yii::t('user', 'Имя'),
'last_name' => Yii::t('user', 'Фамилия'),
'middle_name' => Yii::t('user', 'Отчество'),
'full_name' => Yii::t('user', 'Полное имя'),
'telegram' => Yii::t('user', ' Телеграм'),
'location' => Yii::t('user', 'Локация'),
'phone' => Yii::t('user', 'Телефон'),
];
}
public function getUser(): \yii\db\ActiveQuery
{
return $this->hasOne(User::class, ['id' => 'user_id']);
}
public function getFullName(): string
{
return implode(' ', [$this->last_name, $this->first_name, $this->middle_name]);
}
// todo refactor
public function behaviors(): array
{
return [
[
'class' => AttributeBehavior::class,
'attributes' => [
ActiveRecord::EVENT_BEFORE_INSERT => 'full_name',
ActiveRecord::EVENT_BEFORE_UPDATE => 'full_name',
],
'value' => function ($event) {
return self::getFullName();
},
],
];
}
}

View File

@ -0,0 +1,103 @@
<?php
namespace common\components\user\models;
use Yii;
use yii\db\ActiveQuery;
use yii\db\ActiveRecord;
use yii\behaviors\AttributeBehavior;
/**
* This is the model class for table "{{%user_employee}}".
*
* @property int $id
* @property int $user_id
* @property string $first_name
* @property string|null $last_name
* @property string|null $middle_name
* @property string|null $full_name
* @property string|null $about
* @property string|null $nick_name
* @property string|null $web
* @property string|null $telegram
* @property int|null $birthday
* @property string|null $avatar
* @property string|null $location
* @property string|null $phone
* @property int|null $test_try_count
* @property int|null $test_result
* @property int|null $test_at
* @property int|null $verified_at
* @property int|null $blocked_at
*
* @property User $user
*/
class UserEmployee extends ActiveRecord
{
public static function tableName(): string
{
return '{{%user_employee}}';
}
public function rules()
{
return [
[['user_id', 'first_name'], 'required'],
[['user_id', 'birthday', 'test_try_count', 'test_result', 'test_at', 'verified_at', 'blocked_at'], 'integer'],
[['first_name', 'last_name', 'middle_name', 'full_name', 'about', 'nick_name', 'web', 'telegram', 'avatar', 'location', 'phone'], 'string', 'max' => 255],
[['user_id'], 'exist', 'skipOnError' => true, 'targetClass' => User::class, 'targetAttribute' => ['user_id' => 'id']],
];
}
public function attributeLabels(): array
{
return [
'id' => Yii::t('user', 'ID'),
'user_id' => Yii::t('user', 'Пользователь'),
'first_name' => Yii::t('user', 'Имя'),
'last_name' => Yii::t('user', 'Фамилия'),
'middle_name' => Yii::t('user', 'Отчество'),
'full_name' => Yii::t('user', 'Полное имя'),
'about' => Yii::t('user', ''),
'nick_name' => Yii::t('user', 'Ник'),
'web' => Yii::t('user', 'Соцсеть'),
'telegram' => Yii::t('user', 'Телеграм'),
'birthday' => Yii::t('user', 'День рождения'),
'avatar' => Yii::t('user', 'Аватар'),
'location' => Yii::t('user', 'Локация'),
'phone' => Yii::t('user', 'Телефон'),
'test_try_count' => Yii::t('user', 'Попыток тестирования'),
'test_result' => Yii::t('user', 'Результат тестирования'),
'test_at' => Yii::t('user', 'Дата тестирования'),
'verified_at' => Yii::t('user', 'Аккаунт подтверждён'),
'blocked_at' => Yii::t('user', 'Заблокирован'),
];
}
public function getUser(): ActiveQuery
{
return $this->hasOne(User::class, ['id' => 'user_id']);
}
public function getFullName(): string
{
return implode(' ', [$this->last_name, $this->first_name, $this->middle_name]);
}
// todo refactor all usersTypes
public function behaviors(): array
{
return [
[
'class' => AttributeBehavior::class,
'attributes' => [
ActiveRecord::EVENT_BEFORE_INSERT => 'full_name',
ActiveRecord::EVENT_BEFORE_UPDATE => 'full_name',
],
'value' => function ($event) {
return self::getFullName();
},
],
];
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace common\components\user\models;
use Yii;
use yii\base\Model;
/**
* @property string $password
* @property string $passwordConfirm
*/
class UserPassword extends Model
{
public $password;
public $passwordConfirm;
public function rules(): array
{
return [
[['password', 'passwordConfirm'], 'required'],
[['password', 'passwordConfirm'], 'string'],
];
}
public function attributeLabels(): array
{
return [
'password' => Yii::t('user', 'Пароль'),
'passwordConfirm' => Yii::t('user', 'Повторите пароль'),
];
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace common\components\user\models\search;
use yii\data\ActiveDataProvider;
use common\components\user\models\User;
class UserSearch extends User
{
public function scenarios(): array
{
return User::scenarios();
}
public function search($params): ?ActiveDataProvider
{
$query = User::find()
->select([User::tableName() . '.*', 'auth_assignment.item_name as role'])
->leftJoin('auth_assignment', 'auth_assignment.user_id = ' . User::tableName() . '.id');
$dataProvider = new ActiveDataProvider([
'query' => $query,
'sort' => [
'defaultOrder' => ['id' => SORT_ASC],
],
]);
$this->load($params);
if (!$this->validate()) {
return $dataProvider;
}
$query->andFilterWhere([
'status' => $this->status,
'auth_assignment.item_name' => $this->role,
]);
return $dataProvider;
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace common\helpers;
use Yii;
class HtmlHelper
{
const ICON_CREATE = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-plus" viewBox="0 0 16 16"><path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4"/></svg>';
const ICON_UPDATE = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-pencil-square" viewBox="0 0 16 16"><path d="M15.502 1.94a.5.5 0 0 1 0 .706L14.459 3.69l-2-2L13.502.646a.5.5 0 0 1 .707 0l1.293 1.293zm-1.75 2.456-2-2L4.939 9.21a.5.5 0 0 0-.121.196l-.805 2.414a.25.25 0 0 0 .316.316l2.414-.805a.5.5 0 0 0 .196-.12l6.813-6.814z"/><path fill-rule="evenodd" d="M1 13.5A1.5 1.5 0 0 0 2.5 15h11a1.5 1.5 0 0 0 1.5-1.5v-6a.5.5 0 0 0-1 0v6a.5.5 0 0 1-.5.5h-11a.5.5 0 0 1-.5-.5v-11a.5.5 0 0 1 .5-.5H9a.5.5 0 0 0 0-1H2.5A1.5 1.5 0 0 0 1 2.5z"/></svg>';
const ICON_DELETE = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16"><path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0z"/><path d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4zM2.5 3h11V2h-11z"/></svg>';
const ICON_REFRESH = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-repeat" viewBox="0 0 16 16"><path d="M11.534 7h3.932a.25.25 0 0 1 .192.41l-1.966 2.36a.25.25 0 0 1-.384 0l-1.966-2.36a.25.25 0 0 1 .192-.41m-11 2h3.932a.25.25 0 0 0 .192-.41L2.692 6.23a.25.25 0 0 0-.384 0L.342 8.59A.25.25 0 0 0 .534 9"/><path fill-rule="evenodd" d="M8 3c-1.552 0-2.94.707-3.857 1.818a.5.5 0 1 1-.771-.636A6.002 6.002 0 0 1 13.917 7H12.9A5 5 0 0 0 8 3M3.1 9a5.002 5.002 0 0 0 8.757 2.182.5.5 0 1 1 .771.636A6.002 6.002 0 0 1 2.083 9z"/></svg>';
const ICON_BAN = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-ban" viewBox="0 0 16 16"><path d="M15 8a6.97 6.97 0 0 0-1.71-4.584l-9.874 9.875A7 7 0 0 0 15 8M2.71 12.584l9.874-9.875a7 7 0 0 0-9.874 9.874ZM16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0"/></svg>';
const ICON_MAIN_SETTINGS = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-gear" viewBox="0 0 16 16"><path d="M8 4.754a3.246 3.246 0 1 0 0 6.492 3.246 3.246 0 0 0 0-6.492M5.754 8a2.246 2.246 0 1 1 4.492 0 2.246 2.246 0 0 1-4.492 0"/><path d="M9.796 1.343c-.527-1.79-3.065-1.79-3.592 0l-.094.319a.873.873 0 0 1-1.255.52l-.292-.16c-1.64-.892-3.433.902-2.54 2.541l.159.292a.873.873 0 0 1-.52 1.255l-.319.094c-1.79.527-1.79 3.065 0 3.592l.319.094a.873.873 0 0 1 .52 1.255l-.16.292c-.892 1.64.901 3.434 2.541 2.54l.292-.159a.873.873 0 0 1 1.255.52l.094.319c.527 1.79 3.065 1.79 3.592 0l.094-.319a.873.873 0 0 1 1.255-.52l.292.16c1.64.893 3.434-.902 2.54-2.541l-.159-.292a.873.873 0 0 1 .52-1.255l.319-.094c1.79-.527 1.79-3.065 0-3.592l-.319-.094a.873.873 0 0 1-.52-1.255l.16-.292c.893-1.64-.902-3.433-2.541-2.54l-.292.159a.873.873 0 0 1-1.255-.52zm-2.633.283c.246-.835 1.428-.835 1.674 0l.094.319a1.873 1.873 0 0 0 2.693 1.115l.291-.16c.764-.415 1.6.42 1.184 1.185l-.159.292a1.873 1.873 0 0 0 1.116 2.692l.318.094c.835.246.835 1.428 0 1.674l-.319.094a1.873 1.873 0 0 0-1.115 2.693l.16.291c.415.764-.42 1.6-1.185 1.184l-.291-.159a1.873 1.873 0 0 0-2.693 1.116l-.094.318c-.246.835-1.428.835-1.674 0l-.094-.319a1.873 1.873 0 0 0-2.692-1.115l-.292.16c-.764.415-1.6-.42-1.184-1.185l.159-.291A1.873 1.873 0 0 0 1.945 8.93l-.319-.094c-.835-.246-.835-1.428 0-1.674l.319-.094A1.873 1.873 0 0 0 3.06 4.377l-.16-.292c-.415-.764.42-1.6 1.185-1.184l.292.159a1.873 1.873 0 0 0 2.692-1.115z"/></svg>';
const ICON_SETTINGS = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-house-gear" viewBox="0 0 16 16"><path d="M7.293 1.5a1 1 0 0 1 1.414 0L11 3.793V2.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v3.293l2.354 2.353a.5.5 0 0 1-.708.708L8 2.207l-5 5V13.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 1 0 1h-4A1.5 1.5 0 0 1 2 13.5V8.207l-.646.647a.5.5 0 1 1-.708-.708z"/><path d="M11.886 9.46c.18-.613 1.048-.613 1.229 0l.043.148a.64.64 0 0 0 .921.382l.136-.074c.561-.306 1.175.308.87.869l-.075.136a.64.64 0 0 0 .382.92l.149.045c.612.18.612 1.048 0 1.229l-.15.043a.64.64 0 0 0-.38.921l.074.136c.305.561-.309 1.175-.87.87l-.136-.075a.64.64 0 0 0-.92.382l-.045.149c-.18.612-1.048.612-1.229 0l-.043-.15a.64.64 0 0 0-.921-.38l-.136.074c-.561.305-1.175-.309-.87-.87l.075-.136a.64.64 0 0 0-.382-.92l-.148-.044c-.613-.181-.613-1.049 0-1.23l.148-.043a.64.64 0 0 0 .382-.921l-.074-.136c-.306-.561.308-1.175.869-.87l.136.075a.64.64 0 0 0 .92-.382zM14 12.5a1.5 1.5 0 1 0-3 0 1.5 1.5 0 0 0 3 0"/></svg>';
const ICON_HELP = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-question-circle" viewBox="0 0 16 16"><path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"/><path d="M5.255 5.786a.237.237 0 0 0 .241.247h.825c.138 0 .248-.113.266-.25.09-.656.54-1.134 1.342-1.134.686 0 1.314.343 1.314 1.168 0 .635-.374.927-.965 1.371-.673.489-1.206 1.06-1.168 1.987l.003.217a.25.25 0 0 0 .25.246h.811a.25.25 0 0 0 .25-.25v-.105c0-.718.273-.927 1.01-1.486.609-.463 1.244-.977 1.244-2.056 0-1.511-1.276-2.241-2.673-2.241-1.267 0-2.655.59-2.75 2.286m1.557 5.763c0 .533.425.927 1.01.927.609 0 1.028-.394 1.028-.927 0-.552-.42-.94-1.029-.94-.584 0-1.009.388-1.009.94"/></svg>';
const ICON_INFO = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-info-circle" viewBox="0 0 16 16"><path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"/><path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0"/></svg>';
const ICON_USER = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-person-circle" viewBox="0 0 16 16"><path d="M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0"/><path fill-rule="evenodd" d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8m8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1"/></svg>';
const ICON_PEOPLE = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-people" viewBox="0 0 16 16"><path d="M15 14s1 0 1-1-1-4-5-4-5 3-5 4 1 1 1 1zm-7.978-1L7 12.996c.001-.264.167-1.03.76-1.72C8.312 10.629 9.282 10 11 10c1.717 0 2.687.63 3.24 1.276.593.69.758 1.457.76 1.72l-.008.002-.014.002zM11 7a2 2 0 1 0 0-4 2 2 0 0 0 0 4m3-2a3 3 0 1 1-6 0 3 3 0 0 1 6 0M6.936 9.28a6 6 0 0 0-1.23-.247A7 7 0 0 0 5 9c-4 0-5 3-5 4q0 1 1 1h4.216A2.24 2.24 0 0 1 5 13c0-1.01.377-2.042 1.09-2.904.243-.294.526-.569.846-.816M4.92 10A5.5 5.5 0 0 0 4 13H1c0-.26.164-1.03.76-1.724.545-.636 1.492-1.256 3.16-1.275ZM1.5 5.5a3 3 0 1 1 6 0 3 3 0 0 1-6 0m3-2a2 2 0 1 0 0 4 2 2 0 0 0 0-4"/></svg>';
const ICON_LOGIN = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-door-closed" viewBox="0 0 16 16"><path d="M3 2a1 1 0 0 1 1-1h8a1 1 0 0 1 1 1v13h1.5a.5.5 0 0 1 0 1h-13a.5.5 0 0 1 0-1H3zm1 13h8V2H4z"/><path d="M9 9a1 1 0 1 0 2 0 1 1 0 0 0-2 0"/></svg>';
const ICON_LOGOUT = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-door-open" viewBox="0 0 16 16"><path d="M8.5 10c-.276 0-.5-.448-.5-1s.224-1 .5-1 .5.448.5 1-.224 1-.5 1"/><path d="M10.828.122A.5.5 0 0 1 11 .5V1h.5A1.5 1.5 0 0 1 13 2.5V15h1.5a.5.5 0 0 1 0 1h-13a.5.5 0 0 1 0-1H3V1.5a.5.5 0 0 1 .43-.495l7-1a.5.5 0 0 1 .398.117M11.5 2H11v13h1V2.5a.5.5 0 0 0-.5-.5M4 1.934V15h6V1.077z"/></svg>';
const ICON_EMAIL = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-envelope" viewBox="0 0 16 16"><path d="M0 4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm2-1a1 1 0 0 0-1 1v.217l7 4.2 7-4.2V4a1 1 0 0 0-1-1zm13 2.383-4.708 2.825L15 11.105zm-.034 6.876-5.64-3.471L8 9.583l-1.326-.795-5.64 3.47A1 1 0 0 0 2 13h12a1 1 0 0 0 .966-.741M1 11.105l4.708-2.897L1 5.383z"/></svg>';
const ICON_ABC = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-alphabet-uppercase" viewBox="0 0 16 16"><path d="M1.226 10.88H0l2.056-6.26h1.42l2.047 6.26h-1.29l-.48-1.61H1.707l-.48 1.61ZM2.76 5.818h-.054l-.75 2.532H3.51zm3.217 5.062V4.62h2.56c1.09 0 1.808.582 1.808 1.54 0 .762-.444 1.22-1.05 1.372v.055c.736.074 1.365.587 1.365 1.528 0 1.119-.89 1.766-2.133 1.766zM7.18 5.55v1.675h.8c.812 0 1.171-.308 1.171-.853 0-.51-.328-.822-.898-.822zm0 2.537V9.95h.903c.951 0 1.342-.312 1.342-.909 0-.591-.382-.954-1.095-.954zm5.089-.711v.775c0 1.156.49 1.803 1.347 1.803.705 0 1.163-.454 1.212-1.096H16v.12C15.942 10.173 14.95 11 13.607 11c-1.648 0-2.573-1.073-2.573-2.849v-.78c0-1.775.934-2.871 2.573-2.871 1.347 0 2.34.849 2.393 2.087v.115h-1.172c-.05-.665-.516-1.156-1.212-1.156-.849 0-1.347.67-1.347 1.83"/></svg>';
const ICON_BOOK = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-book" viewBox="0 0 16 16"><path d="M1 2.828c.885-.37 2.154-.769 3.388-.893 1.33-.134 2.458.063 3.112.752v9.746c-.935-.53-2.12-.603-3.213-.493-1.18.12-2.37.461-3.287.811zm7.5-.141c.654-.689 1.782-.886 3.112-.752 1.234.124 2.503.523 3.388.893v9.923c-.918-.35-2.107-.692-3.287-.81-1.094-.111-2.278-.039-3.213.492zM8 1.783C7.015.936 5.587.81 4.287.94c-1.514.153-3.042.672-3.994 1.105A.5.5 0 0 0 0 2.5v11a.5.5 0 0 0 .707.455c.882-.4 2.303-.881 3.68-1.02 1.409-.142 2.59.087 3.223.877a.5.5 0 0 0 .78 0c.633-.79 1.814-1.019 3.222-.877 1.378.139 2.8.62 3.681 1.02A.5.5 0 0 0 16 13.5v-11a.5.5 0 0 0-.293-.455c-.952-.433-2.48-.952-3.994-1.105C10.413.809 8.985.936 8 1.783"/></svg>';
const ICON_STAT = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-bar-chart-line" viewBox="0 0 16 16"><path d="M11 2a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v12h.5a.5.5 0 0 1 0 1H.5a.5.5 0 0 1 0-1H1v-3a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v3h1V7a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v7h1zm1 12h2V2h-2zm-3 0V7H7v7zm-5 0v-3H2v3z"/></svg>';
const ICON_MAIN = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-grid" viewBox="0 0 16 16"><path d="M1 2.5A1.5 1.5 0 0 1 2.5 1h3A1.5 1.5 0 0 1 7 2.5v3A1.5 1.5 0 0 1 5.5 7h-3A1.5 1.5 0 0 1 1 5.5zM2.5 2a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5zm6.5.5A1.5 1.5 0 0 1 10.5 1h3A1.5 1.5 0 0 1 15 2.5v3A1.5 1.5 0 0 1 13.5 7h-3A1.5 1.5 0 0 1 9 5.5zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5zM1 10.5A1.5 1.5 0 0 1 2.5 9h3A1.5 1.5 0 0 1 7 10.5v3A1.5 1.5 0 0 1 5.5 15h-3A1.5 1.5 0 0 1 1 13.5zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5zm6.5.5A1.5 1.5 0 0 1 10.5 9h3a1.5 1.5 0 0 1 1.5 1.5v3a1.5 1.5 0 0 1-1.5 1.5h-3A1.5 1.5 0 0 1 9 13.5zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5z"/></svg>';
const ICON_FAQ = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-patch-question" viewBox="0 0 16 16"><path d="M8.05 9.6c.336 0 .504-.24.554-.627.04-.534.198-.815.847-1.26.673-.475 1.049-1.09 1.049-1.986 0-1.325-.92-2.227-2.262-2.227-1.02 0-1.792.492-2.1 1.29A1.7 1.7 0 0 0 6 5.48c0 .393.203.64.545.64.272 0 .455-.147.564-.51.158-.592.525-.915 1.074-.915.61 0 1.03.446 1.03 1.084 0 .563-.208.885-.822 1.325-.619.433-.926.914-.926 1.64v.111c0 .428.208.745.585.745"/><path d="m10.273 2.513-.921-.944.715-.698.622.637.89-.011a2.89 2.89 0 0 1 2.924 2.924l-.01.89.636.622a2.89 2.89 0 0 1 0 4.134l-.637.622.011.89a2.89 2.89 0 0 1-2.924 2.924l-.89-.01-.622.636a2.89 2.89 0 0 1-4.134 0l-.622-.637-.89.011a2.89 2.89 0 0 1-2.924-2.924l.01-.89-.636-.622a2.89 2.89 0 0 1 0-4.134l.637-.622-.011-.89a2.89 2.89 0 0 1 2.924-2.924l.89.01.622-.636a2.89 2.89 0 0 1 4.134 0l-.715.698a1.89 1.89 0 0 0-2.704 0l-.92.944-1.32-.016a1.89 1.89 0 0 0-1.911 1.912l.016 1.318-.944.921a1.89 1.89 0 0 0 0 2.704l.944.92-.016 1.32a1.89 1.89 0 0 0 1.912 1.911l1.318-.016.921.944a1.89 1.89 0 0 0 2.704 0l.92-.944 1.32.016a1.89 1.89 0 0 0 1.911-1.912l-.016-1.318.944-.921a1.89 1.89 0 0 0 0-2.704l-.944-.92.016-1.32a1.89 1.89 0 0 0-1.912-1.911z"/><path d="M7.001 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0"/></svg>';
const ICON_HELP_SQUARE = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-question-square" viewBox="0 0 16 16"><path d="M14 1a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2z"/><path d="M5.255 5.786a.237.237 0 0 0 .241.247h.825c.138 0 .248-.113.266-.25.09-.656.54-1.134 1.342-1.134.686 0 1.314.343 1.314 1.168 0 .635-.374.927-.965 1.371-.673.489-1.206 1.06-1.168 1.987l.003.217a.25.25 0 0 0 .25.246h.811a.25.25 0 0 0 .25-.25v-.105c0-.718.273-.927 1.01-1.486.609-.463 1.244-.977 1.244-2.056 0-1.511-1.276-2.241-2.673-2.241-1.267 0-2.655.59-2.75 2.286m1.557 5.763c0 .533.425.927 1.01.927.609 0 1.028-.394 1.028-.927 0-.552-.42-.94-1.029-.94-.584 0-1.009.388-1.009.94"/></svg>';
const ICON_STOP = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-sign-stop" viewBox="0 0 16 16"><path d="M3.16 10.08c-.931 0-1.447-.493-1.494-1.132h.653c.065.346.396.583.891.583.524 0 .83-.246.83-.62 0-.303-.203-.467-.637-.572l-.656-.164c-.61-.147-.978-.51-.978-1.078 0-.706.597-1.184 1.444-1.184.853 0 1.386.475 1.436 1.087h-.645c-.064-.32-.352-.542-.797-.542-.472 0-.77.246-.77.6 0 .261.196.437.553.522l.654.161c.673.164 1.06.487 1.06 1.11 0 .736-.574 1.228-1.544 1.228Zm3.427-3.51V10h-.665V6.57H4.753V6h3.006v.568H6.587Z"/><path fill-rule="evenodd" d="M11.045 7.73v.544c0 1.131-.636 1.805-1.661 1.805-1.026 0-1.664-.674-1.664-1.805V7.73c0-1.136.638-1.807 1.664-1.807s1.66.674 1.66 1.807Zm-.674.547v-.553c0-.827-.422-1.234-.987-1.234-.572 0-.99.407-.99 1.234v.553c0 .83.418 1.237.99 1.237.565 0 .987-.408.987-1.237m1.15-2.276h1.535c.82 0 1.316.55 1.316 1.292 0 .747-.501 1.289-1.321 1.289h-.865V10h-.665zm1.436 2.036c.463 0 .735-.272.735-.744s-.272-.741-.735-.741h-.774v1.485z"/><path fill-rule="evenodd" d="M4.893 0a.5.5 0 0 0-.353.146L.146 4.54A.5.5 0 0 0 0 4.893v6.214a.5.5 0 0 0 .146.353l4.394 4.394a.5.5 0 0 0 .353.146h6.214a.5.5 0 0 0 .353-.146l4.394-4.394a.5.5 0 0 0 .146-.353V4.893a.5.5 0 0 0-.146-.353L11.46.146A.5.5 0 0 0 11.107 0zM1 5.1 5.1 1h5.8L15 5.1v5.8L10.9 15H5.1L1 10.9z"/></svg>';
const ICON_VIRUS = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-virus" viewBox="0 0 16 16"><path d="M8 0a1 1 0 0 1 1 1v1.402c0 .511.677.693.933.25l.7-1.214a1 1 0 0 1 1.733 1l-.701 1.214c-.256.443.24.939.683.683l1.214-.701a1 1 0 0 1 1 1.732l-1.214.701c-.443.256-.262.933.25.933H15a1 1 0 1 1 0 2h-1.402c-.512 0-.693.677-.25.933l1.214.701a1 1 0 1 1-1 1.732l-1.214-.7c-.443-.257-.939.24-.683.682l.701 1.214a1 1 0 1 1-1.732 1l-.701-1.214c-.256-.443-.933-.262-.933.25V15a1 1 0 1 1-2 0v-1.402c0-.512-.677-.693-.933-.25l-.701 1.214a1 1 0 0 1-1.732-1l.7-1.214c.257-.443-.24-.939-.682-.683l-1.214.701a1 1 0 1 1-1-1.732l1.214-.701c.443-.256.261-.933-.25-.933H1a1 1 0 1 1 0-2h1.402c.511 0 .693-.677.25-.933l-1.214-.701a1 1 0 1 1 1-1.732l1.214.701c.443.256.939-.24.683-.683l-.701-1.214a1 1 0 0 1 1.732-1l.701 1.214c.256.443.933.261.933-.25V1a1 1 0 0 1 1-1m2 5a1 1 0 1 0-2 0 1 1 0 0 0 2 0M6 7a1 1 0 1 0-2 0 1 1 0 0 0 2 0m1 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2m5-3a1 1 0 1 0-2 0 1 1 0 0 0 2 0"/></svg>';
const ICON_TOOLS = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-tools" viewBox="0 0 16 16"><path d="M1 0 0 1l2.2 3.081a1 1 0 0 0 .815.419h.07a1 1 0 0 1 .708.293l2.675 2.675-2.617 2.654A3.003 3.003 0 0 0 0 13a3 3 0 1 0 5.878-.851l2.654-2.617.968.968-.305.914a1 1 0 0 0 .242 1.023l3.27 3.27a.997.997 0 0 0 1.414 0l1.586-1.586a.997.997 0 0 0 0-1.414l-3.27-3.27a1 1 0 0 0-1.023-.242L10.5 9.5l-.96-.96 2.68-2.643A3.005 3.005 0 0 0 16 3q0-.405-.102-.777l-2.14 2.141L12 4l-.364-1.757L13.777.102a3 3 0 0 0-3.675 3.68L7.462 6.46 4.793 3.793a1 1 0 0 1-.293-.707v-.071a1 1 0 0 0-.419-.814zm9.646 10.646a.5.5 0 0 1 .708 0l2.914 2.915a.5.5 0 0 1-.707.707l-2.915-2.914a.5.5 0 0 1 0-.708M3 11l.471.242.529.026.287.445.445.287.026.529L5 13l-.242.471-.026.529-.445.287-.287.445-.529.026L3 15l-.471-.242L2 14.732l-.287-.445L1.268 14l-.026-.529L1 13l.242-.471.026-.529.445-.287.287-.445.529-.026z"/></svg>';
const ICON_FINGERPRINT = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-fingerprint" viewBox="0 0 16 16"><path d="M8.06 6.5a.5.5 0 0 1 .5.5v.776a11.5 11.5 0 0 1-.552 3.519l-1.331 4.14a.5.5 0 0 1-.952-.305l1.33-4.141a10.5 10.5 0 0 0 .504-3.213V7a.5.5 0 0 1 .5-.5Z"/><path d="M6.06 7a2 2 0 1 1 4 0 .5.5 0 1 1-1 0 1 1 0 1 0-2 0v.332q0 .613-.066 1.221A.5.5 0 0 1 6 8.447q.06-.555.06-1.115zm3.509 1a.5.5 0 0 1 .487.513 11.5 11.5 0 0 1-.587 3.339l-1.266 3.8a.5.5 0 0 1-.949-.317l1.267-3.8a10.5 10.5 0 0 0 .535-3.048A.5.5 0 0 1 9.569 8m-3.356 2.115a.5.5 0 0 1 .33.626L5.24 14.939a.5.5 0 1 1-.955-.296l1.303-4.199a.5.5 0 0 1 .625-.329"/><path d="M4.759 5.833A3.501 3.501 0 0 1 11.559 7a.5.5 0 0 1-1 0 2.5 2.5 0 0 0-4.857-.833.5.5 0 1 1-.943-.334m.3 1.67a.5.5 0 0 1 .449.546 10.7 10.7 0 0 1-.4 2.031l-1.222 4.072a.5.5 0 1 1-.958-.287L4.15 9.793a9.7 9.7 0 0 0 .363-1.842.5.5 0 0 1 .546-.449Zm6 .647a.5.5 0 0 1 .5.5c0 1.28-.213 2.552-.632 3.762l-1.09 3.145a.5.5 0 0 1-.944-.327l1.089-3.145c.382-1.105.578-2.266.578-3.435a.5.5 0 0 1 .5-.5Z"/><path d="M3.902 4.222a5 5 0 0 1 5.202-2.113.5.5 0 0 1-.208.979 4 4 0 0 0-4.163 1.69.5.5 0 0 1-.831-.556m6.72-.955a.5.5 0 0 1 .705-.052A4.99 4.99 0 0 1 13.059 7v1.5a.5.5 0 1 1-1 0V7a3.99 3.99 0 0 0-1.386-3.028.5.5 0 0 1-.051-.705M3.68 5.842a.5.5 0 0 1 .422.568q-.044.289-.044.59c0 .71-.1 1.417-.298 2.1l-1.14 3.923a.5.5 0 1 1-.96-.279L2.8 8.821A6.5 6.5 0 0 0 3.058 7q0-.375.054-.736a.5.5 0 0 1 .568-.422m8.882 3.66a.5.5 0 0 1 .456.54c-.084 1-.298 1.986-.64 2.934l-.744 2.068a.5.5 0 0 1-.941-.338l.745-2.07a10.5 10.5 0 0 0 .584-2.678.5.5 0 0 1 .54-.456"/><path d="M4.81 1.37A6.5 6.5 0 0 1 14.56 7a.5.5 0 1 1-1 0 5.5 5.5 0 0 0-8.25-4.765.5.5 0 0 1-.5-.865m-.89 1.257a.5.5 0 0 1 .04.706A5.48 5.48 0 0 0 2.56 7a.5.5 0 0 1-1 0c0-1.664.626-3.184 1.655-4.333a.5.5 0 0 1 .706-.04ZM1.915 8.02a.5.5 0 0 1 .346.616l-.779 2.767a.5.5 0 1 1-.962-.27l.778-2.767a.5.5 0 0 1 .617-.346m12.15.481a.5.5 0 0 1 .49.51c-.03 1.499-.161 3.025-.727 4.533l-.07.187a.5.5 0 0 1-.936-.351l.07-.187c.506-1.35.634-2.74.663-4.202a.5.5 0 0 1 .51-.49"/></svg>';
const ICON_CHECK_TEXT = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-spellcheck" viewBox="0 0 16 16"><path d="M8.217 11.068c1.216 0 1.948-.869 1.948-2.31v-.702c0-1.44-.727-2.305-1.929-2.305-.742 0-1.328.347-1.499.889h-.063V3.983h-1.29V11h1.27v-.791h.064c.21.532.776.86 1.499.86zm-.43-1.025c-.66 0-1.113-.518-1.113-1.28V8.12c0-.825.42-1.343 1.098-1.343.684 0 1.075.518 1.075 1.416v.45c0 .888-.386 1.401-1.06 1.401zm-5.583 1.035c.767 0 1.201-.356 1.406-.737h.059V11h1.216V7.519c0-1.314-.947-1.783-2.11-1.783C1.355 5.736.75 6.42.69 7.27h1.216c.064-.323.313-.552.84-.552s.864.249.864.771v.464H2.346C1.145 7.953.5 8.568.5 9.496c0 .977.693 1.582 1.704 1.582m.42-.947c-.44 0-.845-.235-.845-.718 0-.395.269-.684.84-.684h.991v.538c0 .503-.444.864-.986.864m8.897.567c-.577-.4-.9-1.088-.9-1.983v-.65c0-1.42.894-2.338 2.305-2.338 1.352 0 2.119.82 2.139 1.806h-1.187c-.04-.351-.283-.776-.918-.776-.674 0-1.045.517-1.045 1.328v.625c0 .468.121.834.343 1.067z"/><path d="M14.469 9.414a.75.75 0 0 1 .117 1.055l-4 5a.75.75 0 0 1-1.116.061l-2.5-2.5a.75.75 0 1 1 1.06-1.06l1.908 1.907 3.476-4.346a.75.75 0 0 1 1.055-.117"/></svg>';
}

View File

@ -0,0 +1,26 @@
<?php
namespace common\helpers;
use Yii;
class MailHelper
{
static public function sendMail($emailTo, $emailFrom, $subject, $body, $view = null): bool
{
if (!$emailFrom) {
$emailFrom = [Yii::$app->params['senderEmail'] => Yii::$app->params['senderName']];
}
return Yii::$app
->mailer
->compose(
$view,
$body
)
->setFrom($emailFrom)
->setTo($emailTo)
->setSubject($subject)
->send();
}
}

View File

@ -0,0 +1,172 @@
<?php
namespace common\helpers;
use Yii;
class UserHelper
{
//userGenders
const GENDER_MALE = 1;
const GENDER_FEMALE = 2;
//userStatuses
const STATUS_BLOCKED = 1;
const STATUS_NEW = 10;
const STATUS_ACTIVE = 100;
// userTypes
const TYPE_CUSTOMER = 1;
const TYPE_EMPLOYEE = 10;
const TYPE_ADMIN = 100;
//RBAC
const ROLE_ADMIN = 'admin';
const ROLE_SUPERVISOR = 'supervisor';
const ROLE_CUSTOMER = 'customer';
const ROLE_MANAGER = 'manager';
public static function statusList(): array
{
return [
self::STATUS_BLOCKED,
self::STATUS_NEW,
self::STATUS_ACTIVE,
];
}
public static function statusNameList($status = null)
{
$statusList = [
self::STATUS_BLOCKED => Yii::t('user', 'Забанен'),
self::STATUS_NEW => Yii::t('user', 'Новый'),
self::STATUS_ACTIVE => Yii::t('user', 'Активен'),
];
if ($status) {
return isset($statusList[$status]) ? $statusList[$status] : null;
}
return $statusList;
}
public static function typeList(): array
{
return [
self::TYPE_ADMIN,
self::TYPE_CUSTOMER,
self::TYPE_EMPLOYEE,
];
}
public static function clientTypeList(): array
{
return [
self::TYPE_CUSTOMER,
self::TYPE_EMPLOYEE,
];
}
public static function typeNameList($type = null): array|string|null
{
$typeNames = [
self::TYPE_ADMIN => Yii::t('user', 'Администратор'),
self::TYPE_CUSTOMER => Yii::t('user', 'Заказчик'),
self::TYPE_EMPLOYEE => Yii::t('user', 'Сотрудник'),
];
if ($type) {
return isset($typeNames[$type]) ? $typeNames[$type] : null;
}
return $typeNames;
}
public static function roleList(): array
{
return [
self::ROLE_ADMIN,
self::ROLE_SUPERVISOR,
self::ROLE_CUSTOMER,
self::ROLE_MANAGER,
];
}
public static function roleNameList($role = null)
{
$roleNames = [
self::ROLE_ADMIN => Yii::t('user', 'Администратор'),
self::ROLE_SUPERVISOR => Yii::t('user', 'Супервайзер'),
self::ROLE_CUSTOMER => Yii::t('user', 'Заказчик'),
self::ROLE_MANAGER => Yii::t('user', 'Контент менеджер'),
];
if ($role) {
return isset($roleNames[$role]) ? $roleNames[$role] : null;
}
return $roleNames;
}
public function locationPhoneCodes($country = null)
{
$phoneCodes = [
'rus' => '+7',
'blr' => '+375',
'kaz' => '+7',
'arm' => '+374',
'kgz' => '+996',
];
if ($country) {
return isset($phoneCodes[$country]) ? $phoneCodes[$country] : null;
}
return $phoneCodes;
}
public static function locationNames($country = null)
{
$countryNames = [
'rus' => Yii::t('user', 'Россия'),
'blr' => Yii::t('user', 'Белоруссия'),
'kaz' => Yii::t('user', 'Казахстан'),
'arm' => Yii::t('user', 'Армения'),
'kgs' => Yii::t('user', 'Кыргызстан'),
];
if ($country) {
return isset($countryNames[$country]) ? $countryNames[$country] : null;
}
return $countryNames;
}
public static function phoneRuFormat($phone)
{
$phone = trim($phone);
$result = preg_replace(
[
'/[\+]?([7|8])[-|\s]?\([-|\s]?(\d{3})[-|\s]?\)[-|\s]?(\d{3})[-|\s]?(\d{2})[-|\s]?(\d{2})/',
'/[\+]?([7|8])[-|\s]?(\d{3})[-|\s]?(\d{3})[-|\s]?(\d{2})[-|\s]?(\d{2})/',
'/[\+]?([7|8])[-|\s]?\([-|\s]?(\d{4})[-|\s]?\)[-|\s]?(\d{2})[-|\s]?(\d{2})[-|\s]?(\d{2})/',
'/[\+]?([7|8])[-|\s]?(\d{4})[-|\s]?(\d{2})[-|\s]?(\d{2})[-|\s]?(\d{2})/',
'/[\+]?([7|8])[-|\s]?\([-|\s]?(\d{4})[-|\s]?\)[-|\s]?(\d{3})[-|\s]?(\d{3})/',
'/[\+]?([7|8])[-|\s]?(\d{4})[-|\s]?(\d{3})[-|\s]?(\d{3})/',
],
[
'+7$2$3$4$5',
'+7$2$3$4$5',
'+7$2$3$4$5',
'+7$2$3$4$5',
'+7$2$3$4',
'+7$2$3$4',
],
$phone
);
$result = preg_replace('~\\D+~u', '', $result);
return $result;
}
}

View File

@ -1,7 +1,12 @@
{
"name": "yiisoft/yii2-app-advanced",
"description": "Yii 2 Advanced Project Template",
"keywords": ["yii2", "framework", "advanced", "project template"],
"keywords": [
"yii2",
"framework",
"advanced",
"project template"
],
"homepage": "https://www.yiiframework.com/",
"type": "project",
"license": "BSD-3-Clause",
@ -14,10 +19,19 @@
},
"minimum-stability": "dev",
"require": {
"php": ">=7.4.0",
"php": ">=8.0",
"ext-json": "*",
"yiisoft/yii2": "~2.0.45",
"yiisoft/yii2-bootstrap5": "~2.0.2",
"yiisoft/yii2-symfonymailer": "~2.0.3"
"yiisoft/yii2-symfonymailer": "~2.0.3",
"bower-asset/font-awesome": "~4.7",
"unclead/yii2-multiple-input": "~2.27.0",
"yiisoft/yii2-jui": "~2.0.0",
"2amigos/yii2-tinymce-widget" : "~1.1",
"ckeditor/ckeditor": "~4.22.1",
"sunhater/kcfinder": "*",
"bower-asset/slick-carousel": "*",
"kartik-v/yii2-widget-select2": "dev-master"
},
"require-dev": {
"yiisoft/yii2-debug": "~2.1.0",
@ -34,9 +48,18 @@
},
"autoload-dev": {
"psr-4": {
"common\\tests\\": ["common/tests/", "common/tests/_support"],
"backend\\tests\\": ["backend/tests/", "backend/tests/_support"],
"frontend\\tests\\": ["frontend/tests/", "frontend/tests/_support"]
"common\\tests\\": [
"common/tests/",
"common/tests/_support"
],
"backend\\tests\\": [
"backend/tests/",
"backend/tests/_support"
],
"frontend\\tests\\": [
"frontend/tests/",
"frontend/tests/_support"
]
}
},
"config": {

5640
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -6,3 +6,13 @@
192.168.65.1 - - [13/Jun/2024:16:49:29 +0300] "GET /assets/f5fa1b4e/jquery.js HTTP/1.1" 200 285314 "http://web.local/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
192.168.65.1 - - [13/Jun/2024:16:49:29 +0300] "GET /assets/e5b7c8da/yii.js HTTP/1.1" 200 20981 "http://web.local/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
192.168.65.1 - - [13/Jun/2024:16:49:29 +0300] "GET /assets/46f1484b/dist/js/bootstrap.bundle.js HTTP/1.1" 200 207819 "http://web.local/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
192.168.65.1 - - [13/Jun/2024:17:12:31 +0300] "GET / HTTP/1.1" 200 1545 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
192.168.65.1 - - [13/Jun/2024:17:12:31 +0300] "GET /assets/f5fa1b4e/jquery.js HTTP/1.1" 304 0 "http://web.local/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
192.168.65.1 - - [13/Jun/2024:17:12:31 +0300] "GET /assets/46f1484b/dist/css/bootstrap.css HTTP/1.1" 304 0 "http://web.local/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
192.168.65.1 - - [13/Jun/2024:17:12:31 +0300] "GET /assets/e5b7c8da/yii.js HTTP/1.1" 304 0 "http://web.local/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
192.168.65.1 - - [13/Jun/2024:17:12:31 +0300] "GET /assets/46f1484b/dist/js/bootstrap.bundle.js HTTP/1.1" 304 0 "http://web.local/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
192.168.65.1 - - [13/Jun/2024:17:15:11 +0300] "GET / HTTP/1.1" 200 1545 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
192.168.65.1 - - [13/Jun/2024:17:15:11 +0300] "GET /assets/46f1484b/dist/css/bootstrap.css HTTP/1.1" 304 0 "http://web.local/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
192.168.65.1 - - [13/Jun/2024:17:15:11 +0300] "GET /assets/f5fa1b4e/jquery.js HTTP/1.1" 304 0 "http://web.local/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
192.168.65.1 - - [13/Jun/2024:17:15:11 +0300] "GET /assets/e5b7c8da/yii.js HTTP/1.1" 304 0 "http://web.local/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
192.168.65.1 - - [13/Jun/2024:17:15:11 +0300] "GET /assets/46f1484b/dist/js/bootstrap.bundle.js HTTP/1.1" 304 0 "http://web.local/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"

View File

@ -0,0 +1,11 @@
<?php
return yii\helpers\ArrayHelper::merge(
require dirname(dirname(__DIR__)) . '/common/config/codeception-local.php',
require __DIR__ . '/main.php',
require __DIR__ . '/main-local.php',
require __DIR__ . '/test.php',
require __DIR__ . '/test-local.php',
[
]
);

View File

@ -0,0 +1,25 @@
<?php
$config = [
'components' => [
'request' => [
// !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
'cookieValidationKey' => '',
],
],
];
if (!YII_ENV_TEST) {
// configuration adjustments for 'dev' environment
$config['bootstrap'][] = 'debug';
$config['modules']['debug'] = [
'class' => \yii\debug\Module::class,
];
$config['bootstrap'][] = 'gii';
$config['modules']['gii'] = [
'class' => \yii\gii\Module::class,
];
}
return $config;

View File

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

View File

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

View File

@ -0,0 +1,27 @@
<?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';
require __DIR__ . '/../../common/config/bootstrap.php';
require __DIR__ . '/../config/bootstrap.php';
$config = yii\helpers\ArrayHelper::merge(
require __DIR__ . '/../../common/config/main.php',
require __DIR__ . '/../../common/config/main-local.php',
require __DIR__ . '/../../common/config/test.php',
require __DIR__ . '/../../common/config/test-local.php',
require __DIR__ . '/../config/main.php',
require __DIR__ . '/../config/main-local.php',
require __DIR__ . '/../config/test.php',
require __DIR__ . '/../config/test-local.php'
);
(new yii\web\Application($config))->run();

View File

@ -0,0 +1,18 @@
<?php
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';
require __DIR__ . '/../../common/config/bootstrap.php';
require __DIR__ . '/../config/bootstrap.php';
$config = yii\helpers\ArrayHelper::merge(
require __DIR__ . '/../../common/config/main.php',
require __DIR__ . '/../../common/config/main-local.php',
require __DIR__ . '/../config/main.php',
require __DIR__ . '/../config/main-local.php'
);
(new yii\web\Application($config))->run();

View File

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

View File

@ -0,0 +1,16 @@
<?php
return yii\helpers\ArrayHelper::merge(
require __DIR__ . '/main.php',
require __DIR__ . '/main-local.php',
require __DIR__ . '/test.php',
require __DIR__ . '/test-local.php',
[
'components' => [
'request' => [
// !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
'cookieValidationKey' => '',
],
],
]
);

View File

@ -0,0 +1,43 @@
<?php
return [
'components' => [
'db' => [
'class' => \yii\db\Connection::class,
'dsn' => 'mysql:host=localhost;dbname=web',
'username' => 'root',
'password' => 'root',
'charset' => 'utf8',
],
'mailer' => [
'class' => \yii\symfonymailer\Mailer::class,
'viewPath' => '@common/mail',
// send all mails to a file by default.
'useFileTransport' => true,
// You have to set
//
// 'useFileTransport' => false,
//
// and configure a transport for the mailer to send real emails.
//
// SMTP server example:
// 'transport' => [
// 'scheme' => 'smtps',
// 'host' => '',
// 'username' => '',
// 'password' => '',
// 'port' => 465,
// 'dsn' => 'native://default',
// ],
//
// DSN example:
// 'transport' => [
// 'dsn' => 'smtp://user:pass@smtp.example.com:25',
// ],
//
// See: https://symfony.com/doc/current/mailer.html#using-built-in-transports
// Or if you use a 3rd party service, see:
// https://symfony.com/doc/current/mailer.html#using-a-3rd-party-transport
],
],
];

View File

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

View File

@ -0,0 +1,9 @@
<?php
return [
'components' => [
'db' => [
'dsn' => 'mysql:host=localhost;dbname=yii2advanced_test',
],
],
];

View File

@ -0,0 +1,8 @@
<?php
return [
'bootstrap' => ['gii'],
'modules' => [
'gii' => 'yii\gii\Module',
],
];

View File

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

View File

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

View File

@ -0,0 +1,11 @@
<?php
return yii\helpers\ArrayHelper::merge(
require dirname(dirname(__DIR__)) . '/common/config/codeception-local.php',
require __DIR__ . '/main.php',
require __DIR__ . '/main-local.php',
require __DIR__ . '/test.php',
require __DIR__ . '/test-local.php',
[
]
);

View File

@ -0,0 +1,25 @@
<?php
$config = [
'components' => [
'request' => [
// !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
'cookieValidationKey' => '',
],
],
];
if (!YII_ENV_TEST) {
// configuration adjustments for 'dev' environment
$config['bootstrap'][] = 'debug';
$config['modules']['debug'] = [
'class' => \yii\debug\Module::class,
];
$config['bootstrap'][] = 'gii';
$config['modules']['gii'] = [
'class' => \yii\gii\Module::class,
];
}
return $config;

View File

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

View File

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

View File

@ -0,0 +1,28 @@
<?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';
require __DIR__ . '/../../common/config/bootstrap.php';
require __DIR__ . '/../config/bootstrap.php';
$config = yii\helpers\ArrayHelper::merge(
require __DIR__ . '/../../common/config/main.php',
require __DIR__ . '/../../common/config/main-local.php',
require __DIR__ . '/../../common/config/test.php',
require __DIR__ . '/../../common/config/test-local.php',
require __DIR__ . '/../config/main.php',
require __DIR__ . '/../config/main-local.php',
require __DIR__ . '/../config/test.php',
require __DIR__ . '/../config/test-local.php'
);
(new yii\web\Application($config))->run();

View File

@ -0,0 +1,18 @@
<?php
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';
require __DIR__ . '/../../common/config/bootstrap.php';
require __DIR__ . '/../config/bootstrap.php';
$config = yii\helpers\ArrayHelper::merge(
require __DIR__ . '/../../common/config/main.php',
require __DIR__ . '/../../common/config/main-local.php',
require __DIR__ . '/../config/main.php',
require __DIR__ . '/../config/main-local.php'
);
(new yii\web\Application($config))->run();

View File

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

24
environments/stage/yii Normal file
View File

@ -0,0 +1,24 @@
#!/usr/bin/env php
<?php
/**
* Yii console bootstrap file.
*/
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';
require __DIR__ . '/common/config/bootstrap.php';
require __DIR__ . '/console/config/bootstrap.php';
$config = yii\helpers\ArrayHelper::merge(
require __DIR__ . '/common/config/main.php',
require __DIR__ . '/common/config/main-local.php',
require __DIR__ . '/console/config/main.php',
require __DIR__ . '/console/config/main-local.php'
);
$application = new yii\console\Application($config);
$exitCode = $application->run();
exit($exitCode);

View File

@ -0,0 +1,28 @@
#!/usr/bin/env php
<?php
/**
* Yii console bootstrap 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';
require __DIR__ . '/common/config/bootstrap.php';
require __DIR__ . '/console/config/bootstrap.php';
$config = yii\helpers\ArrayHelper::merge(
require __DIR__ . '/common/config/main.php',
require __DIR__ . '/common/config/main-local.php',
require __DIR__ . '/common/config/test.php',
require __DIR__ . '/common/config/test-local.php',
require __DIR__ . '/console/config/main.php',
require __DIR__ . '/console/config/main-local.php',
require __DIR__ . '/console/config/test.php',
require __DIR__ . '/console/config/test-local.php'
);
$application = new yii\console\Application($config);
$exitCode = $application->run();
exit($exitCode);

View File

@ -0,0 +1,15 @@
@echo off
rem -------------------------------------------------------------
rem Yii command line bootstrap script for Windows.
rem -------------------------------------------------------------
@setlocal
set YII_PATH=%~dp0
if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe
"%PHP_COMMAND%" "%YII_PATH%yii_test" %*
@endlocal