Initial commit

This commit is contained in:
Ivan Petrov
2025-12-24 19:19:01 +03:00
commit a7097c6178
19493 changed files with 94306 additions and 0 deletions

110
engine/Main/trait.admin.php Normal file
View File

@@ -0,0 +1,110 @@
<?php
defined('ROOT_DIR') || exit;
trait Admin {
public array $admin_pages = array();
public array $admin_pages_list = array();
public function admin_page_set($parent_slug, $page_slug, $page_title, $params, $function, $hide = false, $icon = false)
{
if(!isset($params)) $params = array();
$params["page"] = $page_slug;
if($parent_slug && isset($this->admin_pages_list[$parent_slug])) {
$parent = $this->admin_pages_list[$parent_slug];
$link = &$parent["link"];
$parent_level = $link["level"];
$page_info = array(
"level" => $parent_level + 1,
"title" => $page_title,
"function" => $function,
"pages" => array(),
"params" => $params,
"hide" => $hide,
"icon" => $icon,
"slug" => $page_slug
);
$link["pages"][$page_slug] = $page_info;
$page_info["link"] = &$link["pages"][$page_slug];
} else {
$page_info = array(
"level" => 0,
"title" => $page_title,
"function" => $function,
"pages" => array(),
"params" => $params,
"hide" => $hide,
"icon" => $icon,
"slug" => $page_slug
);
$this->admin_pages[$page_slug] = $page_info;
$page_info["link"] = &$this->admin_pages[$page_slug];
}
$this->admin_pages_list[$page_slug] = $page_info;
}
// Проверить есть ли у страницы дочерний элемент по с нужной подстрокой
public function admin_page_has_search($parent_slug, $search): bool
{
if(!$search) return true;
$parent_page = $this->admin_pages_list[$parent_slug];
$parent_page_link = &$parent_page["link"];
if(mb_strpos(mb_strtolower($parent_page["title"]), mb_strtolower($search)) !== false) return true;
return $this->admin_page_has_search_recurse($parent_page_link["pages"], $search);
}
// Рекурсивная функция проверки дочерних элементов для поиска подстроки
private function admin_page_has_search_recurse($pages, $search): bool
{
foreach ($pages as $key => $page) {
if(mb_strpos(mb_strtolower($page["title"]), mb_strtolower($search)) !== false) return true;
$result = $this->admin_page_has_child_recurse($page["pages"], $search);
if($result === true) return true;
}
return false;
}
// Проверить есть ли у страницы дочерний элемент
public function admin_page_has_child($parent_slug, $page_slug): bool
{
$parent_page = &$this->admin_pages_list[$parent_slug]["link"];
if($parent_slug == $page_slug) return true;
return $this->admin_page_has_child_recurse($parent_page["pages"], $page_slug);
}
// Рекурсивная функция проверки дочерних элементов
private function admin_page_has_child_recurse($pages, $page_slug): bool
{
foreach ($pages as $key => $page) {
if($key === $page_slug) return true;
$result = $this->admin_page_has_child_recurse($page["pages"], $page_slug);
if($result === true) return true;
}
return false;
}
// Отрисовка страницы в админ панели
public function admin_page_render($slug, $dive = false): bool
{
$page = $this->admin_pages_list[$slug];
if(!isset($page)) return false;
$link = &$page["link"];
if($page["level"] == 0 && $dive === true)
return $this->admin_page_render(array_shift($link["pages"])["slug"]);
if(is_callable($page["function"])) {
$page["function"]();
return true;
}
$func_name = $page["function"];
$func_body = $GLOBALS[$func_name];
$func_body();
return true;
}
}

View File

@@ -0,0 +1,50 @@
<?php
// Работа с уведомлениями
defined('ROOT_DIR') || exit;
trait Alerts {
// Получить список уведомлений
public function alerts_get_list($group = "main")
{
$session_name = $this->session_get_name();
$alerts = $this->db_query("SELECT * FROM `bive_alerts` WHERE `session` = ? AND `is_read` = 0 AND `group` = ?", array($session_name, $group), false);
if (!count($alerts)) return false;
$this->alerts_read_all($group);
return $alerts;
}
// Добавить уведомление
public function alerts_add($message, $type = "notice", $group = "main")
{
$session_name = $this->session_get_name();
$this->db_query("INSERT INTO `bive_alerts`(`session`, `message`, `type`, `group`) VALUES (?, ?, ?, ?)", array($session_name, $message, $type, $group), true);
}
// Пометить все уведомления как прочитанные
public function alerts_read_all($group = "main")
{
$session_name = $this->session_get_name();
$this->db_query("UPDATE `bive_alerts` SET `is_read` = 1 WHERE `session` = ? AND `is_read` = 0 AND `group` = ?", array($session_name, $group), true);
}
public function alerts_view($group = "main"): bool
{
$alerts = $this->alerts_get_list($group);
if(!$alerts) return false;
$alerts_dom = new DOM("div");
$alerts_dom->setAttribute("class", "b_alerts");
foreach ($alerts as $key => $alert) {
$alert_dom = new DOM("div");
$alert_dom->setAttribute("class", "b_alert " . $alert["type"]);
$alert_dom->prepend($alert["message"]);
$alerts_dom->prepend($alert_dom);
}
$alerts_dom->view();
return true;
}
}

View File

@@ -0,0 +1,133 @@
<?php
// Работа с выводом содержимого пользователям
defined('ROOT_DIR') || exit;
trait Content {
private array $meta_tags = array();
private array $link_tags = array();
private array $script_tags = array();
private string $title = "Bive Engine";
// олучить значение заголовка
public function title_get(): string
{
return $this->title;
}
// Поместить значение заголовка
public function title_set($title): bool
{
$this->title = $title;
return true;
}
// Добавить мета тег
public function meta_add($name, $content): bool
{
$this->meta_tags[$name] = $content;
return true;
}
// Удалить значение
public function meta_remove($name): bool
{
if(!isset($this->meta_tags[$name])) return false;
unlink($this->meta_tags[$name]);
return true;
}
// Рендер мета тегов
public function meta_render()
{
$meta_dom = new DOM("meta", false);
$meta_dom->setAttribute("charset", "utf-8");
echo $meta_dom->render();
foreach ($this->meta_tags as $key => $value) {
$meta_dom = new DOM("meta", false);
$meta_dom->setAttribute("name", $key);
$meta_dom->setAttribute("content", $this->get_view($value));
echo $meta_dom->render();
}
}
// Добавить тег Link
public function link_add($params): bool
{
if(gettype($params) != "array") return false;
$this->link_tags[] = $params;
return true;
}
// Отрендерить теги типа Link
public function link_render()
{
foreach ($this->link_tags as $key => $link_params) {
$link_dom = new DOM("link", false);
foreach ($link_params as $index => $value){
$link_dom->setAttribute($index, $value);
}
echo $link_dom->render();
}
}
// Подключить CSS скрипт
public function css_style_connect($path): bool
{
if(!$path) return false;
$this->link_add(array("rel" => "stylesheet", "href" => $path));
return true;
}
// Добавить тег Script
public function script_add($params): bool
{
if(gettype($params) != "array") return false;
$this->script_tags[] = $params;
return true;
}
// Отрендерить теги типа Script
public function script_render()
{
foreach ($this->script_tags as $key => $scripts_params) {
$scripts_dom = new DOM("script");
foreach ($scripts_params as $index => $value){
$scripts_dom->setAttribute($index, $value);
}
echo $scripts_dom->render();
}
}
// Безопасно выводит информацию
function view($text): bool
{
echo $this->get_view($text);
return true;
}
// Получить строку для безопасного использования
function get_view($text): string
{
return htmlspecialchars($text);
}
// Склонение слова в зависимости от числа
function pluralize($num, $words) {
$num = abs($num) % 100;
if ($num > 10 && $num < 20) {
return $words[2];
} else {
$i = $num % 10;
if ($i == 1) {
return $words[0];
} elseif (in_array($i, array(2, 3, 4))) {
return $words[1];
} else {
return $words[2];
}
}
}
}

