mirror of
https://github.com/avecms/AVE.cms.git
synced 2026-08-01 00:45:44 +00:00
272 lines
11 KiB
PHP
272 lines
11 KiB
PHP
<?php
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------------------
|
|
| AVE.cms
|
|
|--------------------------------------------------------------------------------------
|
|
| @package AVE.cms
|
|
| @file adminx/modules/PublicSite/PresentationModel.php
|
|
| @author AVE.cms <support@ave-cms.ru>
|
|
| @copyright 2007-2026 (c) AVE.cms
|
|
| @link https://ave-cms.ru
|
|
| @version 3.3
|
|
*/
|
|
|
|
namespace App\Adminx\PublicSite;
|
|
|
|
defined('BASEPATH') || die('Direct access to this location is not allowed.');
|
|
|
|
use App\Common\DatabaseSchema;
|
|
use App\Common\ModuleManager;
|
|
use App\Content\CatalogTables;
|
|
use App\Content\ContentTables;
|
|
use App\Content\Documents\DocumentPickerRepository;
|
|
use App\Content\Presentation\PresentationAssignmentRepository;
|
|
use App\Content\Presentation\PresentationPreview;
|
|
use App\Content\Presentation\PresentationRepository;
|
|
use App\Content\Presentation\PresentationSyntax;
|
|
use DB;
|
|
|
|
class PresentationModel
|
|
{
|
|
protected static $targets;
|
|
|
|
public static function documents($limit = 100)
|
|
{
|
|
return (new DocumentPickerRepository())->search('', array(), max(1, min(100, (int) $limit)));
|
|
}
|
|
|
|
public static function legacySources()
|
|
{
|
|
$sources = array();
|
|
self::appendLegacy(
|
|
$sources,
|
|
CatalogTables::table('catalog_card_templates'),
|
|
'Товарные карточки',
|
|
'/catalog/card-templates',
|
|
'ti-layout-cards'
|
|
);
|
|
self::appendLegacy(
|
|
$sources,
|
|
CatalogTables::table('catalog_filter_templates'),
|
|
'Фильтры товарного каталога',
|
|
'/catalog/filter-templates',
|
|
'ti-adjustments-horizontal'
|
|
);
|
|
return $sources;
|
|
}
|
|
|
|
public static function assignmentTargets()
|
|
{
|
|
if (self::$targets !== null) { return self::$targets; }
|
|
$targets = array(
|
|
'global' => array(array(
|
|
'key' => '0', 'title' => 'Везде', 'meta' => 'Общее назначение',
|
|
'url' => '', 'available' => true,
|
|
)),
|
|
'rubric' => array(),
|
|
'request' => array(),
|
|
'catalog' => array(),
|
|
'module' => array(),
|
|
);
|
|
$rubrics = ContentTables::table('rubrics');
|
|
if (DatabaseSchema::tableExists($rubrics)) {
|
|
$rows = DB::query('SELECT Id,rubric_title FROM ' . $rubrics . ' ORDER BY rubric_title,Id')->getAll() ?: array();
|
|
foreach ($rows as $row) {
|
|
$targets['rubric'][] = array(
|
|
'key' => (string) (int) $row['Id'],
|
|
'title' => self::decode($row['rubric_title']),
|
|
'meta' => 'Рубрика #' . (int) $row['Id'],
|
|
'url' => '/rubrics/' . (int) $row['Id'],
|
|
'available' => true,
|
|
);
|
|
}
|
|
}
|
|
|
|
$requests = ContentTables::table('request');
|
|
if (DatabaseSchema::tableExists($requests)) {
|
|
$rows = DB::query('SELECT Id,request_title,request_alias FROM ' . $requests . ' ORDER BY request_title,Id')->getAll() ?: array();
|
|
foreach ($rows as $row) {
|
|
$title = self::decode($row['request_title']);
|
|
if ($title === '') { $title = 'Запрос #' . (int) $row['Id']; }
|
|
$targets['request'][] = array(
|
|
'key' => (string) (int) $row['Id'],
|
|
'title' => $title,
|
|
'meta' => trim((string) $row['request_alias']) !== '' ? (string) $row['request_alias'] : 'Запрос #' . (int) $row['Id'],
|
|
'url' => '/requests/' . (int) $row['Id'],
|
|
'available' => true,
|
|
);
|
|
}
|
|
}
|
|
|
|
$items = CatalogTables::table('module_catalog_items');
|
|
$settings = CatalogTables::table('module_catalog_settings');
|
|
if (DatabaseSchema::tableExists($items) && DatabaseSchema::tableExists($settings)) {
|
|
$rows = DB::query(
|
|
'SELECT i.id,i.name,i.level,i.rubric_id,i.field_id,COALESCE(s.purpose,\'content\') purpose'
|
|
. ' FROM ' . $items . ' i LEFT JOIN ' . $settings
|
|
. ' s ON s.rubric_id=i.rubric_id AND s.field_id=i.field_id'
|
|
. ' ORDER BY s.purpose DESC,i.rubric_id,i.field_id,i.level,i.position,i.id'
|
|
)->getAll() ?: array();
|
|
foreach ($rows as $row) {
|
|
$targets['catalog'][] = array(
|
|
'key' => (string) (int) $row['id'],
|
|
'title' => str_repeat('— ', max(0, min(5, (int) $row['level']))) . self::decode($row['name']),
|
|
'meta' => ((string) $row['purpose'] === 'commerce' ? 'Товарный раздел' : 'Раздел контента') . ' #' . (int) $row['id'],
|
|
'url' => '/catalog/' . (int) $row['rubric_id'] . '/' . (int) $row['field_id'] . '?item=' . (int) $row['id'],
|
|
'available' => true,
|
|
);
|
|
}
|
|
}
|
|
|
|
foreach (ModuleManager::all() as $module) {
|
|
if (empty($module['installed']) || empty($module['enabled'])) { continue; }
|
|
$extension = isset($module['admin_extension']) && is_array($module['admin_extension'])
|
|
? $module['admin_extension']
|
|
: array();
|
|
$url = isset($extension['url']) ? trim((string) $extension['url']) : '';
|
|
if ($url === '') { $url = '/modules/' . (string) $module['code']; }
|
|
$targets['module'][] = array(
|
|
'key' => strtolower((string) $module['code']),
|
|
'title' => (string) $module['name'],
|
|
'meta' => 'Модуль · ' . (string) $module['code'],
|
|
'url' => $url,
|
|
'available' => true,
|
|
);
|
|
}
|
|
|
|
usort($targets['module'], function ($left, $right) {
|
|
return strcasecmp((string) $left['title'], (string) $right['title']);
|
|
});
|
|
self::$targets = $targets;
|
|
return self::$targets;
|
|
}
|
|
|
|
public static function assignments($presentationId)
|
|
{
|
|
$targets = self::assignmentTargetMap();
|
|
$items = PresentationAssignmentRepository::all((int) $presentationId);
|
|
foreach ($items as &$item) {
|
|
$key = (string) $item['target_type'] . ':' . (string) $item['target_key'];
|
|
$target = isset($targets[$key]) ? $targets[$key] : null;
|
|
$item['target_title'] = $target ? (string) $target['title'] : 'Цель больше не существует';
|
|
$item['target_meta'] = $target ? (string) $target['meta'] : $key;
|
|
$item['target_url'] = $target ? (string) $target['url'] : '';
|
|
$item['target_available'] = (bool) $target;
|
|
}
|
|
|
|
unset($item);
|
|
return $items;
|
|
}
|
|
|
|
public static function validateAssignment(array $input)
|
|
{
|
|
$type = strtolower(trim(isset($input['target_type']) ? (string) $input['target_type'] : ''));
|
|
$key = strtolower(trim(isset($input['target_key']) ? (string) $input['target_key'] : ''));
|
|
if ($type === 'global') { $key = '0'; }
|
|
$targets = self::assignmentTargetMap();
|
|
if (!isset($targets[$type . ':' . $key])) {
|
|
throw new \InvalidArgumentException('Выберите существующую цель назначения');
|
|
}
|
|
|
|
$input['target_type'] = $type;
|
|
$input['target_key'] = $key;
|
|
return $input;
|
|
}
|
|
|
|
public static function diagnose($presentationId, array $input)
|
|
{
|
|
$presentationId = (int) $presentationId;
|
|
$item = PresentationRepository::one($presentationId);
|
|
if (!$item) { throw new \RuntimeException('Представление не найдено'); }
|
|
$checks = array();
|
|
$markup = array(
|
|
'Элемент' => isset($input['draft_item_markup']) ? (string) $input['draft_item_markup'] : (string) $item['draft_item_markup'],
|
|
'Обёртка' => isset($input['draft_wrapper_markup']) ? (string) $input['draft_wrapper_markup'] : (string) $item['draft_wrapper_markup'],
|
|
'Пустой результат' => isset($input['draft_empty_markup']) ? (string) $input['draft_empty_markup'] : (string) $item['draft_empty_markup'],
|
|
);
|
|
if (trim($markup['Элемент']) === '') {
|
|
$checks[] = self::check('error', 'Шаблон элемента пуст', 'Заполните разметку одного материала.');
|
|
} else {
|
|
$syntax = PresentationSyntax::checkMany($markup);
|
|
$checks[] = self::check($syntax['ok'] ? 'ok' : 'error', 'Twig-шаблоны', $syntax['message']);
|
|
}
|
|
|
|
$assignments = self::assignments($presentationId);
|
|
if (!$assignments) {
|
|
$checks[] = self::check('warning', 'Нет назначений', 'Представление сохранено, но пока нигде не используется.', '/public-site/presentations');
|
|
} else {
|
|
$missing = array_filter($assignments, function ($assignment) { return empty($assignment['target_available']); });
|
|
$native = array_filter($assignments, function ($assignment) { return (string) $assignment['mode'] === 'native'; });
|
|
$checks[] = self::check(
|
|
$missing ? 'error' : 'ok',
|
|
'Цели назначения',
|
|
$missing ? 'Есть связи с удалёнными рубриками, запросами, разделами или модулями.' : 'Все ' . count($assignments) . ' назначений ведут к существующим целям.'
|
|
);
|
|
if ($native && empty($item['is_published'])) {
|
|
$checks[] = self::check('error', 'Новое представление не опубликовано', 'Native-назначение начнёт работать только после публикации.');
|
|
} elseif ($native) {
|
|
$checks[] = self::check('ok', 'Публичный режим', count($native) . ' назначений используют опубликованную версию.');
|
|
} else {
|
|
$checks[] = self::check('ok', 'Публичный сайт защищён', 'Назначений native нет: текущая разметка сайта не заменяется.');
|
|
}
|
|
}
|
|
|
|
$documentId = isset($input['preview_document_id']) ? (int) $input['preview_document_id'] : 0;
|
|
if ($documentId > 0) {
|
|
try {
|
|
PresentationPreview::render($documentId, $input);
|
|
$checks[] = self::check('ok', 'Предпросмотр', 'Черновик успешно собран на документе #' . $documentId . '.');
|
|
} catch (\Throwable $e) {
|
|
$checks[] = self::check('error', 'Предпросмотр', $e->getMessage());
|
|
}
|
|
} else {
|
|
$checks[] = self::check('warning', 'Предпросмотр не проверен', 'Выберите реальный материал и повторите диагностику.');
|
|
}
|
|
|
|
$summary = array('ok' => 0, 'warning' => 0, 'error' => 0);
|
|
foreach ($checks as $check) { $summary[$check['status']]++; }
|
|
return array(
|
|
'ready' => $summary['error'] === 0,
|
|
'summary' => $summary,
|
|
'checks' => $checks,
|
|
);
|
|
}
|
|
|
|
protected static function appendLegacy(array &$sources, $table, $title, $url, $icon)
|
|
{
|
|
if (!DatabaseSchema::tableExists($table)) { return; }
|
|
$sources[] = array(
|
|
'title' => (string) $title,
|
|
'url' => (string) $url,
|
|
'icon' => (string) $icon,
|
|
'count' => (int) DB::query('SELECT COUNT(*) FROM ' . $table)->getValue(),
|
|
);
|
|
}
|
|
|
|
protected static function assignmentTargetMap()
|
|
{
|
|
$map = array();
|
|
foreach (self::assignmentTargets() as $type => $items) {
|
|
foreach ($items as $item) { $map[$type . ':' . (string) $item['key']] = $item; }
|
|
}
|
|
|
|
return $map;
|
|
}
|
|
|
|
protected static function check($status, $title, $message, $url = '')
|
|
{
|
|
return array(
|
|
'status' => (string) $status,
|
|
'title' => (string) $title,
|
|
'message' => (string) $message,
|
|
'url' => (string) $url,
|
|
);
|
|
}
|
|
|
|
protected static function decode($value)
|
|
{
|
|
return html_entity_decode(stripslashes((string) $value), ENT_QUOTES, 'UTF-8');
|
|
}
|
|
}
|