Compare commits
9 Commits
fc70051761
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 2b02ca4471 | |||
| b86b8ff923 | |||
| 2ab819ff30 | |||
| a64ed080bb | |||
| 6242304843 | |||
| 2655a793f5 | |||
| de0354f9cb | |||
| 4a4d5b083f | |||
| 68b5741f46 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,3 +5,4 @@ views_cache
|
|||||||
resources/upload
|
resources/upload
|
||||||
resources/tmp
|
resources/tmp
|
||||||
composer.lock
|
composer.lock
|
||||||
|
kernel/app_themes
|
||||||
8
app/themes/custom/CustomTheme.php
Normal file
8
app/themes/custom/CustomTheme.php
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace app\themes\custom;
|
||||||
|
|
||||||
|
class CustomTheme extends \kernel\app_themes\custom\CustomTheme
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
18
app/themes/custom/assets/CustomThemesAssets.php
Normal file
18
app/themes/custom/assets/CustomThemesAssets.php
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace app\themes\custom\assets;
|
||||||
|
|
||||||
|
use kernel\Assets;
|
||||||
|
|
||||||
|
class CustomThemesAssets extends Assets
|
||||||
|
{
|
||||||
|
protected function createCSS(): void
|
||||||
|
{
|
||||||
|
$this->registerCSS(slug: "main", resource: "/css/styles.css");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function createJS(): void
|
||||||
|
{
|
||||||
|
$this->registerJS(slug: "webpack", resource: "/js/scripts.js");
|
||||||
|
}
|
||||||
|
}
|
||||||
28
app/themes/custom/controllers/MainController.php
Normal file
28
app/themes/custom/controllers/MainController.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace app\themes\custom\controllers;
|
||||||
|
|
||||||
|
use kernel\Controller;
|
||||||
|
|
||||||
|
class MainController extends Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
protected function init(): void
|
||||||
|
{
|
||||||
|
parent::init();
|
||||||
|
$this->cgView->viewPath = APP_DIR . "/themes/custom/views/main/";
|
||||||
|
$this->cgView->layout = "main.php";
|
||||||
|
$this->cgView->layoutPath = APP_DIR . "/themes/custom/views/layout/";
|
||||||
|
$this->cgView->addVarToLayout("resources", "/resources/themes/custom");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function actionIndex(): void
|
||||||
|
{
|
||||||
|
$this->cgView->render("index.php");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function actionAbout(): void
|
||||||
|
{
|
||||||
|
$this->cgView->render("about.php");
|
||||||
|
}
|
||||||
|
}
|
||||||
15
app/themes/custom/manifest.json
Normal file
15
app/themes/custom/manifest.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "Custom",
|
||||||
|
"version": "0.1",
|
||||||
|
"author": "ItGuild",
|
||||||
|
"slug": "custom",
|
||||||
|
"type": "theme",
|
||||||
|
"description": "Custom theme",
|
||||||
|
"preview": "preview.png",
|
||||||
|
"resource": "/resources/themes/custom",
|
||||||
|
"resource_path": "{RESOURCES}/themes/custom",
|
||||||
|
"theme_class": "app\\themes\\custom\\CustomTheme",
|
||||||
|
"theme_class_file": "{APP}/themes/custom/CustomTheme.php",
|
||||||
|
"routs": "routs/custom.php",
|
||||||
|
"dependence": "photo,tag"
|
||||||
|
}
|
||||||
12
app/themes/custom/routs/custom.php
Normal file
12
app/themes/custom/routs/custom.php
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use kernel\App;
|
||||||
|
|
||||||
|
|
||||||
|
App::$collector->get('/', [\app\themes\custom\controllers\MainController::class, 'actionIndex']);
|
||||||
|
App::$collector->get('/about', [\app\themes\custom\controllers\MainController::class, 'actionAbout']);
|
||||||
|
//App::$collector->get('/page/{page_number}', [\app\modules\tag\controllers\TagController::class, 'actionIndex']);
|
||||||
|
//App::$collector->get('/create', [\app\modules\tag\controllers\TagController::class, 'actionCreate']);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
92
app/themes/custom/views/layout/main.php
Normal file
92
app/themes/custom/views/layout/main.php
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
<?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>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
|
||||||
|
<?php $assets->getCSSAsSTR(); ?>
|
||||||
|
<meta name="description" content=""/>
|
||||||
|
<meta name="author" content=""/>
|
||||||
|
<title><?= $title ?></title>
|
||||||
|
<?= $view->getMeta() ?>
|
||||||
|
<link rel="icon" type="image/x-icon" href="<?= $resources ?>/assets/favicon.ico"/>
|
||||||
|
<!-- Font Awesome icons (free version)-->
|
||||||
|
<script src="https://use.fontawesome.com/releases/v6.3.0/js/all.js" crossorigin="anonymous"></script>
|
||||||
|
<!-- Google fonts-->
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic" rel="stylesheet"
|
||||||
|
type="text/css"/>
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800"
|
||||||
|
rel="stylesheet" type="text/css"/>
|
||||||
|
<!-- Core theme CSS (includes Bootstrap)-->
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- Navigation-->
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-light" id="mainNav">
|
||||||
|
<div class="container px-4 px-lg-5">
|
||||||
|
<a class="navbar-brand" href="/">Custom theme</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarResponsive"
|
||||||
|
aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
Menu
|
||||||
|
<i class="fas fa-bars"></i>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||||
|
<ul class="navbar-nav ms-auto py-4 py-lg-0">
|
||||||
|
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="/">На главную</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="/about">О нас</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<?= $content ?>
|
||||||
|
|
||||||
|
<!-- Footer-->
|
||||||
|
<footer class="border-top">
|
||||||
|
<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">
|
||||||
|
<ul class="list-inline text-center">
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<a href="#!">
|
||||||
|
<span class="fa-stack fa-lg">
|
||||||
|
<i class="fas fa-circle fa-stack-2x"></i>
|
||||||
|
<i class="fab fa-twitter fa-stack-1x fa-inverse"></i>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<a href="#!">
|
||||||
|
<span class="fa-stack fa-lg">
|
||||||
|
<i class="fas fa-circle fa-stack-2x"></i>
|
||||||
|
<i class="fab fa-facebook-f fa-stack-1x fa-inverse"></i>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<a href="#!">
|
||||||
|
<span class="fa-stack fa-lg">
|
||||||
|
<i class="fas fa-circle fa-stack-2x"></i>
|
||||||
|
<i class="fab fa-github fa-stack-1x fa-inverse"></i>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="small text-center text-muted fst-italic">Copyright © IT Guild Micro Framework</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
<!-- Bootstrap core JS-->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<!-- Core theme JS-->
|
||||||
|
<?php $assets->getJSAsStr(); ?>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
36
app/themes/custom/views/main/about.php
Normal file
36
app/themes/custom/views/main/about.php
Normal 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>
|
||||||
86
app/themes/custom/views/main/index.php
Normal file
86
app/themes/custom/views/main/index.php
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
<?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 it’s 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>
|
||||||
@@ -7,6 +7,7 @@ $dotenv->load();
|
|||||||
include_once __DIR__ . "/bootstrap/db.php";
|
include_once __DIR__ . "/bootstrap/db.php";
|
||||||
include_once __DIR__ . "/bootstrap/header.php";
|
include_once __DIR__ . "/bootstrap/header.php";
|
||||||
include_once __DIR__ . "/bootstrap/secure.php";
|
include_once __DIR__ . "/bootstrap/secure.php";
|
||||||
|
include_once __DIR__ . "/bootstrap/notification.php";
|
||||||
const ROOT_DIR = __DIR__;
|
const ROOT_DIR = __DIR__;
|
||||||
const KERNEL_DIR = __DIR__ . "/kernel";
|
const KERNEL_DIR = __DIR__ . "/kernel";
|
||||||
const KERNEL_MODULES_DIR = __DIR__ . "/kernel/modules";
|
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";
|
const APP_DIR = ROOT_DIR . "/app";
|
||||||
|
|
||||||
|
\kernel\Theme::$assetsCollector = new \kernel\AssetsCollector();
|
||||||
|
|
||||||
function getConst($text): array|false|string
|
function getConst($text): array|false|string
|
||||||
{
|
{
|
||||||
|
|||||||
4
bootstrap/notification.php
Normal file
4
bootstrap/notification.php
Normal 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());
|
||||||
@@ -5,6 +5,7 @@ namespace kernel;
|
|||||||
|
|
||||||
|
|
||||||
use kernel\helpers\Debug;
|
use kernel\helpers\Debug;
|
||||||
|
use kernel\modules\notification\NotificationDispatcher;
|
||||||
use kernel\modules\user\models\User;
|
use kernel\modules\user\models\User;
|
||||||
use kernel\services\ModuleService;
|
use kernel\services\ModuleService;
|
||||||
use kernel\services\ThemeService;
|
use kernel\services\ThemeService;
|
||||||
@@ -21,10 +22,14 @@ class App
|
|||||||
|
|
||||||
static User $user;
|
static User $user;
|
||||||
|
|
||||||
|
static NotificationDispatcher $notificationDispatcher;
|
||||||
|
|
||||||
static array $secure;
|
static array $secure;
|
||||||
|
|
||||||
public ModuleService $moduleService;
|
public ModuleService $moduleService;
|
||||||
|
|
||||||
|
static Hook $hook;
|
||||||
|
|
||||||
public ThemeService $themeService;
|
public ThemeService $themeService;
|
||||||
|
|
||||||
public static Database $db;
|
public static Database $db;
|
||||||
@@ -41,6 +46,7 @@ class App
|
|||||||
|
|
||||||
public function load(): static
|
public function load(): static
|
||||||
{
|
{
|
||||||
|
App::$hook = new Hook();
|
||||||
$this->moduleService = new ModuleService();
|
$this->moduleService = new ModuleService();
|
||||||
App::$collector = new CgRouteCollector();
|
App::$collector = new CgRouteCollector();
|
||||||
$this->setRouting();
|
$this->setRouting();
|
||||||
@@ -53,6 +59,7 @@ class App
|
|||||||
include KERNEL_DIR . "/routs/admin.php";
|
include KERNEL_DIR . "/routs/admin.php";
|
||||||
include ROOT_DIR . "/rout.php";
|
include ROOT_DIR . "/rout.php";
|
||||||
$modules_routs = $this->moduleService->getModulesRouts();
|
$modules_routs = $this->moduleService->getModulesRouts();
|
||||||
|
$this->moduleService->setModulesHooks();
|
||||||
foreach ($modules_routs as $rout){
|
foreach ($modules_routs as $rout){
|
||||||
include "$rout";
|
include "$rout";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,12 @@ class Assets
|
|||||||
protected array $jsHeader = [];
|
protected array $jsHeader = [];
|
||||||
protected array $jsBody = [];
|
protected array $jsBody = [];
|
||||||
|
|
||||||
|
protected array $collectorJs = [];
|
||||||
|
|
||||||
protected array $css = [];
|
protected array $css = [];
|
||||||
|
|
||||||
|
protected array $collectorCss = [];
|
||||||
|
|
||||||
protected string $resourceURI = "/resource";
|
protected string $resourceURI = "/resource";
|
||||||
|
|
||||||
public function __construct(string $resourceURI)
|
public function __construct(string $resourceURI)
|
||||||
@@ -26,7 +30,7 @@ class Assets
|
|||||||
$this->resourceURI = $resourceURI;
|
$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;
|
$resource = $addResourceURI ? $this->resourceURI . $resource : $resource;
|
||||||
if ($body) {
|
if ($body) {
|
||||||
@@ -34,12 +38,14 @@ class Assets
|
|||||||
} else {
|
} else {
|
||||||
$this->jsHeader[$slug] = $resource;
|
$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;
|
$resource = $addResourceURI ? $this->resourceURI . $resource : $resource;
|
||||||
$this->css[$slug] = $resource;
|
$this->css[$slug] = $resource;
|
||||||
|
$this->collectorCss[$slug] = ['resource' => $resource, 'after' => $after];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getJSAsStr(bool $body = true): void
|
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
128
kernel/AssetsCollector.php
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
345
kernel/CollectionTableRenderer.php
Normal file
345
kernel/CollectionTableRenderer.php
Normal 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 .= ' ';
|
||||||
|
}
|
||||||
|
$html .= '</td>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Кастомные колонки
|
||||||
|
foreach (array_keys($this->customColumns) as $columnName) {
|
||||||
|
$html .= '<td>';
|
||||||
|
if (isset($this->filters[$columnName])) {
|
||||||
|
$html .= $this->filters[$columnName];
|
||||||
|
} else {
|
||||||
|
$html .= ' ';
|
||||||
|
}
|
||||||
|
$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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,12 +9,13 @@ class Flash
|
|||||||
|
|
||||||
public static function setMessage(string $type, string $msg): void
|
public static function setMessage(string $type, string $msg): void
|
||||||
{
|
{
|
||||||
Session::start();
|
self::start();
|
||||||
Session::set($type, $msg);
|
Session::set($type, $msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getMessage(string $type): string
|
public static function getMessage(string $type): string
|
||||||
{
|
{
|
||||||
|
self::start();
|
||||||
$msg = Session::get($type, false);
|
$msg = Session::get($type, false);
|
||||||
Session::remove($type);
|
Session::remove($type);
|
||||||
|
|
||||||
@@ -23,7 +24,16 @@ class Flash
|
|||||||
|
|
||||||
public static function hasMessage(string $type): bool
|
public static function hasMessage(string $type): bool
|
||||||
{
|
{
|
||||||
|
self::start();
|
||||||
|
|
||||||
return Session::has($type);
|
return Session::has($type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function start()
|
||||||
|
{
|
||||||
|
if (!Session::isStarted()){
|
||||||
|
Session::start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
41
kernel/Hook.php
Normal file
41
kernel/Hook.php
Normal 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
15
kernel/Theme.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace kernel;
|
||||||
|
|
||||||
|
class Theme
|
||||||
|
{
|
||||||
|
|
||||||
|
static AssetsCollector $assetsCollector;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -5,8 +5,12 @@
|
|||||||
* @var string $title
|
* @var string $title
|
||||||
* @var \kernel\CgView $view
|
* @var \kernel\CgView $view
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use kernel\Theme;
|
||||||
|
|
||||||
\Josantonius\Session\Facades\Session::start();
|
\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>
|
<!doctype html>
|
||||||
<html lang="en">
|
<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">
|
<link href="https://fonts.googleapis.com/css?family=Poppins:300,400,500,600,700,800,900" rel="stylesheet">
|
||||||
|
|
||||||
<?php $assets->getCSSAsSTR(); ?>
|
<?php Theme::$assetsCollector->renderCss(); ?>
|
||||||
<?php $assets->getJSAsStr(body: false); ?>
|
<?php Theme::$assetsCollector->renderJs(body: false); ?>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
@@ -92,6 +96,6 @@ $assets = new \kernel\admin_themes\default\DefaultAdminThemeAssets($resources)
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php $assets->getJSAsStr(); ?>
|
<?php Theme::$assetsCollector->renderJs(); ?>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
31
kernel/app_themes/custom/CustomTheme.php
Normal file
31
kernel/app_themes/custom/CustomTheme.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace kernel\app_themes\custom;
|
||||||
|
|
||||||
|
use kernel\modules\menu\service\MenuService;
|
||||||
|
use kernel\services\MigrationService;
|
||||||
|
|
||||||
|
class CustomTheme
|
||||||
|
{
|
||||||
|
public MenuService $menuService;
|
||||||
|
public MigrationService $migrationService;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->menuService = new MenuService();
|
||||||
|
$this->migrationService = new MigrationService();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function init(): void
|
||||||
|
{
|
||||||
|
//TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deactivate(): void
|
||||||
|
{
|
||||||
|
//TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
8
kernel/app_themes/custom/services/CustomThemeService.php
Normal file
8
kernel/app_themes/custom/services/CustomThemeService.php
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace kernel\app_themes\custom\services;
|
||||||
|
|
||||||
|
class CustomThemeService
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -49,6 +49,9 @@ class AdminConsoleController extends ConsoleController
|
|||||||
$out = $this->migrationService->runAtPath("kernel/modules/secure/migrations");
|
$out = $this->migrationService->runAtPath("kernel/modules/secure/migrations");
|
||||||
$this->out->r("create secret_code table", "green");
|
$this->out->r("create secret_code table", "green");
|
||||||
|
|
||||||
|
$out = $this->migrationService->runAtPath("kernel/modules/notification/migrations");
|
||||||
|
$this->out->r("create notification table", "green");
|
||||||
|
|
||||||
$this->optionService->createFromParams(
|
$this->optionService->createFromParams(
|
||||||
key: "admin_theme_paths",
|
key: "admin_theme_paths",
|
||||||
value: "{\"paths\": [\"{KERNEL_ADMIN_THEMES}\", \"{APP}/admin_themes\"]}",
|
value: "{\"paths\": [\"{KERNEL_ADMIN_THEMES}\", \"{APP}/admin_themes\"]}",
|
||||||
@@ -170,6 +173,13 @@ class AdminConsoleController extends ConsoleController
|
|||||||
]);
|
]);
|
||||||
$this->out->r("create item menu option", "green");
|
$this->out->r("create item menu option", "green");
|
||||||
|
|
||||||
|
$this->menuService->createItem([
|
||||||
|
"label" => "Уведомления",
|
||||||
|
"url" => "/admin/notification",
|
||||||
|
"slug" => "notification"
|
||||||
|
]);
|
||||||
|
$this->out->r("create notification option", "green");
|
||||||
|
|
||||||
$user = new CreateUserForm();
|
$user = new CreateUserForm();
|
||||||
$user->load([
|
$user->load([
|
||||||
'username' => 'admin',
|
'username' => 'admin',
|
||||||
|
|||||||
@@ -71,10 +71,15 @@ class MigrationController extends ConsoleController
|
|||||||
$dmr = new DatabaseMigrationRepository(App::$db->capsule->getDatabaseManager(), 'migration');
|
$dmr = new DatabaseMigrationRepository(App::$db->capsule->getDatabaseManager(), 'migration');
|
||||||
|
|
||||||
$m = new Migrator($dmr, App::$db->capsule->getDatabaseManager(), new Filesystem());
|
$m = new Migrator($dmr, App::$db->capsule->getDatabaseManager(), new Filesystem());
|
||||||
if (\kernel\App::$db->schema->hasTable('option')) {
|
if (isset($this->argv['path'])){
|
||||||
$migrationPaths = array_merge($this->moduleService->getModulesMigrationsPaths(), [ROOT_DIR . '/migrations']);
|
$migrationPaths = [ROOT_DIR . $this->argv['path']];
|
||||||
} else {
|
}
|
||||||
$migrationPaths = [ROOT_DIR . '/migrations'];
|
else {
|
||||||
|
if (\kernel\App::$db->schema->hasTable('option')) {
|
||||||
|
$migrationPaths = array_merge($this->moduleService->getModulesMigrationsPaths(), [ROOT_DIR . '/migrations']);
|
||||||
|
} else {
|
||||||
|
$migrationPaths = [ROOT_DIR . '/migrations'];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$res = $m->run($migrationPaths);
|
$res = $m->run($migrationPaths);
|
||||||
@@ -94,7 +99,13 @@ class MigrationController extends ConsoleController
|
|||||||
|
|
||||||
$m = new Migrator($dmr, App::$db->capsule->getDatabaseManager(), new Filesystem());
|
$m = new Migrator($dmr, App::$db->capsule->getDatabaseManager(), new Filesystem());
|
||||||
//$migrationPaths = array_merge(App::$migrationsPaths, [WORKSPACE_DIR . '/console/migrations']);
|
//$migrationPaths = array_merge(App::$migrationsPaths, [WORKSPACE_DIR . '/console/migrations']);
|
||||||
$migrationPaths = [ROOT_DIR . '/migrations'];
|
if (isset($this->argv['path'])){
|
||||||
|
$migrationPaths = [ROOT_DIR . $this->argv['path']];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$migrationPaths = [ROOT_DIR . '/migrations'];
|
||||||
|
}
|
||||||
|
|
||||||
$res = $m->rollback($migrationPaths, ['step' => $step]);
|
$res = $m->rollback($migrationPaths, ['step' => $step]);
|
||||||
print_r($step);
|
print_r($step);
|
||||||
foreach ($res as $re) {
|
foreach ($res as $re) {
|
||||||
|
|||||||
@@ -23,6 +23,12 @@ class Html
|
|||||||
return "<a href='$link' $paramsStr>";
|
return "<a href='$link' $paramsStr>";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function link(string $title, string $link, array $params = []): string
|
||||||
|
{
|
||||||
|
$paramsStr = self::createParams($params);
|
||||||
|
return "<a href='$link' $paramsStr>$title</a>";
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array $data
|
* @param array $data
|
||||||
* @return string
|
* @return string
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class SMTP
|
|||||||
/**
|
/**
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public function send_html(array $params)
|
public function send_html(array $params): bool
|
||||||
{
|
{
|
||||||
if (!isset($params['address'])){
|
if (!isset($params['address'])){
|
||||||
return false;
|
return false;
|
||||||
@@ -35,6 +35,6 @@ class SMTP
|
|||||||
$body = $params['body'] ?? 'Нет информации';
|
$body = $params['body'] ?? 'Нет информации';
|
||||||
$this->mail->msgHTML($body);
|
$this->mail->msgHTML($body);
|
||||||
|
|
||||||
$this->mail->send();
|
return $this->mail->send();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,4 +13,9 @@ class Version
|
|||||||
3 => intval($version),
|
3 => intval($version),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function compare($current, $version): bool|int
|
||||||
|
{
|
||||||
|
return version_compare($current, $version);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "Kernel",
|
"name": "Kernel",
|
||||||
"version": "0.1.5",
|
"version": "0.1.11",
|
||||||
"author": "ITGuild",
|
"author": "ITGuild",
|
||||||
"slug": "kernel",
|
"slug": "kernel",
|
||||||
"type": "kernel",
|
"type": "kernel",
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ class ModuleShopClientController extends AdminController
|
|||||||
Files::uploadByUrl($_ENV['MODULE_SHOP_URL'] . $module_info['path_to_archive'], RESOURCES_DIR . "/tmp/modules");
|
Files::uploadByUrl($_ENV['MODULE_SHOP_URL'] . $module_info['path_to_archive'], RESOURCES_DIR . "/tmp/modules");
|
||||||
$this->moduleService->installModule('/resources/tmp/modules/' . basename($module_info['path_to_archive']));
|
$this->moduleService->installModule('/resources/tmp/modules/' . basename($module_info['path_to_archive']));
|
||||||
|
|
||||||
Flash::setMessage("success", "Модуль успешно установлен.");
|
Flash::setMessage("success", "Модуль успешно загружен.");
|
||||||
$this->redirect('/admin/module_shop_client', 302);
|
$this->redirect('/admin/module_shop_client', 302);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,10 +254,9 @@ class ModuleShopClientController extends AdminController
|
|||||||
$adminThemeInfo = json_decode($adminThemeInfo->getBody()->getContents(), true);
|
$adminThemeInfo = json_decode($adminThemeInfo->getBody()->getContents(), true);
|
||||||
Files::uploadByUrl($_ENV['MODULE_SHOP_URL'] . $adminThemeInfo['path_to_archive'], RESOURCES_DIR . "/tmp/admin_themes");
|
Files::uploadByUrl($_ENV['MODULE_SHOP_URL'] . $adminThemeInfo['path_to_archive'], RESOURCES_DIR . "/tmp/admin_themes");
|
||||||
if ($this->adminThemeService->install('/resources/tmp/admin_themes/' . basename($adminThemeInfo['path_to_archive']))) {
|
if ($this->adminThemeService->install('/resources/tmp/admin_themes/' . basename($adminThemeInfo['path_to_archive']))) {
|
||||||
Flash::setMessage("success", "Тема админ-панели успешно установлена.");
|
Flash::setMessage("success", "Тема админ-панели успешно загружена.");
|
||||||
} else {
|
} else {
|
||||||
Session::start();
|
Flash::setMessage("error", implode(";", $this->adminThemeService->getErrors()));
|
||||||
Session::set("error", implode(";", $this->adminThemeService->getErrors()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->redirect('/admin/module_shop_client', 302);
|
$this->redirect('/admin/module_shop_client', 302);
|
||||||
@@ -306,7 +305,7 @@ class ModuleShopClientController extends AdminController
|
|||||||
$themeInfo = json_decode($themeInfo->getBody()->getContents(), true);
|
$themeInfo = json_decode($themeInfo->getBody()->getContents(), true);
|
||||||
Files::uploadByUrl($_ENV['MODULE_SHOP_URL'] . $themeInfo['path_to_archive'], RESOURCES_DIR . "/tmp/themes");
|
Files::uploadByUrl($_ENV['MODULE_SHOP_URL'] . $themeInfo['path_to_archive'], RESOURCES_DIR . "/tmp/themes");
|
||||||
if ($this->themeService->install('/resources/tmp/themes/' . basename($themeInfo['path_to_archive']))) {
|
if ($this->themeService->install('/resources/tmp/themes/' . basename($themeInfo['path_to_archive']))) {
|
||||||
Flash::setMessage("success", "Тема сайта успешно установлена.");
|
Flash::setMessage("success", "Тема сайта успешно загружена.");
|
||||||
} else {
|
} else {
|
||||||
Session::start();
|
Session::start();
|
||||||
Session::set("error", implode(";", $this->themeService->getErrors()));
|
Session::set("error", implode(";", $this->themeService->getErrors()));
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ $table_info = [
|
|||||||
"version" => "Версия",
|
"version" => "Версия",
|
||||||
"description" => "Описание",
|
"description" => "Описание",
|
||||||
"installations" => "Установки",
|
"installations" => "Установки",
|
||||||
"views" => "Просмотры"
|
"views" => "Просмотры",
|
||||||
|
"dependence" => "Зависимости"
|
||||||
],
|
],
|
||||||
"params" => ["class" => "table table-bordered"],
|
"params" => ["class" => "table table-bordered"],
|
||||||
"baseUrl" => "/admin/module_shop_client",
|
"baseUrl" => "/admin/module_shop_client",
|
||||||
|
|||||||
31
kernel/modules/notification/NotificationDispatcher.php
Normal file
31
kernel/modules/notification/NotificationDispatcher.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace kernel\modules\notification;
|
||||||
|
|
||||||
|
use kernel\Flash;
|
||||||
|
use kernel\modules\notification\contracts\NotificationChannelInterface;
|
||||||
|
use kernel\modules\notification\contracts\NotificationMessage;
|
||||||
|
use kernel\modules\user\models\User;
|
||||||
|
|
||||||
|
class NotificationDispatcher
|
||||||
|
{
|
||||||
|
protected array $channels = [];
|
||||||
|
|
||||||
|
public function addChannel(string $channelName, NotificationChannelInterface $channel): void
|
||||||
|
{
|
||||||
|
$this->channels[$channelName] = $channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function dispatch(NotificationMessage $notification, User $user): void
|
||||||
|
{
|
||||||
|
foreach ($notification->via() as $channelName) {
|
||||||
|
if (isset($this->channels[$channelName])) {
|
||||||
|
try {
|
||||||
|
$this->channels[$channelName]->send($notification, $user);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Flash::setMessage("error", $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
42
kernel/modules/notification/NotificationModule.php
Normal file
42
kernel/modules/notification/NotificationModule.php
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace kernel\modules\notification;
|
||||||
|
|
||||||
|
use kernel\Module;
|
||||||
|
use kernel\modules\menu\service\MenuService;
|
||||||
|
use kernel\services\MigrationService;
|
||||||
|
|
||||||
|
class NotificationModule extends Module
|
||||||
|
{
|
||||||
|
|
||||||
|
public MenuService $menuService;
|
||||||
|
public MigrationService $migrationService;
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->menuService = new MenuService();
|
||||||
|
$this->migrationService = new MigrationService();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function init(): void
|
||||||
|
{
|
||||||
|
$this->migrationService->runAtPath("kernel/modules/notification/migrations");
|
||||||
|
|
||||||
|
$this->menuService->createItem([
|
||||||
|
"label" => "Уведомления",
|
||||||
|
"url" => "/admin/notification",
|
||||||
|
"slug" => "notification"
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function deactivate(): void
|
||||||
|
{
|
||||||
|
$this->migrationService->rollbackAtPath("kernel/modules/notification/migrations");
|
||||||
|
$this->menuService->removeItemBySlug("notification");
|
||||||
|
}
|
||||||
|
}
|
||||||
26
kernel/modules/notification/channels/DatabaseChannel.php
Normal file
26
kernel/modules/notification/channels/DatabaseChannel.php
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace kernel\modules\notification\channels;
|
||||||
|
|
||||||
|
use kernel\Flash;
|
||||||
|
use kernel\modules\notification\contracts\NotificationChannelInterface;
|
||||||
|
use kernel\modules\notification\contracts\NotificationMessage;
|
||||||
|
use kernel\modules\notification\models\User;
|
||||||
|
|
||||||
|
class DatabaseChannel implements NotificationChannelInterface
|
||||||
|
{
|
||||||
|
public function send(NotificationMessage $notification, User $user): bool
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$user->notifications()->create([
|
||||||
|
'message' => $notification->getMessage(),
|
||||||
|
'data' => $notification->toArray()
|
||||||
|
]);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Flash::setMessage("error", $e->getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
34
kernel/modules/notification/channels/EmailChannel.php
Normal file
34
kernel/modules/notification/channels/EmailChannel.php
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace kernel\modules\notification\channels;
|
||||||
|
|
||||||
|
use kernel\helpers\SMTP;
|
||||||
|
use kernel\modules\notification\contracts\NotificationChannelInterface;
|
||||||
|
use kernel\modules\notification\contracts\NotificationMessage;
|
||||||
|
use kernel\modules\user\models\User;
|
||||||
|
use PHPMailer\PHPMailer\Exception;
|
||||||
|
|
||||||
|
class EmailChannel implements NotificationChannelInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function send(NotificationMessage $notification, User $user): bool
|
||||||
|
{
|
||||||
|
$smtp = new SMTP();
|
||||||
|
// Здесь можно использовать Laravel Mail
|
||||||
|
// \Illuminate\Support\Facades\Mail::to($user->email)
|
||||||
|
// ->send(new \App\Mail\NotificationMail(
|
||||||
|
// $notification->getSubject(),
|
||||||
|
// $notification->getMessage()
|
||||||
|
// ));
|
||||||
|
|
||||||
|
return $smtp->send_html([
|
||||||
|
'address' => $user->email,
|
||||||
|
'subject' => $notification->getSubject(),
|
||||||
|
'body' => $notification->getMessage(),
|
||||||
|
'from_name' => $_ENV['MAIL_SMTP_USERNAME'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
22
kernel/modules/notification/channels/SmsChannel.php
Normal file
22
kernel/modules/notification/channels/SmsChannel.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace kernel\modules\notification\channels;
|
||||||
|
|
||||||
|
use kernel\modules\notification\contracts\NotificationChannelInterface;
|
||||||
|
use kernel\modules\notification\contracts\NotificationMessage;
|
||||||
|
use kernel\modules\user\models\User;
|
||||||
|
|
||||||
|
class SmsChannel implements NotificationChannelInterface
|
||||||
|
{
|
||||||
|
public function send(NotificationMessage $notification, User $user): bool
|
||||||
|
{
|
||||||
|
if (empty($user->phone)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Интеграция с SMS-сервисом
|
||||||
|
//$smsService = new \App\Services\SmsService();
|
||||||
|
//return $smsService->send($user->phone, $notification->getMessage());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
42
kernel/modules/notification/channels/TelegramChannel.php
Normal file
42
kernel/modules/notification/channels/TelegramChannel.php
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace kernel\modules\notification\channels;
|
||||||
|
|
||||||
|
use kernel\Flash;
|
||||||
|
use kernel\modules\notification\contracts\NotificationChannelInterface;
|
||||||
|
use kernel\modules\notification\contracts\NotificationMessage;
|
||||||
|
use kernel\modules\notification\models\User;
|
||||||
|
|
||||||
|
class TelegramChannel implements NotificationChannelInterface
|
||||||
|
{
|
||||||
|
protected string $botToken;
|
||||||
|
|
||||||
|
public function __construct(string $botToken)
|
||||||
|
{
|
||||||
|
$this->botToken = $botToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function send(NotificationMessage $notification, User $user): bool
|
||||||
|
{
|
||||||
|
if (empty($user->telegram_chat_id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$httpClient = new \GuzzleHttp\Client();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$response = $httpClient->post("https://api.telegram.org/bot{$this->botToken}/sendMessage", [
|
||||||
|
'form_params' => [
|
||||||
|
'chat_id' => $user->telegram_chat_id,
|
||||||
|
'text' => $notification->getMessage(),
|
||||||
|
'parse_mode' => 'HTML'
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $response->getStatusCode() === 200;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Flash::setMessage("error", $e->getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace kernel\modules\notification\contracts;
|
||||||
|
|
||||||
|
|
||||||
|
use kernel\modules\notification\models\User;
|
||||||
|
|
||||||
|
interface NotificationChannelInterface
|
||||||
|
{
|
||||||
|
public function send(NotificationMessage $notification, User $user): bool;
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace kernel\modules\notification\contracts;
|
||||||
|
|
||||||
|
abstract class NotificationMessage
|
||||||
|
{
|
||||||
|
protected array $channels = [];
|
||||||
|
|
||||||
|
abstract public function getMessage(): string;
|
||||||
|
abstract public function getSubject(): string;
|
||||||
|
|
||||||
|
public function via(): array
|
||||||
|
{
|
||||||
|
return $this->channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addChannel(string $channel): void
|
||||||
|
{
|
||||||
|
if (!in_array($channel, $this->channels)) {
|
||||||
|
$this->channels[] = $channel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toArray(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'message' => $this->getMessage(),
|
||||||
|
'subject' => $this->getSubject(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace kernel\modules\notification\controllers;
|
||||||
|
|
||||||
|
use JetBrains\PhpStorm\NoReturn;
|
||||||
|
use kernel\AdminController;
|
||||||
|
use kernel\Flash;
|
||||||
|
use kernel\helpers\Debug;
|
||||||
|
use kernel\modules\notification\models\forms\CreateNotificationForm;
|
||||||
|
use kernel\modules\notification\models\Notification;
|
||||||
|
use kernel\modules\notification\service\NotificationService;
|
||||||
|
use kernel\modules\option\models\forms\CreateOptionForm;
|
||||||
|
|
||||||
|
class NotificationController extends AdminController
|
||||||
|
{
|
||||||
|
|
||||||
|
private NotificationService $optionService;
|
||||||
|
|
||||||
|
public function init(): void
|
||||||
|
{
|
||||||
|
parent::init();
|
||||||
|
$this->cgView->viewPath = KERNEL_MODULES_DIR . '/notification/views/';
|
||||||
|
$this->optionService = new NotificationService();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function actionCreate(): void
|
||||||
|
{
|
||||||
|
$this->cgView->render('form.php');
|
||||||
|
}
|
||||||
|
|
||||||
|
#[NoReturn] public function actionAdd(): void
|
||||||
|
{
|
||||||
|
$optionForm = new CreateNotificationForm();
|
||||||
|
$optionForm->load($_REQUEST);
|
||||||
|
if ($optionForm->validate()) {
|
||||||
|
$option = $this->optionService->create($optionForm);
|
||||||
|
if ($option) {
|
||||||
|
Flash::setMessage("success", "notification успешно создана.");
|
||||||
|
$this->redirect('/admin/notification');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Flash::setMessage("error", $optionForm->getErrorsStr());
|
||||||
|
$this->redirect('/admin/notification/create');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function actionIndex($page_number = 1): void
|
||||||
|
{
|
||||||
|
$this->cgView->render('index.php', ['page_number' => $page_number]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function actionView(int $id): void
|
||||||
|
{
|
||||||
|
$option = Notification::find($id);
|
||||||
|
|
||||||
|
if (!$option) {
|
||||||
|
throw new \Exception('notification not found');
|
||||||
|
}
|
||||||
|
$this->cgView->render("view.php", ['option' => $option]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function actionUpdate(int $id): void
|
||||||
|
{
|
||||||
|
$model = Notification::find($id);
|
||||||
|
|
||||||
|
if (!$model) {
|
||||||
|
throw new \Exception('notification not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->cgView->render("form.php", ['model' => $model]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function actionEdit(int $id): void
|
||||||
|
{
|
||||||
|
$option = Notification::find($id);
|
||||||
|
if (!$option) {
|
||||||
|
throw new \Exception('Option not found');
|
||||||
|
}
|
||||||
|
$optionForm = new CreateOptionForm();
|
||||||
|
$optionForm->load($_REQUEST);
|
||||||
|
if ($optionForm->validate()) {
|
||||||
|
$option = $this->optionService->update($optionForm, $option);
|
||||||
|
if ($option) {
|
||||||
|
$this->redirect('/admin/notification/view/' . $option->id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->redirect('/admin/notification/update/' . $id);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[NoReturn] public function actionDelete(int $id): void
|
||||||
|
{
|
||||||
|
Notification::find($id)->delete();
|
||||||
|
Flash::setMessage("success", "notification успешно удалена.");
|
||||||
|
$this->redirect('/admin/notification');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
13
kernel/modules/notification/manifest.json
Normal file
13
kernel/modules/notification/manifest.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"name": "Notifications",
|
||||||
|
"version": "0.1",
|
||||||
|
"author": "ITGuild",
|
||||||
|
"slug": "notification",
|
||||||
|
"type": "entity",
|
||||||
|
"description": "Notifications module",
|
||||||
|
"module_class": "kernel\\modules\\notification\\NotificationModule",
|
||||||
|
"module_class_file": "{KERNEL_MODULES}/notification/NotificationModule.php",
|
||||||
|
"routs": "routs/notification.php",
|
||||||
|
"migration_path": "migrations",
|
||||||
|
"dependence": "user,menu"
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
\kernel\App::$db->schema->create('notification', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
|
||||||
|
// Связь с пользователем
|
||||||
|
$table->unsignedBigInteger('user_id');
|
||||||
|
|
||||||
|
// Основные данные уведомления
|
||||||
|
$table->string('type')->index(); // Класс уведомления (например, App\Notifications\OrderCreated)
|
||||||
|
$table->text('message'); // Текст уведомления
|
||||||
|
$table->string('subject')->nullable(); // Тема (для email)
|
||||||
|
$table->json('data')->nullable(); // Дополнительные данные в JSON
|
||||||
|
|
||||||
|
// Статус уведомления
|
||||||
|
$table->boolean('is_read')->default(false);
|
||||||
|
$table->integer('status')->default(1);
|
||||||
|
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
\kernel\App::$db->schema->dropIfExists('notification');
|
||||||
|
}
|
||||||
|
};
|
||||||
71
kernel/modules/notification/models/Notification.php
Normal file
71
kernel/modules/notification/models/Notification.php
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace kernel\modules\notification\models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use kernel\modules\user\models\User;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property int $id
|
||||||
|
* @property int $user_id
|
||||||
|
* @property bool $is_read
|
||||||
|
* @property array|string $data
|
||||||
|
* @property string $type
|
||||||
|
* @property string $message
|
||||||
|
* @property string $subject
|
||||||
|
* @property int $status
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Notification extends Model
|
||||||
|
{
|
||||||
|
const DISABLE_STATUS = 0;
|
||||||
|
const TO_SEND_STATUS = 1;
|
||||||
|
const SENT_STATUS = 2;
|
||||||
|
|
||||||
|
protected $table = 'notification';
|
||||||
|
|
||||||
|
protected $fillable = ['user_id', 'message', 'is_read', 'data', 'type', 'subject', 'status'];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'is_read' => 'boolean',
|
||||||
|
'data' => 'array'
|
||||||
|
];
|
||||||
|
|
||||||
|
public static function labels(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'user_id' => 'Пользователь',
|
||||||
|
'message' => 'Сообщение',
|
||||||
|
'subject' => 'Тема',
|
||||||
|
'is_read' => 'Прочитано',
|
||||||
|
'data' => 'Данные',
|
||||||
|
'type' => 'Тип',
|
||||||
|
'status' => 'Статус'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function markAsRead(): static
|
||||||
|
{
|
||||||
|
$this->update(['is_read' => true]);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public static function getStatus(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
self::DISABLE_STATUS => "Не активный",
|
||||||
|
self::TO_SEND_STATUS => "На отправку",
|
||||||
|
self::SENT_STATUS => "Отправлено",
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
13
kernel/modules/notification/models/User.php
Normal file
13
kernel/modules/notification/models/User.php
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace kernel\modules\notification\models;
|
||||||
|
|
||||||
|
class User extends \kernel\modules\user\models\User
|
||||||
|
{
|
||||||
|
|
||||||
|
public function notifications(): \Illuminate\Database\Eloquent\Relations\HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(Notification::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace kernel\modules\notification\models\forms;
|
||||||
|
|
||||||
|
use kernel\FormModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property string $key
|
||||||
|
* @property string $value
|
||||||
|
* @property string $label
|
||||||
|
* @property integer $status
|
||||||
|
*/
|
||||||
|
class CreateNotificationForm extends FormModel
|
||||||
|
{
|
||||||
|
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'user_id' => 'required|integer',
|
||||||
|
'message' => 'required',
|
||||||
|
'is_read' => '',
|
||||||
|
'data' => '',
|
||||||
|
'type' => 'required',
|
||||||
|
'subject' => '',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
19
kernel/modules/notification/routs/notification.php
Normal file
19
kernel/modules/notification/routs/notification.php
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use kernel\App;
|
||||||
|
use Phroute\Phroute\RouteCollector;
|
||||||
|
|
||||||
|
App::$collector->group(["prefix" => "admin"], function (RouteCollector $router) {
|
||||||
|
App::$collector->group(["before" => "auth"], function (RouteCollector $router) {
|
||||||
|
App::$collector->group(["prefix" => "notification"], callback: function (RouteCollector $router) {
|
||||||
|
App::$collector->get('/', [\kernel\modules\notification\controllers\NotificationController::class, 'actionIndex']);
|
||||||
|
App::$collector->get('/page/{page_number}', [\kernel\modules\notification\controllers\NotificationController::class, 'actionIndex']);
|
||||||
|
App::$collector->get('/create', [\kernel\modules\notification\controllers\NotificationController::class, 'actionCreate']);
|
||||||
|
App::$collector->post("/", [\kernel\modules\notification\controllers\NotificationController::class, 'actionAdd']);
|
||||||
|
App::$collector->get('/view/{id}', [\kernel\modules\notification\controllers\NotificationController::class, 'actionView']);
|
||||||
|
App::$collector->any('/update/{id}', [\kernel\modules\notification\controllers\NotificationController::class, 'actionUpdate']);
|
||||||
|
App::$collector->any("/edit/{id}", [\kernel\modules\notification\controllers\NotificationController::class, 'actionEdit']);
|
||||||
|
App::$collector->get('/delete/{id}', [\kernel\modules\notification\controllers\NotificationController::class, 'actionDelete']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
57
kernel/modules/notification/service/NotificationService.php
Normal file
57
kernel/modules/notification/service/NotificationService.php
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace kernel\modules\notification\service;
|
||||||
|
|
||||||
|
use kernel\FormModel;
|
||||||
|
use kernel\modules\notification\models\Notification;
|
||||||
|
|
||||||
|
class NotificationService
|
||||||
|
{
|
||||||
|
|
||||||
|
public function create(FormModel $form_model): false|Notification
|
||||||
|
{
|
||||||
|
$model = new Notification();
|
||||||
|
$model->user_id = $form_model->getItem('user_id');
|
||||||
|
$model->message = $form_model->getItem('message');
|
||||||
|
$model->is_read = $form_model->getItem('is_read');
|
||||||
|
$model->data = $form_model->getItem('data');
|
||||||
|
$model->type = $form_model->getItem('type');
|
||||||
|
$model->subject = $form_model->getItem('subject');
|
||||||
|
$model->status = $form_model->getItem('status');
|
||||||
|
if ($model->save()) {
|
||||||
|
return $model;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(FormModel $form_model, Notification $notification): false|Notification
|
||||||
|
{
|
||||||
|
$notification->user_id = $form_model->getItem('user_id');
|
||||||
|
$notification->message = $form_model->getItem('message');
|
||||||
|
$notification->is_read = $form_model->getItem('is_read');
|
||||||
|
$notification->data = $form_model->getItem('data');
|
||||||
|
$notification->type = $form_model->getItem('type');
|
||||||
|
$notification->subject = $form_model->getItem('subject');
|
||||||
|
$notification->status = $form_model->getItem('status');
|
||||||
|
if ($notification->save()) {
|
||||||
|
return $notification;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// public function createOptionArr(): array
|
||||||
|
// {
|
||||||
|
// foreach (Option::all()->toArray() as $option) {
|
||||||
|
// $optionArr[$option['id']] = $option['key'];
|
||||||
|
// }
|
||||||
|
// if (!empty($optionArr)) {
|
||||||
|
// return $optionArr;
|
||||||
|
// }
|
||||||
|
// return [];
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace kernel\modules\option\table\columns;
|
||||||
|
|
||||||
|
use Itguild\Tables\ActionColumn\ActionColumn;
|
||||||
|
|
||||||
|
class OptionDeleteActionColumn extends ActionColumn
|
||||||
|
{
|
||||||
|
protected string $prefix = "/delete/";
|
||||||
|
|
||||||
|
public function fetch(): string
|
||||||
|
{
|
||||||
|
$link = $this->baseUrl . $this->prefix . $this->id;
|
||||||
|
return " <a href='$link' class='btn btn-danger'>Удалить</a> ";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace kernel\modules\option\table\columns;
|
||||||
|
|
||||||
|
use Itguild\Tables\ActionColumn\ActionColumn;
|
||||||
|
|
||||||
|
class OptionEditActionColumn extends ActionColumn
|
||||||
|
{
|
||||||
|
protected string $prefix = "/update/";
|
||||||
|
|
||||||
|
public function fetch(): string
|
||||||
|
{
|
||||||
|
$link = $this->baseUrl . $this->prefix . $this->id;
|
||||||
|
return " <a href='$link' class='btn btn-success'>Редактировать</a> ";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace kernel\modules\option\table\columns;
|
||||||
|
|
||||||
|
use Itguild\Tables\ActionColumn\ActionColumn;
|
||||||
|
|
||||||
|
class OptionViewActionColumn extends ActionColumn
|
||||||
|
{
|
||||||
|
|
||||||
|
protected string $prefix = "/";
|
||||||
|
|
||||||
|
public function fetch()
|
||||||
|
{
|
||||||
|
$link = $this->baseUrl . $this->prefix . $this->id;
|
||||||
|
return " <a href='$link' class='btn btn-primary'>Просмотр</a> ";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
84
kernel/modules/notification/views/form.php
Normal file
84
kernel/modules/notification/views/form.php
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @var Notification $model
|
||||||
|
*/
|
||||||
|
|
||||||
|
use itguild\forms\ActiveForm;
|
||||||
|
use kernel\modules\notification\models\Notification;
|
||||||
|
|
||||||
|
$form = new ActiveForm();
|
||||||
|
$form->beginForm(isset($model) ? "/admin/notification/edit/" . $model->id : "/admin/notification");
|
||||||
|
|
||||||
|
$form->field(class: \itguild\forms\inputs\Select::class, name: "user_id", params: [
|
||||||
|
'class' => "form-control",
|
||||||
|
'value' => $model->user_id ?? ''
|
||||||
|
])
|
||||||
|
->setLabel(Notification::labels()['user_id'])
|
||||||
|
->setOptions(\kernel\modules\user\service\UserService::createUsernameArr())
|
||||||
|
->render();
|
||||||
|
|
||||||
|
$form->field(\itguild\forms\inputs\TextInput::class, 'subject', [
|
||||||
|
'class' => "form-control",
|
||||||
|
'placeholder' => Notification::labels()['subject'],
|
||||||
|
'value' => $model->subject ?? ''
|
||||||
|
])
|
||||||
|
->setLabel(Notification::labels()['subject'])
|
||||||
|
->render();
|
||||||
|
|
||||||
|
$form->field(\itguild\forms\inputs\TextInput::class, 'type', [
|
||||||
|
'class' => "form-control",
|
||||||
|
'placeholder' => Notification::labels()['type'],
|
||||||
|
'value' => $model->type ?? ''
|
||||||
|
])
|
||||||
|
->setLabel(Notification::labels()['type'])
|
||||||
|
->render();
|
||||||
|
|
||||||
|
$form->field(\itguild\forms\inputs\TextArea::class, 'message', [
|
||||||
|
'class' => "form-control",
|
||||||
|
'placeholder' => Notification::labels()['message'],
|
||||||
|
'value' => $model->message ?? ''
|
||||||
|
])
|
||||||
|
->setLabel(Notification::labels()['message'])
|
||||||
|
->render();
|
||||||
|
|
||||||
|
$form->field(\itguild\forms\inputs\Checkbox::class, 'is_read', [
|
||||||
|
'class' => "form-check-input",
|
||||||
|
'placeholder' => Notification::labels()['is_read'],
|
||||||
|
'value' => $model->is_read ?? ''
|
||||||
|
])
|
||||||
|
->setLabel(Notification::labels()['is_read'])
|
||||||
|
->render();
|
||||||
|
|
||||||
|
$form->field(\itguild\forms\inputs\Select::class, 'status', [
|
||||||
|
'class' => "form-control",
|
||||||
|
'value' => $model->status ?? ''
|
||||||
|
])
|
||||||
|
->setLabel("Статус")
|
||||||
|
->setOptions(Notification::getStatus())
|
||||||
|
->render();
|
||||||
|
|
||||||
|
?>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-2">
|
||||||
|
<?php
|
||||||
|
$form->field(\itguild\forms\inputs\Button::class, name: "btn-submit", params: [
|
||||||
|
'class' => "btn btn-primary ",
|
||||||
|
'value' => 'Отправить',
|
||||||
|
'typeInput' => 'submit'
|
||||||
|
])
|
||||||
|
->render();
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-2">
|
||||||
|
<?php
|
||||||
|
$form->field(\itguild\forms\inputs\Button::class, name: "btn-reset", params: [
|
||||||
|
'class' => "btn btn-warning",
|
||||||
|
'value' => 'Сбросить',
|
||||||
|
'typeInput' => 'reset'
|
||||||
|
])
|
||||||
|
->render();
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
$form->endForm();
|
||||||
44
kernel/modules/notification/views/index.php
Normal file
44
kernel/modules/notification/views/index.php
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @var \Illuminate\Database\Eloquent\Collection $options
|
||||||
|
* @var int $page_number
|
||||||
|
*/
|
||||||
|
|
||||||
|
use Itguild\EloquentTable\EloquentDataProvider;
|
||||||
|
use Itguild\EloquentTable\ListEloquentTable;
|
||||||
|
use kernel\modules\notification\models\Notification;
|
||||||
|
use kernel\widgets\IconBtn\IconBtnCreateWidget;
|
||||||
|
use kernel\widgets\IconBtn\IconBtnDeleteWidget;
|
||||||
|
use kernel\widgets\IconBtn\IconBtnEditWidget;
|
||||||
|
use kernel\widgets\IconBtn\IconBtnViewWidget;
|
||||||
|
|
||||||
|
$table = new ListEloquentTable(new EloquentDataProvider(Notification::class, [
|
||||||
|
'current_page' => $page_number,
|
||||||
|
'per_page' => 5,
|
||||||
|
'params' => ["class" => "table table-bordered", "border" => "2"],
|
||||||
|
'baseUrl' => "/admin/notification",
|
||||||
|
]));
|
||||||
|
|
||||||
|
$table->beforePrint(function () {
|
||||||
|
return IconBtnCreateWidget::create(['url' => '/admin/notification/create'])->run();
|
||||||
|
});
|
||||||
|
|
||||||
|
$table->columns([
|
||||||
|
"status" => [
|
||||||
|
"value" => function ($cell) {
|
||||||
|
return Notification::getStatus()[$cell];
|
||||||
|
}]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$table->addAction(function($row) {
|
||||||
|
return IconBtnViewWidget::create(['url' => '/admin/notification/view/' . $row['id']])->run();
|
||||||
|
});
|
||||||
|
$table->addAction(function($row) {
|
||||||
|
return IconBtnEditWidget::create(['url' => '/admin/Notification/update/' . $row['id']])->run();
|
||||||
|
});
|
||||||
|
$table->addAction(function($row) {
|
||||||
|
return IconBtnDeleteWidget::create(['url' => '/admin/Notification/delete/' . $row['id']])->run();
|
||||||
|
});
|
||||||
|
|
||||||
|
$table->create();
|
||||||
|
$table->render();
|
||||||
32
kernel/modules/notification/views/view.php
Normal file
32
kernel/modules/notification/views/view.php
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @var \Illuminate\Database\Eloquent\Collection $option
|
||||||
|
*/
|
||||||
|
|
||||||
|
use Itguild\EloquentTable\ViewEloquentTable;
|
||||||
|
use Itguild\EloquentTable\ViewJsonTableEloquentModel;
|
||||||
|
use kernel\IGTabel\btn\DangerBtn;
|
||||||
|
use kernel\IGTabel\btn\PrimaryBtn;
|
||||||
|
use kernel\IGTabel\btn\SuccessBtn;
|
||||||
|
use kernel\widgets\IconBtn\IconBtnDeleteWidget;
|
||||||
|
use kernel\widgets\IconBtn\IconBtnEditWidget;
|
||||||
|
use kernel\widgets\IconBtn\IconBtnListWidget;
|
||||||
|
|
||||||
|
$table = new ViewEloquentTable(new ViewJsonTableEloquentModel($option, [
|
||||||
|
'params' => ["class" => "table table-bordered", "border" => "2"],
|
||||||
|
'baseUrl' => "/admin/user",
|
||||||
|
]));
|
||||||
|
$table->beforePrint(function () use ($option) {
|
||||||
|
$btn = IconBtnListWidget::create(['url' => '/admin/option'])->run();
|
||||||
|
$btn .= IconBtnEditWidget::create(['url' => '/admin/option/update/' . $option->id])->run();
|
||||||
|
$btn .= IconBtnDeleteWidget::create(['url' => '/admin/option/delete/' . $option->id])->run();
|
||||||
|
return $btn;
|
||||||
|
});
|
||||||
|
|
||||||
|
$table->rows([
|
||||||
|
'status' => (function ($data) {
|
||||||
|
return \kernel\modules\option\models\Notification::getStatus()[$data];
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
$table->create();
|
||||||
|
$table->render();
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace kernel\modules\option\service;
|
namespace kernel\modules\option\service;
|
||||||
|
|
||||||
use kernel\FormModel;
|
use kernel\FormModel;
|
||||||
|
use kernel\helpers\Debug;
|
||||||
use kernel\modules\option\models\Option;
|
use kernel\modules\option\models\Option;
|
||||||
|
|
||||||
class OptionService
|
class OptionService
|
||||||
@@ -49,6 +50,26 @@ class OptionService
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function createOrUpdate(string $key, string $value, string $label = ''): false|Option
|
||||||
|
{
|
||||||
|
/** @var Option $option */
|
||||||
|
$option = self::getItemObject($key);
|
||||||
|
if (!$option) {
|
||||||
|
$option = new Option();
|
||||||
|
$option->key = $key;
|
||||||
|
}
|
||||||
|
$option->value = $value;
|
||||||
|
if (!empty($label)){
|
||||||
|
$option->label = $label;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($option->save()) {
|
||||||
|
return $option;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $key
|
* @param $key
|
||||||
* @return false|array|string
|
* @return false|array|string
|
||||||
@@ -63,6 +84,20 @@ class OptionService
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $key
|
||||||
|
* @return false|array|string|Option
|
||||||
|
*/
|
||||||
|
public static function getItemObject($key): false|array|string|Option
|
||||||
|
{
|
||||||
|
$item = Option::where("key", $key)->first();
|
||||||
|
if ($item){
|
||||||
|
return $item;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public static function removeOptionByKey(string $key): bool
|
public static function removeOptionByKey(string $key): bool
|
||||||
{
|
{
|
||||||
$option = Option::where("key", $key)->first();
|
$option = Option::where("key", $key)->first();
|
||||||
|
|||||||
@@ -33,4 +33,9 @@ class PostService
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function getListArr()
|
||||||
|
{
|
||||||
|
return Post::pluck('title', 'id')->toArray();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,7 @@ use kernel\App;
|
|||||||
use kernel\Flash;
|
use kernel\Flash;
|
||||||
use kernel\helpers\Debug;
|
use kernel\helpers\Debug;
|
||||||
use kernel\Mailing;
|
use kernel\Mailing;
|
||||||
|
use kernel\modules\secure\models\forms\ChangePasswordForm;
|
||||||
use kernel\modules\secure\models\forms\LoginEmailForm;
|
use kernel\modules\secure\models\forms\LoginEmailForm;
|
||||||
use kernel\modules\secure\models\forms\LoginForm;
|
use kernel\modules\secure\models\forms\LoginForm;
|
||||||
use kernel\modules\secure\models\forms\RegisterForm;
|
use kernel\modules\secure\models\forms\RegisterForm;
|
||||||
@@ -40,7 +41,7 @@ class SecureController extends AdminController
|
|||||||
// $this->cgView->render('login.php');
|
// $this->cgView->render('login.php');
|
||||||
}
|
}
|
||||||
|
|
||||||
#[NoReturn] public function actionAuth(): void
|
#[NoReturn] public function actionAuth($basePath = '/admin'): void
|
||||||
{
|
{
|
||||||
$loginForm = new LoginForm();
|
$loginForm = new LoginForm();
|
||||||
$loginForm->load($_REQUEST);
|
$loginForm->load($_REQUEST);
|
||||||
@@ -51,19 +52,36 @@ class SecureController extends AdminController
|
|||||||
else {
|
else {
|
||||||
$field = "username";
|
$field = "username";
|
||||||
}
|
}
|
||||||
|
|
||||||
$user = $this->userService->getByField($field, $loginForm->getItem("username"));
|
$user = $this->userService->getByField($field, $loginForm->getItem("username"));
|
||||||
if (!$user){
|
if (!$user){
|
||||||
Flash::setMessage("error", "User not found.");
|
Flash::setMessage("error", "User not found.");
|
||||||
$this->redirect("/admin/login", code: 302);
|
$this->redirect($basePath . "/login", code: 302);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (password_verify($loginForm->getItem("password"), $user->password_hash)) {
|
if (password_verify($loginForm->getItem("password"), $user->password_hash)) {
|
||||||
setcookie('user_id', $user->id, time()+60*60*24, '/', $_SERVER['SERVER_NAME'], false);
|
setcookie('user_id', $user->id, time()+60*60*24, '/', $_SERVER['SERVER_NAME'], false);
|
||||||
$this->redirect("/admin", code: 302);
|
$this->redirect($basePath . '/', code: 302);
|
||||||
} else {
|
} else {
|
||||||
Flash::setMessage("error", "Username or password incorrect.");
|
Flash::setMessage("error", "Username or password incorrect.");
|
||||||
$this->redirect("/admin/login", code: 302);
|
$this->redirect($basePath . "/login", code: 302);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[NoReturn] public function actionChangePassword($basePath = '/admin'): void
|
||||||
|
{
|
||||||
|
$changePasswordForm = new ChangePasswordForm();
|
||||||
|
$changePasswordForm->load($_REQUEST);
|
||||||
|
|
||||||
|
$user = UserService::getAuthUser();
|
||||||
|
|
||||||
|
if (password_verify($changePasswordForm->getItem("old_password"), $user->password_hash)) {
|
||||||
|
$user->password_hash = password_hash($changePasswordForm->getItem("new_password"), PASSWORD_DEFAULT);
|
||||||
|
$user->save();
|
||||||
|
Flash::setMessage("success", "Пароль успешно изменен.");
|
||||||
|
$this->redirect($basePath . '', code: 302);
|
||||||
|
} else {
|
||||||
|
Flash::setMessage("error", "Username or password incorrect.");
|
||||||
|
$this->redirect($basePath . "", code: 302);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,25 +166,25 @@ class SecureController extends AdminController
|
|||||||
$this->cgView->render('register.php');
|
$this->cgView->render('register.php');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function actionRegistration(): void
|
public function actionRegistration($basePath = '/admin'): void
|
||||||
{
|
{
|
||||||
$regForm = new RegisterForm();
|
$regForm = new RegisterForm();
|
||||||
$regForm->load($_REQUEST);
|
$regForm->load($_REQUEST);
|
||||||
|
|
||||||
if ($this->userService->getByField('username', $regForm->getItem("username"))) {
|
if ($this->userService->getByField('username', $regForm->getItem("username"))) {
|
||||||
Flash::setMessage("error", "Username already exists.");
|
Flash::setMessage("error", "Username already exists.");
|
||||||
$this->redirect("/admin/register", code: 302);
|
$this->redirect($basePath . "/register", code: 302);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->userService->getByField('email', $regForm->getItem("email"))) {
|
if ($this->userService->getByField('email', $regForm->getItem("email"))) {
|
||||||
Flash::setMessage("error", "Email already exists.");
|
Flash::setMessage("error", "Email already exists.");
|
||||||
$this->redirect("/admin/register", code: 302);
|
$this->redirect($basePath . "/register", code: 302);
|
||||||
}
|
}
|
||||||
|
|
||||||
$user = $this->userService->create($regForm);
|
$user = $this->userService->create($regForm);
|
||||||
if ($user){
|
if ($user){
|
||||||
setcookie('user_id', $user->id, time()+60*60*24, '/', $_SERVER['SERVER_NAME'], false);
|
setcookie('user_id', $user->id, time()+60*60*24, '/', $_SERVER['SERVER_NAME'], false);
|
||||||
$this->redirect("/admin", code: 302);
|
$this->redirect($basePath . "/", code: 302);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
18
kernel/modules/secure/models/forms/ChangePasswordForm.php
Normal file
18
kernel/modules/secure/models/forms/ChangePasswordForm.php
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace kernel\modules\secure\models\forms;
|
||||||
|
|
||||||
|
use kernel\FormModel;
|
||||||
|
|
||||||
|
class ChangePasswordForm extends FormModel
|
||||||
|
{
|
||||||
|
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'old_password' => 'required|min-str-len:6|max-str-len:50',
|
||||||
|
'new_password' => 'required|min-str-len:6|max-str-len:50',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ use JetBrains\PhpStorm\NoReturn;
|
|||||||
use kernel\AdminController;
|
use kernel\AdminController;
|
||||||
use kernel\EntityRelation;
|
use kernel\EntityRelation;
|
||||||
use kernel\FileUpload;
|
use kernel\FileUpload;
|
||||||
|
use kernel\Flash;
|
||||||
use kernel\helpers\Debug;
|
use kernel\helpers\Debug;
|
||||||
use kernel\modules\user\models\forms\CreateUserForm;
|
use kernel\modules\user\models\forms\CreateUserForm;
|
||||||
use kernel\modules\user\models\User;
|
use kernel\modules\user\models\User;
|
||||||
@@ -55,6 +56,7 @@ class UserController extends AdminController
|
|||||||
$this->redirect("/admin/user/view/" . $user->id);
|
$this->redirect("/admin/user/view/" . $user->id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Flash::setMessage("error", $userForm->getErrorsStr());
|
||||||
$this->redirect("/admin/user/create");
|
$this->redirect("/admin/user/create");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace kernel\modules\user\service;
|
namespace kernel\modules\user\service;
|
||||||
|
|
||||||
|
use itguild\forms\ActiveForm;
|
||||||
use kernel\FormModel;
|
use kernel\FormModel;
|
||||||
use kernel\helpers\Debug;
|
use kernel\helpers\Debug;
|
||||||
use kernel\modules\user\models\User;
|
use kernel\modules\user\models\User;
|
||||||
@@ -122,4 +123,12 @@ class UserService
|
|||||||
$user->save();
|
$user->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function getList(): array
|
||||||
|
{
|
||||||
|
return User::select('id', 'username')->get()
|
||||||
|
->pluck('username', 'id')
|
||||||
|
->toArray();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -47,6 +47,8 @@ if (!isset($model)) {
|
|||||||
$model = new User();
|
$model = new User();
|
||||||
}
|
}
|
||||||
$entityRelations->renderEntityAdditionalPropertyFormBySlug("user", $model);
|
$entityRelations->renderEntityAdditionalPropertyFormBySlug("user", $model);
|
||||||
|
|
||||||
|
|
||||||
?>
|
?>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-2">
|
<div class="col-sm-2">
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \Illuminate\Database\Eloquent\Collection $user
|
* @var User $user
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use kernel\modules\user\models\User;
|
use kernel\modules\user\models\User;
|
||||||
@@ -55,3 +55,5 @@ $table->rows([
|
|||||||
]);
|
]);
|
||||||
$table->create();
|
$table->create();
|
||||||
$table->render();
|
$table->render();
|
||||||
|
|
||||||
|
\kernel\App::$hook->runHooksByEntity('user_view', ['user' => $user]);
|
||||||
@@ -105,6 +105,11 @@ class AdminThemeService
|
|||||||
if ($adminThemePaths) {
|
if ($adminThemePaths) {
|
||||||
$path = json_decode($adminThemePaths->value);
|
$path = json_decode($adminThemePaths->value);
|
||||||
foreach ($path->paths as $p) {
|
foreach ($path->paths as $p) {
|
||||||
|
if (!is_dir(getConst($p))){
|
||||||
|
$old_mask = umask(0);
|
||||||
|
mkdir(getConst($p), permissions: 0775, recursive: true);
|
||||||
|
umask($old_mask);
|
||||||
|
}
|
||||||
$dirs[] = getConst($p);
|
$dirs[] = getConst($p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,12 +44,11 @@ class KernelService
|
|||||||
|
|
||||||
$kernel_info = $this->getKernelInfo();
|
$kernel_info = $this->getKernelInfo();
|
||||||
|
|
||||||
$kernelVersion = Version::getIntVersionByString($kernel_info['version']);
|
|
||||||
|
|
||||||
foreach ($modules_info as $mod) {
|
foreach ($modules_info as $mod) {
|
||||||
$modVersion = Version::getIntVersionByString($mod['version']);
|
if ($mod['slug'] === $kernel_info['slug'] ) {
|
||||||
if ($mod['slug'] === $kernel_info['slug'] && $modVersion <= $kernelVersion) {
|
if (Version::compare($kernel_info['version'], $mod['version']) >= 0){
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -185,6 +185,11 @@ class ModuleService
|
|||||||
if ($module_paths) {
|
if ($module_paths) {
|
||||||
$path = json_decode($module_paths->value);
|
$path = json_decode($module_paths->value);
|
||||||
foreach ($path->paths as $p) {
|
foreach ($path->paths as $p) {
|
||||||
|
if (!is_dir(getConst($p))) {
|
||||||
|
$old_mask = umask(0);
|
||||||
|
mkdir(getConst($p), permissions: 0775, recursive: true);
|
||||||
|
umask($old_mask);
|
||||||
|
}
|
||||||
$dirs[] = getConst($p);
|
$dirs[] = getConst($p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -273,6 +278,17 @@ class ModuleService
|
|||||||
return $routs;
|
return $routs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setModulesHooks(): void
|
||||||
|
{
|
||||||
|
$hooks = [];
|
||||||
|
$modules = $this->getActiveModules();
|
||||||
|
foreach ($modules as $module) {
|
||||||
|
if (isset($module['hooks'])) {
|
||||||
|
include $module['path'] . "/" . $module['hooks'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
@@ -476,11 +492,13 @@ class ModuleService
|
|||||||
|
|
||||||
$mod_info = $this->getModuleInfoBySlug($slug);
|
$mod_info = $this->getModuleInfoBySlug($slug);
|
||||||
|
|
||||||
$currentVersion = Version::getIntVersionByString($mod_info['version']);
|
$currentVersion = $mod_info['version'];
|
||||||
foreach ($modules_info as $mod) {
|
foreach ($modules_info as $mod) {
|
||||||
$modVersion = Version::getIntVersionByString($mod['version']);
|
$modVersion = $mod['version'];
|
||||||
if ($mod['slug'] === $mod_info['slug'] && $modVersion <= $currentVersion) {
|
if ($mod['slug'] === $mod_info['slug']) {
|
||||||
return true;
|
if (Version::compare($currentVersion, $modVersion) >= 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -162,6 +162,11 @@ class ThemeService
|
|||||||
if ($ThemePaths) {
|
if ($ThemePaths) {
|
||||||
$path = json_decode($ThemePaths->value);
|
$path = json_decode($ThemePaths->value);
|
||||||
foreach ($path->paths as $p) {
|
foreach ($path->paths as $p) {
|
||||||
|
if (!is_dir(getConst($p))){
|
||||||
|
$old_mask = umask(0);
|
||||||
|
mkdir(getConst($p), permissions: 0775, recursive: true);
|
||||||
|
umask($old_mask);
|
||||||
|
}
|
||||||
$dirs[] = getConst($p);
|
$dirs[] = getConst($p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -250,9 +255,9 @@ class ThemeService
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isset($manifest['theme_kernel_path'])) {
|
if (isset($manifest['theme_kernel_path'])) {
|
||||||
$fileHelper->copy_folder($tmpThemeDirFull . "meta/kernel", $manifest['theme_app_path']);
|
$fileHelper->copy_folder($tmpThemeDirFull . "meta/kernel", $manifest['theme_kernel_path']);
|
||||||
} else {
|
} else {
|
||||||
$fileHelper->copy_folder($tmpThemeDirFull . "meta/kernel", APP_DIR . '/themes/' . $manifest['slug']);
|
$fileHelper->copy_folder($tmpThemeDirFull . "meta/kernel", KERNEL_DIR . '/app_themes/' . $manifest['slug']);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($manifest['resource_path'])) {
|
if (isset($manifest['resource_path'])) {
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ $table->columns([
|
|||||||
'value' => $get['title'] ?? ''
|
'value' => $get['title'] ?? ''
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
]
|
]);
|
||||||
|
|
||||||
$table->beforePrint(function () {
|
$table->beforePrint(function () {
|
||||||
return IconBtnCreateWidget::create(['url' => '/admin/{slug}/create'])->run();
|
return IconBtnCreateWidget::create(['url' => '/admin/{slug}/create'])->run();
|
||||||
|
|||||||
103
resources/themes/custom/about.html
Normal file
103
resources/themes/custom/about.html
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||||
|
<meta name="description" content="" />
|
||||||
|
<meta name="author" content="" />
|
||||||
|
<title>Clean Blog - Start Bootstrap Theme</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="assets/favicon.ico" />
|
||||||
|
<!-- Font Awesome icons (free version)-->
|
||||||
|
<script src="https://use.fontawesome.com/releases/v6.3.0/js/all.js" crossorigin="anonymous"></script>
|
||||||
|
<!-- Google fonts-->
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic" rel="stylesheet" type="text/css" />
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet" type="text/css" />
|
||||||
|
<!-- Core theme CSS (includes Bootstrap)-->
|
||||||
|
<link href="css/styles.css" rel="stylesheet" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- Navigation-->
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-light" id="mainNav">
|
||||||
|
<div class="container px-4 px-lg-5">
|
||||||
|
<a class="navbar-brand" href="index.html">Start Bootstrap</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
Menu
|
||||||
|
<i class="fas fa-bars"></i>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||||
|
<ul class="navbar-nav ms-auto py-4 py-lg-0">
|
||||||
|
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="index.html">Home</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="about.html">About</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="post.html">Sample Post</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="contact.html">Contact</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<!-- Page Header-->
|
||||||
|
<header class="masthead" style="background-image: url('assets/img/about-bg.jpg')">
|
||||||
|
<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>
|
||||||
|
<!-- Footer-->
|
||||||
|
<footer class="border-top">
|
||||||
|
<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">
|
||||||
|
<ul class="list-inline text-center">
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<a href="#!">
|
||||||
|
<span class="fa-stack fa-lg">
|
||||||
|
<i class="fas fa-circle fa-stack-2x"></i>
|
||||||
|
<i class="fab fa-twitter fa-stack-1x fa-inverse"></i>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<a href="#!">
|
||||||
|
<span class="fa-stack fa-lg">
|
||||||
|
<i class="fas fa-circle fa-stack-2x"></i>
|
||||||
|
<i class="fab fa-facebook-f fa-stack-1x fa-inverse"></i>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<a href="#!">
|
||||||
|
<span class="fa-stack fa-lg">
|
||||||
|
<i class="fas fa-circle fa-stack-2x"></i>
|
||||||
|
<i class="fab fa-github fa-stack-1x fa-inverse"></i>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="small text-center text-muted fst-italic">Copyright © Your Website 2023</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
<!-- Bootstrap core JS-->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<!-- Core theme JS-->
|
||||||
|
<script src="js/scripts.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
resources/themes/custom/assets/favicon.ico
Normal file
BIN
resources/themes/custom/assets/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
BIN
resources/themes/custom/assets/img/about-bg.jpeg
Normal file
BIN
resources/themes/custom/assets/img/about-bg.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 356 KiB |
BIN
resources/themes/custom/assets/img/home-bg.jpeg
Normal file
BIN
resources/themes/custom/assets/img/home-bg.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 74 KiB |
158
resources/themes/custom/contact.html
Normal file
158
resources/themes/custom/contact.html
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||||
|
<meta name="description" content="" />
|
||||||
|
<meta name="author" content="" />
|
||||||
|
<title>Clean Blog - Start Bootstrap Theme</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="assets/favicon.ico" />
|
||||||
|
<!-- Font Awesome icons (free version)-->
|
||||||
|
<script src="https://use.fontawesome.com/releases/v6.3.0/js/all.js" crossorigin="anonymous"></script>
|
||||||
|
<!-- Google fonts-->
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic" rel="stylesheet" type="text/css" />
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet" type="text/css" />
|
||||||
|
<!-- Core theme CSS (includes Bootstrap)-->
|
||||||
|
<link href="css/styles.css" rel="stylesheet" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- Navigation-->
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-light" id="mainNav">
|
||||||
|
<div class="container px-4 px-lg-5">
|
||||||
|
<a class="navbar-brand" href="index.html">Start Bootstrap</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
Menu
|
||||||
|
<i class="fas fa-bars"></i>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||||
|
<ul class="navbar-nav ms-auto py-4 py-lg-0">
|
||||||
|
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="index.html">Home</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="about.html">About</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="post.html">Sample Post</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="contact.html">Contact</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<!-- Page Header-->
|
||||||
|
<header class="masthead" style="background-image: url('assets/img/contact-bg.jpg')">
|
||||||
|
<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>Contact Me</h1>
|
||||||
|
<span class="subheading">Have questions? I have answers.</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>Want to get in touch? Fill out the form below to send me a message and I will get back to you as soon as possible!</p>
|
||||||
|
<div class="my-5">
|
||||||
|
<!-- * * * * * * * * * * * * * * *-->
|
||||||
|
<!-- * * SB Forms Contact Form * *-->
|
||||||
|
<!-- * * * * * * * * * * * * * * *-->
|
||||||
|
<!-- This form is pre-integrated with SB Forms.-->
|
||||||
|
<!-- To make this form functional, sign up at-->
|
||||||
|
<!-- https://startbootstrap.com/solution/contact-forms-->
|
||||||
|
<!-- to get an API token!-->
|
||||||
|
<form id="contactForm" data-sb-form-api-token="API_TOKEN">
|
||||||
|
<div class="form-floating">
|
||||||
|
<input class="form-control" id="name" type="text" placeholder="Enter your name..." data-sb-validations="required" />
|
||||||
|
<label for="name">Name</label>
|
||||||
|
<div class="invalid-feedback" data-sb-feedback="name:required">A name is required.</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-floating">
|
||||||
|
<input class="form-control" id="email" type="email" placeholder="Enter your email..." data-sb-validations="required,email" />
|
||||||
|
<label for="email">Email address</label>
|
||||||
|
<div class="invalid-feedback" data-sb-feedback="email:required">An email is required.</div>
|
||||||
|
<div class="invalid-feedback" data-sb-feedback="email:email">Email is not valid.</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-floating">
|
||||||
|
<input class="form-control" id="phone" type="tel" placeholder="Enter your phone number..." data-sb-validations="required" />
|
||||||
|
<label for="phone">Phone Number</label>
|
||||||
|
<div class="invalid-feedback" data-sb-feedback="phone:required">A phone number is required.</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-floating">
|
||||||
|
<textarea class="form-control" id="message" placeholder="Enter your message here..." style="height: 12rem" data-sb-validations="required"></textarea>
|
||||||
|
<label for="message">Message</label>
|
||||||
|
<div class="invalid-feedback" data-sb-feedback="message:required">A message is required.</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<!-- Submit success message-->
|
||||||
|
<!---->
|
||||||
|
<!-- This is what your users will see when the form-->
|
||||||
|
<!-- has successfully submitted-->
|
||||||
|
<div class="d-none" id="submitSuccessMessage">
|
||||||
|
<div class="text-center mb-3">
|
||||||
|
<div class="fw-bolder">Form submission successful!</div>
|
||||||
|
To activate this form, sign up at
|
||||||
|
<br />
|
||||||
|
<a href="https://startbootstrap.com/solution/contact-forms">https://startbootstrap.com/solution/contact-forms</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Submit error message-->
|
||||||
|
<!---->
|
||||||
|
<!-- This is what your users will see when there is-->
|
||||||
|
<!-- an error submitting the form-->
|
||||||
|
<div class="d-none" id="submitErrorMessage"><div class="text-center text-danger mb-3">Error sending message!</div></div>
|
||||||
|
<!-- Submit Button-->
|
||||||
|
<button class="btn btn-primary text-uppercase disabled" id="submitButton" type="submit">Send</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<!-- Footer-->
|
||||||
|
<footer class="border-top">
|
||||||
|
<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">
|
||||||
|
<ul class="list-inline text-center">
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<a href="#!">
|
||||||
|
<span class="fa-stack fa-lg">
|
||||||
|
<i class="fas fa-circle fa-stack-2x"></i>
|
||||||
|
<i class="fab fa-twitter fa-stack-1x fa-inverse"></i>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<a href="#!">
|
||||||
|
<span class="fa-stack fa-lg">
|
||||||
|
<i class="fas fa-circle fa-stack-2x"></i>
|
||||||
|
<i class="fab fa-facebook-f fa-stack-1x fa-inverse"></i>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<a href="#!">
|
||||||
|
<span class="fa-stack fa-lg">
|
||||||
|
<i class="fas fa-circle fa-stack-2x"></i>
|
||||||
|
<i class="fab fa-github fa-stack-1x fa-inverse"></i>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="small text-center text-muted fst-italic">Copyright © Your Website 2023</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
<!-- Bootstrap core JS-->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<!-- Core theme JS-->
|
||||||
|
<script src="js/scripts.js"></script>
|
||||||
|
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *-->
|
||||||
|
<!-- * * SB Forms JS * *-->
|
||||||
|
<!-- * * Activate your form at https://startbootstrap.com/solution/contact-forms * *-->
|
||||||
|
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *-->
|
||||||
|
<script src="https://cdn.startbootstrap.com/sb-forms-latest.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
0
resources/themes/custom/css/bootstrap.min.css
vendored
Normal file
0
resources/themes/custom/css/bootstrap.min.css
vendored
Normal file
0
resources/themes/custom/css/bootstrap/_media.css
Normal file
0
resources/themes/custom/css/bootstrap/_media.css
Normal file
0
resources/themes/custom/css/style.css
Normal file
0
resources/themes/custom/css/style.css
Normal file
10798
resources/themes/custom/css/styles.css
Normal file
10798
resources/themes/custom/css/styles.css
Normal file
File diff suppressed because it is too large
Load Diff
0
resources/themes/custom/images/bg_1.jpg
Normal file
0
resources/themes/custom/images/bg_1.jpg
Normal file
0
resources/themes/custom/images/logo.jpg
Normal file
0
resources/themes/custom/images/logo.jpg
Normal file
153
resources/themes/custom/index.html
Normal file
153
resources/themes/custom/index.html
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||||
|
<meta name="description" content="" />
|
||||||
|
<meta name="author" content="" />
|
||||||
|
<title>Clean Blog - Start Bootstrap Theme</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="assets/favicon.ico" />
|
||||||
|
<!-- Font Awesome icons (free version)-->
|
||||||
|
<script src="https://use.fontawesome.com/releases/v6.3.0/js/all.js" crossorigin="anonymous"></script>
|
||||||
|
<!-- Google fonts-->
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic" rel="stylesheet" type="text/css" />
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet" type="text/css" />
|
||||||
|
<!-- Core theme CSS (includes Bootstrap)-->
|
||||||
|
<link href="css/styles.css" rel="stylesheet" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- Navigation-->
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-light" id="mainNav">
|
||||||
|
<div class="container px-4 px-lg-5">
|
||||||
|
<a class="navbar-brand" href="index.html">Start Bootstrap</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
Menu
|
||||||
|
<i class="fas fa-bars"></i>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||||
|
<ul class="navbar-nav ms-auto py-4 py-lg-0">
|
||||||
|
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="index.html">Home</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="about.html">About</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="post.html">Sample Post</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="contact.html">Contact</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<!-- Page Header-->
|
||||||
|
<header class="masthead" style="background-image: url('assets/img/home-bg.jpg')">
|
||||||
|
<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 Start Bootstrap</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="post.html">
|
||||||
|
<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="post.html"><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="post.html">
|
||||||
|
<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="post.html">
|
||||||
|
<h2 class="post-title">Failure is not an option</h2>
|
||||||
|
<h3 class="post-subtitle">Many say exploration is part of our destiny, but it’s 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>
|
||||||
|
<!-- Footer-->
|
||||||
|
<footer class="border-top">
|
||||||
|
<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">
|
||||||
|
<ul class="list-inline text-center">
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<a href="#!">
|
||||||
|
<span class="fa-stack fa-lg">
|
||||||
|
<i class="fas fa-circle fa-stack-2x"></i>
|
||||||
|
<i class="fab fa-twitter fa-stack-1x fa-inverse"></i>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<a href="#!">
|
||||||
|
<span class="fa-stack fa-lg">
|
||||||
|
<i class="fas fa-circle fa-stack-2x"></i>
|
||||||
|
<i class="fab fa-facebook-f fa-stack-1x fa-inverse"></i>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<a href="#!">
|
||||||
|
<span class="fa-stack fa-lg">
|
||||||
|
<i class="fas fa-circle fa-stack-2x"></i>
|
||||||
|
<i class="fab fa-github fa-stack-1x fa-inverse"></i>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="small text-center text-muted fst-italic">Copyright © Your Website 2023</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
<!-- Bootstrap core JS-->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<!-- Core theme JS-->
|
||||||
|
<script src="js/scripts.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
0
resources/themes/custom/js/bootstrap.min.js
vendored
Normal file
0
resources/themes/custom/js/bootstrap.min.js
vendored
Normal file
0
resources/themes/custom/js/jquery.min.js
vendored
Normal file
0
resources/themes/custom/js/jquery.min.js
vendored
Normal file
30
resources/themes/custom/js/main.js
Normal file
30
resources/themes/custom/js/main.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
(function ($) {
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var fullHeight = function () {
|
||||||
|
|
||||||
|
$('.js-fullheight').css('height', $(window).height());
|
||||||
|
$(window).resize(function () {
|
||||||
|
$('.js-fullheight').css('height', $(window).height());
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
fullHeight();
|
||||||
|
|
||||||
|
$('#sidebarCollapse').on('click', function () {
|
||||||
|
$('#sidebar').toggleClass('active');
|
||||||
|
});
|
||||||
|
|
||||||
|
})(jQuery);
|
||||||
|
|
||||||
|
window.addEventListener("load", function () {
|
||||||
|
let closeBtn = document.querySelectorAll(".closeAlertBtn");
|
||||||
|
closeBtn.forEach((item) => {
|
||||||
|
item.addEventListener("click", closeAlert);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function closeAlert() {
|
||||||
|
this.parentNode.setAttribute("style", "display: none;")
|
||||||
|
}
|
||||||
5
resources/themes/custom/js/popper.js
Normal file
5
resources/themes/custom/js/popper.js
Normal file
File diff suppressed because one or more lines are too long
29
resources/themes/custom/js/scripts.js
Normal file
29
resources/themes/custom/js/scripts.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
/*!
|
||||||
|
* Start Bootstrap - Clean Blog v6.0.9 (https://startbootstrap.com/theme/clean-blog)
|
||||||
|
* Copyright 2013-2023 Start Bootstrap
|
||||||
|
* Licensed under MIT (https://github.com/StartBootstrap/startbootstrap-clean-blog/blob/master/LICENSE)
|
||||||
|
*/
|
||||||
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
|
let scrollPos = 0;
|
||||||
|
const mainNav = document.getElementById('mainNav');
|
||||||
|
const headerHeight = mainNav.clientHeight;
|
||||||
|
window.addEventListener('scroll', function() {
|
||||||
|
const currentTop = document.body.getBoundingClientRect().top * -1;
|
||||||
|
if ( currentTop < scrollPos) {
|
||||||
|
// Scrolling Up
|
||||||
|
if (currentTop > 0 && mainNav.classList.contains('is-fixed')) {
|
||||||
|
mainNav.classList.add('is-visible');
|
||||||
|
} else {
|
||||||
|
console.log(123);
|
||||||
|
mainNav.classList.remove('is-visible', 'is-fixed');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Scrolling Down
|
||||||
|
mainNav.classList.remove(['is-visible']);
|
||||||
|
if (currentTop > headerHeight && !mainNav.classList.contains('is-fixed')) {
|
||||||
|
mainNav.classList.add('is-fixed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scrollPos = currentTop;
|
||||||
|
});
|
||||||
|
})
|
||||||
127
resources/themes/custom/post.html
Normal file
127
resources/themes/custom/post.html
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||||
|
<meta name="description" content="" />
|
||||||
|
<meta name="author" content="" />
|
||||||
|
<title>Clean Blog - Start Bootstrap Theme</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="assets/favicon.ico" />
|
||||||
|
<!-- Font Awesome icons (free version)-->
|
||||||
|
<script src="https://use.fontawesome.com/releases/v6.3.0/js/all.js" crossorigin="anonymous"></script>
|
||||||
|
<!-- Google fonts-->
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic" rel="stylesheet" type="text/css" />
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet" type="text/css" />
|
||||||
|
<!-- Core theme CSS (includes Bootstrap)-->
|
||||||
|
<link href="css/styles.css" rel="stylesheet" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- Navigation-->
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-light" id="mainNav">
|
||||||
|
<div class="container px-4 px-lg-5">
|
||||||
|
<a class="navbar-brand" href="index.html">Start Bootstrap</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
Menu
|
||||||
|
<i class="fas fa-bars"></i>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||||
|
<ul class="navbar-nav ms-auto py-4 py-lg-0">
|
||||||
|
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="index.html">Home</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="about.html">About</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="post.html">Sample Post</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="contact.html">Contact</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<!-- Page Header-->
|
||||||
|
<header class="masthead" style="background-image: url('assets/img/post-bg.jpg')">
|
||||||
|
<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="post-heading">
|
||||||
|
<h1>Man must explore, and this is exploration at its greatest</h1>
|
||||||
|
<h2 class="subheading">Problems look mighty small from 150 miles up</h2>
|
||||||
|
<span class="meta">
|
||||||
|
Posted by
|
||||||
|
<a href="#!">Start Bootstrap</a>
|
||||||
|
on August 24, 2023
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<!-- Post Content-->
|
||||||
|
<article 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>Never in all their history have men been able truly to conceive of the world as one: a single sphere, a globe, having the qualities of a globe, a round earth in which all the directions eventually meet, in which there is no center because every point, or none, is center — an equal earth which all men occupy as equals. The airman's earth, if free men make it, will be truly round: a globe in practice, not in theory.</p>
|
||||||
|
<p>Science cuts two ways, of course; its products can be used for both good and evil. But there's no turning back from science. The early warnings about technological dangers also come from science.</p>
|
||||||
|
<p>What was most significant about the lunar voyage was not that man set foot on the Moon but that they set eye on the earth.</p>
|
||||||
|
<p>A Chinese tale tells of some men sent to harm a young girl who, upon seeing her beauty, become her protectors rather than her violators. That's how I felt seeing the Earth for the first time. I could not help but love and cherish her.</p>
|
||||||
|
<p>For those who have seen the Earth from space, and for the hundreds and perhaps thousands more who will, the experience most certainly changes your perspective. The things that we share in our world are far more valuable than those which divide us.</p>
|
||||||
|
<h2 class="section-heading">The Final Frontier</h2>
|
||||||
|
<p>There can be no thought of finishing for ‘aiming for the stars.’ Both figuratively and literally, it is a task to occupy the generations. And no matter how much progress one makes, there is always the thrill of just beginning.</p>
|
||||||
|
<p>There can be no thought of finishing for ‘aiming for the stars.’ Both figuratively and literally, it is a task to occupy the generations. And no matter how much progress one makes, there is always the thrill of just beginning.</p>
|
||||||
|
<blockquote class="blockquote">The dreams of yesterday are the hopes of today and the reality of tomorrow. Science has not yet mastered prophecy. We predict too much for the next year and yet far too little for the next ten.</blockquote>
|
||||||
|
<p>Spaceflights cannot be stopped. This is not the work of any one man or even a group of men. It is a historical process which mankind is carrying out in accordance with the natural laws of human development.</p>
|
||||||
|
<h2 class="section-heading">Reaching for the Stars</h2>
|
||||||
|
<p>As we got further and further away, it [the Earth] diminished in size. Finally it shrank to the size of a marble, the most beautiful you can imagine. That beautiful, warm, living object looked so fragile, so delicate, that if you touched it with a finger it would crumble and fall apart. Seeing this has to change a man.</p>
|
||||||
|
<a href="#!"><img class="img-fluid" src="assets/img/post-sample-image.jpg" alt="..." /></a>
|
||||||
|
<span class="caption text-muted">To go places and do things that have never been done before – that’s what living is all about.</span>
|
||||||
|
<p>Space, the final frontier. These are the voyages of the Starship Enterprise. Its five-year mission: to explore strange new worlds, to seek out new life and new civilizations, to boldly go where no man has gone before.</p>
|
||||||
|
<p>As I stand out here in the wonders of the unknown at Hadley, I sort of realize there’s a fundamental truth to our nature, Man must explore, and this is exploration at its greatest.</p>
|
||||||
|
<p>
|
||||||
|
Placeholder text by
|
||||||
|
<a href="http://spaceipsum.com/">Space Ipsum</a>
|
||||||
|
· Images by
|
||||||
|
<a href="https://www.flickr.com/photos/nasacommons/">NASA on The Commons</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
<!-- Footer-->
|
||||||
|
<footer class="border-top">
|
||||||
|
<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">
|
||||||
|
<ul class="list-inline text-center">
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<a href="#!">
|
||||||
|
<span class="fa-stack fa-lg">
|
||||||
|
<i class="fas fa-circle fa-stack-2x"></i>
|
||||||
|
<i class="fab fa-twitter fa-stack-1x fa-inverse"></i>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<a href="#!">
|
||||||
|
<span class="fa-stack fa-lg">
|
||||||
|
<i class="fas fa-circle fa-stack-2x"></i>
|
||||||
|
<i class="fab fa-facebook-f fa-stack-1x fa-inverse"></i>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<a href="#!">
|
||||||
|
<span class="fa-stack fa-lg">
|
||||||
|
<i class="fas fa-circle fa-stack-2x"></i>
|
||||||
|
<i class="fab fa-github fa-stack-1x fa-inverse"></i>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="small text-center text-muted fst-italic">Copyright © Your Website 2023</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
<!-- Bootstrap core JS-->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<!-- Core theme JS-->
|
||||||
|
<script src="js/scripts.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
resources/themes/custom/preview.png
Normal file
BIN
resources/themes/custom/preview.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 918 KiB |
0
resources/themes/custom/scss/bootstrap/_alert.scss
Normal file
0
resources/themes/custom/scss/bootstrap/_alert.scss
Normal file
0
resources/themes/custom/scss/bootstrap/_badge.scss
Normal file
0
resources/themes/custom/scss/bootstrap/_badge.scss
Normal file
0
resources/themes/custom/scss/bootstrap/_card.scss
Normal file
0
resources/themes/custom/scss/bootstrap/_card.scss
Normal file
0
resources/themes/custom/scss/bootstrap/_close.scss
Normal file
0
resources/themes/custom/scss/bootstrap/_close.scss
Normal file
0
resources/themes/custom/scss/bootstrap/_code.scss
Normal file
0
resources/themes/custom/scss/bootstrap/_code.scss
Normal file
0
resources/themes/custom/scss/bootstrap/_forms.scss
Normal file
0
resources/themes/custom/scss/bootstrap/_forms.scss
Normal file
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user