View File

@@ -0,0 +1,34 @@
<?php
// Работа с базой данных
defined('ROOT_DIR') || exit;
require ENGINE_DIR . SLASH . "class.database.php";
trait Database {
private DB $db_class;
public function db_touch()
{
$this->db_class = new DB(DB_HOST, DB_NAME, DB_USER, DB_PASS);
$this->db_class->connect();
}
// Запрос на получение информации из базы
public function db_query($query, $params = array(), $new = false)
{
$ls_key = $query . json_encode($params);
if(!$new && $this->ls_has_key($ls_key))
return $this->ls_get_key($ls_key);
$result = $this->db_class->query($query, $params);
$this->ls_set_key($ls_key, $result);
return $result;
}
// Запрос на сохранение данных в базу
public function db_insert($query, $params)
{
return $this->db_class->insert($query, $params);
}
}

View File

@@ -0,0 +1,54 @@
<?php
// Работа с типами данных
defined('ROOT_DIR') || exit;
trait Datatypes {
public array $datatypes = array();
// Загрузка типов данных
public function datatype_load(): bool
{
$datatypes_dir = PLAYAREA_DIR_NAME . SLASH . "datatypes";
$datatypes = scandir($datatypes_dir, SCANDIR_SORT_DESCENDING);
if(!count($datatypes)) return false;
foreach ($datatypes as $key => $value) {
$name = $this->datatype_convert_name($value);
if(!$name) continue;
require_once $datatypes_dir . SLASH . $value;
$classes = get_declared_classes();
$class_name = end($classes);
$this->datatypes[] = $class_name;
}
return true;
}
private function datatype_convert_name($name): string
{
$parts = explode('.', $name);
if($parts[0] != "datatype" || !isset($parts[1])) return false;
return trim($parts[1]);
}
// Получить Объект по ID
public function get_item_by_id($item_id)
{
$search = new Search(array(
"limit" => 1,
"terms" => array("item_id" => $item_id)
));
$items = $search->collect();
if(!count($items)) return false;
list($item) = $items;
return $item;
}
// Получить Class объекта по ID
public function get_item_class_by_id($item_id)
{
$item = $this->get_item_by_id($item_id);
if($item === false) return false;
return $item->get_class_name();
}
}

View File

@@ -0,0 +1,90 @@
<?php
// Работа с отслеживанием данных
defined('ROOT_DIR') || exit;
trait Events {
private array $events = array();
// Добавить функцию в Событие
public function event_add($name, $function_name, $weight = 10, $lock = false): bool
{
$event_name = strtolower($name);
$this->event_create($name);
if($this->events[$event_name]["lock"]) return false;
if($lock) $this->events[$event_name]["events"] = array();
$this->events[$event_name]["events"][] = array("function_name" => $function_name, "weight" => $weight);
$this->events[$event_name]["lock"] = $lock;
return true;
}
// Запустить Событие
public function event_start($name, $args = array())
{
$event_name = strtolower($name);
$this->event_create($name);
if(!is_array($this->events[$event_name])) return false;
$events = $this->events[$event_name]["events"];
if($this->events[$event_name]["lock"]) {
$func_name = $events[0]["function_name"];
if(is_callable($func_name)) {
return $func_name(...$args);
}
if(!function_exists($func_name)) return false;
return $func_name(...$args);
}
$results = array();
foreach ($events as $key => $event) {
$func_name = $event["function_name"];
if(is_callable($func_name)) {
$results[] = $func_name(...$args);
} else {
if(!function_exists($func_name)) return false;
$results[] = $func_name(...$args);
}
}
return $results;
}
// Поймать Событие
public function event_form_capture(): bool
{
$values = array_replace_recursive($_GET, $_POST);
foreach ($_FILES as $key => $field) {
$file = $this->files_field_formate($key);
if($file === false) continue;
if(isset($values[$key])) $values[$key] = array_replace_recursive($values[$key], $file);
else $values[$key] = $file;
}
if(!isset($values["b_event"])) return false;
$this->event_start($values["b_event"], array($values));
return true;
}
// Создать Событие для формы
public function event_form($name)
{
$meta_dom = new DOM("input", false);
$meta_dom->setAttribute("type", "hidden");
$meta_dom->setAttribute("name", "b_event");
$meta_dom->setAttribute("value", $name);
echo $meta_dom->render();
}
// Создать Событие
private function event_create($name): bool
{
if(isset($this->events[$name])) return true;
$this->events[$name] = array();
$this->events[$name]["events"] = array();
return true;
}
}

View File

@@ -0,0 +1,48 @@
<?php
// Работа с полями для данных
defined('ROOT_DIR') || exit;
trait Fields {
public array $fields = array();
public function field_register($key, $field): bool
{
$this->fields[$key] = $field;
return true;
}
public function field_render_edit($key, $name, $content = "")
{
$field = $this->field_get($key);
if(!$field) return "";
$field->set_name($name);
$field->set_content($content);
$field->render_edit();
}
public function field_render_value($key, $name, $content = "")
{
$field = $this->field_get($key);
if(!$field) return "";
$field->set_name($name);
$field->set_content($content);
return $field->render_value();
}
public function field_render_db_value($key, $name, $value = "", $old_value = "")
{
$field = $this->field_get($key);
if(!$field) return "";
$field->set_name($name);
$field->set_content($value);
return $field->render_db_value($old_value);
}
public function field_get($name)
{
if(!isset($this->fields[$name])) return false;
return $this->fields[$name];
}
}

121
engine/Main/trait.files.php Normal file
View File

