mirror of
https://github.com/avecms/AVE.cms.git
synced 2026-08-01 08:45:44 +00:00
264 lines
7.9 KiB
PHP
264 lines
7.9 KiB
PHP
<?php
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------------------
|
|
| AVE.cms
|
|
|--------------------------------------------------------------------------------------
|
|
| @package AVE.cms
|
|
| @file system/App/Frontend/BlockRenderer.php
|
|
| @author AVE.cms <support@ave-cms.ru>
|
|
| @copyright 2007-2026 (c) AVE.cms
|
|
| @link https://ave-cms.ru
|
|
| @version 3.3
|
|
*/
|
|
|
|
namespace App\Frontend;
|
|
|
|
defined('BASEPATH') || die('Direct access to this location is not allowed.');
|
|
|
|
use App\Common\PublicSysblockRegistry;
|
|
use App\Common\Lifecycle;
|
|
use App\Common\AdminLocation;
|
|
use App\Frontend\Debug\PublicLayoutInspector;
|
|
use App\Frontend\Media\ThumbnailUrl;
|
|
use App\Frontend\Media\WatermarkService;
|
|
use App\Helpers\Debug;
|
|
use App\Helpers\Json;
|
|
use App\Helpers\Url;
|
|
|
|
/** Renders unified blocks and their nested public tags. */
|
|
class BlockRenderer
|
|
{
|
|
protected static $stack = array();
|
|
protected $maxDepth = 20;
|
|
|
|
public function visual($identifier)
|
|
{
|
|
$id = $this->identifier($identifier);
|
|
if ($id === '') {
|
|
return false;
|
|
}
|
|
|
|
$key = 'block:' . $id;
|
|
if (!$this->enter($key)) {
|
|
return '';
|
|
}
|
|
|
|
$rendering = Lifecycle::event('content.block.rendering', 'block', 'rendering', $id, array(
|
|
'document' => PublicPageContext::document(),
|
|
), null, array(), 'block_renderer');
|
|
if ($rendering->cancelled()) {
|
|
$this->leave($key);
|
|
return $this->inspect((string) $rendering->result(), $id, 'block');
|
|
}
|
|
|
|
Debug::startTime('BLOCK_' . $id);
|
|
$resolvedId = $id;
|
|
if (is_numeric($id) && PublicSysblockRegistry::metadata('block-' . $id)) {
|
|
$resolvedId = 'block-' . $id;
|
|
}
|
|
|
|
$output = $this->system($resolvedId, null, 'block');
|
|
PublicProfiler::record(array('BLOCKS', $id), Debug::endTime('BLOCK_' . $id));
|
|
$this->leave($key);
|
|
$rendered = Lifecycle::event('content.block.rendered', 'block', 'rendered', $id, array(
|
|
'document_id' => PublicPageContext::documentId(),
|
|
), $output, array(), 'block_renderer');
|
|
return (string) $rendered->result();
|
|
}
|
|
|
|
public function system($identifier, $rawParams = null, $source = 'sysblock')
|
|
{
|
|
$id = $this->identifier($identifier);
|
|
if ($id === '') {
|
|
return false;
|
|
}
|
|
|
|
$params = $this->params($rawParams);
|
|
$key = 'sysblock:' . $id . ':' . md5(serialize($params));
|
|
if (!$this->enter($key)) {
|
|
return '';
|
|
}
|
|
|
|
$rendering = Lifecycle::event('content.sysblock.rendering', 'sysblock', 'rendering', $id, array(
|
|
'params' => $params,
|
|
'document' => PublicPageContext::document(),
|
|
), null, array(), 'block_renderer');
|
|
if ($rendering->cancelled()) {
|
|
$this->leave($key);
|
|
return $this->inspect((string) $rendering->result(), $id, $source);
|
|
}
|
|
|
|
Debug::startTime('SYSBLOCK_' . $id);
|
|
$resolved = PublicSysblockRegistry::render($id, $params, array(
|
|
'core' => null,
|
|
'document' => PublicPageContext::document(),
|
|
));
|
|
if (!empty($resolved['final'])) {
|
|
$this->leave($key);
|
|
$rendered = Lifecycle::event('content.sysblock.rendered', 'sysblock', 'rendered', $id, array(
|
|
'params' => $params,
|
|
'document_id' => PublicPageContext::documentId(),
|
|
), isset($resolved['output']) ? (string) $resolved['output'] : '', array('final' => true), 'block_renderer');
|
|
return $this->inspect((string) $rendered->result(), $id, $source);
|
|
}
|
|
|
|
$output = isset($resolved['output']) ? (string) $resolved['output'] : '';
|
|
$output = $this->tags($output);
|
|
$output = preg_replace_callback(
|
|
'/\[(?:sys:param|sysparam):([A-Za-z0-9-+_]+)\]/',
|
|
function ($match) use ($params) {
|
|
if (!array_key_exists($match[1], $params)) {
|
|
return '';
|
|
}
|
|
|
|
return is_scalar($params[$match[1]]) ? (string) $params[$match[1]] : Json::encode($params[$match[1]], JSON_UNESCAPED_UNICODE);
|
|
},
|
|
$output
|
|
);
|
|
if (!empty($resolved['evaluate'])) {
|
|
$output = \App\Common\StoredPhpRuntime::render($output, get_defined_vars());
|
|
}
|
|
|
|
PublicProfiler::record(array('SYSBLOCK', $id), Debug::endTime('SYSBLOCK_' . $id));
|
|
$this->leave($key);
|
|
$rendered = Lifecycle::event('content.sysblock.rendered', 'sysblock', 'rendered', $id, array(
|
|
'params' => $params,
|
|
'document_id' => PublicPageContext::documentId(),
|
|
), $output, array('final' => false), 'block_renderer');
|
|
return $this->inspect((string) $rendered->result(), $id, $source);
|
|
}
|
|
|
|
protected function inspect($output, $identifier, $source)
|
|
{
|
|
if (!PublicLayoutInspector::enabled()) {
|
|
return (string) $output;
|
|
}
|
|
|
|
$row = PublicSysblockRegistry::metadata($identifier);
|
|
$id = is_array($row) && isset($row['id']) ? (int) $row['id'] : 0;
|
|
$alias = is_array($row) && isset($row['sysblock_alias']) ? (string) $row['sysblock_alias'] : (string) $identifier;
|
|
$title = is_array($row) && isset($row['sysblock_name']) ? (string) $row['sysblock_name'] : 'Блок ' . $alias;
|
|
$tagName = $source === 'block' ? 'block' : 'sysblock';
|
|
|
|
return PublicLayoutInspector::annotate($output, 'block', array(
|
|
'id' => $id > 0 ? $id : $identifier,
|
|
'title' => $title,
|
|
'alias' => $alias,
|
|
'tag' => '[tag:' . $tagName . ':' . $identifier . ']',
|
|
'edit_url' => $id > 0 ? AdminLocation::url('blocks?edit=' . $id) : AdminLocation::url('blocks'),
|
|
));
|
|
}
|
|
|
|
protected function tags($output)
|
|
{
|
|
$documentId = PublicPageContext::documentId();
|
|
$output = str_replace(
|
|
array('[tag:mediapath]', '[tag:path]', '[tag:docid]', '[tag:home]'),
|
|
array(
|
|
ABS_PATH . 'templates/' . (defined('THEME_FOLDER') ? THEME_FOLDER : DEFAULT_THEME_FOLDER) . '/',
|
|
ABS_PATH,
|
|
$documentId,
|
|
Url::home(),
|
|
),
|
|
(string) $output
|
|
);
|
|
if (strpos($output, '[tag:breadcrumb]') !== false) {
|
|
$output = str_replace('[tag:breadcrumb]', (new BreadcrumbRenderer())->render(), $output);
|
|
}
|
|
|
|
$requestRenderer = new RequestRenderer();
|
|
$output = preg_replace_callback(
|
|
'/\[tag:request:([A-Za-z0-9-_]{1,20}+)\]/',
|
|
function ($match) use ($requestRenderer) {
|
|
return $requestRenderer->render($match);
|
|
},
|
|
$output
|
|
);
|
|
|
|
if ($documentId > 0) {
|
|
$fieldRenderer = new DocumentFieldRenderer();
|
|
$output = preg_replace_callback(
|
|
'/\[tag:fld:([a-zA-Z0-9-_]+)\]\[([0-9]+)]\[([0-9]+)]/',
|
|
function ($match) use ($fieldRenderer, $documentId) {
|
|
return $fieldRenderer->element($match[1], $match[2], $match[3], $documentId);
|
|
},
|
|
$output
|
|
);
|
|
$output = preg_replace_callback(
|
|
'/\[tag:fld:([a-zA-Z0-9-_]+)(|[:(\d)])+?\]/',
|
|
function ($match) use ($fieldRenderer, $documentId) {
|
|
return $fieldRenderer->render($match, $documentId);
|
|
},
|
|
$output
|
|
);
|
|
$watermarks = new WatermarkService();
|
|
$output = preg_replace_callback(
|
|
'/\[tag:watermark:(.+?):([a-zA-Z]+):([0-9]+)\]/',
|
|
array($watermarks, 'fromTag'),
|
|
$output
|
|
);
|
|
$output = preg_replace_callback(
|
|
'/\[tag:([rcfts]\d+x\d+r*):(.*?)]/',
|
|
array(ThumbnailUrl::class, 'fromTag'),
|
|
$output
|
|
);
|
|
}
|
|
|
|
$output = preg_replace_callback(
|
|
'/\[tag:block:([A-Za-z0-9-_]{1,20}+)\]/',
|
|
function ($match) {
|
|
return $this->visual($match[1]);
|
|
},
|
|
$output
|
|
);
|
|
return preg_replace_callback(
|
|
'/\[tag:sysblock:([A-Za-z0-9-_]{1,20}+)(|:\{(.*?)\})\]/',
|
|
function ($match) {
|
|
return $this->system($match[1], isset($match[2]) ? $match[2] : null);
|
|
},
|
|
$output
|
|
);
|
|
}
|
|
|
|
protected function identifier($value)
|
|
{
|
|
if (is_array($value)) {
|
|
$value = isset($value[1]) ? $value[1] : '';
|
|
}
|
|
|
|
$value = trim((string) $value);
|
|
return preg_match('/^[A-Za-z0-9_-]{1,20}$/', $value) ? $value : '';
|
|
}
|
|
|
|
protected function params($raw)
|
|
{
|
|
if (is_array($raw)) {
|
|
return $raw;
|
|
}
|
|
|
|
$raw = trim((string) $raw, ':');
|
|
if ($raw === '') {
|
|
return array();
|
|
}
|
|
|
|
return Json::toArray($raw);
|
|
}
|
|
|
|
protected function enter($key)
|
|
{
|
|
if (count(self::$stack) >= $this->maxDepth || isset(self::$stack[$key])) {
|
|
error_log('Public block recursion stopped: ' . $key);
|
|
return false;
|
|
}
|
|
|
|
self::$stack[$key] = true;
|
|
return true;
|
|
}
|
|
|
|
protected function leave($key)
|
|
{
|
|
unset(self::$stack[$key]);
|
|
}
|
|
}
|