diff --git a/.gitignore b/.gitignore
index 4dbb9c8..42349cc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,4 +4,5 @@ vendor
views_cache
resources/upload
resources/tmp
-composer.lock
\ No newline at end of file
+composer.lock
+kernel/app_themes
\ No newline at end of file
diff --git a/bootstrap.php b/bootstrap.php
index b9b36fc..5552476 100644
--- a/bootstrap.php
+++ b/bootstrap.php
@@ -7,6 +7,7 @@ $dotenv->load();
include_once __DIR__ . "/bootstrap/db.php";
include_once __DIR__ . "/bootstrap/header.php";
include_once __DIR__ . "/bootstrap/secure.php";
+include_once __DIR__ . "/bootstrap/notification.php";
const ROOT_DIR = __DIR__;
const KERNEL_DIR = __DIR__ . "/kernel";
const KERNEL_MODULES_DIR = __DIR__ . "/kernel/modules";
@@ -18,7 +19,7 @@ const KERNEL_APP_MODULES_DIR = KERNEL_DIR . "/app_modules";
const APP_DIR = ROOT_DIR . "/app";
-
+\kernel\Theme::$assetsCollector = new \kernel\AssetsCollector();
function getConst($text): array|false|string
{
diff --git a/bootstrap/notification.php b/bootstrap/notification.php
new file mode 100644
index 0000000..ff60dfd
--- /dev/null
+++ b/bootstrap/notification.php
@@ -0,0 +1,4 @@
+addChannel('email', new \kernel\modules\notification\channels\EmailChannel());
\ No newline at end of file
diff --git a/kernel/App.php b/kernel/App.php
index 7ce9803..e5a34cb 100644
--- a/kernel/App.php
+++ b/kernel/App.php
@@ -5,6 +5,7 @@ namespace kernel;
use kernel\helpers\Debug;
+use kernel\modules\notification\NotificationDispatcher;
use kernel\modules\user\models\User;
use kernel\services\ModuleService;
use kernel\services\ThemeService;
@@ -21,10 +22,14 @@ class App
static User $user;
+ static NotificationDispatcher $notificationDispatcher;
+
static array $secure;
public ModuleService $moduleService;
+ static Hook $hook;
+
public ThemeService $themeService;
public static Database $db;
@@ -41,6 +46,7 @@ class App
public function load(): static
{
+ App::$hook = new Hook();
$this->moduleService = new ModuleService();
App::$collector = new CgRouteCollector();
$this->setRouting();
@@ -53,6 +59,7 @@ class App
include KERNEL_DIR . "/routs/admin.php";
include ROOT_DIR . "/rout.php";
$modules_routs = $this->moduleService->getModulesRouts();
+ $this->moduleService->setModulesHooks();
foreach ($modules_routs as $rout){
include "$rout";
}
diff --git a/kernel/Assets.php b/kernel/Assets.php
index 30c2c8b..b0af05a 100644
--- a/kernel/Assets.php
+++ b/kernel/Assets.php
@@ -7,8 +7,12 @@ class Assets
protected array $jsHeader = [];
protected array $jsBody = [];
+ protected array $collectorJs = [];
+
protected array $css = [];
+ protected array $collectorCss = [];
+
protected string $resourceURI = "/resource";
public function __construct(string $resourceURI)
@@ -26,7 +30,7 @@ class Assets
$this->resourceURI = $resourceURI;
}
- public function registerJS(string $slug, string $resource, bool $body = true, bool $addResourceURI = true): void
+ public function registerJS(string $slug, string $resource, bool $body = true, bool $addResourceURI = true, string $after = null): void
{
$resource = $addResourceURI ? $this->resourceURI . $resource : $resource;
if ($body) {
@@ -34,12 +38,14 @@ class Assets
} else {
$this->jsHeader[$slug] = $resource;
}
+ $this->collectorJs[$slug] = ['resource' => $resource, 'after' => $after, 'body' => $body];
}
- public function registerCSS(string $slug, string $resource, bool $addResourceURI = true): void
+ public function registerCSS(string $slug, string $resource, bool $addResourceURI = true, string $after = null): void
{
$resource = $addResourceURI ? $this->resourceURI . $resource : $resource;
$this->css[$slug] = $resource;
+ $this->collectorCss[$slug] = ['resource' => $resource, 'after' => $after];
}
public function getJSAsStr(bool $body = true): void
@@ -63,4 +69,14 @@ class Assets
}
}
+ public function getCollectorCss(): array
+ {
+ return $this->collectorCss;
+ }
+
+ public function getCollectorJs(): array
+ {
+ return $this->collectorJs;
+ }
+
}
\ No newline at end of file
diff --git a/kernel/AssetsCollector.php b/kernel/AssetsCollector.php
new file mode 100644
index 0000000..a6d2527
--- /dev/null
+++ b/kernel/AssetsCollector.php
@@ -0,0 +1,128 @@
+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 '' . "\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 '' . "\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;
+ }
+
+}
\ No newline at end of file
diff --git a/kernel/CollectionTableRenderer.php b/kernel/CollectionTableRenderer.php
new file mode 100644
index 0000000..3791fc9
--- /dev/null
+++ b/kernel/CollectionTableRenderer.php
@@ -0,0 +1,345 @@
+ '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 = '
buildAttributes($this->tableAttributes) . '>';
+ $html .= $this->renderHeader();
+ if ($this->filterRowEnabled) {
+ $html .= $this->renderFilterRow();
+ }
+ $html .= $this->renderBody();
+ $html .= '
';
+
+ 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 = '';
+
+ // Обычные колонки
+ foreach ($this->columns as $title) {
+ $html .= '' . htmlspecialchars($title) . ' | ';
+ }
+
+ // Кастомные колонки
+ foreach ($this->customColumns as $column) {
+ $html .= '' . htmlspecialchars($column['title']) . ' | ';
+ }
+
+ $html .= '
';
+
+ return $html;
+ }
+
+ /**
+ * Генерация строки фильтров
+ *
+ * @return string
+ */
+ protected function renderFilterRow(): string
+ {
+ $html = 'buildAttributes($this->filterRowAttributes) . '>';
+
+ // Обычные колонки
+ foreach (array_keys($this->columns) as $field) {
+ $html .= '';
+ if (isset($this->filters[$field])) {
+ $html .= $this->filters[$field];
+ } else {
+ $html .= ' ';
+ }
+ $html .= ' | ';
+ }
+
+ // Кастомные колонки
+ foreach (array_keys($this->customColumns) as $columnName) {
+ $html .= '';
+ if (isset($this->filters[$columnName])) {
+ $html .= $this->filters[$columnName];
+ } else {
+ $html .= ' ';
+ }
+ $html .= ' | ';
+ }
+
+ $html .= '
';
+
+ return $html;
+ }
+
+ /**
+ * Генерация тела таблицы
+ *
+ * @return string
+ */
+ protected function renderBody(): string
+ {
+ $html = '';
+
+ foreach ($this->collection as $item) {
+ $html .= '';
+
+ // Обычные колонки
+ foreach (array_keys($this->columns) as $field) {
+ $value = $this->getValue($item, $field);
+ $value = $this->processValue($field, $value, $item);
+ $html .= '' . $value . ' | ';
+ }
+
+ // Кастомные колонки
+ foreach ($this->customColumns as $columnName => $column) {
+ $value = call_user_func($column['callback'], $item, $columnName);
+ $html .= '' . $value . ' | ';
+ }
+
+ $html .= '
';
+ }
+
+ $html .= '';
+
+ 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();
+ }
+}
\ No newline at end of file
diff --git a/kernel/EloquentDataProvider.php b/kernel/EloquentDataProvider.php
deleted file mode 100644
index d1ca335..0000000
--- a/kernel/EloquentDataProvider.php
+++ /dev/null
@@ -1,482 +0,0 @@
-query = $query;
- }
-
- public function setPerPage(int $perPage): self
- {
- $this->perPage = max(1, $perPage);
- return $this;
- }
-
- public function setFilters(array $filters): self
- {
- $this->filters = $filters;
- return $this;
- }
-
- public function setSort(array $sort): self
- {
- $this->sort = $sort;
- return $this;
- }
-
- public function with(array $relations): self
- {
- $this->with = $relations;
- return $this;
- }
-
- public function withCount(array $relations): self
- {
- $this->withCount = $relations;
- return $this;
- }
-
- public function setSearch(array $search): self
- {
- $this->search = $search;
- return $this;
- }
-
- public function allowFilters(array $filters): self
- {
- $this->allowedFilters = $filters;
- return $this;
- }
-
- public function allowSorts(array $sorts): self
- {
- $this->allowedSorts = $sorts;
- return $this;
- }
-
- public function allowSearch(array $fields): self
- {
- $this->allowedSearch = $fields;
- return $this;
- }
-
- public function setDefaultSort(array $sort): self
- {
- $this->defaultSort = $sort;
- return $this;
- }
-
- public function beforeQuery(callable $callback): self
- {
- $this->beforeQueryCallbacks[] = $callback;
- return $this;
- }
-
- public function afterQuery(callable $callback): self
- {
- $this->afterQueryCallbacks[] = $callback;
- return $this;
- }
-
- protected function applyFilters(): void
- {
- foreach ($this->filters as $field => $value) {
- if (!$this->isFilterAllowed($field)) {
- continue;
- }
-
- if (is_array($value)) {
- $this->applyArrayFilter($field, $value);
- } elseif (Str::contains($field, '.')) {
- $this->applyRelationFilter($field, $value);
- } elseif ($value !== null && $value !== '') {
- $this->query->where($field, $value);
- }
- }
- }
-
- protected function applyArrayFilter(string $field, array $value): void
- {
- $operator = strtolower($value[0] ?? null);
- $operand = $value[1] ?? null;
-
- switch ($operator) {
- case 'in':
- $this->query->whereIn($field, (array)$operand);
- break;
- case 'not in':
- $this->query->whereNotIn($field, (array)$operand);
- break;
- case 'between':
- $this->query->whereBetween($field, (array)$operand);
- break;
- case 'not between':
- $this->query->whereNotBetween($field, (array)$operand);
- break;
- case 'null':
- $this->query->whereNull($field);
- break;
- case 'not null':
- $this->query->whereNotNull($field);
- break;
- case 'like':
- $this->query->where($field, 'like', "%{$operand}%");
- break;
- case '>':
- case '<':
- case '>=':
- case '<=':
- case '!=':
- $this->query->where($field, $operator, $operand);
- break;
- default:
- $this->query->whereIn($field, $value);
- }
- }
-
- protected function applyRelationFilter(string $field, $value): void
- {
- [$relation, $column] = explode('.', $field, 2);
-
- $this->query->whereHas($relation, function ($query) use ($column, $value) {
- if (is_array($value)) {
- $query->whereIn($column, $value);
- } else {
- $query->where($column, $value);
- }
- });
- }
-
- protected function applySort(): void
- {
- if (empty($this->sort) && !empty($this->defaultSort)) {
- $this->sort = $this->defaultSort;
- }
-
- foreach ($this->sort as $field => $direction) {
- if (!$this->isSortAllowed($field)) {
- continue;
- }
-
- $direction = strtolower($direction) === 'desc' ? 'desc' : 'asc';
-
- if (Str::contains($field, '.')) {
- $this->applyRelationSort($field, $direction);
- } else {
- $this->query->orderBy($field, $direction);
- }
- }
- }
-
- protected function applyRelationSort(string $field, string $direction): void
- {
- [$relation, $column] = explode('.', $field, 2);
-
- $this->query->with([$relation => function ($query) use ($column, $direction) {
- $query->orderBy($column, $direction);
- }]);
- }
-
- protected function applySearch(): void
- {
- if (empty($this->search) || empty($this->allowedSearch)) {
- return;
- }
-
- $searchTerm = Arr::get($this->search, 'term', '');
- if (empty($searchTerm)) {
- return;
- }
-
- $this->query->where(function ($query) use ($searchTerm) {
- foreach ($this->allowedSearch as $field) {
- if (Str::contains($field, '.')) {
- [$relation, $column] = explode('.', $field, 2);
- $query->orWhereHas($relation, function ($q) use ($column, $searchTerm) {
- $q->where($column, 'like', "%{$searchTerm}%");
- });
- } else {
- $query->orWhere($field, 'like', "%{$searchTerm}%");
- }
- }
- });
- }
-
- protected function applyRelations(): void
- {
- if (!empty($this->with)) {
- $this->query->with($this->with);
- }
-
- if (!empty($this->withCount)) {
- $this->query->withCount($this->withCount);
- }
- }
-
- protected function isFilterAllowed(string $field): bool
- {
- if (empty($this->allowedFilters)) {
- return true;
- }
-
- $baseField = Str::before($field, '.');
-
- return in_array($field, $this->allowedFilters) ||
- in_array($baseField, $this->allowedFilters);
- }
-
- protected function isSortAllowed(string $field): bool
- {
- if (empty($this->allowedSorts)) {
- return true;
- }
-
- $baseField = Str::before($field, '.');
-
- return in_array($field, $this->allowedSorts) ||
- in_array($baseField, $this->allowedSorts);
- }
-
- protected function executeCallbacks(array $callbacks): void
- {
- foreach ($callbacks as $callback) {
- call_user_func($callback, $this->query);
- }
- }
-
- /**
- * Получение данных с ручной пагинацией
- */
- public function getManualPaginated(int $page = 1, int $perPage = null): array
- {
- $perPage = $perPage ?? $this->perPage;
-
- $this->applyRelations();
- $this->applyFilters();
- $this->applySearch();
- $this->applySort();
-
- $total = $this->query->count();
- $results = $this->query
- ->offset(($page - 1) * $perPage)
- ->limit($perPage)
- ->get();
-
- return [
- 'data' => $results,
- 'meta' => [
- 'total' => $total,
- 'per_page' => $perPage,
- 'current_page' => $page,
- 'last_page' => ceil($total / $perPage),
- ]
- ];
- }
-
- public function getAll(): \Illuminate\Database\Eloquent\Collection
- {
- $this->executeCallbacks($this->beforeQueryCallbacks);
-
- $this->applyRelations();
- $this->applyFilters();
- $this->applySearch();
- $this->applySort();
-
- $result = $this->query->get();
-
- $this->executeCallbacks($this->afterQueryCallbacks);
-
- return $result;
- }
-
- public function getFirst(): ?\Illuminate\Database\Eloquent\Model
- {
- $this->executeCallbacks($this->beforeQueryCallbacks);
-
- $this->applyRelations();
- $this->applyFilters();
- $this->applySearch();
- $this->applySort();
-
- $result = $this->query->first();
-
- $this->executeCallbacks($this->afterQueryCallbacks);
-
- return $result;
- }
-
- /**
- * Генерирует массив с основными ссылками пагинации
- *
- * @param array $meta Мета-информация из getManualPaginated()
- * @param string|null $baseUrl Базовый URL (если null - определит автоматически)
- * @return array
- */
- public function getPaginationLinks(array $meta, ?string $baseUrl = null): array
- {
- $currentPage = $meta['current_page'];
- $lastPage = $meta['last_page'];
-
- // Определяем базовый URL
- $baseUrl = $this->normalizeBaseUrl($baseUrl);
-
- $links = [
- 'first' => null,
- 'previous' => null,
- 'current' => $this->buildPageUrl($baseUrl, $currentPage),
- 'next' => null,
- 'last' => null,
- ];
-
- // Первая страница (если не на первой)
- if ($currentPage > 1) {
- $links['first'] = $this->buildPageUrl($baseUrl, 1);
- }
-
- // Предыдущая страница
- if ($currentPage > 1) {
- $links['previous'] = $this->buildPageUrl($baseUrl, $currentPage - 1);
- }
-
- // Следующая страница
- if ($currentPage < $lastPage) {
- $links['next'] = $this->buildPageUrl($baseUrl, $currentPage + 1);
- }
-
- // Последняя страница (если не на последней)
- if ($currentPage < $lastPage) {
- $links['last'] = $this->buildPageUrl($baseUrl, $lastPage);
- }
-
- // Дополнительная мета-информация
- $links['meta'] = [
- 'current_page' => $currentPage,
- 'last_page' => $lastPage,
- 'per_page' => $meta['per_page'],
- 'total' => $meta['total']
- ];
-
- return $links;
- }
-
- /**
- * Нормализует базовый URL
- */
- protected function normalizeBaseUrl(?string $url): string
- {
- if ($url !== null) {
- return $url;
- }
-
- if (function_exists('url')) {
- // Laravel
- return url()->current();
- }
-
- // Чистый PHP
- $protocol = ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || ($_SERVER['SERVER_PORT'] ?? null) == 443) ? 'https://' : 'http://';
-
- $host = $_SERVER['HTTP_HOST'] ?? 'localhost';
- $uri = $_SERVER['REQUEST_URI'] ?? '/';
-
- // Удаляем параметр page если он есть
- $uri = preg_replace('/([?&])page=[^&]*(&|$)/', '$1', $uri);
-
- return $protocol . $host . rtrim($uri, '?&');
- }
-
- /**
- * Строит URL для конкретной страницы
- */
- protected function buildPageUrl(string $baseUrl, int $page): string
- {
- if ($page <= 1) {
- return $baseUrl; // Первая страница без параметра
- }
-
- $separator = strpos($baseUrl, '?') === false ? '?' : '&';
- return $baseUrl . $separator . 'page=' . $page;
- }
-
- public function renderPaginationLinks(array $meta, string $url, int $showPages = 5): string
- {
- $currentPage = $meta['current_page'];
- $lastPage = $meta['last_page'];
-
- // Определяем диапазон страниц для отображения
- $startPage = max(1, $currentPage - floor($showPages / 2));
- $endPage = min($lastPage, $startPage + $showPages - 1);
-
- $html = '';
-
- return $html;
- }
-
- public function setPaginationTemplate(array $tpl)
- {
-
- }
-
- public function getQuery(): Builder
- {
- return $this->query;
- }
-}
\ No newline at end of file
diff --git a/kernel/EntityRelation.php b/kernel/EntityRelation.php
index 0b29061..6050f20 100644
--- a/kernel/EntityRelation.php
+++ b/kernel/EntityRelation.php
@@ -209,11 +209,11 @@ class EntityRelation
return [];
}
- public function getAdditionalPropertyByEntityId(string $entity, string $entity_id, string $additionalPropertySlug, array $params = []): string
+ public function getAdditionalPropertyByEntityId(string $entity, string $entity_id, string $additionalPropertySlug): string
{
$moduleClass = $this->getAdditionalPropertyClassBySlug($additionalPropertySlug);
if ($moduleClass and method_exists($moduleClass, "getItem")) {
- return $moduleClass->getItem($entity, $entity_id, $params);
+ return $moduleClass->getItem($entity, $entity_id);
}
return "";
@@ -276,20 +276,4 @@ class EntityRelation
}
}
}
-
- public function callModuleMethod(string $slug, string $method, array $params)
- {
- $module = $this->moduleService->getModuleInfoBySlug($slug);
- if (isset($module['module_class'])) {
- $moduleClass = new $module['module_class']();
- if (method_exists($moduleClass, $method)) {
-
- return call_user_func_array([$moduleClass, $method], $params);
- } else {
- echo "Метод $method не существует";
- }
- }
-
- return false;
- }
}
\ No newline at end of file
diff --git a/kernel/Hook.php b/kernel/Hook.php
new file mode 100644
index 0000000..b938356
--- /dev/null
+++ b/kernel/Hook.php
@@ -0,0 +1,41 @@
+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;
+ }
+
+}
\ No newline at end of file
diff --git a/kernel/Request.php b/kernel/Request.php
index 257f135..0a6229c 100644
--- a/kernel/Request.php
+++ b/kernel/Request.php
@@ -132,18 +132,6 @@ class Request
return $_GET[$param] ?? $defaultValue;
}
- /**
- * @param string $param
- * @return mixed
- */
- public function except(string $param): mixed
- {
- $params = $this->get();
- unset($param);
-
- return $params;
- }
-
/**
* Возвращает POST - параметр.
diff --git a/kernel/Theme.php b/kernel/Theme.php
new file mode 100644
index 0000000..17ca504
--- /dev/null
+++ b/kernel/Theme.php
@@ -0,0 +1,15 @@
+registerAsset(new \kernel\admin_themes\default\DefaultAdminThemeAssets($resources));
?>
@@ -18,8 +22,8 @@ $assets = new \kernel\admin_themes\default\DefaultAdminThemeAssets($resources)
- getCSSAsSTR(); ?>
- getJSAsStr(body: false); ?>
+ renderCss(); ?>
+ renderJs(body: false); ?>
@@ -63,6 +67,15 @@ $assets = new \kernel\admin_themes\default\DefaultAdminThemeAssets($resources)
Выход
+
+ About
+
+
+ Portfolio
+
+
+ Contact
+
@@ -83,6 +96,6 @@ $assets = new \kernel\admin_themes\default\DefaultAdminThemeAssets($resources)
-getJSAsStr(); ?>
+renderJs(); ?>