This commit is contained in:
2025-07-14 12:15:41 +03:00
parent a64ed080bb
commit 273ac72207
974 changed files with 483955 additions and 14 deletions

View File

@@ -0,0 +1,8 @@
<?php
namespace app\modules\user_custom_fields;
class UserCustomFieldsModule extends \kernel\app_modules\user_custom_fields\UserCustomFieldsModule
{
}

View File

@@ -0,0 +1,8 @@
<?php
namespace app\modules\user_custom_fields\controllers;
class UserCustomFieldsController extends \kernel\app_modules\user_custom_fields\controllers\UserCustomFieldsController
{
}

View File

@@ -0,0 +1,13 @@
{
"name": "Доп. поля пользователей",
"version": "0.1",
"author": "ITGuild",
"slug": "user_custom_fields",
"type": "additional_property",
"description": "Доп. поля пользователей module",
"module_class": "app\\modules\\user_custom_fields\\UserCustomFieldsModule",
"module_class_file": "{APP}/modules/user_custom_fields/UserCustomFieldsModule.php",
"routs": "routs/user_custom_fields.php",
"migration_path": "migrations",
"dependence": "user,menu"
}

View File

@@ -0,0 +1,2 @@
<?php
include KERNEL_APP_MODULES_DIR . "/user_custom_fields/routs/user_custom_fields.php";

View File

@@ -0,0 +1,8 @@
<?php
namespace app\modules\user_stage;
class UserStageModule extends \kernel\app_modules\user_stage\UserStageModule
{
}

View File

@@ -0,0 +1,8 @@
<?php
namespace app\modules\user_stage\controllers;
class UserStageController extends \kernel\app_modules\user_stage\controllers\UserStageController
{
}

View File

@@ -0,0 +1,46 @@
<?php
use Itguild\EloquentTable\EloquentDataProvider;
use Itguild\EloquentTable\ListEloquentTable;
use itguild\forms\builders\SelectBuilder;
use kernel\CollectionTableRenderer;
\kernel\App::$hook->add(
'user_view',
function (\kernel\modules\user\models\User $user) {
$userWithStage = \kernel\app_modules\user_stage\models\User::find($user->id);
$table = new CollectionTableRenderer($userWithStage->stages);
$table->setColumns([
'id' => 'ID',
'title' => 'Название',
]);
$table->addValueProcessor('title', function($value, $model, $field) {
$style = \kernel\app_modules\user_stage\services\UserStageService::getStageStyle($model);
return "<div $style>$value</div>";
});
$table->addCustomColumn('status', 'Статус', function ($stage){
$text = \kernel\app_modules\user_stage\services\UserStageService::getStageStatusText($stage);
$style = \kernel\app_modules\user_stage\services\UserStageService::getStageStyle($stage);
return "<div $style>$text</div>";
});
$table->addFilter('status', SelectBuilder::build('status', [
'class' => 'form-control',
])->setOptions(['Завершен', 'Открыт', 'Закрыт'])->create()->fetch());
$table->addCustomColumn('action', 'Действия', function ($stage) use ($user){
$btn = '<a href="/admin/user_stage/set_stage/' . $stage->id . '/2/' . $user->id . '" class="btn btn-sm btn-primary">Открыть</a> ';
$btn .= '<a href="/admin/user_stage/set_stage/' . $stage->id . '/1/' . $user->id . '" class="btn btn-sm btn-primary">Закрыть</a> ';
$btn .= '<a href="/admin/user_stage/set_stage/' . $stage->id . '/3/' . $user->id . '" class="btn btn-sm btn-primary">Завершить</a>';
return $btn;
});
return $table->fetch();
}
);

View File

@@ -0,0 +1,14 @@
{
"name": "Этапы пользователя",
"version": "0.1",
"author": "ITGuild",
"slug": "user_stage",
"type": "entity",
"hooks": "hooks/user_stage_hooks.php",
"description": "Этапы пользователя module",
"module_class": "app\\modules\\user_stage\\UserStageModule",
"module_class_file": "{APP}/modules/user_stage/UserStageModule.php",
"routs": "routs/user_stage.php",
"migration_path": "migrations",
"dependence": "user,menu,user_custom_fields"
}

View File

@@ -0,0 +1,2 @@
<?php
include KERNEL_APP_MODULES_DIR . "/user_stage/routs/user_stage.php";

View File

@@ -0,0 +1,8 @@
<?php
namespace app\themes\svo;
class SvoTheme extends \kernel\app_themes\svo\SvoTheme
{
}

View File