@@ -0,0 +1,121 @@
<?php
// Работа с файлами
defined('ROOT_DIR') || exit;
trait Files {
// Получить файл по ID
public function file_get($file_id)
{
$keys = $this->db_query("SELECT * FROM `bive_files` WHERE `id` = ?", array($file_id), true);
if(!count($keys)) return false;
return $keys[0];
}
// Удалить файл по его ID
public function file_delete($file_id): bool
{
$db_file = $this->file_get($file_id);
if(!$db_file) return false;
$file_path = ROOT_DIR . SLASH . STORAGE_DIR_NAME . "/files/" . $db_file["file_name"];
unlink($file_path);
$this->db_query("DELETE FROM `bive_files` WHERE `id` = ?", array($file_id), true);
return true;
}
// Проверить есть ли файл по его ID
public function file_has($file_id): bool
{
$db_file = $this->file_get($file_id);
if($db_file === false) return false;
$file_path = ROOT_DIR . SLASH . STORAGE_DIR_NAME . "/files/" . $db_file["file_name"];
return file_exists($file_path);
}
// Получить ссылку на файл по ID
public function file_get_link($file_id)
{
$db_file = $this->file_get($file_id);
if($db_file === false) return false;
return $this->router_get_root_uri() . "/" . STORAGE_DIR_NAME . "/files/" . $db_file["file_name"];
}
// Сохранить файл и вернуть его ID в базе
public function files_save($file, $max_size = 5242880, $file_types = array("jpeg", "jpg", "png", "svg", "webp"))
{
if(!isset($file) || $file["error"] != 0) return false;
$storage = $this->get_storage_dir();
$file_name = $file["name"];
$file_size = $file["size"];
$file_type = strtolower(pathinfo(SLASH . $file_name, PATHINFO_EXTENSION));
if ($file_size > $max_size) {
$this->alerts_add("Размер файла превышает минимально допустимый.", "error", "file");
return false;
}
$db_name = $this->files_create_name() . "." . $file_type;
$targetDir = $storage . SLASH . "files" . SLASH; // Директория для сохранения загруженных файлов
$targetFile = $targetDir . basename($db_name); // Полный путь до файла
if(!in_array($file_type, $file_types)) {
$this->alerts_add("Недопустимое расширение файла.", "error", "file");
return false;
}
if (move_uploaded_file($file["tmp_name"], $targetFile)) {
return $this->files_db_add($db_name, $file_size, $file_type, $targetFile);
} else {
$this->alerts_add("Произошла ошибка при загрузке файла.", "error", "file");
return false;
}
}
// Сохранить файлы по имени поля
public function files_save_by_name($name, $max_size = 5242880, $file_types = array("jpeg", "jpg", "png", "svg", "webp"))
{
if(!isset($_FILES[$name]) || $_FILES[$name]["error"] != 0) return false;
return $this->files_save($_FILES[$name], $max_size, $file_types);
}
// Добавление файла в базу данных
public function files_db_add($file_name, $weight, $mime_type, $path)
{
global $b;
return $b->db_insert("INSERT INTO `bive_files`(`path`, `mime_type`, `weight`, `file_name`) VALUES (?, ?, ?, ?)", array($path, $mime_type, $weight, $file_name));
}
// Форматирование файлов под поле
public function files_field_formate($name)
{
$file = $_FILES[$name];
if(!isset($file)) return false;
if(is_string($file["name"])) return $file;
$fields = array();
$this->files_field_formate_recurse($file, $fields);
return $fields;
}
// Рекурсивная функция форматирования файлов
private function files_field_formate_recurse($files_array, &$fields, $prop = null)
{
foreach ($files_array as $prop_key => $prop_value) {
if(is_array($prop_value)){
if($prop) $this->files_field_formate_recurse($prop_value,$fields[$prop_key], $prop);
else $this->files_field_formate_recurse($prop_value,$fields, $prop_key);
} else {
$fields[$prop_key][$prop] = $prop_value;
}
}
}
// Рандомное название для файла
public function files_create_name()
{
return bin2hex(random_bytes(16));
}
}

View File

@@ -0,0 +1,41 @@
<?php
defined('ROOT_DIR') || exit;
// Локальное хранилище
// Служит для хранения информации в рамках одного запроса
trait LocalStorage {
private array $key_value = array();
// Получить значение по ключу
public function ls_get_key($key)
{
return $this->key_value[md5($key)];
}
// Записать значение по ключу
public function ls_set_key($key, $value)
{
return $this->key_value[md5($key)] = $value;
}
// Есть ли ключ
public function ls_has_key($key): bool
{
return isset($this->key_value[md5($key)]);
}
// Получить длину локального хранилища
public function ls_get_size(): int
{
return count($this->key_value);
}
// Удалить ключ
public function ls_remove_key($key): bool
{
unlink($this->key_value[md5($key)]);
return true;
}
}

View File

@@ -0,0 +1,39 @@
<?php
// Работа с путями
defined('ROOT_DIR') || exit;
trait Path
{
// Получить директорию движка
public function get_engine_path(): string
{
$r = $this->get_root_dir();
return $r . "/" .ENGINE_DIR_NAME;
}
// Получить корневую директорию
function get_root_dir(): string
{
return ROOT_DIR;
}
// Получить директорию хранилища
function get_storage_dir(): string
{
return ROOT_DIR . SLASH . STORAGE_DIR_NAME;
}
// Получить директорию пользовательского контента
function get_playarea_dir(): string
{
return ROOT_DIR . SLASH . PLAYAREA_DIR_NAME;
}
// Получить директорию со скриптами
function get_scripts_dir(): string
{
return $this->get_playarea_dir() . SLASH . "scripts";
}
}

View File

@@ -0,0 +1,17 @@
<?php
// Работа с песочницей
defined('ROOT_DIR') || exit;
trait PlayArea {
public function pa_connect_main(){
require_once $this->get_playarea_dir() . SLASH . "main.php";
}
// Получить папку с ассетами
public function pa_get_assets_dir(){
$root = $this->router_get_root_uri();
return $root . SLASH . PLAYAREA_DIR_NAME . SLASH . 'assets' . SLASH;
}
}

View File

@@ -0,0 +1,90 @@
<?php
// Работа с плагинами
defined('ROOT_DIR') || exit;
trait Plugins {
public array $plugins = array();
// Загрузка всех плагинов системы
public function plugins_load(): bool
{
$plugins_dir = $this->plugins_get_dir();
$plugins = scandir($plugins_dir, SCANDIR_SORT_DESCENDING);
if(!count($plugins)) return false;
foreach ($plugins as $key => $plugin_dir)
$this->plugin_load($plugin_dir);
return true;
}
// Загрузка плагина
public function plugin_load($plugin_dir){
global $b;
if($this->plugins[$plugin_dir]) return;
$plugins_dir = $this->plugins_get_dir();
$plugin_path = $plugins_dir . SLASH . $plugin_dir . SLASH;
$plugin_manifest_path = $plugin_path . "manifest.yaml";
// Проверка наличия manifest.yaml
if(!file_exists($plugin_manifest_path)) return;
$manifest = $this->yaml_read($plugin_manifest_path);
$plugin_script_path = $plugin_path . $manifest["plugin_script"];
// Подгрузка зависимостей
if(isset($manifest["plugin_dependencies"])) {
foreach ($manifest["plugin_dependencies"] as $key => $plugin_dependence) {
$this->plugin_load($plugin_dependence);
}
}
// Проверка наличия исполняемого скрипта
if(!file_exists($plugin_script_path)) return;
$plugin_class = $this->plugin_register($manifest, $plugin_dir);
$this->ls_set_key("plugin", $plugin_class);
require_once $plugin_script_path;
}
// Получить текущий плагин
public function plugin_get()
{
return $this->ls_get_key("plugin");
}
// Регистрация плагина
public function plugin_register($manifest, $plugin_dir)
{
$plugin_class = $this->plugin_create_class($manifest, $plugin_dir);
$this->plugins[$plugin_dir] = array(
"plugin_name" => $manifest["plugin_name"],
"plugin_description" => $manifest["plugin_description"],
"plugin_author" => $manifest["plugin_author"],
"plugin_version" => $manifest["plugin_version"],
"class" => $plugin_class
);
return $plugin_class;
}
// Функция создания класса плагина
public function plugin_create_class($manifest, $plugin_dir)
{
$plugin_class_name = $manifest["plugin_class"] ?? "Plugin";
return new $plugin_class_name($plugin_dir);
}
// Получить папку где хранятся плагины
public function plugins_get_dir(): string
{
return ROOT_DIR . SLASH . PLAYAREA_DIR_NAME . SLASH . "plugins";
}
// Прочитать конфигурацию YAML
public function yaml_read($filePath)
{
if (!extension_loaded('yaml'))
die('Расширение YAML не установлено.');
$ymlContent = file_get_contents($filePath);
return yaml_parse($ymlContent);
}
}

View File

