phplibrary/src/Controller/Action.php
2025-11-02 12:33:26 +03:00

464 lines
14 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace ctiso\Controller;
use Exception;
use ctiso\Path;
use ctiso\Url;
use ctiso\Model\Factory;
use ctiso\HttpRequest;
use ctiso\Settings;
use ctiso\Database;
use ctiso\View\Composite;
use ctiso\View\View;
use App\Controller\State;
/**
* Контроллер страниц
*/
class Action implements ActionInterface
{
const TEMPLATE_EXTENSION = ".html"; // Расширение для шаблонов
const ACTION_PREFIX = "action"; // Префикс для функций действий
// Параметры устанавливаются при создании контроллера
public string $name = ''; // Имя модуля
/** @var \ctiso\Controller\Front */
public $front;
public string $modulePath = ''; // Путь к модулю
public string $moduleTitle = '';
public string $viewPathPrefix = ''; // Путь к шаблонам контроллера
/** Соединение с базой данных */
public Database $db;
// Фильтры
/** @var ?\ctiso\Filter\ActionAccess Обьект хранит параметры доступа */
public $access = null;
/** @var ?\ctiso\Filter\ActionLogger Обьект для ведения лога */
public $logger = null;
/** @var Factory Обьект для создания моделей */
private $factory = null; // Ссылка на обьект создания модели
private array $helpers = []; // Помошники для действий
/** @var ?Url Параметры для ссылки */
public $part = null;
/** @var \ctiso\Registry Ссылка на настройки */
public $config;
/** @var \ctiso\Role\User Обьект пользователя */
public $user;
/** @var \ctiso\View\View Для Widgets */
public $view = null;
public array $childNodes = [];
public array $ctrlValues = [];
public array $childViews = [];
function __construct() {
$this->part = new Url();
}
public function setUp(): void {
}
/**
* Загрузка файла настроек
* @param string $name
* @return array
*/
public function loadConfig($name) {
$basePath = $this->config->get('site', 'path');
$filename = Path::join($basePath, 'modules', $name);
$settings = [];
if (file_exists($filename)) {
$settings = include($filename);
} else {
throw new Exception('Невозможно загрузить файл настроек ' . $name);
}
return $settings;
}
public function getConnection(): Database
{
return $this->db;
}
/**
* Путь к установке модуля
* @param string $name
* @return string
*/
public function installPath($name)
{
$basePath = $this->config->get('system', 'path');
return Path::join($basePath, "modules", $name);
}
/**
* Добавляет подсказки
* @param View $view
* @param string $name
*/
public function addSuggest(View $view, $name): void {
$suggest = [];
$file = Path::join($this->modulePath, 'help', $name . '.suggest');
if (file_exists($file)) {
$view->suggestions = include($file);
}
}
/**
* Поиск иконки
* @param string $icon
* @param int $size
* @return string Путь к иконке
*/
function findIcon($icon, $size) {
$webPath = $this->config->get('site', 'web');
return Path::join($webPath, 'icons', $size . 'x' . $size, $icon . '.png');
}
/**
* Создает представление
* @param string $name
* @param class-string $viewClass
* @return Composite
*/
public function getView($name, $viewClass = Composite::class)
{
$file = $name . self::TEMPLATE_EXTENSION;
$basePath = $this->config->get('system', 'path');
$webPath = $this->config->get('system', 'web');
$list = [
Path::join($this->modulePath, 'templates', $this->viewPathPrefix) => Path::join($webPath, "modules", $this->name, 'templates', $this->viewPathPrefix),
Path::join($basePath, "templates") => Path::join($webPath, "templates")
];
// Поиск файла для шаблона
foreach($list as $ospath => $path) {
$template = Path::join($ospath, $file);
if(file_exists($template)) { break; }
}
/** @var \ctiso\View\Composite */
$tpl = new $viewClass($template);
$tpl->config = $this->config;
$stylePath = Path::join($webPath, "assets", "css");
$iconsPath = Path::join($webPath, 'icons');
$scriptPath = Path::join($webPath, 'assets');
$tpl->set('icons', $iconsPath); // Путь к файлам текущей темы
$tpl->set('assets', $stylePath);
$tpl->set('script', $scriptPath); // Путь к файлам скриптов
$tpl->set('template', $path); // Путь к файлам текущего шаблона
$tpl->setAlias([
'assets' => $stylePath,
'icons' => $iconsPath,
'script' => $scriptPath,
// Для media и template поиск происходит как для файлов шаблонов
'media' => $list,
'template' => $list
]);
$this->addSuggest($tpl, $name);
return $tpl;
}
/**
* @template T
* @param class-string<T> $name
* @return T
*/
public function getModel($name)
{
if (!$this->factory) {
$this->factory = new Factory($this->db, $this->config, $this->user);
}
return $this->factory->getModel($name);
}
/**
* Выбор действия
* Т.к действия являются методами класса то
* 1. Можно переопределить действия
* 2. Использовать наследование чтобы добавить к старому обработчику новое поведение
* @param HttpRequest $request запроса
* @return View|string
*/
public function preProcess(HttpRequest $request)
{
$action = self::ACTION_PREFIX . ucfirst($request->getAction());
if (!method_exists($this, $action)) {
$action = "actionIndex";
}
$view = $this->forward($action, $request);
if ($view instanceof View) {
$view->active_module = get_class($this);
$view->module_action = $action;
}
return $view;
}
/**
* Выполнение действия
* @param HttpRequest $request
* @return View|string
*/
public function execute(HttpRequest $request)
{
$result = $this->preProcess($request);
if (!empty($result)) {
$this->view = $result;
}
$text = $this->render();
return $text;
}
/**
* Перенаправление на другой контроллер
* @param string $action
* @param HttpRequest $args
* @return mixed
*/
public function forward($action, HttpRequest $args) {
$value = call_user_func([$this, $action], $args);
return $value;
}
/**
* Страница по умолчанию
* @param HttpRequest $request
* @return View|string
*/
public function actionIndex(HttpRequest $request) {
return "";
}
/**
* Добавление части ссылки
* @param string $key
* @param string $value
*/
public function addUrlPart($key, $value): void {
$this->part->addQueryParam($key, $value);
}
/**
* Генерация ссылки c учетом прав пользователя на ссылки
* @param string $actionName Действие
* @param array $param Дополнительные параметры
* 'mode' означает что элемент до отправки обрабатывается javascript
* @return Url|null
*/
public function nUrl($actionName, array $param = [])
{
$access = $this->access;
$url = new Url();
if ($access == null || $access->checkAction($actionName)) {
$moduleName = explode("\\", strtolower(get_class($this)));
if (count($moduleName) > 2) {
array_shift($moduleName);
if ($moduleName[0] == $moduleName[1]) {
array_shift($moduleName);
}
}
$param = array_merge(['module' => implode("\\", $moduleName), "action" => $actionName], $param);
$url->setParent($this->part);
$url->setQuery($param);
}
return $url;
}
/**
* Генерация ссылки на действие контроллера
* Ajax определяется автоматически mode = ajax используется для смены layout
* @param string $name
* @param array $param
* @return Url|null
*
* @example ?action=$name&mode=ajax
* {$param[i].key = $param[i].value}
*/
public function aUrl($name, array $param = [])
{
return $this->nUrl($name, array_merge(['mode' => 'ajax'], $param));
}
/**
* Добавление помошника контроллера
* @param class-string $class
*/
public function addHelper($class): void
{
$this->helpers [] = $class;
}
/**
* Вызов помошников контроллера
* @param HttpRequest $request
* @return mixed
*/
public function callHelpers(HttpRequest $request)
{
$action = self::ACTION_PREFIX . $request->getAction();
foreach ($this->helpers as $helper) {
if (method_exists($helper, $action)) {
return call_user_func([$helper, $action], $request, $this);
} else {
return $helper->actionIndex($request, $this); // Вместо return response ???
}
}
}
/**
* Загрузка файла класса
* @deprecated Веместо его нужно использовать автозагрузку
* @param string $path
* @param mixed $setup
* @param string $prefix
* @return mixed
*/
public function loadClass($path, $setup = null, $prefix = '')
{
if (file_exists($path)) {
require_once ($path);
$class = $prefix . pathinfo($path, PATHINFO_FILENAME);
return new $class($setup);
}
throw new Exception("NO CLASS $path");
}
/**
* Загрузка настроек
* @param string $path
* @return array
*/
public function loadSettings($path)
{
$result = new Settings($path);
$result->read();
return $result->export();
}
/**
* @param string $name
*/
public function setView($name): void
{
$this->view = $this->getView($name);
}
/**
* Установка заголовка для отображения
* @param string $title
*/
public function setTitle($title): void
{
$this->view->setTitle($title);
}
/**
* Добавление widget к отображению
* @param string $section
* @param View $node
*/
public function addChild($section, $node): void
{
$this->childNodes[$section] = $node;
}
/**
* Установка значения контроллера
* @param string $name
* @param mixed $value
*/
public function setValue($name, $value): void
{
$this->ctrlValues[$name] = $value;
}
/**
* Добавление дочернего отображения к текущему отображению
* @param string $section
* @param View $node
*/
public function addView($section, $node): void
{
$this->childViews[$section] = $node;
}
/**
* Генерация содержания
* Путаница c execute и render
* @return View|string
*/
public function render()
{
$view = $this->view;
if ($view instanceof View) {
$this->view->assignValues($this->ctrlValues);
/** @var ?Composite $node */
$node = null;
foreach ($this->childNodes as $name => $node) {
$node->make($this);
$this->view->setView($name, $node->view);
}
foreach ($this->childViews as $name => $node) {
$this->view->setView($name, $node);
}
}
return $this->view;
}
/**
* Установка идентификатора страницы
* @return int
*/
function getPageId(HttpRequest $request) {
$pageId = time();
$request->session()->set('page', $pageId);
return $pageId;
}
/**
* Проверка идентификатора страницы
* @param int $page Идентификатор страницы
* @return bool
*/
function checkPageId(HttpRequest $request, $page)
{
if ($request->get('__forced__')) {
return true;
}
$_page = $request->session()->get('page');
$result = ($_page && $_page == $page);
$request->session()->clean('page');
return $result;
}
/**
* @return State
*/
function _getActionPath() {
return new State('index');
}
function redirect(string $action): void {
header('location: ' . $action);
exit();
}
}