464 lines
14 KiB
PHP
464 lines
14 KiB
PHP
<?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();
|
||
}
|
||
}
|