Compare commits

...

18 Commits

Author SHA1 Message Date
57cd3b8c62 main page fix 2025-07-20 17:33:39 +03:00
1547950d4c lk fix 2025-07-20 16:41:09 +03:00
d3af15c5ac first prod 2025-07-20 15:02:06 +03:00
273ac72207 first 2025-07-14 12:15:41 +03:00
a64ed080bb v0.1.7 2025-01-28 16:44:34 +03:00
6242304843 flash msg fix 2025-01-28 12:47:44 +03:00
2655a793f5 some fix 2025-01-24 16:47:37 +03:00
de0354f9cb add dependencies to view module shop client 2025-01-24 16:26:11 +03:00
4a4d5b083f some fix 2025-01-24 16:20:15 +03:00
68b5741f46 some fix 2025-01-24 15:14:36 +03:00
fc70051761 theme service fix 2025-01-24 15:00:22 +03:00
b1dacff877 Merge branch 'master' of https://git.itguild.info/ItGuild/igmf 2025-01-24 14:30:49 +03:00
e904cedf40 some 2025-01-24 14:28:07 +03:00
64a6cc4340 Merge branch 'master' of https://git.itguild.info/ItGuild/igmf 2025-01-24 13:59:55 +03:00
b79483dafd some 2025-01-24 13:59:03 +03:00
c69314b531 Merge branch 'master' of https://git.itguild.info/ItGuild/igmf 2025-01-24 12:15:48 +03:00
3c025a4cbc Merge branch 'master' of https://git.itguild.info/ItGuild/igmf 2025-01-24 11:56:47 +03:00
6a7cde15e9 some fix 2025-01-24 11:56:29 +03:00
1102 changed files with 541966 additions and 45 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "Photo",
"version": "0.1",
"version": "0.2",
"author": "ITGuild",
"slug": "photo",
"type": "additional_property",

View File

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

View File

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

View File

@@ -0,0 +1,13 @@
{
"name": "Tags",
"version": "0.1.1",
"author": "ITGuild",
"slug": "tag",
"type": "additional_property",
"description": "Tags module",
"app_module_path": "{APP}/modules/{slug}",
"module_class": "app\\modules\\tag\\TagModule",
"module_class_file": "{APP}/modules/tag/TagModule.php",
"routs": "routs/tag.php",
"dependence": "menu"
}

View File

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

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\modules\view;
class ViewModule extends \kernel\app_modules\view\ViewModule
{
}

View File

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

View File

@@ -0,0 +1,12 @@
{
"name": "Просмотры",
"version": "0.1",
"author": "ITGuild",
"slug": "view",
"description": "Просмотры module",
"module_class": "app\\modules\\view\\ViewModule",
"module_class_file": "{APP}/modules/view/ViewModule.php",
"routs": "routs/view.php",
"migration_path": "migrations",
"dependence": "menu"
}

View File

@@ -0,0 +1,2 @@
<?php
include KERNEL_APP_MODULES_DIR . "/view/routs/view.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,36 @@
<?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,
after: 'bootstrap'
);
}
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,
after: 'jquery',
);
$this->registerJS(
slug: "slider",
resource: "/slider.js",
after: 'select2',
);
}
}

View File