@@ -0,0 +1,21 @@
<?php
namespace app\themes\svo\assets;
use kernel\Assets;
use kernel\helpers\Debug;
class AdminSliderAssets extends Assets
{
protected function createCSS(): void
{
$this->registerCSS(slug: "select2", resource: "https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css", addResourceURI: false);
}
protected function createJS(): void
{
$this->registerJS(slug: "select2", resource: "https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js", addResourceURI: false);
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace app\themes\svo\assets;
use kernel\Assets;
class SvoLpThemeAssets extends Assets
{
protected function createCSS(): void
{
$this->registerCSS(slug: "bootstrap", resource: "/css/netic/bootstrap.css");
$this->registerCSS(slug: "style", resource: "/css/netic/style.css");
$this->registerCSS(slug: "responsive", resource: "/css/netic/responsive.css");
$this->registerCSS(slug: "mCustomScrollbar", resource: "/css/netic/jquery.mCustomScrollbar.min.css");
}
protected function createJS(): void
{
$this->registerJS(slug: "jquery", resource: "/js/netic/jquery.min.js");
$this->registerJS(slug: "popper", resource: "/js/netic/popper.min.js");
$this->registerJS(slug: "bootstrap", resource: "/js/netic/bootstrap.bundle.js");
$this->registerJS(slug: "jquery-3", resource: "/js/netic/jquery-3.0.0.min.js");
$this->registerJS(slug: "plugin", resource: "/js/netic/plugin.js");
$this->registerJS(slug: "mCustomScrollbar", resource: "/js/netic/jquery.mCustomScrollbar.concat.min.js");
$this->registerJS(slug: "custom", resource: "/js/netic/custom.js");
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace app\themes\svo\assets;
use kernel\Assets;
class SvoThemesAssets extends Assets
{
protected function createCSS(): void
{
$this->registerCSS(slug: "tabler-icons", resource: "/assets/fonts/tabler-icons.min.css");
$this->registerCSS(slug: "feather", resource: "/assets/fonts/feather.css");
$this->registerCSS(slug: "fontawesome", resource: "/assets/fonts/fontawesome.css");
$this->registerCSS(slug: "material", resource: "/assets/fonts/material.css");
$this->registerCSS(slug: "style", resource: "/assets/css/style.css");
$this->registerCSS(slug: "style-preset", resource: "/assets/css/style-preset.css");
}
protected function createJS(): void
{
$this->registerJS(slug: "popper", resource: "/assets/js/plugins/popper.min.js");
$this->registerJS(slug: "simplebar", resource: "/assets/js/plugins/simplebar.min.js");
$this->registerJS(slug: "bootstrap", resource: "/assets/js/plugins/bootstrap.min.js");
$this->registerJS(slug: "custom-font", resource: "/assets/js/fonts/custom-font.js");
$this->registerJS(slug: "pcoded", resource: "/assets/js/pcoded.js");
$this->registerJS(slug: "feather", resource: "/assets/js/plugins/feather.min.js");
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace app\themes\svo\controllers;
use kernel\Controller;
use kernel\modules\user\service\UserService;
class LpController extends Controller
{
protected \kernel\modules\user\models\User $user;
protected function init(): void
{
parent::init();
$this->cgView->viewPath = APP_DIR . "/themes/svo/views/lp/";
$this->cgView->layout = "lp.php";
$this->cgView->layoutPath = APP_DIR . "/themes/svo/views/layout/";
$this->cgView->addVarToLayout("resources", "/resources/themes/svo/assets");
$user = UserService::getAuthUser();
if ($user){
$this->cgView->addVarToLayout("currentUser", $user);
$this->user = $user;
}
}
public function actionIndex(): void
{
$this->cgView->render('index.php');
}
}

View File

@@ -0,0 +1,116 @@
<?php
namespace app\themes\svo\controllers;
use JetBrains\PhpStorm\NoReturn;
use kernel\App;
use kernel\app_modules\user_custom_fields\models\CustomField;
use kernel\app_modules\user_custom_fields\models\forms\CreateUserCustomValueForm;
use kernel\app_modules\user_custom_fields\services\CustomFieldService;
use kernel\app_modules\user_custom_fields\services\UserCustomValuesService;
use kernel\app_modules\user_stage\models\User;
use kernel\app_modules\user_stage\models\UserStage;
use kernel\Controller;
use kernel\Flash;
use kernel\helpers\Debug;
use kernel\modules\user\service\UserService;
use kernel\Request;
class MainController extends Controller
{
protected \kernel\modules\user\models\User $user;
protected function init(): void
{
parent::init();
$this->cgView->viewPath = APP_DIR . "/themes/svo/views/main/";
$this->cgView->layout = "lk.php";
$this->cgView->layoutPath = APP_DIR . "/themes/svo/views/layout/";
$this->cgView->addVarToLayout("resources", "/resources/themes/svo");
$user = UserService::getAuthUser();
if ($user){
$this->cgView->addVarToLayout("currentUser", $user);
$this->user = $user;
}
}
public function actionIndex(): void
{
$this->cgView->render("index.php");
}
public function actionAbout(): void
{
$this->cgView->render("about.php");
}
public function actionLk(): void
{
$user = \kernel\app_modules\user_stage\models\User::find($this->user->id);
$stages = UserStage::orderBy('id')->get();
$user->stages()->sync($stages);
$firstStage = UserStage::where('slug', 'registraciya')->first();
if ($user->isClosed($firstStage)){
$user->openStage($firstStage);
}
$this->cgView->render("lk_index.php", ['user' => $user]);
}
public function actionStage(int $id): void
{
$stage = UserStage::find($id);
$fieldsSlug = explode(", ", $stage->user_fields);
$fields = CustomField::whereIn("slug", $fieldsSlug)->get();
$this->cgView->render("stage.php", ['fields' => $fields, 'stage' => $stage]);
}
#[NoReturn] public function actionStageSave(): void
{
$request = new Request();
$stageId = $request->post('stage_id');
$stage = UserStage::find($stageId);
$fieldsSlug = explode(", ", $stage->user_fields);
$fields = CustomField::whereIn("slug", $fieldsSlug)->get();
$service = new UserCustomValuesService();
$form = new CreateUserCustomValueForm();
foreach ($fields as $field){
/* @var CustomField $field */
if (isset($request->data[$field->slug])){
UserCustomValuesService::deleteByUserAndField($this->user->id, $field->id);
$form->load([
'user_id' => $this->user->id,
'custom_field_id' => $field->id,
'value' => $request->data[$field->slug]
]);
$service->create($form);
}
}
Flash::setMessage("success", "Данные сохранены");
$this->redirect("/lk/", 302);
}
public function actionSettings(): void
{
$this->cgView->render("settings.php");
}
public function actionLogin(): void
{
$this->cgView->layout = "logreg.php";
$this->cgView->render("login.php");
}
public function actionRegister(): void
{
$this->cgView->layout = "logreg.php";
$this->cgView->render("register.php");
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace app\themes\svo\controllers;
use kernel\AdminController;
class SvoAdminController extends AdminController
{
protected function init(): void
{
parent::init();
$this->cgView->viewPath = APP_DIR . "/themes/svo/views/admin/";
}
public function actionThemeSettings(): void
{
$this->cgView->render('theme_settings.php');
}
}

View File

@@ -0,0 +1,15 @@
{
"name": "SVO",
"version": "0.1",
"author": "ItGuild",
"slug": "svo",
"type": "theme",
"description": "СВО",
"preview": "preview.jpg",
"resource": "/resources/themes/svo",
"resource_path": "{RESOURCES}/themes/svo",
"theme_class": "app\\themes\\svo\\SvoTheme",
"theme_class_file": "{APP}/themes/svo/SvoTheme.php",
"routs": "routs/svo.php",
"dependence": "menu,secure,post,option,user"
}

View File

@@ -0,0 +1,19 @@
<?php
namespace app\themes\svo\middlewares;
use kernel\Middleware;
class LkAuthMiddleware extends Middleware
{
function handler()
{
if(!isset($_COOKIE['user_id']))
{
header('Location: /lk/login', true, 302);
return false;
}
}
}

View File

@@ -0,0 +1,54 @@
<?php
use kernel\App;
use kernel\CgRouteCollector;
App::$collector->filter("auth", [\app\themes\svo\middlewares\LkAuthMiddleware::class, "handler"]);
App::$collector->get('/', [\app\themes\svo\controllers\LpController::class, 'actionIndex']);
App::$collector->group(["prefix" => "admin"], function (CgRouteCollector $router) {
App::$collector->group(["before" => "auth"], function (CGRouteCollector $router) {
App::$collector->group(["prefix" => "svo-theme"], function (CGRouteCollector $router) {
App::$collector->get('/settings', [\app\themes\svo\controllers\SvoAdminController::class, 'actionThemeSettings']);
});
});
});
App::$collector->group(["prefix" => "lk"], function (CgRouteCollector $router){
App::$collector->group(["before" => "auth"], function (CgRouteCollector $router){
App::$collector->get('/', [\app\themes\svo\controllers\MainController::class, 'actionLk']);
App::$collector->get('/settings', [\app\themes\svo\controllers\MainController::class, 'actionSettings']);
App::$collector->get('/stage/{id}', [\app\themes\svo\controllers\MainController::class, 'actionStage']);
App::$collector->post('/stage/save', [\app\themes\svo\controllers\MainController::class, 'actionStageSave']);
});
App::$collector->get('/login', [\app\themes\svo\controllers\MainController::class, 'actionLogin']);
App::$collector->get('/register', [\app\themes\svo\controllers\MainController::class, 'actionRegister']);
App::$collector->post(
'/auth',
[\kernel\modules\secure\controllers\SecureController::class, 'actionAuth'],
additionalInfo: ['vars' => ['basePath' => '/lk']]
);
App::$collector->post(
'/registration',
[\kernel\modules\secure\controllers\SecureController::class, 'actionRegistration'],
additionalInfo: ['vars' => ['basePath' => '/lk']]
);
App::$collector->get(
'/logout',
[\kernel\modules\secure\controllers\SecureController::class, 'actionLogout'],
);
App::$collector->post(
'/settings/change_password',
[\kernel\modules\secure\controllers\SecureController::class, 'actionChangePassword'],
additionalInfo: ['vars' => ['basePath' => '/lk/settings']]
);
});
//App::$collector->get('/page/{page_number}', [\app\modules\tag\controllers\TagController::class, 'actionIndex']);
//App::$collector->get('/create', [\app\modules\tag\controllers\TagController::class, 'actionCreate']);

View File

@@ -0,0 +1,53 @@
<?php
/**
* @var string $resources
* @var \kernel\CgView $view
*/
new \app\themes\svo\assets\AdminSliderAssets($resources);
?>
<ul class="nav nav-tabs" id="myTab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="home-tab" data-toggle="tab" data-target="#home" type="button" role="tab" aria-controls="home" aria-selected="true">
Основные
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="slider-tab" data-toggle="tab" data-target="#slider" type="button" role="tab" aria-controls="slider" aria-selected="false">
Слайдер
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="contact-tab" data-toggle="tab" data-target="#contact" type="button" role="tab" aria-controls="contact" aria-selected="false">
Contact
</button>
</li>
</ul>
<div class="tab-content" id="myTabContent">
<div class="tab-pane fade show active" id="home" role="tabpanel" aria-labelledby="home-tab">
Основные настройки темы
</div>
<div class="tab-pane fade" id="slider" role="tabpanel" aria-labelledby="slider-tab">
<div class="form-group">
<label>Заголовок</label>
<br>
<select id="postSelect" style="width: 300px">
<option value="1">Option 1</option>
<option value="2">Option 2</option>
<option value="3">Option 3</option>
<option value="4">Option 4</option>
</select>
</div>
</div>
<div class="tab-pane fade" id="contact" role="tabpanel" aria-labelledby="contact-tab">
Contact
</div>
</div>
<script>
$(document).ready(function() {
// Инициализация Select2
$('#postSelect').select2();
});
</script>

View File

@@ -0,0 +1,266 @@
<?php
/**
* @var string $content
* @var string $resources
* @var string $title
* @var \kernel\modules\user\models\User $currentUser
* @var \kernel\CgView $view
*/
$assets = new \app\themes\svo\assets\SvoThemesAssets($resources);
?>
<!DOCTYPE html>
<html lang="en">
<!-- [Head] start -->
<head>
<title>Compact Layout | Mantis Bootstrap 5 Admin Template</title>
<!-- [Meta] -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0, minimal-ui">
<?php $assets->getCSSAsSTR(); ?>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="description"
content="Mantis is made using Bootstrap 5 design framework. Download the free admin template & use it for your project.">
<meta name="keywords"
content="Mantis, Dashboard UI Kit, Bootstrap 5, Admin Template, Admin Dashboard, CRM, CMS, Bootstrap Admin Template">
<meta name="author" content="CodedThemes">
<!-- [Favicon] icon -->
<link rel="icon" href="<?= $resources ?>/assets/images/favicon.svg" type="image/x-icon">
<!-- [Google Font] Family -->
<link rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Public+Sans:wght@300;400;500;600;700&display=swap"
id="main-font-link">
<script type="application/javascript">var resource = '<?= $resources ?>'</script>
</head>
<!-- [Head] end -->
<!-- [Body] Start -->
<body data-pc-preset="preset-1" data-pc-direction="ltr" data-pc-theme="light" data-pc-direction="ltr">
<!-- [ Pre-loader ] start -->
<div class="loader-bg">
<div class="loader-track">
<div class="loader-fill"></div>
</div>
</div>
<!-- [ Pre-loader ] End -->
<!-- [ Sidebar Menu ] start -->
<nav class="pc-sidebar">
<div class="navbar-wrapper">
<div class="m-header">
<a href="/lk" class="b-brand">
<!-- ======== Change your logo from here ============ -->
<img src="<?= $resources ?>/assets/images/logo-dark.svg" alt="" class="logo logo-lg">
<img src="<?= $resources ?>/assets/images/favicon.svg" alt="" class="logo logo-sm">
</a>
</div>
<div class="navbar-content">
<ul class="pc-navbar">
<li class="pc-item">
<a href="/lk/" class="pc-link">
<span class="pc-micon"><i class="ti ti-dashboard"></i></span>
<span class="pc-mtext">Этапы конкурса</span>
</a>
</li>
<li class="pc-item">
<a href="/lk/settings" class="pc-link">
<span class="pc-micon"><i class="ti ti-settings"></i></span>
<span class="pc-mtext">Настройки</span>
</a>
</li>
<li class="pc-item">
<a href="/lk/logout" class="pc-link">
<span class="pc-micon"><i class="ti ti-logout"></i></span>
<span class="pc-mtext">Выход</span>
</a>
</li>
</ul>
</div>
<div class="pc-compact-submenu">
<div class="pc-compact-title">
<div class="d-flex align-items-center">
<div class="flex-shrink-0">
<div class="avtar avtar-xs bg-light-primary">
<i class=""></i>
</div>
</div>
<div class="flex-grow-1 ms-2">
<h5 class="mb-0">title</h5>
</div>
</div>
</div>
<div class="pc-compact-list"></div>
</div>
</div>
</nav>
<!-- [ Sidebar Menu ] end -->
<!-- [ Header Topbar ] start -->
<header class="pc-header">
<div class="header-wrapper"> <!-- [Mobile Media Block] start -->
<div class="me-auto pc-mob-drp">
<ul class="list-unstyled">
<!-- ======= Menu collapse Icon ===== -->
<li class="pc-h-item pc-sidebar-collapse">
<a href="#" class="pc-head-link ms-0" id="sidebar-hide">
<i class="ti ti-menu-2"></i>
</a>
</li>
<li class="pc-h-item pc-sidebar-popup">
<a href="#" class="pc-head-link ms-0" id="mobile-collapse">
<i class="ti ti-menu-2"></i>
</a>
</li>
</ul>
</div>
<!-- [Mobile Media Block end] -->
<div class="ms-auto">
<ul class="list-unstyled">
<li class="dropdown pc-h-item header-user-profile">
<a
class="pc-head-link dropdown-toggle arrow-none me-0"
data-bs-toggle="dropdown"
href="#"
role="button"
aria-haspopup="false"
data-bs-auto-close="outside"
aria-expanded="false"
>
<img src="<?= $resources ?>/assets/images/user/avatar-2.jpg" alt="user-image"
class="user-avtar">
<span><?= $currentUser->email ?? 'No name' ?></span>
</a>
<div class="dropdown-menu dropdown-user-profile dropdown-menu-end pc-h-dropdown">
<div class="dropdown-header">
<div class="d-flex mb-1">
<div class="flex-shrink-0">
<img src="<?= $resources ?>/assets/images/user/avatar-2.jpg" alt="user-image"
class="user-avtar wid-35">
</div>
<div class="flex-grow-1 ms-3">
<h6 class="mb-1">Stebin Ben</h6>
<span>UI/UX Designer</span>
</div>
<a href="#!" class="pc-head-link bg-transparent"><i class="ti ti-power text-danger"></i></a>
</div>
</div>
<div class="tab-content" id="mysrpTabContent">
<div class="tab-pane fade show active" id="drp-tab-1" role="tabpanel"
aria-labelledby="drp-t1" tabindex="0">
<a href="#!" class="dropdown-item">
<i class="ti ti-edit-circle"></i>
<span>Edit Profile</span>
</a>
<a href="#!" class="dropdown-item">
<i class="ti ti-power"></i>
<span>Logout</span>
</a>
</div>
<div class="tab-pane fade" id="drp-tab-2" role="tabpanel" aria-labelledby="drp-t2"
tabindex="0">
<a href="#!" class="dropdown-item">
<i class="ti ti-help"></i>
<span>Support</span>
</a>
<a href="#!" class="dropdown-item">
<i class="ti ti-user"></i>
<span>Account Settings</span>
</a>
<a href="#!" class="dropdown-item">
<i class="ti ti-lock"></i>
<span>Privacy Center</span>
</a>
<a href="#!" class="dropdown-item">
<i class="ti ti-messages"></i>
<span>Feedback</span>
</a>
<a href="#!" class="dropdown-item">
<i class="ti ti-list"></i>
<span>History</span>
</a>
</div>
</div>
</div>
</li>
</ul>
</div>
</div>
</header>
<!-- [ Header ] end -->
<div class="pc-container" xmlns="http://www.w3.org/1999/html">
<div class="pc-content">
<div class="notifications-container">
<?php if (\kernel\Flash::hasMessage("error")): ?>
<!-- Уведомление - ошибка -->
<div class="alert alert-danger alert-dismissible fade show notification-item" role="alert">
<div class="d-flex">
<div class="notification-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" class="feather feather-x-circle">
<circle cx="12" cy="12" r="10"></circle>
<line x1="15" y1="9" x2="9" y2="15"></line>
<line x1="9" y1="9" x2="15" y2="15"></line>
</svg>
</div>
<div class="notification-content">
<h5>Ошибка при загрузке файла</h5>
<p><?= \kernel\Flash::getMessage("error"); ?></p>
</div>
</div>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php endif; ?>
<?php if (\kernel\Flash::hasMessage("success")): ?>
<!-- Уведомление - успех -->
<div class="alert alert-success alert-dismissible fade show notification-item" role="alert">
<div class="d-flex">
<div class="notification-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" class="feather feather-check-circle">
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
<polyline points="22 4 12 14.01 9 11.01"></polyline>
</svg>
</div>
<div class="notification-content">
<h5>Задача выполнена успешно</h5>
<p><?= \kernel\Flash::getMessage("success"); ?></p>
</div>
</div>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php endif; ?>
</div>
<?= $content ?>
</div>
</div>
<footer class="pc-footer">
<div class="footer-wrapper container-fluid">
<div class="row">
<div class="col-auto my-1">
<ul class="list-inline footer-link mb-0">
<!-- <li class="list-inline-item"><a href="../index.html">Home</a></li>-->
<!-- <li class="list-inline-item"><a href="https://codedthemes.gitbook.io/mantis-bootstrap"-->
<!-- target="_blank">Documentation</a></li>-->
<!-- <li class="list-inline-item"><a href="https://codedthemes.authordesk.app/"-->
<!-- target="_blank">Support</a></li>-->
</ul>
</div>
</div>
</div>
</footer> <!-- Required Js -->
<?php $assets->getJSAsStr(); ?>
<script>layout_change('light');</script>
<script>change_box_container('false');</script>
<script>layout_rtl_change('false');</script>
<script>preset_change("preset-1");</script>
<script>font_change("Public-Sans");</script>
<script src="<?= $resources ?>/assets/js/layout-compact.js"></script>
</body>
<!-- [Body] end -->
</html>

View File

@@ -0,0 +1,67 @@
<?php
/**
* @var string $content
* @var string $resources
* @var string $title
* @var \kernel\CgView $view
*/
$assets = new \app\themes\svo\assets\SvoThemesAssets($resources);
?>
<!DOCTYPE html>
<html lang="en">
<!-- [Head] start -->
<head>
<title><?= $title ?></title>
<!-- [Meta] -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0, minimal-ui">
<?php $assets->getCSSAsSTR(); ?>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="description" content="Mantis is made using Bootstrap 5 design framework. Download the free admin template & use it for your project.">
<meta name="keywords" content="Mantis, Dashboard UI Kit, Bootstrap 5, Admin Template, Admin Dashboard, CRM, CMS, Bootstrap Admin Template">
<meta name="author" content="CodedThemes">
<!-- [Favicon] icon -->
<link rel="icon" href="<?= $resources ?>/assets/images/favicon.svg" type="image/x-icon"> <!-- [Google Font] Family -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Public+Sans:wght@300;400;500;600;700&display=swap" id="main-font-link">
<script type="application/javascript">var resource = '<?= $resources ?>'</script>
</head>
<!-- [Head] end -->
<!-- [Body] Start -->
<body>
<!-- [ Pre-loader ] start -->
<div class="loader-bg">
<div class="loader-track">
<div class="loader-fill"></div>
</div>
</div>
<!-- [ Pre-loader ] End -->
<?php if (\kernel\Flash::hasMessage("error")): ?>
<div class="card">
<?= \kernel\Flash::getMessage("error"); ?>
</div>
<?php endif; ?>
<?php if (\kernel\Flash::hasMessage("success")): ?>
<div class="card">
<?= \kernel\Flash::getMessage("success"); ?>
</div>
<?php endif; ?>
<?= $content ?>
<!-- [ Main Content ] end -->
<!-- Required Js -->
<?php $assets->getJSAsStr(); ?>
<script>layout_change('light');</script>
<script>change_box_container('false');</script>
<script>layout_rtl_change('false');</script>
<script>preset_change("preset-1");</script>
<script>font_change("Public-Sans");</script>
</body>
<!-- [Body] end -->
</html>

View File

@@ -0,0 +1,141 @@
<?php
/**
* @var string $content
* @var string $resources
* @var string $title
* @var \kernel\modules\user\models\User $currentUser
* @var \kernel\CgView $view
*/
$assets = new \app\themes\svo\assets\SvoLpThemeAssets($resources);
?>
<!DOCTYPE html>
<html>
<head>
<!-- basic -->
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- mobile metas -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="viewport" content="initial-scale=1, maximum-scale=1">
<!-- site metas -->
<title>Testimonial</title>
<meta name="keywords" content="">
<meta name="description" content="">
<meta name="author" content="">
<?php $assets->getCSSAsSTR(); ?>
<!-- fevicon -->
<link rel="icon" href="<?= $resources ?>/images/netic/favicon.svg" type="image/svg" />
<!-- fonts -->
<link href="https://fonts.googleapis.com/css?family=Poppins:400,700|Sen:400,700,800&display=swap" rel="stylesheet">
<!-- Tweaks for older IEs-->
<link rel="stylesheet" href="https://netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css">
</head>
<body>
<div class="header_section" style="background-image: url(<?= $resources ?>/images/netic/banner-bg.png);">
<div class="container">
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand"href="index.html"><img width="100px" src="<?= $resources ?>/images/netic/logo.png"></a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav ml-auto">
<li class="nav-item">
<a class="nav-link" href="index.html">О конкурсе</a>
</li>
<li class="nav-item">
<a class="nav-link" href="index.html">Новости</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/lk/">Личный кабинет</a>
</li>
</ul>
<form class="form-inline my-2 my-lg-0">
</form>
</div>
</nav>
<div class="custom_bg">
<div class="custom_menu">
<ul>
<li><a href="index.html">О конкурсе</a></li>
<li><a href="index.html">Новости</a></li>
<li><a href="/lk/">Личный кабинет</a></li>
</ul>
</div>
</div>
</div>
<?php \app\themes\svo\widgets\MainSliderWidget::create(['resources' => $resources])->run(); ?>
</div>
<!-- header section end -->
<!-- testimonial section start -->
<!-- testimonial section end -->
<?= $content ?>
<!-- footer section start -->
<div class="footer_section layout_padding">
<div class="container">
<div class="row">
<div class="col-sm-4">
<h3 class="footer_text">Useful links</h3>
<div class="footer_menu">
<ul>
<li class="active"><a href="index.html"><span class="angle_icon active"><i class="fa fa-arrow-right" aria-hidden="true"></i></span> Home</a></li>
<li><a href="#"><span class="angle_icon"><i class="fa fa-arrow-right" aria-hidden="true"></i></span> About</a></li>
<li><a href="services.html"><span class="angle_icon"><i class="fa fa-arrow-right" aria-hidden="true"></i></span> Services</a></li>
<li><a href="domain.html"><span class="angle_icon"><i class="fa fa-arrow-right" aria-hidden="true"></i></span> Domain</a></li>
<li><a href="testimonial.html"><span class="angle_icon"><i class="fa fa-arrow-right" aria-hidden="true"></i></span> Testimonial</a></li>
<li><a href="contact.html"><span class="angle_icon"><i class="fa fa-arrow-right" aria-hidden="true"></i></span> Contact Us</a></li>
</ul>
</div>
</div>
<div class="col-sm-4">
<h3 class="footer_text">Address</h3>
<div class="location_text">
<ul>
<li>
<a href="#">
<span class="padding_left_10"><i class="fa fa-map-marker" aria-hidden="true"></i></span>It is a long established fact that a<br> reader will be distracted</a>
</li>
<li>
<a href="#">
<span class="padding_left_10"><i class="fa fa-phone" aria-hidden="true"></i></span>(+71) 1234567890<br>(+71) 1234567890
</a>
</li>
<li>
<a href="#">
<span class="padding_left_10"><i class="fa fa-envelope" aria-hidden="true"></i></span>demo@gmail.com
</a>
</li>
</ul>
</div>
</div>
<div class="col-sm-4">
<div class="footer_main">
<h3 class="footer_text">Find Us</h3>
<p class="dummy_text">more-or-less normal distribution </p>
<div class="social_icon">
<ul>
<li><a href="#"><i class="fa fa-facebook" aria-hidden="true"></i></a></li>
<li><a href="#"><i class="fa fa-twitter" aria-hidden="true"></i></a></li>
<li><a href="#"><i class="fa fa-instagram" aria-hidden="true"></i></a></li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- footer section end -->
<!-- copyright section start -->
<div class="copyright_section">
<div class="container">
<p class="copyright_text">2020 All Rights Reserved. Design by <a href="https://html.design">Free Html Templates</a>. Distributed by <a href="https://themewagon.com" target="_blank">ThemeWagon</a></p>
</div>
</div>
<!-- copyright section end -->
<!-- Javascript files-->
<?php $assets->getJSAsStr(); ?>
<!-- sidebar -->
</body>
</html>

View File

@@ -0,0 +1,892 @@
<?php
/**
* @var string $content
* @var string $resources
* @var string $title
* @var \kernel\CgView $view
*/
$assets = new \app\themes\custom\assets\CustomThemesAssets($resources);
?>
<!DOCTYPE html>
<html lang="en">
<!-- [Head] start -->
<head>
<title>Compact Layout | Mantis Bootstrap 5 Admin Template</title>
<!-- [Meta] -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0, minimal-ui">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="description" content="Mantis is made using Bootstrap 5 design framework. Download the free admin template & use it for your project.">
<meta name="keywords" content="Mantis, Dashboard UI Kit, Bootstrap 5, Admin Template, Admin Dashboard, CRM, CMS, Bootstrap Admin Template">
<meta name="author" content="CodedThemes">
<!-- [Favicon] icon -->
<link rel="icon" href="../assets/images/favicon.svg" type="image/x-icon"> <!-- [Google Font] Family -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Public+Sans:wght@300;400;500;600;700&display=swap" id="main-font-link">
<!-- [Tabler Icons] https://tablericons.com -->
<link rel="stylesheet" href="../assets/fonts/tabler-icons.min.css" >
<!-- [Feather Icons] https://feathericons.com -->
<link rel="stylesheet" href="../assets/fonts/feather.css" >
<!-- [Font Awesome Icons] https://fontawesome.com/icons -->
<link rel="stylesheet" href="../assets/fonts/fontawesome.css" >
<!-- [Material Icons] https://fonts.google.com/icons -->
<link rel="stylesheet" href="../assets/fonts/material.css" >
<!-- [Template CSS Files] -->
<link rel="stylesheet" href="../assets/css/style.css" id="main-style-link" >
<link rel="stylesheet" href="../assets/css/style-preset.css" >
</head>
<!-- [Head] end -->
<!-- [Body] Start -->
<body data-pc-preset="preset-1" data-pc-direction="ltr" data-pc-theme="light" data-pc-direction="ltr">
<!-- [ Pre-loader ] start -->
<div class="loader-bg">
<div class="loader-track">
<div class="loader-fill"></div>
</div>
</div>
<!-- [ Pre-loader ] End -->
<!-- [ Sidebar Menu ] start -->
<nav class="pc-sidebar">
<div class="navbar-wrapper">
<div class="m-header">
<a href="../dashboard/index.html" class="b-brand">
<!-- ======== Change your logo from here ============ -->
<img src="../assets/images/logo-dark.svg" alt="" class="logo logo-lg">
<img src="../assets/images/favicon.svg" alt="" class="logo logo-sm">
</a>
</div>
<div class="navbar-content">
<ul class="pc-navbar">
<li class="pc-item">
<a href="../dashboard/index.html" class="pc-link">
<span class="pc-micon"><i class="ti ti-dashboard"></i></span>
<span class="pc-mtext">Dashboard</span>
</a>
</li>
<li class="pc-item pc-caption">
<label>UI Components</label>
<i class="ti ti-dashboard"></i>
</li>
<li class="pc-item">
<a href="../elements/bc_typography.html" class="pc-link">
<span class="pc-micon"><i class="ti ti-typography"></i></span>
<span class="pc-mtext">Typography</span>
</a>
</li>
<li class="pc-item">
<a href="../elements/bc_color.html" class="pc-link">
<span class="pc-micon"><i class="ti ti-color-swatch"></i></span>
<span class="pc-mtext">Color</span>
</a>
</li>
<li class="pc-item">
<a href="../elements/icon-tabler.html" class="pc-link">
<span class="pc-micon"><i class="ti ti-plant-2"></i></span>
<span class="pc-mtext">Icons</span>
</a>
</li>
<li class="pc-item pc-caption">
<label>Pages</label>
<i class="ti ti-news"></i>
</li>
<li class="pc-item">
<a href="../pages/login-v3.html" class="pc-link">
<span class="pc-micon"><i class="ti ti-lock"></i></span>
<span class="pc-mtext">Login</span>
</a>
</li>
<li class="pc-item">
<a href="../pages/register-v3.html" class="pc-link">
<span class="pc-micon"><i class="ti ti-user-plus"></i></span>
<span class="pc-mtext">Register</span>
</a>
</li>
<li class="pc-item pc-caption">
<label>Other</label>
<i class="ti ti-brand-chrome"></i>
</li>
<li class="pc-item pc-hasmenu">
<a href="#!" class="pc-link"><span class="pc-micon"><i class="ti ti-menu"></i></span><span class="pc-mtext">Menu
levels</span><span class="pc-arrow"><i data-feather="chevron-right"></i></span></a>
<ul class="pc-submenu">
<li class="pc-item"><a class="pc-link" href="#!">Level 2.1</a></li>
<li class="pc-item pc-hasmenu">
<a href="#!" class="pc-link">Level 2.2<span class="pc-arrow"><i data-feather="chevron-right"></i></span></a>
<ul class="pc-submenu">
<li class="pc-item"><a class="pc-link" href="#!">Level 3.1</a></li>
<li class="pc-item"><a class="pc-link" href="#!">Level 3.2</a></li>
<li class="pc-item pc-hasmenu">
<a href="#!" class="pc-link">Level 3.3<span class="pc-arrow"><i data-feather="chevron-right"></i></span></a>
<ul class="pc-submenu">
<li class="pc-item"><a class="pc-link" href="#!">Level 4.1</a></li>
<li class="pc-item"><a class="pc-link" href="#!">Level 4.2</a></li>
</ul>
</li>
</ul>
</li>
<li class="pc-item pc-hasmenu">
<a href="#!" class="pc-link">Level 2.3<span class="pc-arrow"><i data-feather="chevron-right"></i></span></a>
<ul class="pc-submenu">
<li class="pc-item"><a class="pc-link" href="#!">Level 3.1</a></li>
<li class="pc-item"><a class="pc-link" href="#!">Level 3.2</a></li>
<li class="pc-item pc-hasmenu">
<a href="#!" class="pc-link">Level 3.3<span class="pc-arrow"><i data-feather="chevron-right"></i></span></a>
<ul class="pc-submenu">
<li class="pc-item"><a class="pc-link" href="#!">Level 4.1</a></li>
<li class="pc-item"><a class="pc-link" href="#!">Level 4.2</a></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li class="pc-item">
<a href="../other/sample-page.html" class="pc-link">
<span class="pc-micon"><i class="ti ti-brand-chrome"></i></span>
<span class="pc-mtext">Sample page</span>
</a>
</li>
</ul>
</div>
<div class="pc-compact-submenu">
<div class="pc-compact-title">
<div class="d-flex align-items-center">
<div class="flex-shrink-0">
<div class="avtar avtar-xs bg-light-primary">
<i class=""></i>
</div>
</div>
<div class="flex-grow-1 ms-2">
<h5 class="mb-0">title</h5>
</div>
</div>
</div>
<div class="pc-compact-list"></div>
</div>
</div>
</nav>
<!-- [ Sidebar Menu ] end -->
<!-- [ Header Topbar ] start -->
<header class="pc-header">
<div class="header-wrapper"> <!-- [Mobile Media Block] start -->
<div class="me-auto pc-mob-drp">
<ul class="list-unstyled">
<!-- ======= Menu collapse Icon ===== -->
<li class="pc-h-item pc-sidebar-collapse">
<a href="#" class="pc-head-link ms-0" id="sidebar-hide">
<i class="ti ti-menu-2"></i>
</a>
</li>
<li class="pc-h-item pc-sidebar-popup">
<a href="#" class="pc-head-link ms-0" id="mobile-collapse">
<i class="ti ti-menu-2"></i>
</a>
</li>
<li class="dropdown pc-h-item d-inline-flex d-md-none">
<a
class="pc-head-link dropdown-toggle arrow-none m-0"
data-bs-toggle="dropdown"
href="#"
role="button"
aria-haspopup="false"
aria-expanded="false"
>
<i class="ti ti-search"></i>
</a>
<div class="dropdown-menu pc-h-dropdown drp-search">
<form class="px-3">
<div class="form-group mb-0 d-flex align-items-center">
<i data-feather="search"></i>
<input type="search" class="form-control border-0 shadow-none" placeholder="Search here. . .">
</div>
</form>
</div>
</li>
<li class="pc-h-item d-none d-md-inline-flex">
<form class="header-search">
<i data-feather="search" class="icon-search"></i>
<input type="search" class="form-control" placeholder="Search here. . .">
</form>
</li>
</ul>
</div>
<!-- [Mobile Media Block end] -->
<div class="ms-auto">
<ul class="list-unstyled">
<li class="dropdown pc-h-item pc-mega-menu">
<a
class="pc-head-link dropdown-toggle arrow-none me-0"
data-bs-toggle="dropdown"
href="#"
role="button"
aria-haspopup="false"
aria-expanded="false"
>
<i class="ti ti-layout-grid"></i>
</a>
<div class="dropdown-menu pc-h-dropdown pc-mega-dmenu">
<div class="row g-0">
<div class="col image-block">
<h2 class="text-white">Explore Components</h2>
<p class="text-white my-4">Try our pre made component pages to check how it feels and suits as per your need.</p>
<div class="row align-items-end">
<div class="col-auto">
<div class="btn btn btn-light">View All <i class="ti ti-arrow-narrow-right"></i></div>
</div>
<div class="col">
<img src="../assets/images/mega-menu/chart.svg" alt="image" class="img-fluid img-charts">
</div>
</div>
</div>
<div class="col">
<h6 class="mega-title">UI Components</h6>
<ul class="pc-mega-list">
<li
><a href="#!" class="dropdown-item"><i class="ti ti-circle"></i> Alerts</a></li
>
<li
><a href="#!" class="dropdown-item"><i class="ti ti-circle"></i> Accordions</a></li
>
<li
><a href="#!" class="dropdown-item"><i class="ti ti-circle"></i> Avatars</a></li
>
<li
><a href="#!" class="dropdown-item"><i class="ti ti-circle"></i> Badges</a></li
>
<li
><a href="#!" class="dropdown-item"><i class="ti ti-circle"></i> Breadcrumbs</a></li
>
<li
><a href="#!" class="dropdown-item"><i class="ti ti-circle"></i> Button</a></li
>
<li
><a href="#!" class="dropdown-item"><i class="ti ti-circle"></i> Buttons Groups</a></li
>
</ul>
</div>
<div class="col">
<h6 class="mega-title">UI Components</h6>
<ul class="pc-mega-list">
<li
><a href="#!" class="dropdown-item"><i class="ti ti-circle"></i> Menus</a></li
>
<li
><a href="#!" class="dropdown-item"><i class="ti ti-circle"></i> Media Sliders / Carousel</a></li
>
<li
><a href="#!" class="dropdown-item"><i class="ti ti-circle"></i> Modals</a></li
>
<li
><a href="#!" class="dropdown-item"><i class="ti ti-circle"></i> Pagination</a></li
>
<li
><a href="#!" class="dropdown-item"><i class="ti ti-circle"></i> Progress Bars &amp; Graphs</a></li
>
<li
><a href="#!" class="dropdown-item"><i class="ti ti-circle"></i> Search Bar</a></li
>
<li
><a href="#!" class="dropdown-item"><i class="ti ti-circle"></i> Tabs</a></li
>
</ul>
</div>
<div class="col">
<h6 class="mega-title">Advance Components</h6>
<ul class="pc-mega-list">
<li
><a href="#!" class="dropdown-item"><i class="ti ti-circle"></i> Advanced Stats</a></li
>
<li
><a href="#!" class="dropdown-item"><i class="ti ti-circle"></i> Advanced Cards</a></li
>
<li
><a href="#!" class="dropdown-item"><i class="ti ti-circle"></i> Lightbox</a></li
>
<li
><a href="#!" class="dropdown-item"><i class="ti ti-circle"></i> Notification</a></li
>
</ul>
</div>
</div>
</div>
</li>
<li class="dropdown pc-h-item">
<a
class="pc-head-link dropdown-toggle arrow-none me-0"
data-bs-toggle="dropdown"
href="#"
role="button"
aria-haspopup="false"
aria-expanded="false"
>
<i class="ti ti-language"></i>
</a>
<div class="dropdown-menu dropdown-menu-end pc-h-dropdown">
<a href="#!" class="dropdown-item">
<i class="ti ti-user"></i>
<span>My Account</span>
</a>
<a href="#!" class="dropdown-item">
<i class="ti ti-settings"></i>
<span>Settings</span>
</a>
<a href="#!" class="dropdown-item">
<i class="ti ti-headset"></i>
<span>Support</span>
</a>
<a href="#!" class="dropdown-item">
<i class="ti ti-lock"></i>
<span>Lock Screen</span>
</a>
<a href="#!" class="dropdown-item">
<i class="ti ti-power"></i>
<span>Logout</span>
</a>
</div>
</li>
<li class="dropdown pc-h-item">
<a
class="pc-head-link dropdown-toggle arrow-none me-0"
data-bs-toggle="dropdown"
href="#"
role="button"
aria-haspopup="false"
aria-expanded="false"
>
<i class="ti ti-bell"></i>
<span class="badge bg-success pc-h-badge">3</span>
</a>
<div class="dropdown-menu dropdown-notification dropdown-menu-end pc-h-dropdown">
<div class="dropdown-header d-flex align-items-center justify-content-between">
<h5 class="m-0">Notification</h5>
<a href="#!" class="pc-head-link bg-transparent"><i class="ti ti-circle-check text-success"></i></a>
</div>
<div class="dropdown-divider"></div>
<div class="dropdown-header px-0 text-wrap header-notification-scroll position-relative" style="max-height: calc(100vh - 215px)">
<div class="list-group list-group-flush w-100">
<a class="list-group-item list-group-item-action">
<div class="d-flex">
<div class="flex-shrink-0">
<div class="user-avtar bg-light-success"><i class="ti ti-gift"></i></div>
</div>
<div class="flex-grow-1 ms-1">
<span class="float-end text-muted">3:00 AM</span>
<p class="text-body mb-1">It's <b>Cristina danny's</b> birthday today.</p>
<span class="text-muted">2 min ago</span>
</div>
</div>
</a>
<a class="list-group-item list-group-item-action">
<div class="d-flex">
<div class="flex-shrink-0">
<div class="user-avtar bg-light-primary"><i class="ti ti-message-circle"></i></div>
</div>
<div class="flex-grow-1 ms-1">
<span class="float-end text-muted">6:00 PM</span>
<p class="text-body mb-1"><b>Aida Burg</b> commented your post.</p>
<span class="text-muted">5 August</span>
</div>
</div>
</a>
<a class="list-group-item list-group-item-action">
<div class="d-flex">
<div class="flex-shrink-0">
<div class="user-avtar bg-light-danger"><i class="ti ti-settings"></i></div>
</div>
<div class="flex-grow-1 ms-1">
<span class="float-end text-muted">2:45 PM</span>
<p class="text-body mb-1">Your Profile is Complete &nbsp;<b>60%</b></p>
<span class="text-muted">7 hours ago</span>
</div>
</div>
</a>
<a class="list-group-item list-group-item-action">
<div class="d-flex">
<div class="flex-shrink-0">
<div class="user-avtar bg-light-primary"><i class="ti ti-headset"></i></div>
</div>
<div class="flex-grow-1 ms-1">
<span class="float-end text-muted">9:10 PM</span>
<p class="text-body mb-1"><b>Cristina Danny </b> invited to join <b> Meeting.</b></p>
<span class="text-muted">Daily scrum meeting time</span>
</div>
</div>
</a>
</div>
</div>
<div class="dropdown-divider"></div>
<div class="text-center py-2">
<a href="#!" class="link-primary">View all</a>
</div>
</div>
</li>
<li class="dropdown pc-h-item">
<a
class="pc-head-link dropdown-toggle arrow-none me-0"
data-bs-toggle="dropdown"
href="#"
role="button"
aria-haspopup="false"
aria-expanded="false"
>
<i class="ti ti-mail"></i>
</a>
<div class="dropdown-menu dropdown-notification dropdown-menu-end pc-h-dropdown">
<div class="dropdown-header d-flex align-items-center justify-content-between">
<h5 class="m-0">Message</h5>
<a href="#!" class="pc-head-link bg-transparent"><i class="ti ti-x text-danger"></i></a>
</div>
<div class="dropdown-divider"></div>
<div class="dropdown-header px-0 text-wrap header-notification-scroll position-relative" style="max-height: calc(100vh - 215px)">
<div class="list-group list-group-flush w-100">
<a class="list-group-item list-group-item-action">
<div class="d-flex">
<div class="flex-shrink-0">
<img src="../assets/images/user/avatar-2.jpg" alt="user-image" class="user-avtar">
</div>
<div class="flex-grow-1 ms-1">
<span class="float-end text-muted">3:00 AM</span>
<p class="text-body mb-1">It's <b>Cristina danny's</b> birthday today.</p>
<span class="text-muted">2 min ago</span>
</div>
</div>
</a>
<a class="list-group-item list-group-item-action">
<div class="d-flex">
<div class="flex-shrink-0">
<img src="../assets/images/user/avatar-1.jpg" alt="user-image" class="user-avtar">
</div>
<div class="flex-grow-1 ms-1">
<span class="float-end text-muted">6:00 PM</span>
<p class="text-body mb-1"><b>Aida Burg</b> commented your post.</p>
<span class="text-muted">5 August</span>
</div>
</div>
</a>
<a class="list-group-item list-group-item-action">
<div class="d-flex">
<div class="flex-shrink-0">
<img src="../assets/images/user/avatar-3.jpg" alt="user-image" class="user-avtar">
</div>
<div class="flex-grow-1 ms-1">
<span class="float-end text-muted">2:45 PM</span>
<p class="text-body mb-1"><b>There was a failure to your setup.</b></p>
<span class="text-muted">7 hours ago</span>
</div>
</div>
</a>
<a class="list-group-item list-group-item-action">
<div class="d-flex">
<div class="flex-shrink-0">
<img src="../assets/images/user/avatar-4.jpg" alt="user-image" class="user-avtar">
</div>
<div class="flex-grow-1 ms-1">
<span class="float-end text-muted">9:10 PM</span>
<p class="text-body mb-1"><b>Cristina Danny </b> invited to join <b> Meeting.</b></p>
<span class="text-muted">Daily scrum meeting time</span>
</div>
</div>
</a>
</div>
</div>
<div class="dropdown-divider"></div>
<div class="text-center py-2">
<a href="#!" class="link-primary">View all</a>
</div>
</div>
</li>
<li class="dropdown pc-h-item">
<a class="pc-head-link me-0" href="#" data-bs-toggle="offcanvas" data-bs-target="#offcanvas_pc_layout">
<i class="ti ti-settings"></i>
</a>
</li>
<li class="dropdown pc-h-item header-user-profile">
<a
class="pc-head-link dropdown-toggle arrow-none me-0"
data-bs-toggle="dropdown"
href="#"
role="button"
aria-haspopup="false"
data-bs-auto-close="outside"
aria-expanded="false"
>
<img src="../assets/images/user/avatar-2.jpg" alt="user-image" class="user-avtar">
<span>Stebin Ben</span>
</a>
<div class="dropdown-menu dropdown-user-profile dropdown-menu-end pc-h-dropdown">
<div class="dropdown-header">
<div class="d-flex mb-1">
<div class="flex-shrink-0">
<img src="../assets/images/user/avatar-2.jpg" alt="user-image" class="user-avtar wid-35">
</div>
<div class="flex-grow-1 ms-3">
<h6 class="mb-1">Stebin Ben</h6>
<span>UI/UX Designer</span>
</div>
<a href="#!" class="pc-head-link bg-transparent"><i class="ti ti-power text-danger"></i></a>
</div>
</div>
<ul class="nav drp-tabs nav-fill nav-tabs" id="mydrpTab" role="tablist">
<li class="nav-item" role="presentation">
<button
class="nav-link active"
id="drp-t1"
data-bs-toggle="tab"
data-bs-target="#drp-tab-1"
type="button"
role="tab"
aria-controls="drp-tab-1"
aria-selected="true"
><i class="ti ti-user"></i> Profile</button
>
</li>
<li class="nav-item" role="presentation">
<button
class="nav-link"
id="drp-t2"
data-bs-toggle="tab"
data-bs-target="#drp-tab-2"
type="button"
role="tab"
aria-controls="drp-tab-2"
aria-selected="false"
><i class="ti ti-settings"></i> Setting</button
>
</li>
</ul>
<div class="tab-content" id="mysrpTabContent">
<div class="tab-pane fade show active" id="drp-tab-1" role="tabpanel" aria-labelledby="drp-t1" tabindex="0">
<a href="#!" class="dropdown-item">
<i class="ti ti-edit-circle"></i>
<span>Edit Profile</span>
</a>
<a href="#!" class="dropdown-item">
<i class="ti ti-user"></i>
<span>View Profile</span>
</a>
<a href="#!" class="dropdown-item">
<i class="ti ti-clipboard-list"></i>
<span>Social Profile</span>
</a>
<a href="#!" class="dropdown-item">
<i class="ti ti-wallet"></i>
<span>Billing</span>
</a>
<a href="#!" class="dropdown-item">
<i class="ti ti-power"></i>
<span>Logout</span>
</a>
</div>
<div class="tab-pane fade" id="drp-tab-2" role="tabpanel" aria-labelledby="drp-t2" tabindex="0">
<a href="#!" class="dropdown-item">
<i class="ti ti-help"></i>
<span>Support</span>
</a>
<a href="#!" class="dropdown-item">
<i class="ti ti-user"></i>
<span>Account Settings</span>
</a>
<a href="#!" class="dropdown-item">
<i class="ti ti-lock"></i>
<span>Privacy Center</span>
</a>
<a href="#!" class="dropdown-item">
<i class="ti ti-messages"></i>
<span>Feedback</span>
</a>
<a href="#!" class="dropdown-item">
<i class="ti ti-list"></i>
<span>History</span>
</a>
</div>
</div>
</div>
</li>
</ul>
</div>
</div>
</header>
<!-- [ Header ] end -->
<!-- [ Main Content ] start -->
<div class="pc-container">
<div class="pc-content">
<!-- [ breadcrumb ] start -->
<div class="page-header">
<div class="page-block">
<div class="row align-items-center">
<div class="col-md-12">
<ul class="breadcrumb">
<li class="breadcrumb-item"><a href="../dashboard/index.html">Home</a></li>
<li class="breadcrumb-item"><a href="javascript: void(0)">Layout</a></li>
<li class="breadcrumb-item" aria-current="page">Compact Layout</li>
</ul>
</div>
<div class="col-md-12">
<div class="page-header-title">
<h2 class="mb-0">Compact Layout</h2>
</div>
</div>
</div>
</div>
</div>
<!-- [ breadcrumb ] end -->
<!-- [ Main Content ] start -->
<div class="row">
<!-- [ sample-page ] start -->
<div class="col-sm-12">
<div class="card">
<div class="card-header">
<h5>Hello card</h5>
</div>
<div class="card-body">
<p
>"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis
aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
</p>
</div>
</div>
</div>
<!-- [ sample-page ] end -->
</div>
<!-- [ Main Content ] end -->
</div>
</div>
<!-- [ Main Content ] end -->
<footer class="pc-footer">
<div class="footer-wrapper container-fluid">
<div class="row">
<div class="col-sm my-1">
<p class="m-0"
>Mantis &#9829; crafted by Team <a href="https://themeforest.net/user/codedthemes" target="_blank">Codedthemes</a> Distributed by <a href="https://themewagon.com/">ThemeWagon</a>.</p
>
</div>
<div class="col-auto my-1">
<ul class="list-inline footer-link mb-0">
<li class="list-inline-item"><a href="../index.html">Home</a></li>
<li class="list-inline-item"><a href="https://codedthemes.gitbook.io/mantis-bootstrap" target="_blank">Documentation</a></li>
<li class="list-inline-item"><a href="https://codedthemes.authordesk.app/" target="_blank">Support</a></li>
</ul>
</div>
</div>
</div>
</footer> <!-- Required Js -->
<script src="../assets/js/plugins/popper.min.js"></script>
<script src="../assets/js/plugins/simplebar.min.js"></script>
<script src="../assets/js/plugins/bootstrap.min.js"></script>
<script src="../assets/js/fonts/custom-font.js"></script>
<script src="../assets/js/pcoded.js"></script>
<script src="../assets/js/plugins/feather.min.js"></script>
<script>layout_change('light');</script>
<script>change_box_container('false');</script>
<script>layout_rtl_change('false');</script>
<script>preset_change("preset-1");</script>
<script>font_change("Public-Sans");</script>
<div class="offcanvas pct-offcanvas offcanvas-end" tabindex="-1" id="offcanvas_pc_layout">
<div class="offcanvas-header bg-primary">
<h5 class="offcanvas-title text-white">Mantis Customizer</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="pct-body" style="height: calc(100% - 60px)">
<div class="offcanvas-body">
<ul class="list-group list-group-flush">
<li class="list-group-item">
<a class="btn border-0 text-start w-100" data-bs-toggle="collapse" href="#pctcustcollapse1">
<div class="d-flex align-items-center">
<div class="flex-shrink-0">
<div class="avtar avtar-xs bg-light-primary">
<i class="ti ti-layout-sidebar f-18"></i>
</div>
</div>
<div class="flex-grow-1 ms-3">
<h6 class="mb-1">Theme Layout</h6>
<span>Choose your layout</span>
</div>
<i class="ti ti-chevron-down"></i>
</div>
</a>
<div class="collapse show" id="pctcustcollapse1">
<div class="pct-content">
<div class="pc-rtl">
<p class="mb-1">Direction</p>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="layoutmodertl">
<label class="form-check-label" for="layoutmodertl">RTL</label>
</div>
</div>
</div>
</div>
</li>
<li class="list-group-item">
<a class="btn border-0 text-start w-100" data-bs-toggle="collapse" href="#pctcustcollapse2">
<div class="d-flex align-items-center">
<div class="flex-shrink-0">
<div class="avtar avtar-xs bg-light-primary">
<i class="ti ti-brush f-18"></i>
</div>
</div>
<div class="flex-grow-1 ms-3">
<h6 class="mb-1">Theme Mode</h6>
<span>Choose light or dark mode</span>
</div>
<i class="ti ti-chevron-down"></i>
</div>
</a>
<div class="collapse show" id="pctcustcollapse2">
<div class="pct-content">
<div class="theme-color themepreset-color theme-layout">
<a href="#!" class="active" onclick="layout_change('light')" data-value="false"
><span><img src="../assets/images/customization/default.svg" alt="img"></span><span>Light</span></a
>
<a href="#!" class="" onclick="layout_change('dark')" data-value="true"
><span><img src="../assets/images/customization/dark.svg" alt="img"></span><span>Dark</span></a
>
</div>
</div>
</div>
</li>
<li class="list-group-item">
<a class="btn border-0 text-start w-100" data-bs-toggle="collapse" href="#pctcustcollapse3">
<div class="d-flex align-items-center">
<div class="flex-shrink-0">
<div class="avtar avtar-xs bg-light-primary">
<i class="ti ti-color-swatch f-18"></i>
</div>
</div>
<div class="flex-grow-1 ms-3">
<h6 class="mb-1">Color Scheme</h6>
<span>Choose your primary theme color</span>
</div>
<i class="ti ti-chevron-down"></i>
</div>
</a>
<div class="collapse show" id="pctcustcollapse3">
<div class="pct-content">
<div class="theme-color preset-color">
<a href="#!" class="active" data-value="preset-1"
><span><img src="../assets/images/customization/theme-color.svg" alt="img"></span><span>Theme 1</span></a
>
<a href="#!" class="" data-value="preset-2"
><span><img src="../assets/images/customization/theme-color.svg" alt="img"></span><span>Theme 2</span></a
>
<a href="#!" class="" data-value="preset-3"
><span><img src="../assets/images/customization/theme-color.svg" alt="img"></span><span>Theme 3</span></a
>
<a href="#!" class="" data-value="preset-4"
><span><img src="../assets/images/customization/theme-color.svg" alt="img"></span><span>Theme 4</span></a
>
<a href="#!" class="" data-value="preset-5"
><span><img src="../assets/images/customization/theme-color.svg" alt="img"></span><span>Theme 5</span></a
>
<a href="#!" class="" data-value="preset-6"
><span><img src="../assets/images/customization/theme-color.svg" alt="img"></span><span>Theme 6</span></a
>
<a href="#!" class="" data-value="preset-7"
><span><img src="../assets/images/customization/theme-color.svg" alt="img"></span><span>Theme 7</span></a
>
<a href="#!" class="" data-value="preset-8"
><span><img src="../assets/images/customization/theme-color.svg" alt="img"></span><span>Theme 8</span></a
>
<a href="#!" class="" data-value="preset-9"
><span><img src="../assets/images/customization/theme-color.svg" alt="img"></span><span>Theme 9</span></a
>
</div>
</div>
</div>
</li>
<li class="list-group-item pc-boxcontainer">
<a class="btn border-0 text-start w-100" data-bs-toggle="collapse" href="#pctcustcollapse4">
<div class="d-flex align-items-center">
<div class="flex-shrink-0">
<div class="avtar avtar-xs bg-light-primary">
<i class="ti ti-border-inner f-18"></i>
</div>
</div>
<div class="flex-grow-1 ms-3">
<h6 class="mb-1">Layout Width</h6>
<span>Choose fluid or container layout</span>
</div>
<i class="ti ti-chevron-down"></i>
</div>
</a>
<div class="collapse show" id="pctcustcollapse4">
<div class="pct-content">
<div class="theme-color themepreset-color boxwidthpreset theme-container">
<a href="#!" class="active" onclick="change_box_container('false')" data-value="false"><span><img src="../assets/images/customization/default.svg" alt="img"></span><span>Fluid</span></a>
<a href="#!" class="" onclick="change_box_container('true')" data-value="true"><span><img src="../assets/images/customization/container.svg" alt="img"></span><span>Container</span></a>
</div>
</div>
</div>
</li>
<li class="list-group-item">
<a class="btn border-0 text-start w-100" data-bs-toggle="collapse" href="#pctcustcollapse5">
<div class="d-flex align-items-center">
<div class="flex-shrink-0">
<div class="avtar avtar-xs bg-light-primary">
<i class="ti ti-typography f-18"></i>
</div>
</div>
<div class="flex-grow-1 ms-3">
<h6 class="mb-1">Font Family</h6>
<span>Choose your font family.</span>
</div>
<i class="ti ti-chevron-down"></i>
</div>
</a>
<div class="collapse show" id="pctcustcollapse5">
<div class="pct-content">
<div class="theme-color fontpreset-color">
<a href="#!" class="active" onclick="font_change('Public-Sans')" data-value="Public-Sans"
><span>Aa</span><span>Public Sans</span></a
>
<a href="#!" class="" onclick="font_change('Roboto')" data-value="Roboto"><span>Aa</span><span>Roboto</span></a>
<a href="#!" class="" onclick="font_change('Poppins')" data-value="Poppins"><span>Aa</span><span>Poppins</span></a>
<a href="#!" class="" onclick="font_change('Inter')" data-value="Inter"><span>Aa</span><span>Inter</span></a>
</div>
</div>
</div>
</li>
<li class="list-group-item">
<div class="collapse show">
<div class="pct-content">
<div class="d-grid">
<button class="btn btn-light-danger" id="layoutreset">Reset Layout</button>
</div>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
<script src="../assets/js/layout-compact.js"></script>
</body>
<!-- [Body] end -->
</html>

View File

@@ -0,0 +1,33 @@
<?php
/**
* @var string $resources
*/
?>
<div class="about_section layout_padding">
<div class="container">
<div class="row">
<div class="col-md-4">
<div class="about_box">
<div class="icon_1"><img src="<?= $resources ?>/images/slide_img.jpg"></div>
<h3 class="faster_text">Faster Loading Speed</h3>
<p class="lorem_text">ike readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text,</p>
</div>
</div>
<div class="col-md-4">
<div class="about_box">
<div class="icon_1"><img src="<?= $resources ?>/images/slide_img.jpg"></div>
<h3 class="faster_text">Faster Loading Speed</h3>
<p class="lorem_text">ike readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text,</p>
</div>
</div>
<div class="col-md-4">
<div class="about_box">
<div class="icon_1"><img src="<?= $resources ?>/images/slide_img.jpg"></div>
<h3 class="faster_text">Faster Loading Speed</h3>
<p class="lorem_text">ike readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text,</p>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,94 @@
<?php
/**
* @var string $stage
* @var string $user
* @var string $url
*/
?>
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Email рассылка</title>
<style type="text/css">
/* Основные стили */
body {
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
line-height: 1.4;
color: #333333;
background-color: #f4f4f4;
}
/* Контейнер письма */
.email-container {
max-width: 600px;
margin: 0 auto;
background: #ffffff;
}
/* Шапка письма */
.email-header {
padding: 20px;
background: #3498db;
color: #ffffff;
text-align: center;
}
/* Основное содержимое */
.email-body {
padding: 20px;
}
/* Подвал письма */
.email-footer {
padding: 20px;
font-size: 12px;
color: #777777;
text-align: center;
background: #eeeeee;
}
/* Кнопка */
.btn {
display: inline-block;
padding: 10px 20px;
background: #3498db;
color: #ffffff;
text-decoration: none;
border-radius: 4px;
margin-top: 15px;
}
</style>
</head>
<body>
<div class="email-container">
<!-- Заголовок письма -->
<div class="email-header">
<h1>Открыт новый этап "<?= $stage ?>"</h1>
</div>
<!-- Тело письма -->
<div class="email-body">
<p>Здравствуйте, <?= $user ?>!</p>
<p>Вам открыт новый этап <?= $stage ?></p>
<a href="<?= $url ?>" class="btn">Перейти на сайт</a>
<p>С уважением,<br><?= $_ENV['APP_NAME'] ?></p>
</div>
<!-- Подвал письма -->
<div class="email-footer">
<p>©<?= date('Y') ?> <?= $_ENV['APP_NAME'] ?>. Все права защищены.</p>
<p>
<a href="<?= $url ?>" style="color: #3498db;"><?= $_ENV['APP_NAME'] ?></a> |
<a href="#" style="color: #3498db;">Отписаться</a>
</p>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,36 @@
<?php
/**
* @var string $resources;
* @var \kernel\CgView $view
*/
$view->setTitle("Старт Bootstrap");
$view->setMeta([
'description' => 'Дефолтная bootstrap тема'
]);
?>
<!-- Page Header-->
<header class="masthead" style="background-image: url('<?= $resources ?>/assets/img/about-bg.jpeg')">
<div class="container position-relative px-4 px-lg-5">
<div class="row gx-4 gx-lg-5 justify-content-center">
<div class="col-md-10 col-lg-8 col-xl-7">
<div class="page-heading">
<h1>About Me</h1>
<span class="subheading">This is what I do.</span>
</div>
</div>
</div>
</div>
</header>
<!-- Main Content-->
<main class="mb-4">
<div class="container px-4 px-lg-5">
<div class="row gx-4 gx-lg-5 justify-content-center">
<div class="col-md-10 col-lg-8 col-xl-7">
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Saepe nostrum ullam eveniet pariatur voluptates odit, fuga atque ea nobis sit soluta odio, adipisci quas excepturi maxime quae totam ducimus consectetur?</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eius praesentium recusandae illo eaque architecto error, repellendus iusto reprehenderit, doloribus, minus sunt. Numquam at quae voluptatum in officia voluptas voluptatibus, minus!</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aut consequuntur magnam, excepturi aliquid ex itaque esse est vero natus quae optio aperiam soluta voluptatibus corporis atque iste neque sit tempora!</p>
</div>
</div>
</div>
</main>

View File

@@ -0,0 +1,87 @@
<?php
/**
* @var string $resources;
* @var \kernel\CgView $view
*/
$view->setTitle("IT Guild Micro Framework");
$view->setMeta([
'description' => 'Default IT Guild Micro Framework theme'
]);
?>
<!-- Page Header-->
<header class="masthead" style="background-image: url('<?= $resources ?>/assets/img/home-bg.jpeg')">
<div class="container position-relative px-4 px-lg-5">
<div class="row gx-4 gx-lg-5 justify-content-center">
<div class="col-md-10 col-lg-8 col-xl-7">
<div class="site-heading">
<h1>Clean Blog</h1>
<span class="subheading">A Blog Theme by IT Guild Micro Framework</span>
</div>
</div>
</div>
</div>
</header>
<!-- Main Content-->
<div class="container px-4 px-lg-5">
<div class="row gx-4 gx-lg-5 justify-content-center">
<div class="col-md-10 col-lg-8 col-xl-7">
<!-- Post preview-->
<div class="post-preview">
<a href="#!">
<h2 class="post-title">Man must explore, and this is exploration at its greatest</h2>
<h3 class="post-subtitle">Problems look mighty small from 150 miles up</h3>
</a>
<p class="post-meta">
Posted by
<a href="#!">Start Bootstrap</a>
on September 24, 2023
</p>
</div>
<!-- Divider-->
<hr class="my-4" />
<!-- Post preview-->
<div class="post-preview">
<a href="#!"><h2 class="post-title">I believe every human has a finite number of heartbeats. I don't intend to waste any of mine.</h2></a>
<p class="post-meta">
Posted by
<a href="#!">Start Bootstrap</a>
on September 18, 2023
</p>
</div>
<!-- Divider-->
<hr class="my-4" />
<!-- Post preview-->
<div class="post-preview">
<a href="#!">
<h2 class="post-title">Science has not yet mastered prophecy</h2>
<h3 class="post-subtitle">We predict too much for the next year and yet far too little for the next ten.</h3>
</a>
<p class="post-meta">
Posted by
<a href="#!">Start Bootstrap</a>
on August 24, 2023
</p>
</div>
<!-- Divider-->
<hr class="my-4" />
<!-- Post preview-->
<div class="post-preview">
<a href="#!">
<h2 class="post-title">Failure is not an option</h2>
<h3 class="post-subtitle">Many say exploration is part of our destiny, but its actually our duty to future generations.</h3>
</a>
<p class="post-meta">
Posted by
<a href="#!">Start Bootstrap</a>
on July 8, 2023
</p>
</div>
<!-- Divider-->
<hr class="my-4" />
<!-- Pager-->
<div class="d-flex justify-content-end mb-4"><a class="btn btn-primary text-uppercase" href="#!">Older Posts →</a></div>
</div>
</div>
</div>

View File

@@ -0,0 +1,49 @@
<?php
/**
* @var \kernel\app_modules\user_stage\models\User $user
*/
use kernel\app_modules\user_stage\services\UserStageService;
?>
<!-- [ Main Content ] start -->
<!-- [ breadcrumb ] start -->
<div class="page-header">
<div class="page-block">
<div class="row align-items-center">
<div class="col-md-12">
<div class="page-header-title">
<h2 class="mb-0">Этапы конкурса</h2>
</div>
</div>
</div>
</div>
</div>
<!-- [ breadcrumb ] end -->
<!-- [ Main Content ] start -->
<div class="row">
<!-- [ sample-page ] start -->
<div class="col-sm-12">
<?php foreach ($user->stages as $stage): ?>
<div class="card <?= UserStageService::getStageClass($stage); ?>">
<a href="/lk/stage/<?= $stage->id ?>" <?= $stage->pivot->is_closed === 1 ? "onclick='return false;'" : "" ?>>
<div class="card-header">
<h5><?= $stage->title ?></h5>
</div>
</a>
<div class="card-body">
<p>
<?= $stage->description ?>
</p>
<p class="mb-0 text-muted text-sm">Статус:
<b><?= UserStageService::getStageStatusText($stage); ?></b></p>
</div>
</div>
<?php endforeach; ?>
</div>
<!-- [ sample-page ] end -->
</div>
<!-- [ Main Content ] end -->
<!-- [ Main Content ] end -->

View File

@@ -0,0 +1,55 @@
<?php
/**
* @var string $resources
*/
?>
<div class="auth-main">
<div class="auth-wrapper v3">
<div class="auth-form">
<div class="auth-header">
<a href="#"><img src="<?= $resources ?>/assets/images/logo-dark.svg" alt="img"></a>
</div>
<div class="card my-5">
<div class="card-body">
<form action="/lk/auth" method="post">
<div class="d-flex justify-content-between align-items-end mb-4">
<h3 class="mb-0"><b>Авторизация</b></h3>
<a href="/registration" class="link-primary">Создать аккаунт?</a>
</div>
<div class="form-group mb-3">
<label class="form-label">Почта</label>
<input type="text" name="username" class="form-control" placeholder="Почта">
</div>
<div class="form-group mb-3">
<label class="form-label">Пароль</label>
<input type="password" name="password" class="form-control" placeholder="Пароль">
</div>
<div class="d-flex mt-1 justify-content-between">
<h5 class="text-secondary f-w-400">Забыл пароль?</h5>
</div>
<div class="d-grid mt-4">
<button type="submit" class="btn btn-primary">Вход</button>
</div>
</form>
<div class="row">
</div>
</div>
</div>
<div class="auth-footer row">
<!-- <div class=""> -->
<div class="col my-1">
<p class="m-0">Copyright © <a href="https://itguild.info/">ITGuild</a></p>
</div>
<div class="col-auto my-1">
<ul class="list-inline footer-link mb-0">
</ul>
</div>
<!-- </div> -->
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,56 @@
<?php
/**
* @var string $resources
*/
?>
<div class="auth-main">
<div class="auth-wrapper v3">
<div class="auth-form">
<div class="auth-header">
<a href="#"><img src="<?= $resources ?>/assets/images/logo-dark.svg" alt="img"></a>
</div>
<div class="card my-5">
<div class="card-body">
<div class="d-flex justify-content-between align-items-end mb-4">
<h3 class="mb-0"><b>Регистрация</b></h3>
<a href="/lk/login" class="link-primary">Уже есть аккаунт?</a>
</div>
<form action="/lk/registration" method="post">
<div class="form-group mb-3">
<label class="form-label">Логин</label>
<input type="text" name="username" class="form-control" placeholder="login">
</div>
<div class="form-group mb-3">
<label class="form-label">Почта</label>
<input type="email" name="email" class="form-control" placeholder="test@mail.ru">
</div>
<div class="form-group mb-3">
<label class="form-label">Пароль</label>
<input type="password" name="password" class="form-control">
</div>
<div class="d-grid mt-3">
<button type="submit" class="btn btn-primary">Создать аккаунт</button>
</div>
</form>
<div class="row">
</div>
</div>
</div>
<div class="auth-footer row">
<!-- <div class=""> -->
<div class="col my-1">
<p class="m-0">Copyright © <a href="https://itguild.info/">ITGuild</a></p>
</div>
<div class="col-auto my-1">
<ul class="list-inline footer-link mb-0">
</ul>
</div>
<!-- </div> -->
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,128 @@
<?php
?>
<div class="row">
<!-- [ sample-page ] start -->
<div class="col-sm-12">
<div class="card">
<div class="card-header pb-0">
<ul class="nav nav-tabs profile-tabs" id="myTab" role="tablist">
<li class="nav-item">
<a class="nav-link active" id="profile-tab-4" data-bs-toggle="tab" href="#profile-4" role="tab"
aria-selected="true">
<i class="ti ti-lock me-2"></i>Сменить пароль
</a>
</li>
<li class="nav-item">
<a class="nav-link" id="profile-tab-6" data-bs-toggle="tab" href="#profile-6" role="tab"
aria-selected="true">
<i class="ti ti-settings me-2"></i>Настройки
</a>
</li>
</ul>
</div>
<div class="card-body">
<div class="tab-content">
<div class="tab-pane show active" id="profile-4" role="tabpanel" aria-labelledby="profile-tab-4">
<div class="card">
<div class="card-header">
<h5>Смена пароля</h5>
</div>
<div class="card-body">
<div class="row">
<form action="/lk/settings/change_password" method="post" id="change_password">
<div class="col-sm-6">
<div class="form-group">
<label class="form-label">Старый пароль</label>
<input name="old_password" type="password" class="form-control">
</div>
<div class="form-group">
<label class="form-label">Новый пароль</label>
<input name="new_password" type="password" class="form-control">
</div>
</div>
</form>
<div class="col-sm-6">
<h5>Пароль должен содержать:</h5>
<ul class="list-group list-group-flush">
<li class="list-group-item"><i class="ti ti-minus me-2"></i> Не менее 8
символов
</li>
<li class="list-group-item"><i class="ti ti-minus me-2"></i> Как минимум 1
строчная буква (a-z)
</li>
<li class="list-group-item"><i class="ti ti-minus me-2"></i> Как минимум, 1
заглавная буква
(A-Z)
</li>
<li class="list-group-item"><i class="ti ti-minus me-2"></i> Не менее 1
числа (0-9)
</li>
</ul>
</div>
</div>
</div>
<div class="card-footer text-end btn-page">
<a href="/lk">
<div class="btn btn-outline-secondary">Отмена</div>
</a>
<button onclick="document.getElementById('change_password').submit()"
class="btn btn-primary">
Редактировать
</button>
</div>
</div>
</div>
<div class="tab-pane" id="profile-6" role="tabpanel" aria-labelledby="profile-tab-6">
<div class="row">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h5>Настройки почты</h5>
</div>
<div class="card-body">
<h6 class="mb-4">Уведомления</h6>
<form action="/lk/settings/save_notification" method="post">
<div class="d-flex align-items-center justify-content-between mb-1">
<div>
<p class="text-muted mb-0">Email уведомления</p>
</div>
<div class="form-check form-switch p-0">
<input name="email_notification" class="m-0 form-check-input h5 position-relative"
type="checkbox"
role="switch" checked="">
</div>
</div>
<div class="d-flex align-items-center justify-content-between mb-1">
<div>
<p class="text-muted mb-0">SMS уведомления</p>
</div>
<div class="form-check form-switch p-0">
<input name="sms_notification" class="m-0 form-check-input h5 position-relative"
type="checkbox"
role="switch">
</div>
</div>
</form>
</div>
</div>
</div>
<div class="col-12 text-end btn-page">
<a href="/lk">
<div class="btn btn-outline-secondary">Отмена</div>
</a>
<button onclick="document.getElementById('change_password').submit()"
class="btn btn-primary">
Редактировать
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- [ sample-page ] end -->
</div>

View File

@@ -0,0 +1,28 @@
<?php
/**
* @var \Illuminate\Database\Eloquent\Collection $fields
* @var \kernel\app_modules\user_stage\models\UserStage $stage
* @var \kernel\modules\user\models\User $currentUser
*/
?>
<div class="row">
<!-- [ sample-page ] start -->
<div class="col-sm-12">
<div class="card">
<div class="card-header">
<h5><?= $stage->title ?></h5>
</div>
<div class="card-body">
<form method="post" action="/lk/stage/save">
<input type="hidden" name="stage_id" value="<?= $stage->id ?>">
<?php foreach ($fields as $field): ?>
<?= \kernel\app_modules\user_custom_fields\services\CustomFieldService::getCustomFieldHtml($field, $currentUser->id) ?>
<?php endforeach; ?>
<div class="d-grid mt-4">
<button type="submit" class="btn btn-primary">Сохранить</button>
</div>
</form>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,56 @@
<?php
/**
* @var string $resources
*/
?>
<div class="banner_section layout_padding">
<div id="my_slider" class="carousel slide" data-ride="carousel">
<div class="carousel-inner">
<div class="carousel-item active">
<div class="container">
<div class="row">
<div class="col-md-6">
<h1 class="banner_taital">Hosting <br>And Domain</h1>
<div class="read_bt"><a href="#">Read More</a></div>
</div>
<div class="col-md-6">
<div class="banner_img"><img src="<?= $resources ?>/images/slide_img.jpg"></div>
</div>
</div>
</div>
</div>
<div class="carousel-item">
<div class="container">
<div class="row">
<div class="col-md-6">
<h1 class="banner_taital">Hosting <br>And Domain</h1>
<div class="read_bt"><a href="#">Read More</a></div>
</div>
<div class="col-md-6">
<div class="banner_img"><img src="<?= $resources ?>/images/slide_img.jpg"></div>
</div>
</div>
</div>
</div>
<div class="carousel-item">
<div class="container">
<div class="row">
<div class="col-md-6">
<h1 class="banner_taital">Hosting <br>And Domain</h1>
<div class="read_bt"><a href="#">Read More</a></div>
</div>
<div class="col-md-6">
<div class="banner_img"><img src="<?= $resources ?>/images/slide_img.jpg"></div>
</div>
</div>
</div>
</div>
</div>
<a class="carousel-control-prev" href="#my_slider" role="button" data-slide="prev">
<i class="fa fa-angle-left"></i>
</a>
<a class="carousel-control-next" href="#my_slider" role="button" data-slide="next">
<i class="fa fa-angle-right"></i>
</a>
</div>
</div>

View File

@@ -0,0 +1,20 @@
<?php
namespace app\themes\svo\widgets;
use kernel\Widget;
class MainSliderWidget extends Widget
{
protected function init(): void
{
$this->cgView->viewPath = APP_DIR . "/themes/svo/views/widget/";
}
public function run(): void
{
$this->cgView->render("main_slider.php", ['resources' => $this->data['resources']]);
}
}

View File

@@ -7,6 +7,7 @@ $dotenv->load();
include_once __DIR__ . "/bootstrap/db.php";
include_once __DIR__ . "/bootstrap/header.php";
include_once __DIR__ . "/bootstrap/secure.php";
include_once __DIR__ . "/bootstrap/notification.php";
const ROOT_DIR = __DIR__;
const KERNEL_DIR = __DIR__ . "/kernel";
const KERNEL_MODULES_DIR = __DIR__ . "/kernel/modules";

View File

@@ -0,0 +1,4 @@
<?php
\kernel\App::$notificationDispatcher = new \kernel\modules\notification\NotificationDispatcher();
\kernel\App::$notificationDispatcher->addChannel('email', new \kernel\modules\notification\channels\EmailChannel());

View File

@@ -5,6 +5,7 @@ namespace kernel;
use kernel\helpers\Debug;
use kernel\modules\notification\NotificationDispatcher;
use kernel\modules\user\models\User;
use kernel\services\ModuleService;
use kernel\services\ThemeService;
@@ -21,10 +22,14 @@ class App
static User $user;
static NotificationDispatcher $notificationDispatcher;
static array $secure;
public ModuleService $moduleService;
static Hook $hook;
public ThemeService $themeService;
public static Database $db;
@@ -41,6 +46,7 @@ class App
public function load(): static
{
App::$hook = new Hook();
$this->moduleService = new ModuleService();
App::$collector = new CgRouteCollector();
$this->setRouting();
@@ -53,6 +59,7 @@ class App
include KERNEL_DIR . "/routs/admin.php";
include ROOT_DIR . "/rout.php";
$modules_routs = $this->moduleService->getModulesRouts();
$this->moduleService->setModulesHooks();
foreach ($modules_routs as $rout){
include "$rout";
}

View File

@@ -0,0 +1,345 @@
<?php
namespace kernel;
use Illuminate\Database\Eloquent\Collection;
class CollectionTableRenderer
{
protected \Illuminate\Database\Eloquent\Collection $collection;
protected array $columns;
protected array $tableAttributes;
protected array $customColumns = [];
protected array $valueProcessors = [];
protected array $filters = [];
protected bool $filterRowEnabled = false;
protected array $filterRowAttributes = [
'class' => 'filter-row'
];
/**
* Конструктор класса
*
* @param Collection $collection
*/
public function __construct(Collection $collection)
{
$this->collection = $collection;
$this->columns = [];
$this->customColumns = [];
$this->valueProcessors = [];
$this->filters = [];
$this->tableAttributes = [
'class' => 'table table-bordered table-striped',
'id' => 'dataTable'
];
}
/**
* Установка столбцов для отображения
*
* @param array $columns Массив столбцов в формате ['field' => 'Заголовок']
* @return $this
*/
public function setColumns(array $columns): static
{
$this->columns = $columns;
return $this;
}
/**
* Добавление кастомной колонки
*
* @param string $columnName Название колонки (ключ)
* @param string $title Заголовок колонки
* @param callable $callback Функция для генерации содержимого
* @return $this
*/
public function addCustomColumn(string $columnName, string $title, callable $callback): static
{
$this->customColumns[$columnName] = [
'title' => $title,
'callback' => $callback
];
return $this;
}
/**
* Добавление обработчика значения для колонки
*
* @param string $columnName Название колонки
* @param callable $processor Функция обработки значения
* @return $this
*/
public function addValueProcessor(string $columnName, callable $processor): static
{
$this->valueProcessors[$columnName] = $processor;
return $this;
}
/**
* Добавление фильтра для колонки
*
* @param string $columnName Название колонки
* @param string $filterHtml HTML-код фильтра
* @return $this
*/
public function addFilter(string $columnName, string $filterHtml): static
{
$this->filters[$columnName] = $filterHtml;
$this->filterRowEnabled = true;
return $this;
}
/**
* Включение/отключение строки фильтров
*
* @param bool $enabled
* @return $this
*/
public function enableFilterRow(bool $enabled = true): static
{
$this->filterRowEnabled = $enabled;
return $this;
}
/**
* Установка атрибутов строки фильтров
*
* @param array $attributes
* @return $this
*/
public function setFilterRowAttributes(array $attributes): static
{
$this->filterRowAttributes = array_merge($this->filterRowAttributes, $attributes);
return $this;
}
/**
* Установка атрибутов для таблицы
*
* @param array $attributes Массив атрибутов HTML
* @return $this
*/
public function setTableAttributes(array $attributes): static
{
$this->tableAttributes = array_merge($this->tableAttributes, $attributes);
return $this;
}
/**
* Получить HTML-код таблицы (без вывода)
*
* @return string
*/
public function getHtml(): string
{
if (empty($this->columns) && empty($this->customColumns) && $this->collection->isNotEmpty()) {
// Автоматически определяем столбцы на основе первой модели
$firstItem = $this->collection->first();
$this->columns = array_combine(
array_keys($firstItem->toArray()),
array_map('ucfirst', array_keys($firstItem->toArray()))
);
}
$html = '<table ' . $this->buildAttributes($this->tableAttributes) . '>';
$html .= $this->renderHeader();
if ($this->filterRowEnabled) {
$html .= $this->renderFilterRow();
}
$html .= $this->renderBody();
$html .= '</table>';
return $html;
}
/**
* Генерация HTML-таблицы
*
* @return string
*/
public function render(): void
{
echo $this->getHtml();
}
public function fetch(): string
{
return $this->getHtml();
}
/**
* Генерация заголовка таблицы
*
* @return string
*/
protected function renderHeader(): string
{
$html = '<thead><tr>';
// Обычные колонки
foreach ($this->columns as $title) {
$html .= '<th>' . htmlspecialchars($title) . '</th>';
}
// Кастомные колонки
foreach ($this->customColumns as $column) {
$html .= '<th>' . htmlspecialchars($column['title']) . '</th>';
}
$html .= '</tr></thead>';
return $html;
}
/**
* Генерация строки фильтров
*
* @return string
*/
protected function renderFilterRow(): string
{
$html = '<tr ' . $this->buildAttributes($this->filterRowAttributes) . '>';
// Обычные колонки
foreach (array_keys($this->columns) as $field) {
$html .= '<td>';
if (isset($this->filters[$field])) {
$html .= $this->filters[$field];
} else {
$html .= '&nbsp;';
}
$html .= '</td>';
}
// Кастомные колонки
foreach (array_keys($this->customColumns) as $columnName) {
$html .= '<td>';
if (isset($this->filters[$columnName])) {
$html .= $this->filters[$columnName];
} else {
$html .= '&nbsp;';
}
$html .= '</td>';
}
$html .= '</tr>';
return $html;
}
/**
* Генерация тела таблицы
*
* @return string
*/
protected function renderBody(): string
{
$html = '<tbody>';
foreach ($this->collection as $item) {
$html .= '<tr>';
// Обычные колонки
foreach (array_keys($this->columns) as $field) {
$value = $this->getValue($item, $field);
$value = $this->processValue($field, $value, $item);
$html .= '<td>' . $value . '</td>';
}
// Кастомные колонки
foreach ($this->customColumns as $columnName => $column) {
$value = call_user_func($column['callback'], $item, $columnName);
$html .= '<td>' . $value . '</td>';
}
$html .= '</tr>';
}
$html .= '</tbody>';
return $html;
}
/**
* Обработка значения ячейки
*
* @param string $field Название поля
* @param mixed $value Значение
* @param mixed $item Весь элемент коллекции
* @return mixed
*/
protected function processValue(string $field, mixed $value, mixed $item): mixed
{
// Если есть обработчик для этого поля - применяем его
if (isset($this->valueProcessors[$field])) {
$value = call_user_func($this->valueProcessors[$field], $value, $item, $field);
}
// Если значение не прошло обработку - экранируем его
// if (is_string($value)) {
// return htmlspecialchars($value);
// }
return $value;
}
/**
* Получение значения из элемента коллекции
*
* @param mixed $item
* @param string $field
* @return mixed
*/
protected function getValue(mixed $item, string $field): mixed
{
// Поддержка dot-notation для отношений
if (str_contains($field, '.')) {
return data_get($item, $field);
}
// Поддержка accessor'ов модели
if (method_exists($item, 'get' . ucfirst($field) . 'Attribute')) {
return $item->{$field};
}
return $item->{$field} ?? null;
}
/**
* Сборка HTML-атрибутов
*
* @param array $attributes
* @return string
*/
protected function buildAttributes(array $attributes): string
{
$result = [];
foreach ($attributes as $key => $value) {
$result[] = $key . '="' . htmlspecialchars($value) . '"';
}
return implode(' ', $result);
}
/**
* Магический метод для преобразования в строку
*
* @return string
*/
public function __toString()
{
return $this->render();
}
}

41
kernel/Hook.php Normal file
View File

@@ -0,0 +1,41 @@
<?php
namespace kernel;
class Hook
{
protected array $pool;
public function add(string $entity, \Closure $handler): void
{
$this->pool[] = [
'entity' => $entity,
'handler' => $handler,
];
}
public function getHooksByEntity(string $entity): array
{
$hooks = [];
foreach ($this->pool as $item){
if ($item['entity'] === $entity){
$hooks[] = $item;
}
}
return $hooks;
}
public function runHooksByEntity(string $entity, array $params = []): void
{
$response = '';
$hooks = $this->getHooksByEntity($entity);
foreach ($hooks as $hook){
$response .= call_user_func_array($hook['handler'], $params ?? []);
}
echo $response;
}
}

View File

@@ -0,0 +1,163 @@
<?php
namespace kernel\app_modules\user_custom_fields;
use Illuminate\Database\Eloquent\Model;
use itguild\forms\builders\SelectBuilder;
use itguild\forms\builders\TextInputBuilder;
use kernel\app_modules\tag\models\Tag;
use kernel\app_modules\tag\service\TagEntityService;
use kernel\app_modules\user_custom_fields\models\CustomField;
use kernel\app_modules\user_custom_fields\models\forms\CreateUserCustomValueForm;
use kernel\app_modules\user_custom_fields\models\UserCustomValues;
use kernel\app_modules\user_custom_fields\services\CustomFieldService;
use kernel\app_modules\user_custom_fields\services\UserCustomValuesService;
use kernel\EntityRelation;
use kernel\helpers\Debug;
use kernel\Module;
use kernel\modules\menu\service\MenuService;
use kernel\Request;
use kernel\services\MigrationService;
class UserCustomFieldsModule extends Module
{
public MenuService $menuService;
public MigrationService $migrationService;
public function __construct()
{
$this->menuService = new MenuService();
$this->migrationService = new MigrationService();
}
public function init(): void
{
$this->migrationService->runAtPath("{KERNEL_APP_MODULES}/user_custom_fields/migrations");
$this->menuService->createItem([
"label" => "Доп. поля пользователей",
"url" => "/admin/custom_field",
"slug" => "custom_field",
]);
$this->menuService->createItem([
"label" => "Список",
"url" => "/admin/custom_field",
"slug" => "custom_field_list",
"parent_slug" => "custom_field"
]);
$this->menuService->createItem([
"label" => "Значения",
"url" => "/admin/custom_field/user_values",
"slug" => "custom_field_user_values",
"parent_slug" => "custom_field"
]);
EntityRelation::addEntityRelation('user', 'user_custom_fields');
}
/**
* @throws \Exception
*/
public function deactivate(): void
{
$this->migrationService->rollbackAtPath("{KERNEL_APP_MODULES}/user_custom_fields/migrations");
$this->menuService->removeItemBySlug("custom_field_user_values");
$this->menuService->removeItemBySlug("custom_field_list");
$this->menuService->removeItemBySlug("custom_field");
EntityRelation::removePropertyFromEntityRelations('user', 'user_custom_fields');
}
public function formInputs(string $entity, Model $model = null): void
{
$fields = CustomFieldService::getCustomFields();
foreach ($fields as $field) {
/* @var CustomField $field */
if (isset($model->id)) {
$value = UserCustomValuesService::getValueByFieldAndUser($field->id, $model->id);
}
if ($field->type === "string"){
$input = TextInputBuilder::build($field->slug, [
'class' => 'form-control',
'placeholder' => $field->label,
'value' => $value->value ?? '',
]);
}
else {
$options = explode(", ", $field->field_options);
$options = array_combine($options, $options);
$input = SelectBuilder::build($field->slug, [
'class' => 'form-control',
'placeholder' => $field->label,
'value' => $value->value ?? '',
])->setOptions($options);
}
$input->setLabel($field->label);
$input->create()->render();
}
}
public function saveInputs(string $entity, Model $model, Request $request): void
{
$service = new UserCustomValuesService();
$form = new CreateUserCustomValueForm();
$fields = CustomFieldService::getCustomFields();
foreach ($fields as $field){
/* @var CustomField $field */
if (isset($request->data[$field->slug])){
UserCustomValuesService::deleteByUserAndField($model->id, $field->id);
$form->load([
'user_id' => $model->id,
'custom_field_id' => $field->id,
'value' => $request->data[$field->slug]
]);
$service->create($form);
}
}
}
public function getItem(string $entity, string $entity_id): string
{
$fields = UserCustomValuesService::getValuesByUserId($entity_id);
$fieldsArr = [];
foreach ($fields as $field){
/* @var UserCustomValues $field */
$fieldsArr[$field->customField->label] = $field->value;
}
$string = implode(', ', array_map(
function ($key, $value) {
return "$key: $value";
},
array_keys($fieldsArr),
$fieldsArr
));
return $string;
}
public function getItems(string $entity, Model $model): string
{
$fields = UserCustomValuesService::getValuesByUserId($model->id);
$fieldsArr = [];
foreach ($fields as $field){
/* @var UserCustomValues $field */
$fieldsArr[$field->customField->label] = $field->value;
}
$string = implode(', ', array_map(
function ($key, $value) {
return "$key: $value";
},
array_keys($fieldsArr),
$fieldsArr
));
return $string;
}
}

View File

@@ -0,0 +1,140 @@
<?php
namespace kernel\app_modules\user_custom_fields\controllers;
use Exception;
use JetBrains\PhpStorm\NoReturn;
use kernel\AdminController;
use kernel\app_modules\user_custom_fields\models\forms\CreateCustomFieldForm;
use kernel\app_modules\user_custom_fields\models\CustomField;
use kernel\app_modules\user_custom_fields\models\forms\CreateUserCustomValueForm;
use kernel\app_modules\user_custom_fields\models\UserCustomValues;
use kernel\app_modules\user_custom_fields\services\CustomFieldService;
use kernel\app_modules\user_custom_fields\services\UserCustomValuesService;
use kernel\Flash;
use kernel\helpers\Debug;
class UserCustomFieldsController extends AdminController
{
private CustomFieldService $user_custom_fieldsService;
protected function init(): void
{
parent::init();
$this->cgView->viewPath = KERNEL_APP_MODULES_DIR . "/user_custom_fields/views/";
$this->user_custom_fieldsService = new CustomFieldService();
}
public function actionCreate(): void
{
$this->cgView->render("form.php");
}
#[NoReturn] public function actionAdd(): void
{
$user_custom_fieldsForm = new CreateCustomFieldForm();
$user_custom_fieldsForm->load($_REQUEST);
if ($user_custom_fieldsForm->validate()){
$user_custom_fields = $this->user_custom_fieldsService->create($user_custom_fieldsForm);
if ($user_custom_fields){
$this->redirect("/admin/custom_field/view/" . $user_custom_fields->id);
}
}
Flash::setMessage("error", $user_custom_fieldsForm->getErrorsStr());
$this->redirect("/admin/custom_field/create");
}
public function actionIndex($page_number = 1): void
{
$this->cgView->render("index.php", ['page_number' => $page_number]);
}
/**
* @throws Exception
*/
public function actionView($id): void
{
$user_custom_fields = CustomField::find($id);
if (!$user_custom_fields){
throw new Exception(message: "The user_custom_fields not found");
}
$this->cgView->render("view.php", ['user_custom_fields' => $user_custom_fields]);
}
/**
* @throws Exception
*/
public function actionUpdate($id): void
{
$model = CustomField::find($id);
if (!$model){
throw new Exception(message: "The user_custom_fields not found");
}
$this->cgView->render("form.php", ['model' => $model]);
}
/**
* @throws Exception
*/
public function actionEdit($id): void
{
$user_custom_fields = CustomField::find($id);
if (!$user_custom_fields){
throw new Exception(message: "The user_custom_fields not found");
}
$user_custom_fieldsForm = new CreateCustomFieldForm();
$user_custom_fieldsService = new CustomFieldService();
$user_custom_fieldsForm->load($_REQUEST);
if ($user_custom_fieldsForm->validate()) {
$user_custom_fields = $user_custom_fieldsService->update($user_custom_fieldsForm, $user_custom_fields);
if ($user_custom_fields) {
$this->redirect("/admin/custom_field/view/" . $user_custom_fields->id);
}
}
$this->redirect("/admin/custom_field/update/" . $id);
}
#[NoReturn] public function actionDelete($id): void
{
$user_custom_fields = CustomField::find($id)->first();
$user_custom_fields->delete();
$this->redirect("/admin/custom_field/");
}
public function actionUserCustomValuesList($page_number = 1): void
{
$this->cgView->render("values_index.php", ['page_number' => $page_number]);
}
public function actionCreateUserCustomValues(): void
{
$this->cgView->render("values_form.php");
}
#[NoReturn] public function actionAddUserCustomValues(): void
{
$service = new UserCustomValuesService();
$form = new CreateUserCustomValueForm();
$form->load($_REQUEST);
UserCustomValuesService::deleteByUserAndField($form->getItem('user_id'), $form->getItem('custom_field_id'));
if ($form->validate()){
$model = $service->create($form);
if ($model){
$this->redirect("/admin/custom_field/user_values");
}
}
Flash::setMessage("error", $form->getErrorsStr());
$this->redirect("/admin/custom_field/user_values");
}
#[NoReturn] public function actionDeleteUserCustomValues($id): void
{
$user_custom_values = UserCustomValues::find($id)->first();
$user_custom_values->delete();
Flash::setMessage("success", "Запись удалена");
$this->redirect("/admin/custom_field/user_values");
}
}

View File

@@ -0,0 +1,48 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public string $migration;
/**
* Run the migrations.
*/
public function up(): void
{
\kernel\App::$db->schema->create('custom_field', function (Blueprint $table) {
$table->id();
$table->string('slug')->unique(); // Название поля (например, 'phone')
$table->string('type')->default('string'); // Тип поля (string, integer, boolean и т.д.)
$table->string('entity')->nullable(true); // Сущность (user, post и т.д.)
$table->string('label'); // Человекочитаемое название
$table->text('field_options'); // Человекочитаемое название
$table->integer('status')->default(1);
$table->timestamps();
});
\kernel\App::$db->schema->create('user_custom_values', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id');
$table->unsignedBigInteger('custom_field_id');
$table->text('value')->nullable();
$table->timestamps();
// $table->foreign('user_id')->references('id')->on('user')->onDelete('cascade');
// $table->foreign('custom_field_id')->references('id')->on('custom_field')->onDelete('cascade');
// $table->unique(['user_id', 'custom_field_id']); // Уникальная пара пользователь-поле
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
\kernel\App::$db->schema->dropIfExists('user_custom_values');
\kernel\App::$db->schema->dropIfExists('custom_field');
}
};

View File

@@ -0,0 +1,72 @@
<?php
namespace kernel\app_modules\user_custom_fields\models;
use Illuminate\Database\Eloquent\Model;
// Добавить @property
/**
* @property int $id
* @property int $status
* @property string $slug
* @property string $label
* @property string $type
* @property string $entity
* @property string $field_options
*/
class CustomField extends Model
{
const DISABLE_STATUS = 0;
const ACTIVE_STATUS = 1;
const TYPE_STRING = 'string';
const TYPE_SELECT = 'select';
protected $table = 'custom_field';
protected $fillable = ['slug', 'label', 'type', 'entity', 'field_options', 'status']; // Заполнить массив. Пример: ['label', 'slug', 'status']
public static function labels(): array
{
// Заполнить массив
// Пример: [
// 'label' => 'Заголовок',
// 'entity' => 'Сущность',
// 'slug' => 'Slug',
// 'status' => 'Статус',
// ]
return [
'slug' => 'Slug',
'label' => 'Название',
'type' => 'Тип',
'entity' => 'Сущность',
'field_options' => 'Опции поля',
'status' => 'Статус',
];
}
/**
* @return string[]
*/
public static function getStatus(): array
{
return [
self::DISABLE_STATUS => "Не активный",
self::ACTIVE_STATUS => "Активный",
];
}
/**
* @return string[]
*/
public static function getTypes(): array
{
return [
self::TYPE_STRING => 'Текст',
self::TYPE_SELECT => 'Список',
];
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace kernel\app_modules\user_custom_fields\models;
use Illuminate\Database\Eloquent\Model;
use kernel\modules\user\models\User;
/**
* @property int $id
* @property int $user_id
* @property int $custom_field_id
* @property string $value
* @property string $created_at
* @property string $updated_at
*/
class UserCustomValues extends Model
{
const DISABLE_STATUS = 0;
const ACTIVE_STATUS = 1;
protected $table = 'user_custom_values';
protected $fillable = ['user_id', 'custom_field_id', 'value'];
public static function labels(): array
{
return [
'user_id' => 'ID пользователя',
'custom_field_id' => 'ID кастомного поля',
'value' => 'Значение',
'created_at' => 'Дата создания',
'updated_at' => 'Дата обновления',
];
}
/**
* @return string[]
*/
public static function getStatus(): array
{
return [
self::DISABLE_STATUS => "Не активный",
self::ACTIVE_STATUS => "Активный",
];
}
public function customField(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->belongsTo(CustomField::class);
}
public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->belongsTo(User::class);
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace kernel\app_modules\user_custom_fields\models\forms;
use kernel\FormModel;
class CreateCustomFieldForm extends FormModel
{
public function rules(): array
{
// Заполнить массив правил
// Пример:
// return [
// 'label' => 'required|min-str-len:5|max-str-len:30',
// 'entity' => 'required',
// 'slug' => '',
// 'status' => ''
// ];
return [
'slug' => 'required|min-str-len:3|max-str-len:30',
'label' => 'required|min-str-len:3|max-str-len:50',
'type' => 'required|min-str-len:3|max-str-len:30',
'entity' => 'required|min-str-len:3|max-str-len:30',
'field_options' => '',
'status' => '',
];
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace kernel\app_modules\user_custom_fields\models\forms;
use kernel\FormModel;
class CreateUserCustomValueForm extends FormModel
{
public function rules(): array
{
// Заполнить массив правил
// Пример:
// return [
// 'label' => 'required|min-str-len:5|max-str-len:30',
// 'entity' => 'required',
// 'slug' => '',
// 'status' => ''
// ];
return [
'user_id' => 'required|integer|min:1',
'custom_field_id' => 'required|integer|min:1',
'value' => '',
];
}
}

View File

@@ -0,0 +1,24 @@
<?php
use kernel\App;
use kernel\CgRouteCollector;
use Phroute\Phroute\RouteCollector;
App::$collector->group(["prefix" => "admin"], function (CgRouteCollector $router) {
App::$collector->group(["before" => "auth"], function (RouteCollector $router) {
App::$collector->group(["prefix" => "custom_field"], function (CGRouteCollector $router) {
App::$collector->get('/', [\app\modules\user_custom_fields\controllers\UserCustomFieldsController::class, 'actionIndex']);
App::$collector->get('/page/{page_number}', [\app\modules\user_custom_fields\controllers\UserCustomFieldsController::class, 'actionIndex']);
App::$collector->get('/create', [\app\modules\user_custom_fields\controllers\UserCustomFieldsController::class, 'actionCreate']);
App::$collector->post("/", [\app\modules\user_custom_fields\controllers\UserCustomFieldsController::class, 'actionAdd']);
App::$collector->get('/view/{id}', [\app\modules\user_custom_fields\controllers\UserCustomFieldsController::class, 'actionView']);
App::$collector->any('/update/{id}', [\app\modules\user_custom_fields\controllers\UserCustomFieldsController::class, 'actionUpdate']);
App::$collector->any("/edit/{id}", [\app\modules\user_custom_fields\controllers\UserCustomFieldsController::class, 'actionEdit']);
App::$collector->get('/delete/{id}', [\app\modules\user_custom_fields\controllers\UserCustomFieldsController::class, 'actionDelete']);
App::$collector->get('/user_values', [\app\modules\user_custom_fields\controllers\UserCustomFieldsController::class, 'actionUserCustomValuesList']);
App::$collector->post('/user_values', [\app\modules\user_custom_fields\controllers\UserCustomFieldsController::class, 'actionAddUserCustomValues']);
App::$collector->get('/user_values/create', [\app\modules\user_custom_fields\controllers\UserCustomFieldsController::class, 'actionCreateUserCustomValues']);
App::$collector->get('/user_values/delete/{id}', [\app\modules\user_custom_fields\controllers\UserCustomFieldsController::class, 'actionDeleteUserCustomValues']);
});
});
});

View File

@@ -0,0 +1,105 @@
<?php
namespace kernel\app_modules\user_custom_fields\services;
use itguild\forms\builders\SelectBuilder;
use itguild\forms\builders\TextInputBuilder;
use kernel\app_modules\user_custom_fields\models\CustomField;
use kernel\app_modules\user_custom_fields\models\forms\CreateCustomFieldForm;
use kernel\FormModel;
class CustomFieldService
{
public function create(FormModel $form_model): false|CustomField
{
$model = new CustomField();
// Пример заполнения:
$model->slug = $form_model->getItem('slug');
$model->label = $form_model->getItem('label');
$model->type = $form_model->getItem('type');
$model->entity = $form_model->getItem('entity');
$model->field_options = $form_model->getItem('field_options');
$model->status = $form_model->getItem('status');
if ($model->save()) {
return $model;
}
return false;
}
public function update(FormModel $form_model, CustomField $custom_field): false|CustomField
{
// Пример обновления:
$custom_field->slug = $form_model->getItem('slug');
$custom_field->label = $form_model->getItem('label');
$custom_field->type = $form_model->getItem('type');
$custom_field->entity = $form_model->getItem('entity');
$custom_field->field_options = $form_model->getItem('field_options');
$custom_field->status = $form_model->getItem('status');
if ($custom_field->save()) {
return $custom_field;
}
return false;
}
public static function getCustomFields()
{
$model = CustomField::where(['entity' => 'user'])->where(['status' => CustomField::ACTIVE_STATUS])->get();
return $model;
}
public static function getList(): array
{
return CustomField::select('id', 'label')->get()
->pluck('label', 'id')
->toArray();
}
public static function getCustomFieldHtml(CustomField $field, int $userId)
{
$value = UserCustomValuesService::getValueByFieldAndUser($field->id, $userId);
if ($field->type === "string"){
$input = TextInputBuilder::build($field->slug, [
'class' => 'form-control',
'placeholder' => $field->label,
'value' => $value->value ?? '',
]);
}
else {
$options = explode(", ", $field->field_options);
$options = array_combine($options, $options);
$input = SelectBuilder::build($field->slug, [
'class' => 'form-control',
'placeholder' => $field->label,
'value' => $value->value ?? '',
])->setOptions($options);
}
$input->setLabel($field->label);
return $input->create()->fetch();
}
public static function getOrCreateBySlug(string $slug): CustomField
{
$model = CustomField::where('slug', $slug)->first();
if (!$model) {
$form = new CreateCustomFieldForm();
$service = new self();
$form->load([
'slug' => $slug,
'label' => $slug,
'entity' => 'user',
'type' => 'string',
]);
$model = $service->create($form);
}
return $model;
}
}

View File

@@ -0,0 +1,79 @@
<?php
namespace kernel\app_modules\user_custom_fields\services;
use kernel\app_modules\user_custom_fields\models\forms\CreateUserCustomValueForm;
use kernel\app_modules\user_custom_fields\models\UserCustomValues;
use kernel\FormModel;
class UserCustomValuesService
{
public function create(FormModel $form_model): false|UserCustomValues
{
$model = new UserCustomValues();
$model->user_id = $form_model->getItem('user_id');
$model->custom_field_id = $form_model->getItem('custom_field_id');
$model->value = $form_model->getItem('value');
if ($model->save()) {
return $model;
}
return false;
}
public function update(FormModel $form_model, UserCustomValues $user_custom_value): false|UserCustomValues
{
$user_custom_value->user_id = $form_model->getItem('user_id');
$user_custom_value->custom_field_id = $form_model->getItem('custom_field_id');
$user_custom_value->value = $form_model->getItem('value');
if ($user_custom_value->save()) {
return $user_custom_value;
}
return false;
}
public static function getValuesByUserId(int $user_id): \Illuminate\Database\Eloquent\Collection
{
return UserCustomValues::with(['customField'])->where(['user_id' => $user_id])->get();
}
public static function getValueByFieldAndUser(int $custom_field_id, int $user_id): UserCustomValues|null
{
return UserCustomValues::where([
'custom_field_id' => $custom_field_id,
'user_id' => $user_id
])->first();
}
public static function deleteByUserAndField(int $user_id, int $custom_field_id): bool
{
$record = self::getValueByFieldAndUser($custom_field_id, $user_id);
if ($record) {
return $record->delete();
}
return false;
}
public static function save(int $userId, string $slug, string $value): UserCustomValues
{
$customField = CustomFieldService::getOrCreateBySlug($slug);
$model = UserCustomValues::where('user_id', $userId)->where('custom_field_id', $customField->id)->first();
if (!$model) {
$service = new self();
$form = new CreateUserCustomValueForm();
$form->load([
'custom_field_id' => $customField->id,
'user_id' => $userId,
'value' => $value,
]);
$model = $service->create($form);
}
return $model;
}
}

View File

@@ -0,0 +1,96 @@
<?php
/**
* @var CustomField $model
*/
use kernel\app_modules\user_custom_fields\models\CustomField;
$form = new \itguild\forms\ActiveForm();
$form->beginForm(isset($model) ? "/admin/custom_field/edit/" . $model->id : "/admin/custom_field", 'multipart/form-data');
// Пример формы:
$form->field(\itguild\forms\inputs\TextInput::class, 'slug', [
'class' => "form-control",
'placeholder' => 'Slug',
'value' => $model->slug ?? ''
])
->setLabel("Slug")
->render();
$form->field(\itguild\forms\inputs\TextInput::class, 'label', [
'class' => "form-control",
'placeholder' => 'Название поля',
'value' => $model->label ?? ''
])
->setLabel("Название поля")
->render();
$form->field(\itguild\forms\inputs\Select::class, 'type', [
'class' => "form-control",
'value' => $model->type ?? ''
])
->setLabel("Тип")
->setOptions(CustomField::getTypes())
->render();
$form->field(\itguild\forms\inputs\TextInput::class, 'entity', [
'class' => "form-control",
'placeholder' => 'Сущность',
'value' => $model->entity ?? ''
])
->setLabel("Сущность")
->render();
$form->field(\itguild\forms\inputs\TextArea::class, 'field_options', [
'class' => "form-control",
'placeholder' => 'Вариант 1, Вариант2, Вариант 3',
'value' => $model->field_options ?? ''
])
->setLabel("Опции поля (через запятую)")
->render();
$form->field(\itguild\forms\inputs\Select::class, 'status', [
'class' => "form-control",
'value' => $model->status ?? ''
])
->setLabel("Статус")
->setOptions(CustomField::getStatus())
->render();
/*
$form->field(class: \itguild\forms\inputs\Select::class, name: "user_id", params: [
'class' => "form-control",
'value' => $model->user_id ?? ''
])
->setLabel("Пользователи")
->setOptions(\kernel\modules\user\service\UserService::createUsernameArr())
->render();
*/
?>
<div class="row">
<div class="col-sm-2">
<?php
$form->field(\itguild\forms\inputs\Button::class, name: "btn-submit", params: [
'class' => "btn btn-primary ",
'value' => 'Отправить',
'typeInput' => 'submit'
])
->render();
?>
</div>
<div class="col-sm-2">
<?php
$form->field(\itguild\forms\inputs\Button::class, name: "btn-reset", params: [
'class' => "btn btn-warning",
'value' => 'Сбросить',
'typeInput' => 'reset'
])
->render();
?>
</div>
</div>
<?php
$form->endForm();

View File

@@ -0,0 +1,78 @@
<?php
/**
* @var \Illuminate\Database\Eloquent\Collection $user_custom_fields
* @var int $page_number
* @var \kernel\CgView $view
*/
use kernel\app_modules\user_custom_fields\models\CustomField;
use Itguild\EloquentTable\EloquentDataProvider;
use Itguild\EloquentTable\ListEloquentTable;
use kernel\widgets\IconBtn\IconBtnCreateWidget;
use kernel\widgets\IconBtn\IconBtnDeleteWidget;
use kernel\widgets\IconBtn\IconBtnEditWidget;
use kernel\widgets\IconBtn\IconBtnViewWidget;
$view->setTitle("Список дополнительных полей");
$view->setMeta([
'description' => 'Список дополнительных полей системы'
]);
//Для использования таблицы с моделью, необходимо создать таблицу в базе данных
$table = new ListEloquentTable(new EloquentDataProvider(CustomField::class, [
'currentPage' => $page_number,
'perPage' => 8,
'params' => ["class" => "table table-bordered", "border" => "2"],
'baseUrl' => "/admin/custom_field"
]));
//$table = new \Itguild\Tables\ListJsonTable(json_encode(
// [
// 'meta' => [
// 'total' => 0,
// 'totalWithFilters' => 0,
// 'columns' => [
// 'title',
// 'slug',
// 'status',
// ],
// 'perPage' => 5,
// 'currentPage' => 1,
// 'baseUrl' => '/admin/some',
// 'params' => [
// 'class' => 'table table-bordered',
// 'border' => 2
// ]
// ],
// 'filters' => [],
// 'data' => [],
// ]
//));
// Пример фильтра
$table->columns([
'slug' => [
'filter' => [
'class' => \Itguild\Tables\Filter\InputTextFilter::class,
'value' => $get['title'] ?? ''
]
],
]);
$table->beforePrint(function () {
return IconBtnCreateWidget::create(['url' => '/admin/custom_field/create'])->run();
});
$table->addAction(function($row) {
return IconBtnViewWidget::create(['url' => '/admin/custom_field/view/' . $row['id']])->run();
});
$table->addAction(function($row) {
return IconBtnEditWidget::create(['url' => '/admin/custom_field/update/' . $row['id']])->run();
});
$table->addAction(function($row) {
return IconBtnDeleteWidget::create(['url' => '/admin/custom_field/delete/' . $row['id']])->run();
});
$table->create();
$table->render();

View File

@@ -0,0 +1,72 @@
<?php
/**
* @var \kernel\app_modules\user_custom_fields\models\UserCustomValues $model
*/
use kernel\app_modules\user_custom_fields\models\CustomField;
$form = new \itguild\forms\ActiveForm();
$form->beginForm(isset($model) ? "/admin/custom_field/user_values/edit/" . $model->id : "/admin/custom_field/user_values", 'multipart/form-data');
// Пример формы:
$form->field(\itguild\forms\inputs\Select::class, 'user_id', [
'class' => "form-control",
'value' => $model->user_id ?? ''
])
->setLabel("Пользователь")
->setOptions(\kernel\modules\user\service\UserService::getList())
->render();
$form->field(\itguild\forms\inputs\Select::class, 'custom_field_id', [
'class' => "form-control",
'value' => $model->custom_field_id ?? ''
])
->setLabel("Поле")
->setOptions(\kernel\app_modules\user_custom_fields\services\CustomFieldService::getList())
->render();
$form->field(\itguild\forms\inputs\TextInput::class, 'value', [
'class' => "form-control",
'placeholder' => 'Значение',
'value' => $model->value ?? ''
])
->setLabel("Значение")
->render();
/*
$form->field(class: \itguild\forms\inputs\Select::class, name: "user_id", params: [
'class' => "form-control",
'value' => $model->user_id ?? ''
])
->setLabel("Пользователи")
->setOptions(\kernel\modules\user\service\UserService::createUsernameArr())
->render();
*/
?>
<div class="row">
<div class="col-sm-2">
<?php
$form->field(\itguild\forms\inputs\Button::class, name: "btn-submit", params: [
'class' => "btn btn-primary ",
'value' => 'Отправить',
'typeInput' => 'submit'
])
->render();
?>
</div>
<div class="col-sm-2">
<?php
$form->field(\itguild\forms\inputs\Button::class, name: "btn-reset", params: [
'class' => "btn btn-warning",
'value' => 'Сбросить',
'typeInput' => 'reset'
])
->render();
?>
</div>
</div>
<?php
$form->endForm();

View File

@@ -0,0 +1,85 @@
<?php
/**
* @var \Illuminate\Database\Eloquent\Collection $user_custom_fields
* @var int $page_number
* @var \kernel\CgView $view
*/
use kernel\app_modules\user_custom_fields\models\CustomField;
use Itguild\EloquentTable\EloquentDataProvider;
use Itguild\EloquentTable\ListEloquentTable;
use kernel\modules\user\models\User;
use kernel\widgets\IconBtn\IconBtnCreateWidget;
use kernel\widgets\IconBtn\IconBtnDeleteWidget;
use kernel\widgets\IconBtn\IconBtnEditWidget;
use kernel\widgets\IconBtn\IconBtnViewWidget;
$view->setTitle("Список значений дополнительных полей");
$view->setMeta([
'description' => 'Список значений дополнительных полей системы'
]);
//Для использования таблицы с моделью, необходимо создать таблицу в базе данных
$table = new ListEloquentTable(new EloquentDataProvider(\kernel\app_modules\user_custom_fields\models\UserCustomValues::class, [
'currentPage' => $page_number,
'perPage' => 8,
'params' => ["class" => "table table-bordered", "border" => "2"],
'baseUrl' => "/admin/custom_field"
]));
//$table = new \Itguild\Tables\ListJsonTable(json_encode(
// [
// 'meta' => [
// 'total' => 0,
// 'totalWithFilters' => 0,
// 'columns' => [
// 'title',
// 'slug',
// 'status',
// ],
// 'perPage' => 5,
// 'currentPage' => 1,
// 'baseUrl' => '/admin/some',
// 'params' => [
// 'class' => 'table table-bordered',
// 'border' => 2
// ]
// ],
// 'filters' => [],
// 'data' => [],
// ]
//));
// Пример фильтра
$table->columns([
'user_id' => [
'value' => function ($data) {
return User::find($data)->username;
},
'filter' => [
'class' => \kernel\filters\BootstrapSelectFilter::class,
'params' => [
'options' => \kernel\modules\user\service\UserService::createUsernameArr(),
'prompt' => 'Не выбрано'
],
'value' => $get['user_id'] ?? '',
],
],
'custom_field_id' => [
'value' => function ($data) {
return CustomField::find($data)->label ?? '';
},
],
]);
$table->beforePrint(function () {
return IconBtnCreateWidget::create(['url' => '/admin/custom_field/user_values/create'])->run();
});
$table->addAction(function($row) {
return IconBtnDeleteWidget::create(['url' => '/admin/custom_field/user_values/delete/' . $row['id']])->run();
});
$table->create();
$table->render();

View File

@@ -0,0 +1,25 @@
<?php
/**
* @var \Illuminate\Database\Eloquent\Collection $user_custom_fields
*/
use Itguild\EloquentTable\ViewEloquentTable;
use Itguild\EloquentTable\ViewJsonTableEloquentModel;
use kernel\widgets\IconBtn\IconBtnDeleteWidget;
use kernel\widgets\IconBtn\IconBtnEditWidget;
use kernel\widgets\IconBtn\IconBtnListWidget;
$table = new ViewEloquentTable(new ViewJsonTableEloquentModel($user_custom_fields, [
'params' => ["class" => "table table-bordered", "border" => "2"],
'baseUrl' => "/admin/user_custom_fields",
]));
$table->beforePrint(function () use ($user_custom_fields) {
$btn = IconBtnListWidget::create(['url' => '/admin/custom_field'])->run();
$btn .= IconBtnEditWidget::create(['url' => '/admin/custom_field/update/' . $user_custom_fields->id])->run();
$btn .= IconBtnDeleteWidget::create(['url' => '/admin/custom_field/delete/' . $user_custom_fields->id])->run();
return $btn;
});
$table->create();
$table->render();

View File

@@ -0,0 +1,20 @@
<?php
namespace kernel\app_modules\user_custom_fields\widgets;
use kernel\app_modules\user_custom_fields\models\CustomField;
use kernel\app_modules\user_custom_fields\services\CustomFieldService;
use kernel\helpers\Debug;
use kernel\Widget;
class UserCustomFieldsInputsWidget extends Widget
{
public function run()
{
$fields = CustomFieldService::getCustomFields();
Debug::dd($fields);
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace kernel\app_modules\user_stage;
use kernel\helpers\Debug;
use kernel\Module;
use kernel\modules\menu\service\MenuService;
use kernel\services\MigrationService;
class UserStageModule extends Module
{
public MenuService $menuService;
public MigrationService $migrationService;
public function __construct()
{
$this->menuService = new MenuService();
$this->migrationService = new MigrationService();
}
public function init(): void
{
$this->migrationService->runAtPath("{KERNEL_APP_MODULES}/user_stage/migrations");
$this->menuService->createItem([
"label" => "Этапы пользователя",
"url" => "/admin/user_stage",
"slug" => "user_stage",
]);
}
public function deactivate(): void
{
$this->migrationService->rollbackAtPath("{KERNEL_APP_MODULES}/user_stage/migrations");
$this->menuService->removeItemBySlug("user_stage");
}
}

View File

@@ -0,0 +1,124 @@
<?php
namespace kernel\app_modules\user_stage\controllers;
use Exception;
use JetBrains\PhpStorm\NoReturn;
use kernel\AdminController;
use kernel\App;
use kernel\app_modules\user_stage\models\forms\CreateUserStageForm;
use kernel\app_modules\user_stage\models\User;
use kernel\app_modules\user_stage\models\UserStage;
use kernel\app_modules\user_stage\notification_messages\UserNewStageNotification;
use kernel\app_modules\user_stage\services\UserStageService;
use kernel\Flash;
class UserStageController extends AdminController
{
private UserStageService $user_stageService;
protected function init(): void
{
parent::init();
$this->cgView->viewPath = KERNEL_APP_MODULES_DIR . "/user_stage/views/";
$this->user_stageService = new UserStageService();
}
public function actionCreate(): void
{
$this->cgView->render("form.php");
}
#[NoReturn] public function actionAdd(): void
{
$user_stageForm = new CreateUserStageForm();
$user_stageForm->load($_REQUEST);
if ($user_stageForm->validate()) {
$user_stage = $this->user_stageService->create($user_stageForm);
if ($user_stage) {
$this->redirect("/admin/user_stage/view/" . $user_stage->id);
}
}
$this->redirect("/admin/user_stage/create");
}
public function actionIndex($page_number = 1): void
{
$this->cgView->render("index.php", ['page_number' => $page_number]);
}
/**
* @throws Exception
*/
public function actionView($id): void
{
$user_stage = UserStage::find($id);
if (!$user_stage) {
throw new Exception(message: "The user_stage not found");
}
$this->cgView->render("view.php", ['user_stage' => $user_stage]);
}
/**
* @throws Exception
*/
public function actionUpdate($id): void
{
$model = UserStage::find($id);
if (!$model) {
throw new Exception(message: "The user_stage not found");
}
$this->cgView->render("form.php", ['model' => $model]);
}
/**
* @throws Exception
*/
public function actionEdit($id): void
{
$user_stage = UserStage::find($id);
if (!$user_stage) {
throw new Exception(message: "The user_stage not found");
}
$user_stageForm = new CreateUserStageForm();
$user_stageService = new UserStageService();
$user_stageForm->load($_REQUEST);
if ($user_stageForm->validate()) {
$user_stage = $user_stageService->update($user_stageForm, $user_stage);
if ($user_stage) {
$this->redirect("/admin/user_stage/view/" . $user_stage->id);
}
}
$this->redirect("/admin/user_stage/update/" . $id);
}
#[NoReturn] public function actionDelete($id): void
{
$user_stage = UserStage::find($id)->first();
$user_stage->delete();
$this->redirect("/admin/user_stage/");
}
#[NoReturn] public function actionSetStage(int $stage_id, int $stage_status, int $user_id): void
{
$user = User::find($user_id);
$stage = UserStage::find($stage_id);
if ($stage_status === 2) {
$notification = new UserNewStageNotification($stage, $user);
App::$notificationDispatcher->dispatch($notification, $user);
$user->openStage($stage);
}
if ($stage_status === 3) {
$user->completeStage($stage);
}
if ($stage_status === 1) {
$user->closeStage($stage);
}
Flash::setMessage('success', 'Этап изменен');
$this->redirect("/admin/user/view/" . $user_id, 302);
}
}

View File

@@ -0,0 +1,49 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
\kernel\App::$db->schema->create('user_stage', function (Blueprint $table) {
$table->id();
$table->string('slug')->nullable(false);
$table->string('title')->nullable(false);
$table->text('description')->nullable(false);
$table->dateTime('start_date')->nullable();
$table->dateTime('end_date')->nullable();
$table->integer('position')->nullable(false)->default(1);
$table->integer('status')->default(1);
$table->text('user_fields')->nullable(true);
$table->timestamps();
});
\kernel\App::$db->schema->create('user_stage_progress', function (Blueprint $table) {
$table->id();
$table->integer('user_id')->nullable(false);
$table->integer('user_stage_id')->nullable(false);
$table->boolean('is_completed')->default(false);
$table->boolean('is_closed')->default(true);
$table->integer('status')->default(1);
$table->timestamp('completed_at')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
\kernel\App::$db->schema->dropIfExists('user_stage_progress');
\kernel\App::$db->schema->dropIfExists('user_stage');
}
};

View File

@@ -0,0 +1,81 @@
<?php
namespace kernel\app_modules\user_stage\models;
class User extends \kernel\modules\user\models\User
{
public function stageProgress(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(UserStageProgress::class);
}
public function stages()
{
return $this->belongsToMany(UserStage::class, 'user_stage_progress')
->withPivot(['is_completed', 'completed_at', 'is_closed', 'status'])
->withTimestamps();
}
// Метод для отметки этапа как завершенного
public function completeStage(UserStage $stage): void
{
$this->stages()->updateExistingPivot($stage->id, [
'is_completed' => true,
'status' => 3,
'completed_at' => date('Y-m-d H:i')
]);
}
public function openStage(UserStage $stage): void
{
$this->stages()->updateExistingPivot($stage->id, [
'status' => 2,
'is_closed' => 0,
]);
}
public function closeStage(UserStage $stage): void
{
$this->stages()->updateExistingPivot($stage->id, [
'status' => 1,
'is_closed' => 1,
]);
}
// Проверка, завершен ли этап
public function hasCompletedStage(UserStage $stage)
{
return $this->stages()
->where('stage_id', $stage->id)
->where('is_completed', true)
->exists();
}
public function isClosed(UserStage $stage)
{
return $this->stages()
->where('user_stage_id', $stage->id)
->where('is_closed', true)
->exists();
}
public function hasStages(): bool
{
if ($this->stages()) {
return true;
}
return false;
}
// Получение текущего этапа (первый незавершенный)
public function currentStage()
{
return $this->stages()
->where('is_completed', false)
->orderBy('stage_id')
->first();
}
}

View File

@@ -0,0 +1,96 @@
<?php
namespace kernel\app_modules\user_stage\models;
use DateTime;
use Illuminate\Database\Eloquent\Model;
// Добавить @property
/**
* @property int $id
* @property int $status
* @property int $position
* @property string $title
* @property string $slug
* @property string $description
* @property string $start_date
* @property string $end_date
* @property string $user_fields
* @property string $dateStartFormatedToForm
* @property string $dateEndFormatedToForm
*/
class UserStage extends Model
{
const DISABLE_STATUS = 0;
const ACTIVE_STATUS = 1;
protected $table = 'user_stage';
protected $fillable = ['title', 'slug', 'description', 'start_date', 'end_date', 'position', 'status', 'user_fields']; // Заполнить массив. Пример: ['label', 'slug', 'status']
public static function labels(): array
{
// Заполнить массив
// Пример: [
// 'label' => 'Заголовок',
// 'entity' => 'Сущность',
// 'slug' => 'Slug',
// 'status' => 'Статус',
// ]
return [
'title' => 'Заголовок',
'slug' => 'Slug',
'description' => 'Описание',
'start_date' => 'Начало',
'end_date' => 'Конец',
'position' => 'Позиция',
'status' => 'Статус',
'user_fields' => 'Поля пользователя',
];
}
/**
* @return string[]
*/
public static function getStatus(): array
{
return [
self::DISABLE_STATUS => "Не активный",
self::ACTIVE_STATUS => "Активный",
];
}
/**
* @throws \Exception
*/
public function getDateStartFormatedToFormAttribute(): string
{
$startDate = new DateTime($this->start_date);
return $startDate->format("Y-m-d");
}
/**
* @throws \Exception
*/
public function getDateEndFormatedToFormAttribute(): string
{
$endDate = new DateTime($this->end_date);
return $endDate->format("Y-m-d");
}
public function userProgress(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(UserStageProgress::class);
}
public function users()
{
return $this->belongsToMany(User::class, 'user_stage_progress')
->withPivot(['is_completed', 'completed_at', 'status', 'is_closed'])
->withTimestamps();
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace kernel\app_modules\user_stage\models;
use Illuminate\Database\Eloquent\Model;
/**
* @property int $user_id
* @property int $user_stage_id
* @property int $status
* @property bool $is_completed
* @property bool $is_closed
* @property string $completed_at
*/
class UserStageProgress extends Model
{
protected $table = 'user_stage_progress';
protected $fillable = ['user_id', 'user_stage_id', 'is_completed', 'is_closed', 'completed_at', 'status'];
public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->belongsTo(User::class);
}
public function stage(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->belongsTo(UserStage::class);
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace kernel\app_modules\user_stage\models\forms;
use kernel\FormModel;
class CreateUserStageForm extends FormModel
{
public function rules(): array
{
// Заполнить массив правил
// Пример:
// return [
// 'label' => 'required|min-str-len:5|max-str-len:30',
// 'entity' => 'required',
// 'slug' => '',
// 'status' => ''
// ];
return [
'title' => 'required|min-str-len:5|max-str-len:30',
'slug' => '',
'description' => 'required|min-str-len:5',
'start_date' => '',
'end_date' => '',
'position' => 'required|integer',
'status' => 'integer',
'user_fields' => '',
];
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace kernel\app_modules\user_stage\notification_messages;
use kernel\app_modules\user_stage\models\User;
use kernel\app_modules\user_stage\models\UserStage;
use kernel\CgView;
use kernel\modules\notification\contracts\NotificationMessage;
use kernel\modules\user\service\UserService;
class UserNewStageNotification extends NotificationMessage
{
protected UserStage $userStage;
protected User $user;
protected CgView $cgView;
public function __construct(UserStage $userStage, User $user)
{
$this->cgView = new CgView();
$this->cgView->viewPath = ROOT_DIR . $_ENV['EMAIL_VIEWS_PATH'];
$this->userStage = $userStage;
$this->user = $user;
$this->channels = ['email'];
}
public function getMessage(): string
{
return $this->cgView->fetch('user_new_stage.php', [
'user' => $this->user->username,
'stage' => $this->userStage->title,
'url' => $_ENV['APP_URL'],
]);
}
public function getSubject(): string
{
return "Вам открыт новый этап {$this->userStage->title}";
}
public function toArray(): array
{
return array_merge(parent::toArray(), [
'stage_id' => $this->userStage->id,
'stage_title' => $this->userStage->title
]);
}
}

View File

@@ -0,0 +1,21 @@
<?php
use kernel\App;
use kernel\CgRouteCollector;
use Phroute\Phroute\RouteCollector;
App::$collector->group(["prefix" => "admin"], function (CgRouteCollector $router) {
App::$collector->group(["before" => "auth"], function (RouteCollector $router) {
App::$collector->group(["prefix" => "user_stage"], function (CGRouteCollector $router) {
App::$collector->get('/', [\app\modules\user_stage\controllers\UserStageController::class, 'actionIndex']);
App::$collector->get('/page/{page_number}', [\app\modules\user_stage\controllers\UserStageController::class, 'actionIndex']);
App::$collector->get('/create', [\app\modules\user_stage\controllers\UserStageController::class, 'actionCreate']);
App::$collector->post("/", [\app\modules\user_stage\controllers\UserStageController::class, 'actionAdd']);
App::$collector->get('/view/{id}', [\app\modules\user_stage\controllers\UserStageController::class, 'actionView']);
App::$collector->any('/update/{id}', [\app\modules\user_stage\controllers\UserStageController::class, 'actionUpdate']);
App::$collector->any("/edit/{id}", [\app\modules\user_stage\controllers\UserStageController::class, 'actionEdit']);
App::$collector->get('/delete/{id}', [\app\modules\user_stage\controllers\UserStageController::class, 'actionDelete']);
App::$collector->get('/set_stage/{stage_id}/{stage_status}/{user_id}', [\app\modules\user_stage\controllers\UserStageController::class, 'actionSetStage']);
});
});
});

View File

@@ -0,0 +1,88 @@
<?php
namespace kernel\app_modules\user_stage\services;
use kernel\helpers\Debug;
use kernel\app_modules\user_stage\models\UserStage;
use kernel\FormModel;
use kernel\helpers\Slug;
class UserStageService
{
public function create(FormModel $form_model): false|UserStage
{
$model = new UserStage();
// Пример заполнения:
$model->description = $form_model->getItem('description');
$model->start_date = $form_model->getItem('start_date');
$model->end_date = $form_model->getItem('end_date');
$model->title = $form_model->getItem('title');
$model->position = $form_model->getItem('position');
$model->status = $form_model->getItem('status');
$model->user_fields = $form_model->getItem('user_fields');
$model->slug = Slug::createSlug($form_model->getItem('title'), UserStage::class);
if ($model->save()) {
return $model;
}
return false;
}
public function update(FormModel $form_model, UserStage $user_stage): false|UserStage
{
// Пример обновления:
$user_stage->description = $form_model->getItem('description');
$user_stage->start_date = $form_model->getItem('start_date');
$user_stage->end_date = $form_model->getItem('end_date');
$user_stage->title = $form_model->getItem('title');
$user_stage->position = $form_model->getItem('position');
$user_stage->status = $form_model->getItem('status');
$user_stage->user_fields = $form_model->getItem('user_fields');
if ($user_stage->save()) {
return $user_stage;
}
return false;
}
public static function getStageClass(UserStage $stage): string
{
if ($stage->pivot->status === 1){
return "grey_border";
}
elseif ($stage->pivot->status === 2){
return "blue_border";
}
else {
return "green_border";
}
}
public static function getStageStatusText(UserStage $stage): string
{
if ($stage->pivot->status === 1){
return "Не доступен";
}
elseif ($stage->pivot->status === 2){
return "Открыт";
}
else {
return "Завершен";
}
}
public static function getStageStyle(UserStage $stage): string
{
if ($stage->pivot->status === 1){
return "style='background-color: rgba(255, 193, 7, 0.1); color: #ffc107;'";
}
elseif ($stage->pivot->status === 2){
return "style='background-color: rgba(13, 202, 240, 0.1); color: #0dcaf0;'";
}
else {
return "style='background-color: rgba(25, 135, 84, 0.1); color: #198754;'";
}
}
}

View File

@@ -0,0 +1,106 @@
<?php
/**
* @var UserStage $model
*/
use kernel\app_modules\user_stage\models\UserStage;
$form = new \itguild\forms\ActiveForm();
$form->beginForm(isset($model) ? "/admin/user_stage/edit/" . $model->id : "/admin/user_stage", 'multipart/form-data');
// Пример формы:
$form->field(\itguild\forms\inputs\TextInput::class, 'title', [
'class' => "form-control",
'placeholder' => 'Заголовок',
'value' => $model->title ?? ''
])
->setLabel("Заголовок")
->render();
$form->field(\itguild\forms\inputs\TextArea::class, 'description', [
'class' => "form-control",
'placeholder' => 'Описание',
'value' => $model->description ?? ''
])
->setLabel("Описание")
->render();
$form->field(\itguild\forms\inputs\TextInput::class, 'position', [
'class' => "form-control",
'placeholder' => 'Позиция',
'type' => 'number',
'value' => $model->position ?? ''
])
->setLabel("Позиция")
->render();
$form->field(\itguild\forms\inputs\TextInput::class, 'start_date', [
'class' => "form-control",
'placeholder' => 'Начало',
'type' => 'date',
'value' => $model->dateStartFormatedToForm ?? ''
])
->setLabel("Начало")
->render();
$form->field(\itguild\forms\inputs\TextInput::class, 'end_date', [
'class' => "form-control",
'placeholder' => 'Конец',
'type' => 'date',
'value' => $model->dateEndFormatedToForm ?? ''
])
->setLabel("Конец")
->render();
$form->field(\itguild\forms\inputs\TextArea::class, 'user_fields', [
'class' => "form-control",
'placeholder' => 'Поля пользователя',
'value' => $model->user_fields ?? ''
])
->setLabel("Поля пользователя")
->render();
$form->field(\itguild\forms\inputs\Select::class, 'status', [
'class' => "form-control",
'value' => $model->status ?? ''
])
->setLabel("Статус")
->setOptions(UserStage::getStatus())
->render();
/*
$form->field(class: \itguild\forms\inputs\Select::class, name: "user_id", params: [
'class' => "form-control",
'value' => $model->user_id ?? ''
])
->setLabel("Пользователи")
->setOptions(\kernel\modules\user\service\UserService::createUsernameArr())
->render();
*/
?>
<div class="row">
<div class="col-sm-2">
<?php
$form->field(\itguild\forms\inputs\Button::class, name: "btn-submit", params: [
'class' => "btn btn-primary ",
'value' => 'Отправить',
'typeInput' => 'submit'
])
->render();
?>
</div>
<div class="col-sm-2">
<?php
$form->field(\itguild\forms\inputs\Button::class, name: "btn-reset", params: [
'class' => "btn btn-warning",
'value' => 'Сбросить',
'typeInput' => 'reset'
])
->render();
?>
</div>
</div>
<?php
$form->endForm();

View File

@@ -0,0 +1,83 @@
<?php
/**
* @var \Illuminate\Database\Eloquent\Collection $user_stage
* @var int $page_number
* @var \kernel\CgView $view
*/
use kernel\app_modules\user_stage\models\UserStage;
use Itguild\EloquentTable\EloquentDataProvider;
use Itguild\EloquentTable\ListEloquentTable;
use kernel\widgets\IconBtn\IconBtnCreateWidget;
use kernel\widgets\IconBtn\IconBtnDeleteWidget;
use kernel\widgets\IconBtn\IconBtnEditWidget;
use kernel\widgets\IconBtn\IconBtnViewWidget;
$view->setTitle("Список user_stage");
$view->setMeta([
'description' => 'Список user_stage системы'
]);
//Для использования таблицы с моделью, необходимо создать таблицу в базе данных
$table = new ListEloquentTable(new EloquentDataProvider(UserStage::class, [
'currentPage' => $page_number,
'perPage' => 8,
'params' => ["class" => "table table-bordered", "border" => "2"],
'baseUrl' => "/admin/user_stage"
]));
//$table = new \Itguild\Tables\ListJsonTable(json_encode(
// [
// 'meta' => [
// 'total' => 0,
// 'totalWithFilters' => 0,
// 'columns' => [
// 'title',
// 'slug',
// 'status',
// ],
// 'perPage' => 5,
// 'currentPage' => 1,
// 'baseUrl' => '/admin/some',
// 'params' => [
// 'class' => 'table table-bordered',
// 'border' => 2
// ]
// ],
// 'filters' => [],
// 'data' => [],
// ]
//));
// Пример фильтра
$table->columns([
'title' => [
'filter' => [
'class' => \Itguild\Tables\Filter\InputTextFilter::class,
'value' => $get['title'] ?? ''
]
],
"status" => [
"value" => function ($cell) {
return UserStage::getStatus()[$cell];
}
]
]);
$table->beforePrint(function () {
return IconBtnCreateWidget::create(['url' => '/admin/user_stage/create'])->run();
});
$table->addAction(function ($row) {
return IconBtnViewWidget::create(['url' => '/admin/user_stage/view/' . $row['id']])->run();
});
$table->addAction(function ($row) {
return IconBtnEditWidget::create(['url' => '/admin/user_stage/update/' . $row['id']])->run();
});
$table->addAction(function ($row) {
return IconBtnDeleteWidget::create(['url' => '/admin/user_stage/delete/' . $row['id']])->run();
});
$table->create();
$table->render();

View File

@@ -0,0 +1,31 @@
<?php
/**
* @var \Illuminate\Database\Eloquent\Collection $user_stage
*/
use Itguild\EloquentTable\ViewEloquentTable;
use Itguild\EloquentTable\ViewJsonTableEloquentModel;
use kernel\widgets\IconBtn\IconBtnDeleteWidget;
use kernel\widgets\IconBtn\IconBtnEditWidget;
use kernel\widgets\IconBtn\IconBtnListWidget;
$table = new ViewEloquentTable(new ViewJsonTableEloquentModel($user_stage, [
'params' => ["class" => "table table-bordered", "border" => "2"],
'baseUrl' => "/admin/user_stage",
]));
$table->beforePrint(function () use ($user_stage) {
$btn = IconBtnListWidget::create(['url' => '/admin/user_stage'])->run();
$btn .= IconBtnEditWidget::create(['url' => '/admin/user_stage/update/' . $user_stage->id])->run();
$btn .= IconBtnDeleteWidget::create(['url' => '/admin/user_stage/delete/' . $user_stage->id])->run();
return $btn;
});
$table->rows([
'status' => (function ($data) {
return \kernel\app_modules\user_stage\models\UserStage::getStatus()[$data];
})
]);
$table->create();
$table->render();

View File

@@ -0,0 +1,42 @@
<?php
namespace kernel\app_themes\svo;
use kernel\modules\menu\service\MenuService;
use kernel\modules\option\service\OptionService;
use kernel\services\MigrationService;
class SvoTheme
{
public MenuService $menuService;
public MigrationService $migrationService;
public OptionService $optionService;
public function __construct()
{
$this->menuService = new MenuService();
$this->migrationService = new MigrationService();
$this->optionService = new OptionService();
}
/**
* @throws \Exception
*/
public function init(): void
{
OptionService::createFromParams('main_news_slider', json_encode(['ids' => []]), 'Слайдер на главной');
$this->menuService->createItem([
"label" => "Настройки темы",
"url" => "/admin/svo-theme/settings",
"slug" => "svo_theme_settings",
]);
}
public function deactivate(): void
{
OptionService::removeOptionByKey('main_news_slider');
$this->menuService->removeItemBySlug("svo_theme_settings");
}
}

View File

@@ -0,0 +1,8 @@
<?php
namespace kernel\app_themes\svo\services;
class SvoThemeService
{
}

View File

@@ -49,6 +49,9 @@ class AdminConsoleController extends ConsoleController
$out = $this->migrationService->runAtPath("kernel/modules/secure/migrations");
$this->out->r("create secret_code table", "green");
$out = $this->migrationService->runAtPath("kernel/modules/notification/migrations");
$this->out->r("create notification table", "green");
$this->optionService->createFromParams(
key: "admin_theme_paths",
value: "{\"paths\": [\"{KERNEL_ADMIN_THEMES}\", \"{APP}/admin_themes\"]}",
@@ -170,6 +173,13 @@ class AdminConsoleController extends ConsoleController
]);
$this->out->r("create item menu option", "green");
$this->menuService->createItem([
"label" => "Уведомления",
"url" => "/admin/notification",
"slug" => "notification"
]);
$this->out->r("create notification option", "green");
$user = new CreateUserForm();
$user->load([
'username' => 'admin',

View File

@@ -24,7 +24,7 @@ class SMTP
/**
* @throws Exception
*/
public function send_html(array $params)
public function send_html(array $params): bool
{
if (!isset($params['address'])){
return false;
@@ -35,6 +35,6 @@ class SMTP
$body = $params['body'] ?? 'Нет информации';
$this->mail->msgHTML($body);
$this->mail->send();
return $this->mail->send();
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace kernel\modules\notification;
use kernel\Flash;
use kernel\modules\notification\contracts\NotificationChannelInterface;
use kernel\modules\notification\contracts\NotificationMessage;
use kernel\modules\user\models\User;
class NotificationDispatcher
{
protected array $channels = [];
public function addChannel(string $channelName, NotificationChannelInterface $channel): void
{
$this->channels[$channelName] = $channel;
}
public function dispatch(NotificationMessage $notification, User $user): void
{
foreach ($notification->via() as $channelName) {
if (isset($this->channels[$channelName])) {
try {
$this->channels[$channelName]->send($notification, $user);
} catch (\Exception $e) {
Flash::setMessage("error", $e->getMessage());
}
}
}
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace kernel\modules\notification;
use kernel\Module;
use kernel\modules\menu\service\MenuService;
use kernel\services\MigrationService;
class NotificationModule extends Module
{
public MenuService $menuService;
public MigrationService $migrationService;
public function __construct()
{
$this->menuService = new MenuService();
$this->migrationService = new MigrationService();
}
/**
* @throws \Exception
*/
public function init(): void
{
$this->migrationService->runAtPath("kernel/modules/notification/migrations");
$this->menuService->createItem([
"label" => "Уведомления",
"url" => "/admin/notification",
"slug" => "notification"
]);
}
/**
* @throws \Exception
*/
public function deactivate(): void
{
$this->migrationService->rollbackAtPath("kernel/modules/notification/migrations");
$this->menuService->removeItemBySlug("notification");
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace kernel\modules\notification\channels;
use kernel\Flash;
use kernel\modules\notification\contracts\NotificationChannelInterface;
use kernel\modules\notification\contracts\NotificationMessage;
use kernel\modules\notification\models\User;
class DatabaseChannel implements NotificationChannelInterface
{
public function send(NotificationMessage $notification, User $user): bool
{
try {
$user->notifications()->create([
'message' => $notification->getMessage(),
'data' => $notification->toArray()
]);
return true;
} catch (\Exception $e) {
Flash::setMessage("error", $e->getMessage());
return false;
}
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace kernel\modules\notification\channels;
use kernel\helpers\SMTP;
use kernel\modules\notification\contracts\NotificationChannelInterface;
use kernel\modules\notification\contracts\NotificationMessage;
use kernel\modules\user\models\User;
use PHPMailer\PHPMailer\Exception;
class EmailChannel implements NotificationChannelInterface
{
/**
* @throws Exception
*/
public function send(NotificationMessage $notification, User $user): bool
{
$smtp = new SMTP();
// Здесь можно использовать Laravel Mail
// \Illuminate\Support\Facades\Mail::to($user->email)
// ->send(new \App\Mail\NotificationMail(
// $notification->getSubject(),
// $notification->getMessage()
// ));
return $smtp->send_html([
'address' => $user->email,
'subject' => $notification->getSubject(),
'body' => $notification->getMessage(),
'from_name' => $_ENV['MAIL_SMTP_USERNAME'],
]);
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace kernel\modules\notification\channels;
use kernel\modules\notification\contracts\NotificationChannelInterface;
use kernel\modules\notification\contracts\NotificationMessage;
use kernel\modules\user\models\User;
class SmsChannel implements NotificationChannelInterface
{
public function send(NotificationMessage $notification, User $user): bool
{
if (empty($user->phone)) {
return false;
}
// Интеграция с SMS-сервисом
//$smsService = new \App\Services\SmsService();
//return $smsService->send($user->phone, $notification->getMessage());
return true;
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace kernel\modules\notification\channels;
use kernel\Flash;
use kernel\modules\notification\contracts\NotificationChannelInterface;
use kernel\modules\notification\contracts\NotificationMessage;
use kernel\modules\notification\models\User;
class TelegramChannel implements NotificationChannelInterface
{
protected string $botToken;
public function __construct(string $botToken)
{
$this->botToken = $botToken;
}
public function send(NotificationMessage $notification, User $user): bool
{
if (empty($user->telegram_chat_id)) {
return false;
}
$httpClient = new \GuzzleHttp\Client();
try {
$response = $httpClient->post("https://api.telegram.org/bot{$this->botToken}/sendMessage", [
'form_params' => [
'chat_id' => $user->telegram_chat_id,
'text' => $notification->getMessage(),
'parse_mode' => 'HTML'
]
]);
return $response->getStatusCode() === 200;
} catch (\Exception $e) {
Flash::setMessage("error", $e->getMessage());
return false;
}
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace kernel\modules\notification\contracts;
use kernel\modules\notification\models\User;
interface NotificationChannelInterface
{
public function send(NotificationMessage $notification, User $user): bool;
}

View File

@@ -0,0 +1,31 @@
<?php
namespace kernel\modules\notification\contracts;
abstract class NotificationMessage
{
protected array $channels = [];
abstract public function getMessage(): string;
abstract public function getSubject(): string;
public function via(): array
{
return $this->channels;
}
public function addChannel(string $channel): void
{
if (!in_array($channel, $this->channels)) {
$this->channels[] = $channel;
}
}
public function toArray(): array
{
return [
'message' => $this->getMessage(),
'subject' => $this->getSubject(),
];
}
}

View File

@@ -0,0 +1,106 @@
<?php
namespace kernel\modules\notification\controllers;
use JetBrains\PhpStorm\NoReturn;
use kernel\AdminController;
use kernel\Flash;
use kernel\helpers\Debug;
use kernel\modules\notification\models\forms\CreateNotificationForm;
use kernel\modules\notification\models\Notification;
use kernel\modules\notification\service\NotificationService;
use kernel\modules\option\models\forms\CreateOptionForm;
class NotificationController extends AdminController
{
private NotificationService $optionService;
public function init(): void
{
parent::init();
$this->cgView->viewPath = KERNEL_MODULES_DIR . '/notification/views/';
$this->optionService = new NotificationService();
}
public function actionCreate(): void
{
$this->cgView->render('form.php');
}
#[NoReturn] public function actionAdd(): void
{
$optionForm = new CreateNotificationForm();
$optionForm->load($_REQUEST);
if ($optionForm->validate()) {
$option = $this->optionService->create($optionForm);
if ($option) {
Flash::setMessage("success", "notification успешно создана.");
$this->redirect('/admin/notification');
}
}
Flash::setMessage("error", $optionForm->getErrorsStr());
$this->redirect('/admin/notification/create');
}
public function actionIndex($page_number = 1): void
{
$this->cgView->render('index.php', ['page_number' => $page_number]);
}
/**
* @throws \Exception
*/
public function actionView(int $id): void
{
$option = Notification::find($id);
if (!$option) {
throw new \Exception('notification not found');
}
$this->cgView->render("view.php", ['option' => $option]);
}
/**
* @throws \Exception
*/
public function actionUpdate(int $id): void
{
$model = Notification::find($id);
if (!$model) {
throw new \Exception('notification not found');
}
$this->cgView->render("form.php", ['model' => $model]);
}
/**
* @throws \Exception
*/
public function actionEdit(int $id): void
{
$option = Notification::find($id);
if (!$option) {
throw new \Exception('Option not found');
}
$optionForm = new CreateOptionForm();
$optionForm->load($_REQUEST);
if ($optionForm->validate()) {
$option = $this->optionService->update($optionForm, $option);
if ($option) {
$this->redirect('/admin/notification/view/' . $option->id);
}
}
$this->redirect('/admin/notification/update/' . $id);
}
#[NoReturn] public function actionDelete(int $id): void
{
Notification::find($id)->delete();
Flash::setMessage("success", "notification успешно удалена.");
$this->redirect('/admin/notification');
}
}

View File

@@ -0,0 +1,13 @@
{
"name": "Notifications",
"version": "0.1",
"author": "ITGuild",
"slug": "notification",
"type": "entity",
"description": "Notifications module",
"module_class": "kernel\\modules\\notification\\NotificationModule",
"module_class_file": "{KERNEL_MODULES}/notification/NotificationModule.php",
"routs": "routs/notification.php",
"migration_path": "migrations",
"dependence": "user,menu"
}

View File

@@ -0,0 +1,41 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
\kernel\App::$db->schema->create('notification', function (Blueprint $table) {
$table->id();
// Связь с пользователем
$table->unsignedBigInteger('user_id');
// Основные данные уведомления
$table->string('type')->index(); // Класс уведомления (например, App\Notifications\OrderCreated)
$table->text('message'); // Текст уведомления
$table->string('subject')->nullable(); // Тема (для email)
$table->json('data')->nullable(); // Дополнительные данные в JSON
// Статус уведомления
$table->boolean('is_read')->default(false);
$table->integer('status')->default(1);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
\kernel\App::$db->schema->dropIfExists('notification');
}
};

View File

@@ -0,0 +1,71 @@
<?php
namespace kernel\modules\notification\models;
use Illuminate\Database\Eloquent\Model;
use kernel\modules\user\models\User;
/**
* @property int $id
* @property int $user_id
* @property bool $is_read
* @property array|string $data
* @property string $type
* @property string $message
* @property string $subject
* @property int $status
*/
class Notification extends Model
{
const DISABLE_STATUS = 0;
const TO_SEND_STATUS = 1;
const SENT_STATUS = 2;
protected $table = 'notification';
protected $fillable = ['user_id', 'message', 'is_read', 'data', 'type', 'subject', 'status'];
protected $casts = [
'is_read' => 'boolean',
'data' => 'array'
];
public static function labels(): array
{
return [
'user_id' => 'Пользователь',
'message' => 'Сообщение',
'subject' => 'Тема',
'is_read' => 'Прочитано',
'data' => 'Данные',
'type' => 'Тип',
'status' => 'Статус'
];
}
public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->belongsTo(User::class);
}
public function markAsRead(): static
{
$this->update(['is_read' => true]);
return $this;
}
/**
* @return string[]
*/
public static function getStatus(): array
{
return [
self::DISABLE_STATUS => "Не активный",
self::TO_SEND_STATUS => "На отправку",
self::SENT_STATUS => "Отправлено",
];
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace kernel\modules\notification\models;
class User extends \kernel\modules\user\models\User
{
public function notifications(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(Notification::class);
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace kernel\modules\notification\models\forms;
use kernel\FormModel;
/**
* @property string $key
* @property string $value
* @property string $label
* @property integer $status
*/
class CreateNotificationForm extends FormModel
{
public function rules(): array
{
return [
'user_id' => 'required|integer',
'message' => 'required',
'is_read' => '',
'data' => '',
'type' => 'required',
'subject' => '',
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
use kernel\App;
use Phroute\Phroute\RouteCollector;
App::$collector->group(["prefix" => "admin"], function (RouteCollector $router) {
App::$collector->group(["before" => "auth"], function (RouteCollector $router) {
App::$collector->group(["prefix" => "notification"], callback: function (RouteCollector $router) {
App::$collector->get('/', [\kernel\modules\notification\controllers\NotificationController::class, 'actionIndex']);
App::$collector->get('/page/{page_number}', [\kernel\modules\notification\controllers\NotificationController::class, 'actionIndex']);
App::$collector->get('/create', [\kernel\modules\notification\controllers\NotificationController::class, 'actionCreate']);
App::$collector->post("/", [\kernel\modules\notification\controllers\NotificationController::class, 'actionAdd']);
App::$collector->get('/view/{id}', [\kernel\modules\notification\controllers\NotificationController::class, 'actionView']);
App::$collector->any('/update/{id}', [\kernel\modules\notification\controllers\NotificationController::class, 'actionUpdate']);
App::$collector->any("/edit/{id}", [\kernel\modules\notification\controllers\NotificationController::class, 'actionEdit']);
App::$collector->get('/delete/{id}', [\kernel\modules\notification\controllers\NotificationController::class, 'actionDelete']);
});
});
});

View File

@@ -0,0 +1,57 @@
<?php
namespace kernel\modules\notification\service;
use kernel\FormModel;
use kernel\modules\notification\models\Notification;
class NotificationService
{
public function create(FormModel $form_model): false|Notification
{
$model = new Notification();
$model->user_id = $form_model->getItem('user_id');
$model->message = $form_model->getItem('message');
$model->is_read = $form_model->getItem('is_read');
$model->data = $form_model->getItem('data');
$model->type = $form_model->getItem('type');
$model->subject = $form_model->getItem('subject');
$model->status = $form_model->getItem('status');
if ($model->save()) {
return $model;
}
return false;
}
public function update(FormModel $form_model, Notification $notification): false|Notification
{
$notification->user_id = $form_model->getItem('user_id');
$notification->message = $form_model->getItem('message');
$notification->is_read = $form_model->getItem('is_read');
$notification->data = $form_model->getItem('data');
$notification->type = $form_model->getItem('type');
$notification->subject = $form_model->getItem('subject');
$notification->status = $form_model->getItem('status');
if ($notification->save()) {
return $notification;
}
return false;
}
// public function createOptionArr(): array
// {
// foreach (Option::all()->toArray() as $option) {
// $optionArr[$option['id']] = $option['key'];
// }
// if (!empty($optionArr)) {
// return $optionArr;
// }
// return [];
// }
}

View File

@@ -0,0 +1,16 @@
<?php
namespace kernel\modules\option\table\columns;
use Itguild\Tables\ActionColumn\ActionColumn;
class OptionDeleteActionColumn extends ActionColumn
{
protected string $prefix = "/delete/";
public function fetch(): string
{
$link = $this->baseUrl . $this->prefix . $this->id;
return " <a href='$link' class='btn btn-danger'>Удалить</a> ";
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace kernel\modules\option\table\columns;
use Itguild\Tables\ActionColumn\ActionColumn;
class OptionEditActionColumn extends ActionColumn
{
protected string $prefix = "/update/";
public function fetch(): string
{
$link = $this->baseUrl . $this->prefix . $this->id;
return " <a href='$link' class='btn btn-success'>Редактировать</a> ";
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace kernel\modules\option\table\columns;
use Itguild\Tables\ActionColumn\ActionColumn;
class OptionViewActionColumn extends ActionColumn
{
protected string $prefix = "/";
public function fetch()
{
$link = $this->baseUrl . $this->prefix . $this->id;
return " <a href='$link' class='btn btn-primary'>Просмотр</a> ";
}
}

View File

@@ -0,0 +1,84 @@
<?php
/**
* @var Notification $model
*/
use itguild\forms\ActiveForm;
use kernel\modules\notification\models\Notification;
$form = new ActiveForm();
$form->beginForm(isset($model) ? "/admin/notification/edit/" . $model->id : "/admin/notification");
$form->field(class: \itguild\forms\inputs\Select::class, name: "user_id", params: [
'class' => "form-control",
'value' => $model->user_id ?? ''
])
->setLabel(Notification::labels()['user_id'])
->setOptions(\kernel\modules\user\service\UserService::createUsernameArr())
->render();
$form->field(\itguild\forms\inputs\TextInput::class, 'subject', [
'class' => "form-control",
'placeholder' => Notification::labels()['subject'],
'value' => $model->subject ?? ''
])
->setLabel(Notification::labels()['subject'])
->render();
$form->field(\itguild\forms\inputs\TextInput::class, 'type', [
'class' => "form-control",
'placeholder' => Notification::labels()['type'],
'value' => $model->type ?? ''
])
->setLabel(Notification::labels()['type'])
->render();
$form->field(\itguild\forms\inputs\TextArea::class, 'message', [
'class' => "form-control",
'placeholder' => Notification::labels()['message'],
'value' => $model->message ?? ''
])
->setLabel(Notification::labels()['message'])
->render();
$form->field(\itguild\forms\inputs\Checkbox::class, 'is_read', [
'class' => "form-check-input",
'placeholder' => Notification::labels()['is_read'],
'value' => $model->is_read ?? ''
])
->setLabel(Notification::labels()['is_read'])
->render();
$form->field(\itguild\forms\inputs\Select::class, 'status', [
'class' => "form-control",
'value' => $model->status ?? ''
])
->setLabel("Статус")
->setOptions(Notification::getStatus())
->render();
?>
<div class="row">
<div class="col-sm-2">
<?php
$form->field(\itguild\forms\inputs\Button::class, name: "btn-submit", params: [
'class' => "btn btn-primary ",
'value' => 'Отправить',
'typeInput' => 'submit'
])
->render();
?>
</div>
<div class="col-sm-2">
<?php
$form->field(\itguild\forms\inputs\Button::class, name: "btn-reset", params: [
'class' => "btn btn-warning",
'value' => 'Сбросить',
'typeInput' => 'reset'
])
->render();
?>
</div>
</div>
<?php
$form->endForm();

View File

@@ -0,0 +1,44 @@
<?php
/**
* @var \Illuminate\Database\Eloquent\Collection $options
* @var int $page_number
*/
use Itguild\EloquentTable\EloquentDataProvider;
use Itguild\EloquentTable\ListEloquentTable;
use kernel\modules\notification\models\Notification;
use kernel\widgets\IconBtn\IconBtnCreateWidget;
use kernel\widgets\IconBtn\IconBtnDeleteWidget;
use kernel\widgets\IconBtn\IconBtnEditWidget;
use kernel\widgets\IconBtn\IconBtnViewWidget;
$table = new ListEloquentTable(new EloquentDataProvider(Notification::class, [
'current_page' => $page_number,
'per_page' => 5,
'params' => ["class" => "table table-bordered", "border" => "2"],
'baseUrl' => "/admin/notification",
]));
$table->beforePrint(function () {
return IconBtnCreateWidget::create(['url' => '/admin/notification/create'])->run();
});
$table->columns([
"status" => [
"value" => function ($cell) {
return Notification::getStatus()[$cell];
}]
]);
$table->addAction(function($row) {
return IconBtnViewWidget::create(['url' => '/admin/notification/view/' . $row['id']])->run();
});
$table->addAction(function($row) {
return IconBtnEditWidget::create(['url' => '/admin/Notification/update/' . $row['id']])->run();
});
$table->addAction(function($row) {
return IconBtnDeleteWidget::create(['url' => '/admin/Notification/delete/' . $row['id']])->run();
});
$table->create();
$table->render();

View File

@@ -0,0 +1,32 @@
<?php
/**
* @var \Illuminate\Database\Eloquent\Collection $option
*/
use Itguild\EloquentTable\ViewEloquentTable;
use Itguild\EloquentTable\ViewJsonTableEloquentModel;
use kernel\IGTabel\btn\DangerBtn;
use kernel\IGTabel\btn\PrimaryBtn;
use kernel\IGTabel\btn\SuccessBtn;
use kernel\widgets\IconBtn\IconBtnDeleteWidget;
use kernel\widgets\IconBtn\IconBtnEditWidget;
use kernel\widgets\IconBtn\IconBtnListWidget;
$table = new ViewEloquentTable(new ViewJsonTableEloquentModel($option, [
'params' => ["class" => "table table-bordered", "border" => "2"],
'baseUrl' => "/admin/user",
]));
$table->beforePrint(function () use ($option) {
$btn = IconBtnListWidget::create(['url' => '/admin/option'])->run();
$btn .= IconBtnEditWidget::create(['url' => '/admin/option/update/' . $option->id])->run();
$btn .= IconBtnDeleteWidget::create(['url' => '/admin/option/delete/' . $option->id])->run();
return $btn;
});
$table->rows([
'status' => (function ($data) {
return \kernel\modules\option\models\Notification::getStatus()[$data];
})
]);
$table->create();
$table->render();

View File

@@ -8,6 +8,7 @@ use kernel\App;
use kernel\Flash;
use kernel\helpers\Debug;
use kernel\Mailing;
use kernel\modules\secure\models\forms\ChangePasswordForm;
use kernel\modules\secure\models\forms\LoginEmailForm;
use kernel\modules\secure\models\forms\LoginForm;
use kernel\modules\secure\models\forms\RegisterForm;
@@ -40,7 +41,7 @@ class SecureController extends AdminController
// $this->cgView->render('login.php');
}
#[NoReturn] public function actionAuth(): void
#[NoReturn] public function actionAuth($basePath = '/admin'): void
{
$loginForm = new LoginForm();
$loginForm->load($_REQUEST);
@@ -51,19 +52,36 @@ class SecureController extends AdminController
else {
$field = "username";
}
$user = $this->userService->getByField($field, $loginForm->getItem("username"));
if (!$user){
Flash::setMessage("error", "User not found.");
$this->redirect("/admin/login", code: 302);
$this->redirect($basePath . "/login", code: 302);
}
if (password_verify($loginForm->getItem("password"), $user->password_hash)) {
setcookie('user_id', $user->id, time()+60*60*24, '/', $_SERVER['SERVER_NAME'], false);
$this->redirect("/admin", code: 302);
$this->redirect($basePath . '/', code: 302);
} else {
Flash::setMessage("error", "Username or password incorrect.");
$this->redirect("/admin/login", code: 302);
$this->redirect($basePath . "/login", code: 302);
}
}
#[NoReturn] public function actionChangePassword($basePath = '/admin'): void
{
$changePasswordForm = new ChangePasswordForm();
$changePasswordForm->load($_REQUEST);
$user = UserService::getAuthUser();
if (password_verify($changePasswordForm->getItem("old_password"), $user->password_hash)) {
$user->password_hash = password_hash($changePasswordForm->getItem("new_password"), PASSWORD_DEFAULT);
$user->save();
Flash::setMessage("success", "Пароль успешно изменен.");
$this->redirect($basePath . '', code: 302);
} else {
Flash::setMessage("error", "Username or password incorrect.");
$this->redirect($basePath . "", code: 302);
}
}
@@ -148,25 +166,25 @@ class SecureController extends AdminController
$this->cgView->render('register.php');
}
public function actionRegistration(): void
public function actionRegistration($basePath = '/admin'): void
{
$regForm = new RegisterForm();
$regForm->load($_REQUEST);
if ($this->userService->getByField('username', $regForm->getItem("username"))) {
Flash::setMessage("error", "Username already exists.");
$this->redirect("/admin/register", code: 302);
$this->redirect($basePath . "/register", code: 302);
}
if ($this->userService->getByField('email', $regForm->getItem("email"))) {
Flash::setMessage("error", "Email already exists.");
$this->redirect("/admin/register", code: 302);
$this->redirect($basePath . "/register", code: 302);
}
$user = $this->userService->create($regForm);
if ($user){
setcookie('user_id', $user->id, time()+60*60*24, '/', $_SERVER['SERVER_NAME'], false);
$this->redirect("/admin", code: 302);
$this->redirect($basePath . "/", code: 302);
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace kernel\modules\secure\models\forms;
use kernel\FormModel;
class ChangePasswordForm extends FormModel
{
public function rules(): array
{
return [
'old_password' => 'required|min-str-len:6|max-str-len:50',
'new_password' => 'required|min-str-len:6|max-str-len:50',
];
}
}

View File

@@ -7,6 +7,7 @@ use JetBrains\PhpStorm\NoReturn;
use kernel\AdminController;
use kernel\EntityRelation;
use kernel\FileUpload;
use kernel\Flash;
use kernel\helpers\Debug;
use kernel\modules\user\models\forms\CreateUserForm;
use kernel\modules\user\models\User;
@@ -55,6 +56,7 @@ class UserController extends AdminController
$this->redirect("/admin/user/view/" . $user->id);
}
}
Flash::setMessage("error", $userForm->getErrorsStr());
$this->redirect("/admin/user/create");
}

View File

@@ -2,6 +2,7 @@
namespace kernel\modules\user\service;
use itguild\forms\ActiveForm;
use kernel\FormModel;
use kernel\helpers\Debug;
use kernel\modules\user\models\User;
@@ -122,4 +123,12 @@ class UserService
$user->save();
}
public static function getList(): array
{
return User::select('id', 'username')->get()
->pluck('username', 'id')
->toArray();
}
}

View File

@@ -47,6 +47,8 @@ if (!isset($model)) {
$model = new User();
}
$entityRelations->renderEntityAdditionalPropertyFormBySlug("user", $model);
?>
<div class="row">
<div class="col-sm-2">

Some files were not shown because too many files have changed in this diff Show More