@@ -0,0 +1,194 @@
<?php
// Работа с адресной строкой
defined('ROOT_DIR') || exit;
trait Router {
private array $routerList = array();
private $fun_not_found;
// Получить адрес
public function router_get_uri()
{
return parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
}
// Регистрация роутера
public function router_add($page, $func, $methods = array("GET", "POST"))
{
$type = 'function';
if(gettype($func) == 'string') {
if(substr($func, strlen($func) - strlen(".php")) == ".php") $type = 'template';
else $type = 'function_name';
}
$this->routerList[] = array(
"page" => $page,
"func" => &$func,
"type" => $type,
"methods" => $methods
);
}
// Получение списка роутов
private function router_get_list(): array
{
return $this->routerList;
}
// Получить сегменты адреса
public function router_get_segments()
{
$uri = $this->router_get_uri();
return explode('/', trim($uri, '/'));
}
// Получить сегмент адреса
public function router_get_segment($segment_num)
{
$segments = $this->router_get_segments();
return $segments[$segment_num];
}
// Определить функцию не найденной страницы
public function router_set_not_found($fun)
{
$this->fun_not_found = &$fun;
}
// Когда страница не найдена
private function router_not_found()
{
http_response_code(404);
if(empty($this->fun_not_found))
echo "not found";
$fun = $this->fun_not_found;
$fun();
}
// Запускаем сканирование
public function router_init()
{
$uri = $this->router_get_uri();
$list = $this->router_get_list();
$method = $this->router_get_method();
foreach ($list as $key => $item) {
if(!in_array($method, $item["methods"], true)) continue;
if(substr($item["page"], -1) == "%"){
$clear_uri = substr($item["page"], 0, -1);
if(strpos($uri, $clear_uri) === 0) {
$this->event_start(base64_encode($item["page"]));
if($item["type"] == "template")
return $this->template_load($item["func"], array(), true);
return $item["func"]();
}
} else if ($uri == $item["page"]) {
$this->event_start(base64_encode($item["page"]));
if($item["type"] == "template")
return $this->template_load($item["func"], array(), true);
return $item["func"]();
}
}
$this->router_not_found();
return true;
}
// Получить метод запроса
public function router_get_method(): string
{
return strtolower($_SERVER['REQUEST_METHOD']);
}
// Получить текущий протокол
public function router_get_protocol(): string
{
if (
isset($_SERVER['REQUEST_SCHEME']) &&
($_SERVER['HTTPS'] == 'on' || $_SERVER['HTTPS'] == 1) ||
isset($_SERVER['HTTP_X_FORWARDED_PROTO']) &&
$_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' ||
isset($_SERVER['REQUEST_SCHEME']) &&
$_SERVER['REQUEST_SCHEME'] == "https"
) return "https";
return "http";
}
// Получить корневой адрес сайта
public function router_get_root_uri(): string
{
$protocol = $this->router_get_protocol();
return $protocol . "://" . $_SERVER['HTTP_HOST'];
}
// Получить канонический адрес
public function router_get_canonical_uri(): string
{
$canonical_link = parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
return $this->router_get_root_uri() . $canonical_link;
}
// Получить полный адрес
public function router_get_full_uri(): string
{
return ((!empty($_SERVER['HTTPS'])) ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
}
// Выполнить редирект на другой адрес
public function router_redirect($path)
{
$root = $this->router_get_root_uri();
header("Location: $root$path");
}
// Выполнить редирект на эту же страницу чтобы очистить POST
public function router_refresh()
{
$current_path = $_SERVER['REQUEST_URI'];
$this->router_redirect($current_path);
}
// Получить ссылку с измененными GET параметрами
public function router_format_get_params($params, $split = false, $exclude = array()): string
{
$string = "?";
$params_list = array();
$params = $this->router_edit_get_params($params, $split, $exclude);
foreach ($params as $key => $value)
$params_list[] = $key . "=" . $value;
return $string . implode("&", $params_list);
}
// Соединить GET параметры со значениями массива
public function router_edit_get_params($params, $split = false, $exclude = array())
{
$current_params = $_GET;
if($split) $params = array_merge($current_params, $params);
foreach ($exclude as $key => $value) unset($params[$value]);
return $params;
}
// Заполнить форму невидимыми полями по переданному массиву
public function router_params_to_form($params, $exclude = array(), $prefix = "", $nested = false)
{
foreach ($params as $key => $value) {
if(in_array($key, $exclude)) continue;
if(is_array($value)) {
if($nested) $this->router_params_to_form($value, $exclude = array(), $prefix . "[". $key . "]", true);
else $this->router_params_to_form($value, $exclude = array(), $prefix . $key, true);
continue;
}
if($nested) $this->router_get_hidden_input($prefix . "[" . $key . "]", $value, $prefix);
else $this->router_get_hidden_input($prefix . $key, $value, $prefix);
}
}
// Добавить невидимое поле на страницу
private function router_get_hidden_input($name, $value, $prefix = "")
{
$meta_dom = new DOM("input", false);
$meta_dom->setAttribute("type", "hidden");
$meta_dom->setAttribute("name", $prefix . $this->get_view($name));
$meta_dom->setAttribute("value", $this->get_view($value));
$meta_dom->view();
}
}

View File

@@ -0,0 +1,43 @@
<?php
// Работа с сессиями
defined('ROOT_DIR') || exit;
trait Sessions {
// Создать сессию для посетителя
public function session_create()
{
if(isset($_COOKIE["b_session"])) return true;
$session_id = bin2hex(random_bytes(16));
setcookie('b_session', $session_id, time() + (30 * 24 * 60 * 60), '/', '', SESSION_SECURE, true);
$_COOKIE["b_session"] = $session_id;
return $session_id;
}
// Получить значение сессии
public function session_get_name()
{
$session_name = $_COOKIE["b_session"];
if(!isset($session_name)) return $this->session_create();
return $session_name;
}
// Получить значение сессии
public function session_get($key)
{
$session_name = $this->session_get_name();
$keys = $this->db_query("SELECT * FROM `bive_session` WHERE `session` = ? AND `key` = ?", array($session_name, $key), true);
if(!isset($keys[0])) return false;
return $keys[0]["value"];
}
// Сохранить значение сессии
public function session_set($key, $value)
{
$session_name = $this->session_get_name();
$old_value = $this->session_get($key);
if($old_value === false) return $this->db_query("INSERT INTO `bive_session`(`session`, `key`, `value`) VALUES (?, ?, ?)", array($session_name, $key, $value), true);
return $this->db_query("UPDATE `bive_session` SET `value` = ? WHERE `session` = ? AND `key` = ?", array($value, $session_name, $key), true);
}
}

View File

@@ -0,0 +1,63 @@
<?php
// Работа с настройками BiveEngine
defined('ROOT_DIR') || exit;
trait Settings {
public array $setting_list = array();
public function setting_register($setting_key, $title, $field_key): string
{
$this->setting_list[$setting_key] = array("title" => $title, "field_key" => $field_key);
return $setting_key;
}
public function setting_get($setting_key)
{
$keys = $this->db_query("SELECT `value` FROM `bive_settings` WHERE `setting_key` = ?", array($setting_key), true);
if(!isset($keys[0])) return false;
return $keys[0]["value"];
}
public function setting_get_value($setting_key)
{
$value = $this->setting_get($setting_key);
if($value === false) return false;
$field_key = $this->setting_get_field($setting_key);
if($field_key === false) return false;
return $this->field_render_value($field_key, $setting_key, $value);
}
public function setting_get_field($setting_key)
{
if(!isset($this->setting_list[$setting_key])) return false;
return $this->setting_list[$setting_key]["field_key"];
}
public function setting_get_title($setting_key)
{
if(!isset($this->setting_list[$setting_key])) return false;
return $this->setting_list[$setting_key]["title"];
}
public function setting_set($setting_key, $value): bool
{
$field_key = $this->setting_get_field($setting_key);
if(!$field_key) return false;
$old_value = $this->setting_get($setting_key);
$value = $this->field_render_db_value($field_key, $setting_key, $value, $old_value);
if($old_value === false) return $this->setting_add($setting_key, $value);
$this->db_query("UPDATE `bive_settings` SET `value` = ? WHERE `setting_key` = ?", array($value, $setting_key), true);
return true;
}
public function setting_add($setting_key, $value)
{
return $this->db_insert("INSERT INTO `bive_settings`(`setting_key`, `value`) VALUES (?, ?)", array($setting_key, $value), true);
}
}

View File

@@ -0,0 +1,119 @@
<?php
// Работа с шаблонами
defined('ROOT_DIR') || exit;
trait Templates {
// Загрузка шаблона
public function template_load($name, $variables = array(), $new = true)
{
$template_path = $this->template_touch_name($name);
return $this->template_load_by_path($template_path, $variables, $new);
}
private function template_touch_name($name): string
{
$template_path = ROOT_DIR . SLASH . PLAYAREA_DIR_NAME . SLASH . "templates" . SLASH . $name;
if(file_exists($template_path)) return $template_path;
$plugin_template_path = ROOT_DIR . SLASH . PLAYAREA_DIR_NAME . SLASH . "plugins" . SLASH . $name;
if(file_exists($plugin_template_path)) return $plugin_template_path;
return $template_path;
}
// Загрузка шаблона по его адресу
public function template_load_by_path($path, $variables = array(), $new = true)
{
// Загрузка шаблона из буффера
$cache = $this->template_cache_load($path, $variables, $new);
if($cache) return $cache;
// Начало буффера
ob_start();
global $b;
require $path;
$buffer = ob_get_contents();
ob_end_clean();
// Конец буффера
// Сохранение буффера в кеш
$rendered = $this->template_variables_apply($buffer, $variables);
if($new === false) $this->template_cache_save($path, $variables, $rendered);
// Вывод буффера пользователю
echo $rendered;
return $buffer;
}
// Применить значения переменных
private function template_variables_apply($content, $variables)
{
return preg_replace_callback('/{{\s*(\w+)\s*}}/', function($match) use ($variables) {
$key = $match[1];
return $variables[$key] ?? $match[0];
}, $content);
}
// Сохранить шаблон
private function template_cache_save($name, $variables, $content): bool
{
if(!TEMPLATE_CACHE) return false;
$path = $this->template_cache_get_full_path($name, $variables);
file_put_contents($path, $content);
return true;
}
// Есть ли шаблон с таким именем
private function template_cache_has($name, $variables): bool
{
$path = $this->template_cache_get_full_path($name, $variables);
return file_exists($path);
}
// Загрузка кешированного шаблона
private function template_cache_load($name, $variables, $new = false)
{
if(!TEMPLATE_CACHE) return false;
if(!$new && $this->template_cache_has($name, $variables)) {
$cache = $this->template_cache_get($name, $variables);
echo $cache;
return $cache;
}
return false;
}
// Получить кеш шаблона
private function template_cache_get($name, $variables): string
{
$path = $this->template_cache_get_full_path($name, $variables);
return file_get_contents($path);
}
// Получить полный путь до кеша шаблона
private function template_cache_get_full_path($name, $variables): string
{
$path = $this->get_storage_dir() . SLASH . "temp" . SLASH . "templates" . SLASH;
return $path . md5($name . json_encode($variables)) . ".php";
}
// Количество кешированных шаблонов
public function template_cache_count(): int
{
$path = $this->get_storage_dir() . SLASH . "temp" . SLASH . "templates" . SLASH;
$files = glob($path."/*");
return count($files);
}
// Удалить весь кеш шаблонов
public function template_cache_remove(){
$path = $this->get_storage_dir() . SLASH . "temp" . SLASH . "templates" . SLASH;
$files = glob($path."/*");
if (count($files) > 0) {
foreach ($files as $file) {
if (file_exists($file)) {
unlink($file);
}
}
}
}
}

39
engine/class.bive.php Normal file
View File

@@ -0,0 +1,39 @@
<?php
defined('ROOT_DIR') || exit;
require ENGINE_DIR . SLASH . "Main" . SLASH . "trait.router.php";
require ENGINE_DIR . SLASH . "Main" . SLASH . "trait.content.php";
require ENGINE_DIR . SLASH . "Main" . SLASH . "trait.paths.php";
require ENGINE_DIR . SLASH . "Main" . SLASH . "trait.database.php";
require ENGINE_DIR . SLASH . "Main" . SLASH . "trait.localstorage.php";
require ENGINE_DIR . SLASH . "Main" . SLASH . "trait.templates.php";
require ENGINE_DIR . SLASH . "Main" . SLASH . "trait.playarea.php";
require ENGINE_DIR . SLASH . "Main" . SLASH . "trait.datatypes.php";
require ENGINE_DIR . SLASH . "Main" . SLASH . "trait.events.php";
require ENGINE_DIR . SLASH . "Main" . SLASH . "trait.sessions.php";
require ENGINE_DIR . SLASH . "Main" . SLASH . "trait.admin.php";
require ENGINE_DIR . SLASH . "Main" . SLASH . "trait.alerts.php";
require ENGINE_DIR . SLASH . "Main" . SLASH . "trait.fields.php";
require ENGINE_DIR . SLASH . "Main" . SLASH . "trait.settings.php";
require ENGINE_DIR . SLASH . "Main" . SLASH . "trait.files.php";
require ENGINE_DIR . SLASH . "Main" . SLASH . "trait.plugins.php";
class Bive {
use Router;
use Content;
use Path;
use Database;
use LocalStorage;
use Templates;
use PlayArea;
use Datatypes;
use Events;
use Sessions;
use Admin;
use Alerts;
use Fields;
use Settings;
use Files;
use Plugins;
}

47
engine/class.database.php Normal file
View File

@@ -0,0 +1,47 @@
<?php
defined('ROOT_DIR') || exit;
class DB {
public string $db_host;
public string $db_name;
public string $db_user;
public string $db_password;
private PDO $dbh;
public function __construct($db_host, $db_name, $db_user, $db_password)
{
$this->db_host = $db_host;
$this->db_name = $db_name;
$this->db_user = $db_user;
$this->db_password = $db_password;
}
public function connect(): DB
{
$this->dbh = new PDO("mysql:host=". $this->db_host. ";dbname=" . $this->db_name . ";charset=utf8mb4", $this->db_user, $this->db_password);
return $this;
}
// Запрос на получение информации из базы
public function query($query, $params)
{
log_message("Начало запроса");
log_message($query);
log_message(json_encode($params));
log_message("Конец запроса");
$sth = $this->dbh->prepare($query);
$sth->execute($params);
return $sth->fetchAll();
}
// Запрос на сохранение данных в базу
public function insert($query, $params)
{
$sth = $this->dbh->prepare($query);
$sth->execute($params);
return $this->dbh->lastInsertId();
}
}

85
engine/class.dom.php Normal file
View File

@@ -0,0 +1,85 @@
<?php
defined('ROOT_DIR') || exit;
class DOM {
private string $tag_name;
private bool $end_tag;
private array $attributes = array();
public array $child_nodes = array();
public function __construct($tag_name, $end_tag = true)
{
$this->tag_name = $tag_name ?? "div";
$this->end_tag = $end_tag;
}
// Добавить атрибут
public function setAttribute($name, $value): bool
{
$this->attributes[$name] = $value;
return true;
}
// Вывести на экран DOM
public function view(): bool
{
echo $this->render();
return true;
}
// Отрендерить DOM
public function render(): string
{
$string_dom = "<" . $this->tag_name . $this->render_attributes() . ">";
if($this->end_tag) $string_dom .= $this->render_child();
if($this->end_tag) $string_dom .= "</". $this->tag_name . ">";
return $string_dom;
}
// Отрендерить Атрибуты DOM
private function render_attributes(): string
{
$string_attributes = "";
foreach ($this->attributes as $key => $value)
$string_attributes .= " $key='$value'";
return $string_attributes;
}
// Отрендерить дочерние DOM
private function render_child(): string
{
$string_child = "";
foreach ($this->child_nodes as $key => $value) {
if(gettype($value) == "string") {
$string_child .= $value;
continue;
}
$string_child .= $value->render();
}
return $string_child;
}
// Добавить дочерний DOM в конец
public function append($content): bool
{
if(gettype($content) != "string" && gettype($content) != "object") return false;
$this->child_nodes[] = $content;
return true;
}
// Добавить дочерний DOM в начало
public function prepend($content): bool
{
if(gettype($content) != "string" && gettype($content) != "object") return false;
array_unshift($this->child_nodes, $content);
return true;
}
// Очистить все дочерние DOM
public function clear(): bool
{
$this->child_nodes = array();
return true;
}
}

319
engine/class.item.php Normal file
View File

@@ -0,0 +1,319 @@
<?php
defined('ROOT_DIR') || exit;
class Item {
public string $item_id;
public int $parent_id = 0;
public Item $parent;
public string $parent_class = "";
public array $props;
public array $props_values;
public string $title = "Объект";
public string $description = "Описание объекта не задано.";
public bool $admin_menu = true;
public bool $visible_content = true;
public string $icon = "format_list_bulleted";
public function __construct($item_id = 0)
{
$this->item_id = $item_id;
}
// Создать Item
public function create($name, $content): bool
{
global $b;
if($this->item_id) return false;
if($content === null) $content = "";
if($name === null) $name = "";
$this->item_id = $b->db_insert("INSERT INTO `bive_items`(`item_name`, `item_content`, `item_class`) VALUES (?, ?, ?)", array($name, $content, $this->get_class_name()));
$this->create_props();
return true;
}
public function delete(): bool
{
global $b;
if(!$this->item_id) return false;
foreach ($this->props as $key => $prop) $prop->delete();
$this->clear_parents();
$this->clear_childs();
$b->db_query("DELETE FROM `bive_items` WHERE `item_id` = ?", array($this->item_id), true);
return true;
}
private function create_props(): bool
{
foreach ($this->props as $key => $prop) $prop->add(null);
return true;
}
// Получить Объект
public function get_item($new = false)
{
global $b;
$result = $b->db_query("SELECT * FROM `bive_items` WHERE `item_id` = ?", [ $this->item_id ], $new);
if(!isset($result[0])) return false;
$this->fill_main_parent($result[0]["item_parent"]);
return $result[0];
}
// Получить класс Объекта
public function get_class_name(): string
{
return static::class;
}
// Получить ID объекта
public function get_item_id($new = false)
{
return $this->get_field("item_id", $new);
}
// Получить ID объекта (алиас)
public function get_id($new = false)
{
return $this->get_item_id($new);
}
// Получить Имя объекта
public function get_item_name($new = false)
{
return $this->get_field("item_name", $new);
}
// Получить Имя объекта (алиас)
public function get_name($new = false)
{
return $this->get_item_name($new);
}
// Получить Ярлык объекта
public function get_item_slug($new = false)
{
return $this->get_field("item_slug", $new);
}
// Получить Ярлык объекта
public function get_slug($new = false)
{
return $this->get_item_slug($new);
}
// Получить Контент объекта
public function get_item_content($new = false)
{
return $this->get_field("item_content", $new);
}
// Получить Контент объекта
public function get_content($new = false)
{
return $this->get_item_content($new);
}
// Получить Родительский объект
public function get_item_parent($new = false)
{
return $this->get_field("item_parent", $new);
}
// Получить родительский объект
public function get_parent()
{
global $b;
$result = $b->db_query("SELECT * FROM `bive_items` WHERE `item_id` = ? LIMIT 1", [ $this->parent_id ], true);
if(!count($result)) return false;
$parent = new $result[0]["item_class"]($result[0]["item_id"]);
return $parent;
}
// Задать Родительский ID
public function set_parent($parent_id, $main = false): bool
{
if($this->item_id == $parent_id) return false;
if($main) {
$this->fill_main_parent($parent_id);
$this->set_field("item_parent", $parent_id);
$this->get_parent();
}
$this->set_second_parent($parent_id);
return true;
}
// Очистить все родительские Объекты
public function clear_parents($class_name = ""): bool
{
global $b;
$where = "";
if($class_name != "") $where = " AND `items_class` LIKE '" . $class_name . "-". $this->get_class_name() ."%'";
$b->db_query("DELETE FROM `bive_items_links` WHERE `child_item_id` = ?" . $where, array($this->item_id), true);
$b->db_query("DELETE FROM `bive_items_intermediate` WHERE `child_item_id` = ?" . $where, array($this->item_id), true);
$this->set_field("item_parent", 0);
return true;
}
public function clear_childs($class_name = ""): bool
{
global $b;
$where = "";
if($class_name != "") $where = " AND `items_class` LIKE '%-" . $class_name . "'";
$b->db_query("DELETE FROM `bive_items_links` WHERE `parent_item_id` = ?" . $where, array($this->item_id), true);
$b->db_query("DELETE FROM `bive_items_intermediate` WHERE `parent_item_id` = ?" . $where, array($this->item_id), true);
return true;
}
// Удалить родительский Объект
public function clear_parent($parent_id)
{
global $b;
$b->db_query("DELETE FROM `bive_items_links` WHERE `child_item_id` = ? AND `parent_item_id` = ?", array($this->item_id, $parent_id), true);
$b->db_query("DELETE FROM `bive_items_intermediate` WHERE `child_item_id` = ? AND `parent_item_id` = ?", array($this->item_id, $parent_id), true);
return true;
}
// Задать родительский ID без сохранения
public function fill_main_parent($parent_id)
{
if($this->item_id == $parent_id) return false;
$this->parent_id = $parent_id;
return $parent_id;
}
// Занести Родительский Объект
public function set_second_parent($parent_id): bool
{
global $b;
if($this->item_id == $parent_id) return false;
$result = $b->db_query("SELECT * FROM `bive_items_links` WHERE `parent_item_id` = ? AND `child_item_id` = ? LIMIT 1", [ $parent_id, $this->item_id ], true);
if(count($result)) return false;
$items_class = $b->get_item_class_by_id($parent_id) . "-" . $this->get_class_name();
$b->db_insert("INSERT INTO `bive_items_links`(`parent_item_id`, `child_item_id`, `items_class`) VALUES (?, ?, ?)", array($parent_id, $this->item_id, $items_class));
return true;
}
// Занести связи в таблицу с оптимизацией
public function set_optimize()
{
global $b;
$parent_list = $this->get_parent_items();
$child_list = $this->get_child_items();
foreach ($parent_list as $key => $item_parent) {
foreach ($child_list as $k => $item_child) {
$items_class = $item_parent->get_class_name() . "-" . $this->get_class_name() . "-" . $item_child->get_class_name();
$b->db_insert("INSERT IGNORE INTO `bive_items_intermediate` (`parent_item_id`, `child_item_id`, `germ_item_id`, `items_class`) VALUES (?, ?, ?, ?)", [ $item_parent->item_id, $this->item_id, $item_child->item_id, $items_class], true);
}
}
return true;
}
// Получить все родительские Объекты
public function get_parent_items($class_name = ""): array
{
global $b;
$parents = array();
$main_parent = $this->get_parent();
if($main_parent && $main_parent->get_class_name() == $class_name) $parents[] = $main_parent;
$class_condition = "";
if($class_name != "") $class_condition = " AND i.`item_class` = '" . $class_name . "' ";
$query = "SELECT * FROM `bive_items_links` as l JOIN `bive_items` as i ON i.`item_id` = l.`parent_item_id` " . $class_condition . " WHERE l.`child_item_id` = ?";
$parent_list = $b->db_query($query, [ $this->item_id ], true);
if(!count($parent_list)) return $parents;
foreach ($parent_list as $key => $db_parent) {
$parent = new $db_parent["item_class"]($db_parent["item_id"]);
$parent->set_parent($db_parent["item_parent"], true);
$parents[] = $parent;
}
return $parents;
}
// Получить Дочерние Объекты
public function get_child_items($class_name = "")
{
global $b;
$childs = array();
$class_condition = "";
if($class_name != "") $class_condition = " AND i.`item_class` = '" . $class_name . "' ";
$query = "SELECT * FROM `bive_items_links` as l JOIN `bive_items` as i ON i.`item_id` = l.`child_item_id` " . $class_condition . " WHERE l.`parent_item_id` = ?";
$child_list = $b->db_query($query, [ $this->item_id ], true);
if(!count($child_list)) return $childs;
foreach ($child_list as $key => $db_child) {
$child = new $db_child["item_class"]($db_child["item_id"]);
$child->set_parent($db_child["item_parent"], true);
$childs[] = $child;
}
return $childs;
}
public function get_main_child_items($class_name = "")
{
global $b;
$childs = array();
$class_condition = "";
if($class_name != "") $class_condition = " AND i.`item_class` = '" . $class_name . "' ";
$query = "SELECT * FROM `bive_items` as i WHERE i.`item_parent` = ? " . $class_condition;
$child_list = $b->db_query($query, [ $this->item_id ], true);
if(!count($child_list)) return $childs;
foreach ($child_list as $key => $db_child) {
$child = new $db_child["item_class"]($db_child["item_id"]);
$child->set_parent($db_child["item_parent"], true);
$childs[] = $child;
}
return $childs;
}
// Получить поле из базы
public function get_field($field_name, $new = false)
{
$item = $this->get_item($new);
if(!$item) return false;
$response = $item[$field_name];
if(!$response) return false;
return $response;
}
// Обновить поле
public function set_field($field_name, $new_value): bool
{
global $b;
$b->db_query("UPDATE `bive_items` SET `" . $field_name . "` = ? WHERE `item_id` = ?", array($new_value, $this->item_id), true);
return true;
}
// Добавить Свойство Объекту
public function set_prop($key, $value): bool
{
if(!$this->props[$key]) return false;
$prop = $this->props[$key];
return $prop->add($value);
}
// Получить Свойство Объекта
public function get_prop($key, $new = true)
{
if(!$this->props[$key]) return false;
$prop = $this->props[$key];
return $prop->get($new);
}
// Получить Свойство Объекта для вывода
public function get_prop_render_value($key, $new = true)
{
if(!$this->props[$key]) return false;
$prop = $this->props[$key];
return $prop->render_value($new);
}
}

36
engine/class.plugin.php Normal file
View File

@@ -0,0 +1,36 @@
<?php
class Plugin {
public string $plugin_path; // Полный путь до плагина
public string $plugin_dir; // Папка плагина
public function __construct($plugin_dir)
{
$this->plugin_dir = $plugin_dir;
$this->plugin_path = $this->get_plugins_dir() . SLASH . $this->plugin_dir . SLASH;
}
public function get_plugins_dir() {
return ROOT_DIR . SLASH . PLAYAREA_DIR_NAME . SLASH . "plugins";
}
public function template_load($name, $variables = array(), $new = true)
{
global $b;
$plugin_template_path = $this->plugin_path . "template" . $name;
return $b->template_load_by_path($plugin_template_path, $variables, $new);
}
public function router_add($page, $func, $methods = array("GET", "POST"))
{
global $b;
$b->router_add($page, function () use ($func) { $func(); }, $methods);
}
public function get_assets_path(): string
{
global $b;
$root = $b->router_get_root_uri();
return $root . SLASH . PLAYAREA_DIR_NAME . SLASH . 'plugins' . SLASH . $this->plugin_dir . SLASH . "assets". SLASH;
}
}

124
engine/class.prop.php Normal file
View File

@@ -0,0 +1,124 @@
<?php
defined('ROOT_DIR') || exit;
class Prop {
private Item $item;
public string $prop_key;
public string $default_value = "";
public string $title = "Prop name";
public bool $table_view = true;
public bool $index = false;
public $field;
public function __construct($item, $prop_key, $default_value, $title, $table_view = true, $field, $index = false)
{
$this->item = $item;
$this->prop_key = $prop_key;
$this->default_value = $default_value;
$this->title = $title;
$this->table_view = $table_view;
$this->field = $field;
$this->index = $index;
}
public function render_value()
{
global $b;
$field = $this->field;
$value = $this->get();
if(!isset($field)) return $value;
return $b->field_render_value($field, $this->prop_key, $value);
}
// Получить значение
public function get($new = true)
{
global $b;
$item_id = $this->item->get_item_id();
$props = $b->db_query("SELECT * FROM `bive_items_props` WHERE `item_id` = ? AND `prop_key` = ?", array($item_id, $this->prop_key), $new);
if(!count($props)) return $this->default_value;
return $props[0]["prop_value"];
}
public function add($value): bool
{
global $b;
$has = $this->has();
$field = $this->field;
$old_value = $this->get(true);
if(isset($field)) $value = $b->field_render_db_value($field, $this->prop_key, $value, $old_value);
if(gettype($value) == "NULL") $value = $this->default_value;
if($has) return $this->update($value);
return $this->create($value);
}
public function has(): bool
{
global $b;
$item_id = $this->item->get_item_id();
$props = $b->db_query("SELECT * FROM `bive_items_props` WHERE `item_id` = ? AND `prop_key` = ? LIMIT 1", array($item_id, $this->prop_key), true);
if(!count($props)) return false;
return true;
}
public function update($value): bool
{
global $b;
$item_id = $this->item->get_item_id();
$b->db_query("UPDATE `bive_items_props` SET `prop_value` = ? WHERE `item_id` = ? AND `prop_key` = ?", array($value, $item_id, $this->prop_key), true);
if($this->index) $this->index_update($value);
return true;
}
private function create($value): bool
{
global $b;
$item_id = $this->item->get_item_id();
$b->db_query("INSERT INTO `bive_items_props`(`prop_key`, `prop_value`, `item_id`) VALUES (?, ?, ?)", array($this->prop_key, $value, $item_id), true);
if($this->index) $this->index_create($value);
return true;
}
public function delete(): bool
{
global $b;
$item_id = $this->item->get_item_id();
$b->db_query("DELETE FROM `bive_items_props` WHERE `item_id` = ? AND `prop_key` = ?", array($item_id, $this->prop_key), true);
if($this->index) $this->index_delete();
return true;
}
public function index_get()
{
global $b;
$item_id = $this->item->get_item_id();
return $b->db_query("SELECT * FROM `bive_items_index` WHERE `item_id` = ? AND `prop_key` = ? LIMIT 1", array($item_id, $this->prop_key), true);
}
public function index_update($value): bool
{
global $b;
$item_id = $this->item->get_item_id();
$index = $this->index_get();
if(!count($index)) $this->index_create($value);
$b->db_query("UPDATE `bive_items_index` SET `prop_value` = ? WHERE `item_id` = ? AND `prop_key` = ?", array($value, $item_id, $this->prop_key), true);
return true;
}
private function index_create($value): bool
{
global $b;
$item_id = $this->item->get_item_id();
$b->db_query("INSERT INTO `bive_items_index`(`prop_key`, `prop_value`, `item_id`) VALUES (?, ?, ?)", array($this->prop_key, $value, $item_id), true);
return true;
}
public function index_delete(): bool
{
global $b;
$item_id = $this->item->get_item_id();
$b->db_query("DELETE FROM `bive_items_index` WHERE `item_id` = ? AND `prop_key` = ?", array($item_id, $this->prop_key), true);
return true;
}
}

223
engine/class.search.php Normal file
View File

@@ -0,0 +1,223 @@
<?php
defined('ROOT_DIR') || exit;
class Search {
public array $query;
private string $sql_query = "";
private array $variables = array();
public int $count = 0;
public function __construct($query)
{
$this->query = $query;
list($sql_query, $variables) = $this->query_construct();
$this->sql_query = $sql_query;
$this->variables = $variables;
}
// Извлечение данных из базы
public function collect($fill = true, $new = true)
{
global $b;
$result = $b->db_query($this->sql_query, $this->variables, $new);
if(!$fill) return $result;
$this->get_count();
$items = array();
foreach ($result as $key => $item) {
$class = new $item["item_class"]($item["item_id"]);
$class->fill_main_parent($item["item_parent"]);
$class->props_values = $this->search_props($item, $class);
$items[] = $class;
}
return $items;
}
// Поиск параметров
private function search_props($item, $class): array
{
$props = explode("-+-", $item["properties"]);
$values = explode("-+-", $item["values"]);
$result = array();
for ($i = 0; $i < count($props); $i++) {
$prop = $props[$i];
$value = $values[$i];
if(!isset($class->props[$prop])) continue;
$result[$prop] = $value;
}
return $result;
}
// Получить количество элементов
public function get_count(){
global $b;
list($query_count, $variables) = $this->query_construct(true);
$result = $b->db_query($query_count, $variables, false);
$this->count = $result[0]["count"];
return $this;
}
// Создание запроса
private function query_construct($count = false): array
{
$variables = array();
$props = $this->query["props"];
$params = $this->query["terms"] ?? array();
if(isset($this->query["class"])) $params["item_class"] = $this->query["class"];
if(isset($this->query["parent_id"])) $params["item_parent"] = $this->query["parent_id"];
$limit = intval($this->query["limit"] ?? 184467440737);
$offset = intval($this->query["offset"] ?? 0);
$order = $this->query_order($this->query["order"]);
$order_inner = $this->query_order_inner($this->query["order"]);
$where_list = array();
list($where_props, $props_var) = $this->query_props($props);
$variables = array_merge($variables, $props_var);
list($where, $where_var) = $this->query_where($params);
$variables = array_merge($variables, $where_var);
if($where_props) $where_list[] = $where_props;
if($where) $where_list[] = $where;
$return_fields = "COUNT(*) as count";
$group_by = $join_props = $limit_query = $offset_query = "";
if(!$count){
$return_fields = " `bive_items`.* ";
$group_by = " GROUP BY `bive_items`.`item_id` ";
$limit_query = " LIMIT $limit ";
$offset_query = " OFFSET $offset ";
if(isset($params["item_class"])) {
$return_fields = "`bive_items`.*, GROUP_CONCAT(p.prop_key SEPARATOR '-+-') AS properties, GROUP_CONCAT(p.prop_value SEPARATOR '-+-') AS `values`";
$join_props = " JOIN `bive_items_props` p ON `bive_items`.`item_id` = p.`item_id` ";
$class = new $params["item_class"](0);
if(!count($class->props)) {
$return_fields = "`bive_items`.*";
$group_by = "";
$join_props = "";
}
}
}
$where_collect = $this->query_binary($where_list);
$result_query = "SELECT $return_fields FROM `bive_items` $join_props $order_inner WHERE $where_collect $group_by $order $limit_query $offset_query;";
return array($result_query, $variables);
}
// Соединение таблиц для сортировки
private function query_order_inner($order): string
{
if(!isset($order) || !isset($order["key"])) return "";
$key = $order["key"];
return "INNER JOIN `bive_items_props` AS `bip` ON `bive_items`.`item_id` = `bip`.`item_id` AND `bip`.`prop_key` = '$key'";
}
// Сортировка
private function query_order($order): string
{
if(!isset($order) || !isset($order["key"])) return "";
$direction = $order["direction"] ?? "ASC";
$type = $order["direction"] == "numeric" ? "CAST(`bip`.`prop_value` AS SIGNED)" : "`bip`.`prop_value`";
return " ORDER BY $type $direction ";
}
private function query_binary($array, $operator = "AND"): string
{
return "(" . implode(" " . mb_strtoupper($operator) . " ", $array) . ")";
}
// Условия поиска по свойствам
private function query_props($props, $operator = "and"): array
{
if(!$props || !count($props)) return array("", array());
$sql = array();
$variables = array();
foreach ($props as $key => $prop) {
if($key == "and") {
list($query, $var) = $this->query_props($prop, "and");
$sql[] = $query;
$variables[] = $var;
}else if($key == "or") {
list($query, $var) = $this->query_props($prop, "or");
$sql[] = $query;
$variables[] = $var;
} else {
list($key, $prop, $not, $op) = $this->query_operators($key, $prop);
$sql[] = $not . "EXISTS (
SELECT 1
FROM bive_items_props
WHERE bive_items.item_id = bive_items_props.item_id
AND bive_items_props.prop_key = '$key' AND bive_items_props.prop_value $op ?
)";
$variables[] = $prop;
}
}
$string_query = $this->query_binary($sql, $operator);
return array($string_query, $variables);
}
// Условия поиска по предметам
private function query_where($where, $operator = "and"): array
{
if(!$where || !count($where)) return array("", array());
$sql = array();
$variables = array();
foreach ($where as $key => $prop) {
if($key == "and") {
list($query, $var) = $this->query_where($prop, "and");
$sql[] = $query;
$variables = array(...$variables, ...$var);
}else if($key == "or") {
list($query, $var) = $this->query_where($prop, "or");
$sql[] = $query;
$variables[] = array(...$variables, ...$var);
} else {
list($key, $prop, $not, $op) = $this->query_operators($key, $prop);
$sql[] = "$not`bive_items`.`$key` $op ?";
$variables[] = $prop;
}
}
$string_query = $this->query_binary($sql, $operator);
return array($string_query, $variables);
}
private function query_operators($key, $prop): array
{
$not = "";
$op = " = ";
// Добавление отрицания
if(mb_substr($key, 0, 1) == "!") {
$key = substr($key, 1);
$not = " NOT ";
}
// Добавление конструкции LIKE
if(mb_substr($key, 0, 1) == "%") {
$prop = "%" . $prop;
$key = substr($key, 1);
$op = " LIKE ";
}
// Добавление конструкции LIKE
if(mb_substr($key, -1) == "%") {
$prop = $prop . "%";
$key = substr($key, 0, -1);
$op = " LIKE ";
}
return array($key, $prop, $not, $op);
}
}