@@ -0,0 +1,30 @@
<?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: "posts", resource: "/css/netic/posts.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,46 @@
<?php
namespace app\themes\svo\controllers;
use app\themes\svo\services\MainPageSliderService;
use kernel\Controller;
use kernel\helpers\Debug;
use kernel\modules\post\models\Post;
use kernel\modules\post\service\PostService;
use kernel\modules\user\service\UserService;
class LpController extends Controller
{
protected \kernel\modules\user\models\User $user;
protected MainPageSliderService $mainPageSliderService;
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();
$this->mainPageSliderService = new MainPageSliderService();
if ($user){
$this->cgView->addVarToLayout("currentUser", $user);
$this->user = $user;
}
}
public function actionIndex(): void
{
$this->cgView->render('index.php', []);
}
public function actionPost(int $postId): void
{
$post = Post::find($postId);
$this->cgView->render('post.php', ['post' => $post]);
}
}

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,48 @@
<?php
namespace app\themes\svo\controllers;
use app\themes\svo\services\MainPageSliderService;
use app\themes\svo\services\SvoThemeService;
use JetBrains\PhpStorm\NoReturn;
use kernel\AdminController;
use kernel\Flash;
use kernel\helpers\Debug;
use kernel\Request;
class SvoAdminController extends AdminController
{
protected function init(): void
{
parent::init();
$this->cgView->viewPath = APP_DIR . "/themes/svo/views/admin/";
$this->cgView->addVarToLayout("svo_theme_resources", "/resources/themes/svo");
}
public function actionThemeSettings(): void
{
$this->cgView->render('theme_settings.php');
}
#[NoReturn] public function actionAddSlide(): void
{
$request = new Request();
$slideId = $request->post('slide');
SvoThemeService::addPostSlide($slideId);
Flash::setMessage('success', 'Слайд добавлен.');
$this->redirect('/admin/svo-theme/settings?tab=slider', 302);
}
#[NoReturn] public function actionDeleteSlide(int $post_id): void
{
$mainPageSlideService = new MainPageSliderService();
$mainPageSlideService->removeSlide($post_id);
Flash::setMessage('success', 'Слайд удален.');
$this->redirect('/admin/svo-theme/settings?tab=slider', 302);
}
}

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,57 @@
<?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->get('/post/{postId}', [\app\themes\svo\controllers\LpController::class, 'actionPost']);
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->get('/delete_slide/{post_id}', [\app\themes\svo\controllers\SvoAdminController::class, 'actionDeleteSlide']);
App::$collector->post('/add_slide', [\app\themes\svo\controllers\SvoAdminController::class, 'actionAddSlide']);
});
});
});
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,90 @@
<?php
namespace app\themes\svo\services;
use kernel\App;
use kernel\helpers\Debug;
use kernel\models\Option;
use kernel\modules\option\service\OptionService;
class MainPageSliderService
{
protected OptionService $optionService;
protected string $sliderKey = 'main_news_slider';
public function __construct()
{
$this->optionService = new OptionService();
}
/**
* Получить список слайдов
*
* @return array
*/
public function getSlides(): array
{
$option = Option::where('key', $this->sliderKey)->first();
if (!$option || empty($option->value)) {
return [];
}
$data = json_decode($option->value, true);
return $data['ids'] ?? [];
}
/**
* Добавить слайд
*
* @param int $slideId
* @return bool
*/
public function addSlide(int $slideId): bool
{
return App::$db->capsule::connection()->transaction(function () use ($slideId) {
$option = Option::firstOrNew(['key' => $this->sliderKey]);
$currentValue = $option->value ? json_decode($option->value, true) : ['ids' => []];
if (in_array($slideId, $currentValue['ids'])) {
return true; // слайд уже есть, ничего не делаем
}
$currentValue['ids'][] = $slideId;
$option->value = json_encode($currentValue);
return $option->save();
});
}
/**
* Удалить слайд
*
* @param int $slideId
* @return bool
*/
public function removeSlide(int $slideId): bool
{
return App::$db->capsule::connection()->transaction(function () use ($slideId) {
/** @var Option $option */
$option = Option::where('key', $this->sliderKey)->first();
if (!$option || empty($option->value)) {
return false;
}
$currentValue = json_decode($option->value, true);
if (!in_array($slideId, $currentValue['ids'] ?? [])) {
return false; // слайда нет в списке
}
$currentValue['ids'] = array_diff($currentValue['ids'], [$slideId]);
$option->value = json_encode($currentValue);
return $option->save();
});
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace app\themes\svo\services;
use kernel\helpers\Debug;
use kernel\models\Option;
use kernel\modules\option\service\OptionService;
use kernel\modules\post\models\Post;
class SvoThemeService
{
public static function getSliderPosts()
{
$slides = json_decode(OptionService::getItem('main_news_slider'), true);
return Post::whereIn('id', $slides['ids'])->get();
}
public static function addPostSlide(int $postId)
{
$slides = json_decode(OptionService::getItem('main_news_slider'), true);
if (!in_array($postId, $slides['ids'])) {
$slides['ids'][] = $postId;
OptionService::createOrUpdate('main_news_slider', json_encode($slides));
}
return $slides;
}
}

View File

@@ -0,0 +1,3 @@
<?php
?>
Посты

View File

@@ -0,0 +1,72 @@
<?php
/**
* @var string $resources
* @var string $svo_theme_resources
* @var \kernel\CgView $view
*/
\kernel\Theme::$assetsCollector->registerAsset(new \app\themes\svo\assets\AdminSliderAssets($svo_theme_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>
<form id="add_slide_form" action="/admin/svo-theme/add_slide" method="post">
<select name="slide" class="form-control" id="postSelect" style="width: 300px">
<?php foreach (\kernel\modules\post\service\PostService::getListArr() as $key => $item): ?>
<option value="<?= $key ?>"><?= $item ?></option>
<?php endforeach; ?>
</select>
</form>
</div>
<br>
<?= \kernel\helpers\Html::link('Добавить', '#', [
'id' => 'saveSliderPost',
'class' => 'btn btn-success',
'onclick' => 'document.getElementById("add_slide_form").submit(); return false;'
]) ?>
<br>
<?php \app\themes\svo\widgets\AdminSliderPostsList::create()->run(); ?>
</div>
<div class="tab-pane fade" id="contact" role="tabpanel" aria-labelledby="contact-tab">
Contact
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Получаем параметр из URL
const urlParams = new URLSearchParams(window.location.search);
const activeTab = urlParams.get('tab');
// Если параметр есть, активируем соответствующую вкладку
if (activeTab) {
const tabTrigger = new bootstrap.Tab(document.querySelector(`[data-target="#${activeTab}"]`));
tabTrigger.show();
}
});
</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" style="background-color: black">
<a href="/lk" class="b-brand">
<!-- ======== Change your logo from here ============ -->
<img width="100px" src="<?= $resources ?>/assets/images/netic/logo.png" 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,274 @@
<?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="#stages">О конкурсе</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#news">Новости</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#contacts">Контакты</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="#stages">О конкурсе</a></li>
<li><a href="#news">Новости</a></li>
<li><a href="#contacts">Контакты</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="services_section layout_padding" id="stages">
<div class="container">
<div class="row">
<div class="col-md-12">
<h1 class="services_taital">Этапы конкурса</h1>
<p class="services_text">Здесь небольшое описание конкурса</p>
</div>
</div>
<div class="services_section_2">
<div id="main_slider"class="carousel slide" data-ride="carousel">
<div class="carousel-inner">
<div class="carousel-item active">
<div class="row">
<div class="col-md-4">
<div class="service_box">
<div class="services_icon">
<img src="<?= $resources ?>/images/netic/icon-4.png" class="image_1">
<img src="<?= $resources ?>/images/netic/icon-7.png" class="image_2">
</div>
<h3 class="wordpress_text">Регистрация</h3>
<p class="opposed_text">Регистрация Регистрация Регистрация Регистрация</p>
</div>
</div>
<div class="col-md-4">
<div class="service_box">
<div class="services_icon">
<img src="<?= $resources ?>/images/netic/icon-5.png" class="image_1">
<img src="<?= $resources ?>/images/netic/icon-5.png" class="image_2">
</div>
<h3 class="wordpress_text">Первый этап</h3>
<p class="opposed_text">Первый этап Первый этап Первый этап Первый этап</p>
</div>
</div>
<div class="col-md-4">
<div class="service_box">
<div class="services_icon">
<img src="<?= $resources ?>/images/netic/icon-6.png" class="image_1">
<img src="<?= $resources ?>/images/netic/icon-9.png" class="image_2">
</div>
<h3 class="wordpress_text">Второй этап</h3>
<p class="opposed_text">Второй этап Второй этап Второй этап Второй этап</p>
</div>
</div>
</div>
</div>
<div class="carousel-item">
<div class="row">
<div class="col-md-4">
<div class="service_box">
<div class="services_icon">
<img src="<?= $resources ?>/images/netic/icon-4.png" class="image_1">
<img src="<?= $resources ?>/images/netic/icon-7.png" class="image_2">
</div>
<h3 class="wordpress_text">Третий этап</h3>
<p class="opposed_text">Третий этап Третий этап Третий этап Третий этап</p>
</div>
</div>
<div class="col-md-4">
<div class="service_box">
<div class="services_icon">
<img src="<?= $resources ?>/images/netic/icon-5.png" class="image_1">
<img src="<?= $resources ?>/images/netic/icon-5.png" class="image_2">
</div>
<h3 class="wordpress_text">Четвертый этап</h3>
<p class="opposed_text">Четвертый этап Четвертый этап Четвертый этап Четвертый этап</p>
</div>
</div>
<div class="col-md-4">
<div class="service_box">
<div class="services_icon">
<img src="<?= $resources ?>/images/netic/icon-6.png" class="image_1">
<img src="<?= $resources ?>/images/netic/icon-9.png" class="image_2">
</div>
<h3 class="wordpress_text">Полуфинал</h3>
<p class="opposed_text">Полуфинал Полуфинал Полуфинал Полуфинал</p>
</div>
</div>
</div>
</div>
<div class="carousel-item">
<div class="row">
<div class="col-md-4">
<div class="service_box">
<div class="services_icon">
<img src="<?= $resources ?>/images/netic/icon-4.png" class="image_1">
<img src="<?= $resources ?>/images/netic/icon-7.png" class="image_2">
</div>
<h3 class="wordpress_text">Финал</h3>
<p class="opposed_text">Финал Финал Финал Финал</p>
</div>
</div>
</div>
</div>
</div>
<a class="carousel-control-prev" href="#main_slider" role="button" data-slide="prev">
<i class="fa fa-angle-left"></i>
</a>
<a class="carousel-control-next" href="#main_slider" role="button" data-slide="next">
<i class="fa fa-angle-right"></i>
</a>
</div>
</div>
</div>
</div>
<div class="contact_section layout_padding" id="contacts">
<div class="container">
<div class="row">
<div class="col-sm-12">
<h1 class="contact_taital">Напишите нам</h1>
</div>
</div>
<div class="contact_section_2">
<div class="row">
<div class="col-md-12">
<div class="mail_section_1">
<input type="text" class="mail_text" placeholder="Имя" name="Имя">
<input type="text" class="mail_text" placeholder="Email" name="Email">
<input type="text" class="mail_text" placeholder="Телефон" name="Телефон">
<textarea class="massage-bt" placeholder="Сообщение" rows="5" id="comment" name="Your Massage"></textarea>
<div class="send_bt"><a href="#">Отправить</a></div>
</div>
</div>
</div>
</div>
</div>
</div>
<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" id="news">
<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,223 @@
<?php
/**
* @var \kernel\modules\post\models\Post $post
*/
?>
<main class="container my-5">
<!-- Хлебные крошки -->
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="#">Главная</a></li>
<li class="breadcrumb-item"><a href="#">Новости</a></li>
<li class="breadcrumb-item active" aria-current="page"><?= $post->title ?></li>
</ol>
</nav>
<!-- Заголовок новости -->
<article>
<header class="mb-4">
<h1 class="font-weight-bold"><?= $post->title ?></h1>
<div class="d-flex align-items-center text-muted mt-3">
<span class="mr-3"><i class="fa fa-calendar mr-2"></i><?= $post->created_at ?></span>
<span class="mr-3"><i class="fa fa-eye mr-2"></i>12 543 просмотра</span>
<span><i class="fa fa-comments-o mr-2"></i>24 комментария</span>
</div>
</header>
<!-- Основное изображение -->
<figure class="mb-4">
<img src="https://via.placeholder.com/1200x600" alt="Иллюстрация к новости" class="img-fluid rounded">
<figcaption class="text-center mt-2 text-muted">Иллюстрация: создание 3D-модели с помощью ИИ</figcaption>
</figure>
<!-- Текст новости -->
<div class="news-content mb-5">
<p class="lead">Компания OpenAI представила новую версию своего алгоритма, способного генерировать трехмерные объекты на основе текстовых описаний.</p>
<p>Новая технология, получившая название 3D-GPT, позволяет создавать высокодетализированные модели всего за несколько секунд. Пользователю достаточно ввести описание объекта, например, "красный спортивный автомобиль с черными полосами", и система сгенерирует соответствующую 3D-модель.</p>
<h2 class="mt-4 mb-3">Как это работает</h2>
<p>Алгоритм основан на комбинации нескольких нейросетевых архитектур:</p>
<ul>
<li>Текстовая модель анализирует описание и выделяет ключевые характеристики</li>
<li>Генеративная сеть создает базовую геометрию объекта</li>
<li>Дополнительные модули добавляют текстуры, материалы и освещение</li>
</ul>
<p>По словам разработчиков, система особенно хорошо справляется с органическими формами и архитектурными объектами.</p>
<div class="alert alert-info mt-4">
<h3 class="alert-heading">Экспертное мнение</h3>
<p>"Эта технология может революционизировать индустрию 3D-моделирования, значительно сократив время и стоимость создания цифровых активов", - отмечает Джон Смит, профессор компьютерной графики в MIT.</p>
</div>
<h2 class="mt-4 mb-3">Практическое применение</h2>
<p>Технология уже тестируется в нескольких областях:</p>
<div class="row mt-3">
<div class="col-md-4 mb-3">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">Игровая индустрия</h5>
<p class="card-text">Быстрое прототипирование персонажей и объектов для видеоигр.</p>
</div>
</div>
</div>
<div class="col-md-4 mb-3">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">Архитектура</h5>
<p class="card-text">Создание 3D-моделей зданий по словесным описаниям заказчиков.</p>
</div>
</div>
</div>
<div class="col-md-4 mb-3">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">Образование</h5>
<p class="card-text">Визуализация исторических объектов и научных концепций.</p>
</div>
</div>
</div>
</div>
<p class="mt-4">Ожидается, что коммерческая версия продукта будет выпущена в начале следующего года. Бета-тестирование начнется уже в сентябре для ограниченного круга разработчиков.</p>
</div>
<!-- Теги и соцсети -->
<div class="d-flex flex-wrap justify-content-between align-items-center border-top border-bottom py-3 mb-5">
<div class="tags mb-2 mb-md-0">
<span class="badge badge-secondary mr-2">#искусственный_интеллект</span>
<span class="badge badge-secondary mr-2">#3d_моделирование</span>
<span class="badge badge-secondary mr-2">#технологии</span>
<span class="badge badge-secondary">#openai</span>
</div>
<div class="social-share">
<button class="btn btn-outline-primary btn-sm mr-2"><i class="fab fa-facebook-f mr-1"></i> Поделиться</button>
<button class="btn btn-outline-info btn-sm mr-2"><i class="fab fa-twitter mr-1"></i> Твитнуть</button>
<button class="btn btn-outline-danger btn-sm"><i class="fab fa-telegram-plane mr-1"></i> Отправить</button>
</div>
</div>
<!-- Автор и источник -->
<footer class="bg-light p-3 rounded mb-5">
<div class="row">
<div class="col-md-8">
<h5>Об авторе</h5>
<div class="d-flex align-items-center">
<img src="https://via.placeholder.com/50" alt="Автор" class="rounded-circle mr-3">
<div>
<h6 class="mb-0">Александра Петрова</h6>
<p class="text-muted mb-0">Технологический обозреватель с 10-летним опытом</p>
</div>
</div>
</div>
<div class="col-md-4 mt-3 mt-md-0">
<h5>Источник</h5>
<p>По материалам пресс-релиза OpenAI и эксклюзивного интервью с разработчиками</p>
</div>
</div>
</footer>
<!-- Похожие новости -->
<section class="mb-5">
<h3 class="mb-4">Похожие новости</h3>
<div class="row">
<div class="col-md-4 mb-4">
<div class="card h-100">
<img src="https://via.placeholder.com/400x200" class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title">Нейросети научились анимировать 2D-рисунки</h5>
<p class="card-text text-muted">12 июля 2023</p>
<a href="#" class="btn btn-outline-primary btn-sm">Читать</a>
</div>
</div>
</div>
<div class="col-md-4 mb-4">
<div class="card h-100">
<img src="https://via.placeholder.com/400x200" class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title">ИИ в дизайне: как алгоритмы меняют творческие профессии</h5>
<p class="card-text text-muted">8 июля 2023</p>
<a href="#" class="btn btn-outline-primary btn-sm">Читать</a>
</div>
</div>
</div>
<div class="col-md-4 mb-4">
<div class="card h-100">
<img src="https://via.placeholder.com/400x200" class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title">Обзор новых инструментов для 3D-художников в 2023 году</h5>
<p class="card-text text-muted">3 июля 2023</p>
<a href="#" class="btn btn-outline-primary btn-sm">Читать</a>
</div>
</div>
</div>
</div>
</section>
<!-- Комментарии -->
<section class="mb-5">
<h3 class="mb-4">Комментарии (24)</h3>
<div class="mb-4">
<form>
<div class="form-group">
<textarea class="form-control" rows="3" placeholder="Оставьте ваш комментарий..."></textarea>
</div>
<button type="submit" class="btn btn-primary">Отправить</button>
</form>
</div>
<div class="comment-list">
<div class="comment mb-4">
<div class="d-flex">
<img src="https://via.placeholder.com/40" alt="Аватар" class="rounded-circle mr-3">
<div>
<div class="d-flex justify-content-between align-items-center mb-2">
<h6 class="mb-0">Иван Иванов</h6>
<small class="text-muted">2 часа назад</small>
</div>
<p>Очень интересная технология! Интересно, насколько она будет доступна для индивидуальных разработчиков по цене.</p>
<button class="btn btn-link btn-sm p-0 text-muted">Ответить</button>
</div>
</div>
</div>
<div class="comment mb-4 ml-5">
<div class="d-flex">
<img src="https://via.placeholder.com/40" alt="Аватар" class="rounded-circle mr-3">
<div>
<div class="d-flex justify-content-between align-items-center mb-2">
<h6 class="mb-0">Мария Смирнова</h6>
<small class="text-muted">1 час назад</small>
</div>
<p>@Иван Иванов, по предварительной информации, будет несколько тарифных планов, включая бесплатный с ограничениями.</p>
<button class="btn btn-link btn-sm p-0 text-muted">Ответить</button>
</div>
</div>
</div>
<div class="comment mb-4">
<div class="d-flex">
<img src="https://via.placeholder.com/40" alt="Аватар" class="rounded-circle mr-3">
<div>
<div class="d-flex justify-content-between align-items-center mb-2">
<h6 class="mb-0">Алексей Технарев</h6>
<small class="text-muted">30 минут назад</small>
</div>
<p>Жду не дождусь, когда можно будет попробовать! Уже есть несколько идей, как это можно применить в нашем проекте.</p>
<button class="btn btn-link btn-sm p-0 text-muted">Ответить</button>
</div>
</div>
</div>
<button class="btn btn-outline-secondary">Показать еще комментарии</button>
</div>
</section>
</article>
</main>

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,25 @@
<?php
/**
* @var \Illuminate\Database\Eloquent\Collection $posts
*/
use kernel\CollectionTableRenderer;
$table = new CollectionTableRenderer($posts);
$table->setColumns([
'id' => 'ID',
'title' => 'Название',
]);
$table->setTableAttributes([
'style' => 'margin-top:20px;'
]);
$table->addCustomColumn('action', 'Действия', function ($post) {
$btn = '<a href="/admin/svo-theme/delete_slide/' . $post->id . '" class="btn btn-sm btn-danger">Удалить</a> ';
return $btn;
});
$table->render();

View File

@@ -0,0 +1,37 @@
<?php
/**
* @var string $resources
* @var \Illuminate\Database\Eloquent\Collection $posts
*/
$flag = 0;
?>
<div class="banner_section layout_padding">
<div id="my_slider" class="carousel slide" data-ride="carousel">
<div class="carousel-inner">
<?php foreach ($posts as $key => $post): ?>
<?php /** @var \kernel\modules\post\models\Post $post */ ?>
<div class="carousel-item <?= $flag === 0 ? 'active' : '' ?>">
<div class="container">
<div class="row">
<div class="col-md-6">
<h1 class="banner_taital main_slider_title"><?= $post->title ?></h1>
<div class="read_bt"><a href="/post/<?= $post->id ?>">Читать</a></div>
</div>
<div class="col-md-6">
<div class="banner_img"><img src="<?= $resources ?>/images/slide_img.jpg"></div>
</div>
</div>
</div>
</div>
<?php $flag++; ?>
<?php endforeach; ?>
</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,19 @@
<?php
namespace app\themes\svo\widgets;
use kernel\Widget;
class AdminSliderPostsList extends Widget
{
protected function init(): void
{
$this->cgView->viewPath = APP_DIR . "/themes/svo/views/widget/";
}
public function run(): void
{
$this->cgView->render("admin_slider_posts_list.php", ['posts' => \app\themes\svo\services\SvoThemeService::getSliderPosts()]);
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace app\themes\svo\widgets;
use app\themes\svo\services\MainPageSliderService;
use kernel\modules\post\models\Post;
use kernel\Widget;
class MainSliderWidget extends Widget
{
protected MainPageSliderService $mainPageSliderService;
protected function init(): void
{
$this->cgView->viewPath = APP_DIR . "/themes/svo/views/widget/";
$this->mainPageSliderService = new MainPageSliderService();
}
public function run(): void
{
$slides = $this->mainPageSliderService->getSlides();
$posts = Post::whereIn('id', $slides)->get();
$this->cgView->render("main_slider.php", [
'resources' => $this->data['resources'],
'posts' => $posts,
]);
}
}

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";
@@ -18,7 +19,7 @@ const KERNEL_APP_MODULES_DIR = KERNEL_DIR . "/app_modules";
const APP_DIR = ROOT_DIR . "/app";
\kernel\Theme::$assetsCollector = new \kernel\AssetsCollector();
function getConst($text): array|false|string
{

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

@@ -7,8 +7,12 @@ class Assets
protected array $jsHeader = [];
protected array $jsBody = [];
protected array $collectorJs = [];
protected array $css = [];
protected array $collectorCss = [];
protected string $resourceURI = "/resource";
public function __construct(string $resourceURI)
@@ -26,7 +30,7 @@ class Assets
$this->resourceURI = $resourceURI;
}
public function registerJS(string $slug, string $resource, bool $body = true, bool $addResourceURI = true): void
public function registerJS(string $slug, string $resource, bool $body = true, bool $addResourceURI = true, string $after = null): void
{
$resource = $addResourceURI ? $this->resourceURI . $resource : $resource;
if ($body) {
@@ -34,12 +38,14 @@ class Assets
} else {
$this->jsHeader[$slug] = $resource;
}
$this->collectorJs[$slug] = ['resource' => $resource, 'after' => $after, 'body' => $body];
}
public function registerCSS(string $slug, string $resource, bool $addResourceURI = true): void
public function registerCSS(string $slug, string $resource, bool $addResourceURI = true, string $after = null): void
{
$resource = $addResourceURI ? $this->resourceURI . $resource : $resource;
$this->css[$slug] = $resource;
$this->collectorCss[$slug] = ['resource' => $resource, 'after' => $after];
}
public function getJSAsStr(bool $body = true): void
@@ -63,4 +69,14 @@ class Assets
}
}
public function getCollectorCss(): array
{
return $this->collectorCss;
}
public function getCollectorJs(): array
{
return $this->collectorJs;
}
}

128
kernel/AssetsCollector.php Normal file
View File

@@ -0,0 +1,128 @@
<?php
namespace kernel;
use RuntimeException;
class AssetsCollector
{
protected array $assetsPool = [];
public function registerAsset(Assets $assets): void
{
$this->assetsPool[] = $assets;
}
public function renderCss(): void
{
$css = [];
foreach ($this->assetsPool as $item) {
/** @var Assets $item */
$css = array_merge($css, $item->getCollectorCss());
}
try {
$sortedStyles = $this->sortStyles($css);
// Выводим отсортированные стили
foreach ($sortedStyles as $style) {
echo '<link rel="stylesheet" href="' . htmlspecialchars($style) . '">' . "\n";
}
} catch (RuntimeException $e) {
echo 'Ошибка: ' . $e->getMessage();
}
}
public function renderJs(bool $body = true): void
{
$scripts = [];
foreach ($this->assetsPool as $item) {
/** @var Assets $item */
$scripts = array_merge($scripts, $item->getCollectorJs());
}
try {
$sortedScripts = $this->sortScripts($scripts);
// Разделяем скрипты для head и body
$headScripts = [];
$bodyScripts = [];
foreach ($sortedScripts as $script) {
if ($script['body']) {
$bodyScripts[] = $script['resource'];
} else {
$headScripts[] = $script['resource'];
}
}
// Выводим скрипты для head
if ($body){
$scriptsToRender = $bodyScripts;
}
else {
$scriptsToRender = $headScripts;
}
foreach ($scriptsToRender as $script) {
echo '<script src="' . htmlspecialchars($script) . '"></script>' . "\n";
}
}
catch (RuntimeException $e) {
echo 'Ошибка: ' . $e->getMessage();
}
}
protected function sortStyles(array $styles): array
{
$sorted = [];
$added = [];
// Пока не добавим все стили
while (count($sorted) < count($styles)) {
$found = false;
foreach ($styles as $name => $style) {
// Если стиль еще не добавлен и его зависимости выполнены
if (!isset($added[$name]) &&
(empty($style['after']) || isset($added[$style['after']]))) {
$sorted[] = $style['resource'];
$added[$name] = true;
$found = true;
}
}
if (!$found) {
// Если есть циклическая зависимость
throw new RuntimeException('Обнаружена циклическая зависимость в стилях');
}
}
return $sorted;
}
protected function sortScripts(array $scripts): array
{
$sorted = [];
$added = [];
while (count($sorted) < count($scripts)) {
$found = false;
foreach ($scripts as $name => $script) {
if (!isset($added[$name]) &&
(empty($script['after']) || isset($added[$script['after']]))) {
$sorted[] = $script;
$added[$name] = true;
$found = true;
}
}
if (!$found) {
throw new RuntimeException('Обнаружена циклическая зависимость в скриптах');
}
}
return $sorted;
}
}

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

View File

@@ -9,12 +9,13 @@ class Flash
public static function setMessage(string $type, string $msg): void
{
Session::start();
self::start();
Session::set($type, $msg);
}
public static function getMessage(string $type): string
{
self::start();
$msg = Session::get($type, false);
Session::remove($type);
@@ -23,7 +24,16 @@ class Flash
public static function hasMessage(string $type): bool
{
self::start();
return Session::has($type);
}
public static function start()
{
if (!Session::isStarted()){
Session::start();
}
}
}

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

15
kernel/Theme.php Normal file
View File

@@ -0,0 +1,15 @@
<?php
namespace kernel;
class Theme
{
static AssetsCollector $assetsCollector;
public function __construct()
{
}
}

View File

@@ -5,8 +5,12 @@
* @var string $title
* @var \kernel\CgView $view
*/
use kernel\Theme;
\Josantonius\Session\Facades\Session::start();
$assets = new \kernel\admin_themes\default\DefaultAdminThemeAssets($resources)
//$assets = new \kernel\admin_themes\default\DefaultAdminThemeAssets($resources);
Theme::$assetsCollector->registerAsset(new \kernel\admin_themes\default\DefaultAdminThemeAssets($resources));
?>
<!doctype html>
<html lang="en">
@@ -18,8 +22,8 @@ $assets = new \kernel\admin_themes\default\DefaultAdminThemeAssets($resources)
<link href="https://fonts.googleapis.com/css?family=Poppins:300,400,500,600,700,800,900" rel="stylesheet">
<?php $assets->getCSSAsSTR(); ?>
<?php $assets->getJSAsStr(body: false); ?>
<?php Theme::$assetsCollector->renderCss(); ?>
<?php Theme::$assetsCollector->renderJs(body: false); ?>
</head>
<body>
@@ -92,6 +96,6 @@ $assets = new \kernel\admin_themes\default\DefaultAdminThemeAssets($resources)
</div>
</div>
<?php $assets->getJSAsStr(); ?>
<?php Theme::$assetsCollector->renderJs(); ?>
</body>
</html>

View File

@@ -0,0 +1,124 @@
<?php
namespace kernel\app_modules\tag;
use Illuminate\Database\Eloquent\Model;
use itguild\forms\builders\SelectBuilder;
use kernel\app_modules\tag\models\Tag;
use kernel\app_modules\tag\models\TagEntity;
use kernel\app_modules\tag\service\TagEntityService;
use kernel\Module;
use kernel\modules\menu\service\MenuService;
use kernel\modules\option\service\OptionService;
use kernel\Request;
use kernel\services\MigrationService;
class TagModule 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_APP_MODULES}/tag/migrations");
$this->menuService->createItem([
"label" => "Тэги",
"url" => "/admin/tag",
"slug" => "tag",
]);
$this->menuService->createItem([
"label" => "Тэги",
"url" => "/admin/settings/tag",
"slug" => "tag_settings",
"parent_slug" => "settings"
]);
OptionService::createFromParams("entity_tag_list", "{}", "Список тегов");
}
/**
* @throws \Exception
*/
public function deactivate(): void
{
$this->menuService->removeItemBySlug("tag");
$this->menuService->removeItemBySlug("tag_settings");
OptionService::removeOptionByKey("entity_tag_list");
$this->migrationService->rollbackAtPath("{KERNEL_APP_MODULES}/tag/migrations");
}
public function formInputs(string $entity, Model $model = null): void
{
if (isset($model->id)) {
$value = TagEntityService::getTagsByEntity($entity, $model->id);
}
$input = SelectBuilder::build("tag[]", [
'class' => 'form-control',
'placeholder' => 'Теги',
'value' => $value ?? '',
'multiple' => "multiple",
'options' => Tag::getTagLabelByEntity($entity)
]);
$input->setLabel("Теги");
$input->create()->render();
}
public function saveInputs(string $entity, Model $model, Request $request): void
{
TagEntity::where("entity", $entity)->where("entity_id", $model->id)->delete();
$tags = $request->post("tag");
if (is_array($tags)) {
foreach ($tags as $tag) {
$tagEntity = new TagEntity();
$tagEntity->entity = $entity;
$tagEntity->entity_id = $model->id;
$tagEntity->tag_id = $tag;
$tagEntity->save();
}
}
}
public function getItems(string $entity, Model $model): array|string
{
$tags = TagEntity::where("entity", $entity)->where("entity_id", $model->id)->with("tag")->get();
$tagsStr = "";
foreach ($tags as $tag) {
$tagsStr .= $tag->tag->label . ", ";
}
return substr($tagsStr, 0, -2);
}
public function getItem(string $entity, string $entity_id): string
{
$tags = TagEntity::where("entity", $entity)->where("entity_id", $entity_id)->get();
$tagsStr = "";
foreach ($tags as $tag) {
$tagsStr .= $tag->tag->label . ", ";
}
return substr($tagsStr, 0, -2);
}
public function deleteItems(string $entity, Model $model): void
{
TagEntity::where("entity", $entity)->where("entity_id", $model->id)->delete();
}
}

View File

@@ -0,0 +1,120 @@
<?php
namespace kernel\app_modules\tag\controllers;
use Exception;
use JetBrains\PhpStorm\NoReturn;
use kernel\AdminController;
use kernel\app_modules\tag\models\forms\CreateTagForm;
use kernel\app_modules\tag\models\Tag;
use kernel\app_modules\tag\service\TagService;
use kernel\EntityRelation;
use kernel\Flash;
use kernel\helpers\Debug;
use kernel\models\Option;
use kernel\modules\menu\service\MenuService;
use kernel\Request;
class TagController extends AdminController
{
private TagService $tagService;
protected function init(): void
{
parent::init();
$this->cgView->viewPath = KERNEL_APP_MODULES_DIR . "/tag/views/tag/";
$this->tagService = new TagService();
}
public function actionCreate(): void
{
$this->cgView->render("form.php");
}
#[NoReturn] public function actionAdd(): void
{
$tagForm = new CreateTagForm();
$tagForm->load($_REQUEST);
if ($tagForm->validate()){
$tag = $this->tagService->create($tagForm);
if ($tag){
$this->redirect("/admin/tag/view/" . $tag->id);
}
}
$this->redirect("/admin/tag/create");
}
public function actionIndex($page_number = 1): void
{
$this->cgView->render("index.php", ['page_number' => $page_number]);
}
/**
* @throws Exception
*/
public function actionView($id): void
{
$tag = Tag::find($id);
if (!$tag){
throw new Exception(message: "The tag not found");
}
$this->cgView->render("view.php", ['tag' => $tag]);
}
/**
* @throws Exception
*/
public function actionUpdate($id): void
{
$model = Tag::find($id);
if (!$model){
throw new Exception(message: "The tag not found");
}
$this->cgView->render("form.php", ['model' => $model]);
}
/**
* @throws Exception
*/
public function actionEdit($id): void
{
$tag = Tag::find($id);
if (!$tag){
throw new Exception(message: "The tag not found");
}
$tagForm = new CreateTagForm();
$tagService = new TagService();
$tagForm->load($_REQUEST);
if ($tagForm->validate()) {
$tag = $tagService->update($tagForm, $tag);
if ($tag) {
$this->redirect("/admin/tag/view/" . $tag->id);
}
}
$this->redirect("/admin/tag/update/" . $id);
}
#[NoReturn] public function actionDelete($id): void
{
$post = Tag::find($id)->first();
$post->delete();
$this->redirect("/admin/tag/");
}
public function actionSettings(): void
{
$this->cgView->render('settingsForm.php');
}
#[NoReturn] public function actionSaveSettings(): void
{
$request = new Request();
$entities = $request->post('entity');
EntityRelation::configurationEntitiesByProperty($entities, 'tag');
Flash::setMessage("success", "Настройка прошла успешно");
$this->redirect("/admin/settings/tag", 302);
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace kernel\app_modules\tag\controllers;
use Exception;
use JetBrains\PhpStorm\NoReturn;
use kernel\AdminController;
use kernel\app_modules\tag\models\forms\CreateTagForm;
use kernel\app_modules\tag\models\Tag;
use kernel\app_modules\tag\models\TagEntity;
use kernel\app_modules\tag\service\TagEntityService;
use kernel\app_modules\tag\service\TagService;
use kernel\helpers\Debug;
use kernel\modules\menu\service\MenuService;
class TagEntityController extends AdminController
{
/**
* @return void
*/
protected function init(): void
{
parent::init();
$this->cgView->viewPath = KERNEL_APP_MODULES_DIR . "/tag/views/tag_entity/";
}
/**
* @param $page_number
* @return void
*/
public function actionIndex($page_number = 1): void
{
$this->cgView->render("index.php", ['page_number' => $page_number]);
}
/**
* @throws Exception
*/
public function actionView($id): void
{
$tagEntity = TagEntity::find($id);
if (!$tagEntity){
throw new Exception(message: "The tag entity not found");
}
$this->cgView->render("view.php", ['tagEntity' => $tagEntity]);
}
}

View File

@@ -0,0 +1,33 @@
<?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('tag', function (Blueprint $table) {
$table->increments('id');
$table->string('label', 255)->nullable(false);
$table->string('entity', 255)->nullable(false);
$table->string('slug', 255)->unique();
$table->integer('status')->default(1);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
\kernel\App::$db->schema->dropIfExists('tag');
}
};

View File

@@ -0,0 +1,32 @@
<?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('tag_entity', function (Blueprint $table) {
$table->increments('id');
$table->integer('tag_id')->nullable(false);
$table->string('entity', 255)->nullable(false);
$table->integer('entity_id')->nullable(false);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
\kernel\App::$db->schema->dropIfExists('tag_entity');
}
};

View File

@@ -0,0 +1,61 @@
<?php
namespace kernel\app_modules\tag\models;
use Illuminate\Database\Eloquent\Model;
/**
* @property int $id
* @property string $label
* @property string $entity
* @property int $entity_id
* @property string $slug
* @property int $status
*/
class Tag extends Model
{
const DISABLE_STATUS = 0;
const ACTIVE_STATUS = 1;
protected $table = 'tag';
protected $fillable = ['label', 'slug', 'status'];
public static function labels(): array
{
return [
'label' => 'Заголовок',
'entity' => 'Сущность',
'slug' => 'Slug',
'status' => 'Статус',
];
}
/**
* @return string[]
*/
public static function getStatus(): array
{
return [
self::DISABLE_STATUS => "Не активный",
self::ACTIVE_STATUS => "Активный",
];
}
public static function getTagListByEntity(string $entity): array
{
return self::where("entity", $entity)->get()->toArray();
}
public static function getTagLabelByEntity(string $entity): array
{
$result = [];
$tags = self::getTagListByEntity($entity);
foreach ($tags as $tag){
$result[$tag['id']] = $tag['label'];
}
return $result;
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace kernel\app_modules\tag\models;
use Illuminate\Database\Eloquent\Model;
/**
* @property int $id
* @property int $tag_id
* @property string $entity
* @property int $entity_id
*/
class TagEntity extends Model
{
protected $table = 'tag_entity';
protected $fillable = ['tag_id', 'entity', 'entity_id'];
public static function labels(): array
{
return [
'tag_id' => 'тег',
'entity' => 'Сущность',
'entity_id' => 'Идентификатор сущности',
];
}
public function tag(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->belongsTo(Tag::class);
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace kernel\app_modules\tag\models\forms;
use kernel\FormModel;
class CreateTagEntityForm extends FormModel
{
public function rules(): array
{
return [
'tag_id' => 'required',
'entity' => '',
'entity_id' => '',
];
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace kernel\app_modules\tag\models\forms;
use kernel\FormModel;
class CreateTagForm extends FormModel
{
public function rules(): array
{
return [
'label' => 'required|min-str-len:5|max-str-len:30',
'entity' => 'required',
'slug' => '',
'status' => ''
];
}
}

View File

@@ -0,0 +1,34 @@
<?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" => "tag"], function (CGRouteCollector $router) {
App::$collector->get('/', [\app\modules\tag\controllers\TagController::class, 'actionIndex']);
App::$collector->get('/page/{page_number}', [\app\modules\tag\controllers\TagController::class, 'actionIndex']);
App::$collector->get('/create', [\app\modules\tag\controllers\TagController::class, 'actionCreate']);
App::$collector->post("/", [\app\modules\tag\controllers\TagController::class, 'actionAdd']);
App::$collector->get('/view/{id}', [\app\modules\tag\controllers\TagController::class, 'actionView']);
App::$collector->any('/update/{id}', [\app\modules\tag\controllers\TagController::class, 'actionUpdate']);
App::$collector->any("/edit/{id}", [\app\modules\tag\controllers\TagController::class, 'actionEdit']);
App::$collector->get('/delete/{id}', [\app\modules\tag\controllers\TagController::class, 'actionDelete']);
});
App::$collector->group(["prefix" => "tag_entity"], function (CGRouteCollector $router) {
App::$collector->get('/', [\kernel\app_modules\tag\controllers\TagEntityController::class, 'actionIndex']);
App::$collector->get('/page/{page_number}', [\kernel\app_modules\tag\controllers\TagEntityController::class, 'actionIndex']);
App::$collector->get('/create', [\kernel\app_modules\tag\controllers\TagEntityController::class, 'actionCreate']);
App::$collector->post("/", [\kernel\app_modules\tag\controllers\TagEntityController::class, 'actionAdd']);
App::$collector->get('view/{id}', [\kernel\app_modules\tag\controllers\TagEntityController::class, 'actionView']);
App::$collector->any('/update/{id}', [\kernel\app_modules\tag\controllers\TagEntityController::class, 'actionUpdate']);
App::$collector->any("/edit/{id}", [\kernel\app_modules\tag\controllers\TagEntityController::class, 'actionEdit']);
App::$collector->get('/delete/{id}', [\kernel\app_modules\tag\controllers\TagEntityController::class, 'actionDelete']);
});
App::$collector->group(["prefix" => "settings"], function (CGRouteCollector $router) {
App::$collector->get('/tag', [\app\modules\tag\controllers\TagController::class, 'actionSettings']);
App::$collector->post('/tag/update', [\app\modules\tag\controllers\TagController::class, 'actionSaveSettings']);
});
});
});

View File

@@ -0,0 +1,51 @@
<?php
namespace kernel\app_modules\tag\service;
use kernel\app_modules\tag\models\Tag;
use kernel\app_modules\tag\models\TagEntity;
use kernel\FormModel;
use kernel\helpers\Debug;
use kernel\helpers\Slug;
class TagEntityService
{
public function create(FormModel $form_model): false|TagEntity
{
$model = new TagEntity();
$model->tag_id = $form_model->getItem('tag_id');
$model->entity = $form_model->getItem('entity');
$model->entity_id = $form_model->getItem('entity_id');
if ($model->save()){
return $model;
}
return false;
}
public function update(FormModel $form_model, TagEntity $tag): false|TagEntity
{
$tag->tag_id = $form_model->getItem('tag_id');
$tag->entity = $form_model->getItem('entity');
$tag->entity_id = $form_model->getItem('entity_id');
if ($tag->save()){
return $tag;
}
return false;
}
public static function getTagsByEntity(string $entity, int $entity_id): array
{
$tags= TagEntity::where("entity_id", $entity_id)->where("entity", $entity)->get();
$value = [];
foreach ($tags as $tag) {
$value[$tag->id] = $tag->tag->label;
}
return $value;
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace kernel\app_modules\tag\service;
use kernel\app_modules\tag\models\Tag;
use kernel\FormModel;
use kernel\helpers\Debug;
use kernel\helpers\Slug;
use kernel\services\ModuleService;
class TagService
{
public function create(FormModel $form_model): false|Tag
{
$model = new Tag();
$model->label = $form_model->getItem('label');
$model->entity = $form_model->getItem('entity');
$model->status = $form_model->getItem('status');
$model->slug = Slug::createSlug($form_model->getItem('label'), Tag::class);
if ($model->save()){
return $model;
}
return false;
}
public function update(FormModel $form_model, Tag $tag): false|Tag
{
if ($tag->label !== $form_model->getItem('label')) {
$tag->slug = Slug::createSlug($form_model->getItem('label'), Tag::class);
}
$tag->label = $form_model->getItem('label');
$tag->entity = $form_model->getItem('entity');
$tag->status = $form_model->getItem('status');
if ($tag->save()){
return $tag;
}
return false;
}
}

View File

@@ -0,0 +1,58 @@
<?php
/**
* @var Tag $model
*/
use kernel\app_modules\tag\models\Tag;
$form = new \itguild\forms\ActiveForm();
$form->beginForm(isset($model) ? "/admin/tag/edit/" . $model->id : "/admin/tag");
$form->field(class: \itguild\forms\inputs\TextInput::class, name: "label", params: [
'class' => "form-control",
'placeholder' => 'Заголовок',
'value' => $model->label ?? ''
])
->setLabel("Заголовок")
->render();
$form->field(class: \itguild\forms\inputs\Select::class, name: "entity", params: [
'class' => "form-control",
'value' => $model->entity ?? ''
])
->setLabel("Сущность")
->setOptions(\kernel\EntityRelation::getEntityList())
->render();
$form->field(\itguild\forms\inputs\Select::class, 'status', [
'class' => "form-control",
'value' => $model->status ?? ''
])
->setLabel("Статус")
->setOptions(Tag::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,40 @@
<?php
/**
* @var int $page_number
*/
use Itguild\EloquentTable\EloquentDataProvider;
use Itguild\EloquentTable\ListEloquentTable;
use kernel\app_modules\tag\models\Tag;
use kernel\IGTabel\btn\PrimaryBtn;
use kernel\models\Menu;
use kernel\modules\menu\table\columns\MenuDeleteActionColumn;
use kernel\modules\menu\table\columns\MenuEditActionColumn;
use kernel\modules\menu\table\columns\MenuViewActionColumn;
$table = new ListEloquentTable(new EloquentDataProvider(Tag::class, [
'currentPage' => $page_number,
'perPage' => 8,
'params' => ["class" => "table table-bordered", "border" => "2"],
'baseUrl' => "/admin/tag",
]));
$table->beforePrint(function () {
return PrimaryBtn::create("Создать", "/admin/tag/create")->fetch();
});
$table->columns([
"status" => [
"value" => function ($cell) {
return Tag::getStatus()[$cell];
}]
]);
\kernel\widgets\TagTabsWidget::create()->run();
$table->addAction(\kernel\IGTabel\action_column\ViewActionColumn::class);
$table->addAction(\kernel\IGTabel\action_column\DeleteActionColumn::class);
$table->addAction(\kernel\IGTabel\action_column\EditActionColumn::class);
$table->create();
$table->render();

View File

@@ -0,0 +1,47 @@
<?php
$form = new \itguild\forms\ActiveForm();
$form->beginForm("/admin/settings/tag/update");
//\kernel\helpers\Debug::dd($value);
?>
<div class="row">
<h5>Выберите сущности, к которым хотите прикрепить теги</h5>
</div>
<?php
$form->field(\itguild\forms\inputs\Select::class, "entity[]", [
'class' => "form-control",
'value' => \kernel\EntityRelation::getEntityByProperty('tag') ?? '',
'multiple' => "multiple",
])
->setLabel("Сущности")
->setOptions(\kernel\EntityRelation::getEntityList())
->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,30 @@
<?php
/**
* @var \Illuminate\Database\Eloquent\Collection $tag
*/
use kernel\modules\user\models\User;
use Itguild\EloquentTable\ViewEloquentTable;
use Itguild\EloquentTable\ViewJsonTableEloquentModel;
use kernel\IGTabel\btn\DangerBtn;
use kernel\IGTabel\btn\PrimaryBtn;
use kernel\IGTabel\btn\SuccessBtn;
$table = new ViewEloquentTable(new ViewJsonTableEloquentModel($tag, [
'params' => ["class" => "table table-bordered", "border" => "2"],
'baseUrl' => "/admin/tag",
]));
$table->beforePrint(function () use ($tag) {
$btn = PrimaryBtn::create("Список", "/admin/tag")->fetch();
$btn .= SuccessBtn::create("Редактировать", "/admin/tag/update/" . $tag->id)->fetch();
$btn .= DangerBtn::create("Удалить", "/admin/tag/delete/" . $tag->id)->fetch();
return $btn;
});
$table->rows([
'status' => (function ($data) {
return \kernel\app_modules\tag\models\Tag::getStatus()[$data];
})
]);
$table->create();
$table->render();

View File

@@ -0,0 +1,34 @@
<?php
/**
* @var int $page_number
*/
use Itguild\EloquentTable\EloquentDataProvider;
use Itguild\EloquentTable\ListEloquentTable;
use kernel\app_modules\tag\models\Tag;
use kernel\IGTabel\btn\PrimaryBtn;
$table = new ListEloquentTable(new EloquentDataProvider(\kernel\app_modules\tag\models\TagEntity::class, [
'currentPage' => $page_number,
'perPage' => 8,
'params' => ["class" => "table table-bordered", "border" => "2"],
'baseUrl' => "/admin/tag_entity",
]));
$table->beforePrint(function () {
return PrimaryBtn::create("Создать", "/admin/tag_entity/create")->fetch();
});
$table->columns([
"tag_id" => [
"value" => function ($data) {
return Tag::find($data)->label;
}]
]);
$table->addAction(\kernel\IGTabel\action_column\ViewActionColumn::class);
$table->addAction(\kernel\IGTabel\action_column\DeleteActionColumn::class);
$table->addAction(\kernel\IGTabel\action_column\EditActionColumn::class);
\kernel\widgets\TagTabsWidget::create()->run();
$table->create();
$table->render();

View File

@@ -0,0 +1,30 @@
<?php
/**
* @var \Illuminate\Database\Eloquent\Collection $tagEntity
*/
use kernel\modules\user\models\User;
use Itguild\EloquentTable\ViewEloquentTable;
use Itguild\EloquentTable\ViewJsonTableEloquentModel;
use kernel\IGTabel\btn\DangerBtn;
use kernel\IGTabel\btn\PrimaryBtn;
use kernel\IGTabel\btn\SuccessBtn;
$table = new ViewEloquentTable(new ViewJsonTableEloquentModel($tagEntity, [
'params' => ["class" => "table table-bordered", "border" => "2"],
'baseUrl' => "/admin/tag",
]));
$table->beforePrint(function () use ($tagEntity) {
$btn = PrimaryBtn::create("Список", "/admin/tag_entity")->fetch();
$btn .= SuccessBtn::create("Редактировать", "/admin/tag_entity/update/" . $tagEntity->id)->fetch();
$btn .= DangerBtn::create("Удалить", "/admin/tag_entity/delete/" . $tagEntity->id)->fetch();
return $btn;
});
$table->rows([
'tag_id' => (function ($data) {
return \kernel\app_modules\tag\models\Tag::find($data)->label;
})
]);
$table->create();
$table->render();

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

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