engine */
'php' => 'php',
];
/** @var array Views shared data */
protected $sharedData = [];
/** @var Layout */
protected $layout;
/** @var string текущий layout */
protected $layoutTemplate = 'main';
/** @var array данные текущей страницы */
protected $pageData = [];
/** @var array данные текущей страницы для отправки в JS */
protected $jsData = [];
/** @var array registered packages */
protected $packagesList = [];
/** @var array packages to include */
protected $packages = [];
/** @var array scripts to include */
protected $scripts = [];
/** @var array inline scripts */
protected $scriptsInline = [];
/** @var array open inline scripts (currently building) */
protected $scriptsInlineOpen = [];
/** @var int inline scripts default position */
protected $scriptsInlineDefaultPosition = self::POS_FOOT;
/** @var array styles to include */
protected $styles = [];
/**
* Manifest filename
* @var string
*/
public const MANIFEST_FILE = 'mix-manifest.json';
/** @var array manifest files content cache */
protected $manifestFiles = [];
public function __construct(\bff\contracts\Application $app, Filesystem $files, ?string $path = null)
{
$this->app = $app;
$this->files = $files;
if (!empty($path)) {
$this->addPath($path);
} else {
$this->addPath($this->app->templatesPath());
$this->addPath($this->app->templatesPath('', !$this->app->adminPanel()));
}
# Register core packages
$this->packagesList = $this->app->filter('view.packages.list', [
# admin
'fancybox' => [ # v1.3.4
'js' => '@core/admin/fancybox/fancybox.js',
'css' => '@core/admin/fancybox/style.css',
],
'fancybox2' => [ # v2.1.5
'js' => '@core/fancybox2/jquery.fancybox.pack.js',
'css' => '@core/fancybox2/jquery.fancybox.css',
],
'fancybox3' => [ # v3.5.7
'js' => '@core/fancybox3/jquery.fancybox.js',
'css' => '@core/fancybox3/jquery.fancybox.css',
],
'datepicker' => [
'js' => '@core/admin/datepicker/datepicker.js',
'css' => '@core/admin/datepicker/style.css',
],
'datepicker.bs' => [ # ?
'js' => '@core/admin/bootstrap-datepicker/js/bootstrap-datepicker.min.js',
'css' => '@core/admin/bootstrap-datepicker/css/bootstrap-datepicker3.min.css',
],
'tablednd' => [
'js' => '@core/admin/tablednd.js',
],
'comments' => [
'js' => '@core/admin/comments/comments.js',
'css' => '@core/admin/comments/style.css',
],
# common
'autocomplete' => [
'js' => '@core/autocomplete/autocomplete.js',
],
'autocomplete.fb' => [
'js' => '@core/autocomplete.fb/autocomplete.fb.js',
'css' => '@core/autocomplete.fb/style.css',
],
'dynprops' => [
'js' => '@core/dynprops/dynprops.min.js',
],
'history' => [ # deprecated
'js' => '@core/history/history.min.js',
],
'jcrop' => [ # deprecated
'js' => '@core/jcrop/jquery.jcrop.min.js',
'css' => '@core/jcrop/jquery.Jcrop.css',
],
'jquery' => [ # deprecated
'js' => '@core/jquery/jquery.min.js',
],
'perfect-scrollbar' => [
'js' => '@core/perfect-scrollbar/perfect-scrollbar.min.js',
'css' => '@core/perfect-scrollbar/perfect-scrollbar.css',
],
'publicator' => [
'js' => '@core/publicator/publicator.min.js',
'css' => '@core/publicator/style.css',
],
'publicator.frontend' => [
'js' => '@core/publicator/frontend.min.js',
'css' => '@core/publicator/frontend.css',
],
'qquploader' => [
'js' => '@core/qquploader/fileuploader.js',
'css' => '@core/qquploader/style.css',
],
'tinymce' => [
'js' => [
'@core/tinymce/tinymce.min.js',
'@core/tinymce/jquery.tinymce.min.js',
],
],
'ui.sortable' => [
'js' => [
'@core/jquery.ui/core.js', # v1.8.22
'@core/jquery.ui/sortable.js', # v1.8.22
],
],
'ui.sortable.last' => [
'js' => '@core/jquery.ui/sortable.last.js', # v1.12.1
],
'wysiwyg' => [
'js' => '@core/wysiwyg/wysiwyg.min.js',
'css' => '@core/wysiwyg/style.css',
],
]);
}
public function onNewRequest($request)
{
$this->reset();
}
/**
* Get the filesystem instance
* @return Filesystem
*/
public function getFiles()
{
return $this->files;
}
/**
* Add a views path
* @param string $path
* @param bool $prepend
* @return void
*/
public function addPath(string $path, bool $prepend = false)
{
$path = $this->resolvePath($path);
if ($prepend) {
array_unshift($this->paths, $path);
} else {
$this->paths[] = $path;
}
}
/**
* Resolve the path
* @param string $path
* @return string
*/
protected function resolvePath(string $path)
{
return $path;
}
/**
* Register view extension
* @param string $extension
* @param string $engine
* @return void
*/
public function addExtension(string $extension, string $engine)
{
if (array_key_exists($extension, $this->extensions)) {
unset($this->extensions[$extension]);
}
$this->extensions = [$extension => $engine] + $this->extensions;
}
/**
* View registered package
* @param string $name
* @param array|Closure|null $resources to register/update package
* @return void
*/
public function package(string $name, $resources = null)
{
if ($resources) {
$this->packageRegister($name, $resources);
}
# include once
if (! array_key_exists($name, $this->packages)) {
$this->packages[$name] = true;
$package = $this->packagesList[$name] ?? null;
if ($package) {
$this->packageInclude($package);
}
}
}
/**
* Register package
* @param string $name
* @param array|Closure $resources
* @return void
*/
public function packageRegister(string $name, $resources)
{
$this->packagesList[$name] = $resources;
}
/**
* Register packages
* @param array $packages
* @return void
*/
public function packagesRegister(array $packages)
{
foreach ($packages as $name => $package) {
$this->packageRegister($name, $package);
}
}
/**
* Include package
* @param array|Closure $package
* @param string|null $resource type
* @return void
*/
protected function packageInclude($package, ?string $resource = null)
{
if ($package instanceof Closure) {
$package = $package();
}
if (! is_array($package)) {
return;
}
$resources = (!is_null($resource) ? [$resource] : ['js', 'css']);
foreach ($resources as $resource) {
if (empty($package[$resource])) {
continue;
}
if ($resource === 'js') {
$this->script($package[$resource]);
} elseif ($resource === 'css') {
$this->style($package[$resource]);
}
}
}
/**
* Получаем данные текущей страницы
* @param string $key ключ требуемых данных
* @param bool $default значение по умолчанию
* @return mixed
*/
public function pageData(string $key, $default = false)
{
return $this->pageData[$key] ?? $default;
}
/**
* Сохраняем данные текущей страницы
* @param string|array $key ключ данных
* @param mixed $value значение
* @return void
*/
public function setPageData($key, $value = false)
{
if (is_string($key)) {
$this->pageData[$key] = $value;
} elseif (is_array($key)) {
foreach ($key as $k => $v) {
if (is_string($k)) {
$this->pageData[$k] = $v;
}
}
}
}
/**
* Set data to transfer to javascript
* @param string|array $key unique data key or [key => data, ...]
* @param mixed $data
* @param bool $merge
* @return void
*/
public function jsData($key, $data = null, $merge = true)
{
if (is_string($key)) {
if (
$merge &&
is_array($data) &&
array_key_exists($key, $this->jsData) &&
is_array($this->jsData[$key])
) {
$this->jsData[$key] = array_merge($this->jsData[$key], $data);
} else {
$this->jsData[$key] = $data;
}
} elseif (is_array($key)) {
foreach ($key as $k => $v) {
$this->jsData($k, $v, $merge);
}
}
}
/**
* Render js data to transfer to javascript
* @param array $opts [
* string|bool 'escape' true/'html'/'js'
* int 'jsonOptions' json options
* ]
* @return mixed
*/
public function renderJsData(array $opts = [])
{
$opts = $this->defaults($opts, [
'key' => null,
'escape' => false,
'jsonOptions' => JSON_UNESCAPED_UNICODE,
]);
if (empty($this->jsData)) {
$opts['jsonOptions'] += JSON_FORCE_OBJECT;
}
return HTML::escape(json_encode(
$this->jsData[$opts['key'] ?? null] ?? $this->jsData,
$opts['jsonOptions']
), $opts['escape']);
}
/**
* Начало буферизации вывода
* @return void
*/
public function start()
{
ob_start();
ob_implicit_flush(false);
}
/**
* Окончание буферизации вывода
* @return string
*/
public function stop(): string
{
return ltrim(ob_get_clean());
}
/**
* Add shared views data
* @param string|array $key
* @param mixed $value
* @return mixed
*/
public function share($key, $value = null)
{
if (is_array($key)) {
foreach ($key as $k => $v) {
$this->sharedData[$k] = $v;
}
} else {
$this->sharedData[$key] = $value;
}
return $value;
}
/**
* Get views shared data
* @param string|null $key
* @param mixed $default
* @return array|mixed
*/
public function getShared($key = null, $default = null)
{
if (is_null($key)) {
return $this->sharedData;
}
return $this->sharedData[$key] ?? $default;
}
/**
* Render template
* @param array $data @ref template data
* @param string $view filename without extension
* @param string|null $path absolute path to file (without filename)
* @param array $opts [hookPrefix:string, this:object, tags:bool, throw:bool]
* @return string|ResponseInterface|mixed
*/
public function render(array &$data, string $view, ?string $path, array $opts = [])
{
# view => file path
$filePath = $this->resolveViewPath($view, $path, $opts);
if (empty($filePath)) {
if ($opts['throw'] ?? true) {
throw new Exception(sprintf('Unable to resolve file for view "%s" in path "%s"', $view, $path));
}
return '';
}
# hook: before render (data)
$hookPrefix = $opts['hookPrefix'] ?? 'view.tpl';
if (empty($path) || $path === $this->app->templatesPath()) {
$hookPrefix = 'view.tpl';
}
$hook = $hookPrefix . '.' . $view;
$hookData = [
'data' => &$data,
'filePath' => $filePath,
'fileName' => $this->files->name($filePath),
];
$this->app->hook($hook . '.data', $hookData);
# render
$render = function ($__filePath, $__sharedData, &$aData) {
extract($__sharedData, EXTR_SKIP);
extract($aData, EXTR_REFS | EXTR_OVERWRITE);
return require $__filePath;
};
if (array_key_exists('this', $opts)) { # bind context
$render = $render->bindTo($opts['this'], $opts['this']);
}
$obLevel = ob_get_level();
try {
$this->start();
$response = $render($filePath, $this->sharedData, $data);
$content = $this->stop();
} catch (Throwable $e) {
while (ob_get_level() > $obLevel) {
ob_end_clean();
}
throw $e;
}
if ($response instanceof ResponseInterface) {
return $response;
}
# hook: after render
if ($this->app->hooksAdded($hook)) {
unset($hookData['data']);
$content = $this->app->filter($hook, $content, $data, $hookData);
}
# tags: process
if ($opts['tags'] ?? false) {
$content = $this->app->tagsProcess($content);
}
return $content;
}
/**
* Hook on before template render
* @param string $template
* @param callable $callback function ($data = [&data, filePath, fileName]) {}
* @param string|null $from module.{name}, plugin.{name}, null => /tpl/
* @param int|null $priority
* @return mixed
*/
public function beforeRender(string $template, callable $callback, ?string $from = null, ?int $priority = null)
{
return $this->app->hookAdd('view.' . ($from ?: 'tpl') . '.' . $template . '.data', $callback, $priority);
}
/**
* Hook on after template render
* @param string $template
* @param callable $callback function ($content) { return $content; }
* @param string|null $from module.{name}, plugin.{name}, null => /tpl/
* @param int|null $priority
* @return mixed
*/
public function afterRender(string $template, callable $callback, ?string $from = null, ?int $priority = null)
{
return $this->app->hookAdd('view.' . ($from ?: 'tpl') . '.' . $template, $callback, $priority);
}
/**
* Рендеринг шаблона
* @param string $view название шаблона (без расширения)
* @param array $data данные, которые необходимо передать в шаблон
* @param string|null $from название/объект модуля/плагина, путь к файлу, null - путь к шаблона по умолчанию
* @param array $opts
* @return string|mixed HTML
*/
public function template(string $view, array $data = [], $from = null, array $opts = [])
{
if (empty($from) && strpos(trim($view), static::HINT_DELIMITER) > 0) {
$segments = explode(static::HINT_DELIMITER, $view);
if (count($segments) === 2) {
[$from, $view] = $segments;
}
}
if (is_string($from) && !empty($from)) {
if ($this->app->moduleExists($from)) {
return $this->app->module($from)->template($view, $data, $opts);
} elseif ($this->app->pluginExists($from)) {
return $this->app->plugin($from)->template($view, $data, $opts);
}
} elseif ($from instanceof Module) {
return $from->template($view, $data, $opts);
}
return $this->render($data, $view, $from, $opts);
}
/**
* Формирование пути к файлу шаблона
* @param string $view имя файла шаблона без расширения
* @param string|null $path путь к файлу
* @param array $opts [custom, extension]
* @return string|bool
*/
public function resolveViewPath(string $view, ?string $path = null, array $opts = [])
{
# paths
$paths = $this->paths;
if (! empty($path)) {
array_unshift(
$paths,
$this->resolvePath($path)
);
}
# extensions + subdirs
$dot = strpos($view, '.');
$views = [];
foreach ($this->extensions as $extension => $engine) {
$views[] = $view . '.' . $extension;
if ($dot !== false) {
# sub dir: view.file => view/file (first dot only)
$views[] = substr_replace($view, DS, $dot, 1) . '.' . $extension;
}
}
# file path
$filePath = false;
foreach ($paths as $path) {
foreach ($views as $view) {
$filePath = $this->resolveFileInPath($view, $path, $opts);
if ($filePath !== false) {
break 2;
}
}
}
return $filePath;
}
/**
* Проверям наличие файла по указанному пути
* Учитываем активную тему и кастомизацию
* @param string $fileName
* @param string $dir
* @param array $opts [extension:bool, theme:?bool, custom:bool]
* @return string|bool
*/
public function resolveFileInPath(string $fileName, string $dir, array $opts = [])
{
# relative
$relPath = DS . rtrim(mb_substr($dir, mb_strlen($this->app->basePath())), DS . ' ');
# extension + themed file version
$extension = $opts['extension'] ?? (mb_stripos($relPath, DS . 'plugins') === 0);
if ($extension) {
$themedPath = $this->resolvePath(rtrim($dir, DS) . DS . '_' . $this->app->theme()->getName());
$filePath = $this->resolveFileInPath($fileName, $themedPath, ['extension' => false] + $opts);
if ($filePath) {
return $filePath;
}
}
# active theme
if (($opts['theme'] ?? true) !== false) {
$fileInTheme = $this->resolveFileInTheme(rtrim($relPath, DS) . DS . $fileName);
if ($fileInTheme !== false) {
$filePath = $fileInTheme;
} else {
$filePath = rtrim($dir, DS . ' ') . DS . $fileName;
}
} else {
$filePath = rtrim($dir, DS . ' ') . DS . $fileName;
}
# do not try to resolve relative /file
if ($filePath === DS . $fileName) {
return false;
}
# modification
$filePath = modification($filePath, $opts['custom'] ?? true);
if ($this->files->exists($filePath)) {
return $filePath;
}
return false;
}
/**
* Look for themed version of the file
* @param string $filePath relative file path
* @param bool $asUrl
* @param array $opts [&version]
* @return string|bool path/url to current theme (if file in theme was found)
*/
public function resolveFileInTheme(string $filePath, bool $asUrl = false, array $opts = [])
{
$themePath = $this->app->theme()->fileThemed($filePath, $asUrl, $opts);
if ($themePath !== false) {
return $themePath . $filePath;
}
return false;
}
/**
* Render final html response
* @param array $data
* @param \bff\http\Response|ResponseInterface|null $response
* @return \bff\http\Response|ResponseInterface
*/
public function layoutResponse(array $data, ?ResponseInterface $response = null)
{
$response = $response ?? $this->app->response();
# Render layout and add to response
if (! empty($this->layoutTemplate)) {
if ($response->getBody()->isWritable()) {
$response->getBody()->write(
$this->layoutRender($data, $this->layoutTemplate)
);
}
}
return $response;
}
/**
* Рендеринг layout шаблона
* @param array $data данные, которые необходимо передать в шаблон
* @param string|null $template название layout'a (без расширения)
* @param string|null $path путь к шаблону или false - используем templatesPath
* @param array|null $opts
* @return mixed
*/
public function layoutRender(
array $data,
?string $template = null,
?string $path = null,
array $opts = null
) {
if (empty($template)) {
$template = $this->getLayoutTemplate();
if (empty($template)) {
$template = 'main';
}
}
$layout = $this->getLayout();
$opts['this'] = $opts['this'] ?? $layout;
$opts['tags'] = true;
if (
$this->app->adminPanel() ||
$this->resolveViewPath($layout->getTemplate()) === false # todo
) {
return $this->render($data, 'layout.' . $template, $path, $opts);
}
$layout->addToBody(function () use ($data, $template, $path, $opts) {
return $this->render($data, 'layout.' . $template, $path, $opts);
});
return $layout->render();
}
/**
* Get Layout instance
* @return Layout
*/
public function getLayout()
{
if (! $this->layout) {
$this->layout = new Layout();
}
return $this->layout;
}
/**
* Set Layout instance
* @param Layout $layout
* @return void
*/
public function setLayout($layout)
{
$this->layout = $layout;
}
/**
* Устанавливаем layout
* @param string $layoutTemplate название
* @param array|null $settings
* @return void
* @noinspection PhpUnusedParameterInspection
*/
public function setLayoutTemplate(string $layoutTemplate = '', ?array $settings = null)
{
$this->layoutTemplate = $layoutTemplate;
}
/**
* Получаем текущий layout
* @return string
*/
public function getLayoutTemplate(): string
{
return $this->layoutTemplate;
}
/**
* Подключаем javascript файл
* @param string|array $file название скрипта(без расширения ".js") или полный URL
* @param array|bool $opts [
* 'version' => версия подключаемого файла (для скриптов приложения) или FALSE
* 'top' => подключить и переместить в начало списка
* ]
* @param bool
* @return bool
*/
public function script($file, $opts = []): bool
{
if (empty($file)) {
return false;
}
if (is_bool($opts)) {
$opts = ['core' => $opts];
} elseif (is_string($opts) || is_numeric($opts)) {
$opts = ['version' => $opts];
} elseif (!is_array($opts)) {
$opts = [];
}
$opts = $this->defaults($opts, [
'core' => $this->app->adminPanel(),
'attr' => [],
'version' => null,
'top' => false,
]);
if (! is_array($file)) {
$file = [$file];
}
$list = [];
foreach ($file as $js) {
if (array_key_exists($js, $this->packagesList)) {
$this->package($js);
continue;
}
if (! $this->isUrl($js)) {
if (mb_stripos($js, '/') !== 0) {
$core = $opts['core'];
if (mb_stripos($js, '@core/') === 0) {
$core = true;
$js = mb_substr($js, 6);
}
if ($core) {
$opts['version'] = $opts['version'] ?? $this->manifestCoreVersion('/' . $js);
}
$js = ($core ? '/js/bff/' : '/js/') . $js; # name.js
}
$js = $js . (mb_substr($js, -3) !== '.js' ? '.js' : '');
$js = $this->app->url($js, $opts['version']);
}
if (! in_array($js, $this->scripts, true)) {
$list[$js] = [
'url' => $js,
'attr' => $opts['attr'],
];
}
}
if (empty($list)) {
return false;
}
if ($opts['top']) {
foreach ($this->scripts as $k => $v) {
$list[$k] = $v;
}
$this->scripts = $list;
} else {
foreach ($list as $k => $v) {
$this->scripts[$k] = $v;
}
}
return true;
}
/**
* Формирование списка подключаемых JavaScript файлов
* @param array|null $opts:
* bool html
* bool hooks
* bool minify
* bool adminPanel
* @return array|string
*/
public function scriptsRender(?array $opts = [])
{
if (is_null($opts)) {
$list = $this->scripts;
$this->scripts = [];
return $list;
}
$opts = $this->defaults($opts, [
'list' => [],
'html' => true,
'hooks' => true,
'minify' => false,
'adminPanel' => $this->app->adminPanel(),
]);
if ($opts['hooks']) {
# Extend scripts list
$this->app->hook(($opts['adminPanel'] ? 'admin.' : '') . 'js.extra');
}
$list = $this->scripts;
if (! empty($opts['list'])) {
$list = $opts['list'];
}
if ($opts['minify']) {
Minifier::process($list);
}
$list = $this->app->filter(($opts['adminPanel'] ? 'admin.' : '') . 'js.includes', $list, $opts);
foreach ($list as $k => $v) {
if (! is_array($v)) {
$list[$k] = ['url' => $v];
}
}
func::sortByPriority($list, 'priority');
if ($opts['html']) {
$html = '';
foreach ($list as $v) {
$attr = $this->defaults($v['attr'] ?? [], [
'src' => $v['url'],
'type' => 'text/javascript',
'charset' => 'utf-8',
]);
$html .= '' . PHP_EOL;
}
return $html;
}
return $list;
}
/**
* Strip surrounding ";
if ($reset) {
unset($this->scriptsInline[$position]);
}
return $html;
}
/**
* Set inline scripts default position
* @param int|string|null $position
* @return int previous position
*/
public function scriptsInlineDefaultPosition($position = null)
{
if ($position) {
$before = $this->scriptsInlineDefaultPosition;
$this->scriptsInlineDefaultPosition = $position;
return $before;
}
return $this->scriptsInlineDefaultPosition;
}
/**
* Подключаем CSS файл
* @param string|array $file название css файла(без расширения ".css") или полный URL
* @param array|string|int $opts доп. параметры [version=>mixed, top=>bool]
* @return bool
*/
public function style($file, $opts = [])
{
if (empty($file)) {
return false;
}
if (! is_array($file)) {
$file = [$file];
}
if (is_string($opts) || is_numeric($opts)) {
$opts = ['version' => $opts];
}
$opts = $this->defaults($opts, [
'version' => null,
'top' => false,
]);
$list = [];
foreach ($file as $k => $v) {
$name = (is_string($k) ? $k : $v);
if (array_key_exists($name, $this->packagesList)) {
$this->package($name);
continue;
}
if (isset($this->styles[$name])) {
continue;
}
$extension = (mb_substr($name, -4) !== '.css' ? '.css' : '');
if ($this->isUrl($name)) {
$list[$name] = $name . $extension . (!empty($opts['version']) ? '?v=' . $opts['version'] : '');
} else {
if (mb_stripos($name, '@core/') === 0) {
$name = mb_substr($name, 6);
$opts['version'] = $opts['version'] ?? $this->manifestCoreVersion('/' . $name);
$name = '/js/bff/' . $name;
} elseif (mb_stripos($name, '/') !== 0) {
$name = '/css/' . $name; # name.css
}
$list[$name] = $this->app->url($name . $extension, $opts['version']);
}
}
if (empty($list)) {
return false;
}
if ($opts['top']) {
foreach ($this->styles as $k => $v) {
$list[$k] = $v;
}
$this->styles = $list;
} else {
foreach ($list as $k => $v) {
$this->styles[$k] = $v;
}
}
return true;
}
/**
* Формирование списка подключаемых CSS файлов
* @param array|null $opts:
* array list
* bool html
* bool hooks
* bool minify
* bool adminPanel
* @return string|array
*/
public function stylesRender(?array $opts = [])
{
if (is_null($opts)) {
$list = $this->styles;
$this->styles = [];
return $list;
}
$opts = $this->defaults($opts, [
'list' => [],
'html' => true,
'hooks' => true,
'minify' => false,
'adminPanel' => $this->app->adminPanel(),
]);
$html = '';
if ($opts['hooks']) {
# Extend styles list
$this->start();
$this->app->hook(($opts['adminPanel'] ? 'admin.' : '') . 'css.extra'); # output allowed
$html .= $this->stop();
}
$list = $this->styles;
if (! empty($opts['list'])) {
$list = $opts['list'];
}
if ($opts['minify']) {
Minifier::process($list);
}
$list = $this->app->filter(($opts['adminPanel'] ? 'admin.' : '') . 'css.includes', $list, $opts);
foreach ($list as $k => $v) {
if (! is_array($v)) {
$list[$k] = ['url' => $v];
}
}
func::sortByPriority($list, 'priority');
if ($opts['html']) {
foreach ($list as $v) {
$attr = $this->defaults($v['attr'] ?? [], [
'href' => $v['url'],
'type' => 'text/css',
'rel' => 'stylesheet',
]);
$html .= '' . PHP_EOL;
}
return $html;
}
return $list;
}
/**
* File manifest version
* @param string $file
* @param string $manifest file path
* @param string $hot file path
* @param array $options [hashOnly]
* @return string
*/
public function manifestVersion(string $file, string $manifest, string $hot, array $options = [])
{
if (! array_key_exists($manifest, $this->manifestFiles)) {
if ($this->files->exists($manifest)) {
$this->manifestFiles[$manifest] = json_decode($this->files->get($manifest), true) ?? [];
}
}
$hashOnly = $options['hashOnly'] ?? true;
if (array_key_exists($file, $this->manifestFiles[$manifest] ?? [])) {
if ($this->app->isDebug() && Request::cookie($this->app->cookieKey('hot'))) {
if ($this->files->exists($hot)) {
$hotUrl = rtrim($this->files->get($hot));
if (
mb_stripos($hotUrl, 'http://') === 0 ||
mb_stripos($hotUrl, 'https://') === 0
) {
$options['hotUrl'] = rtrim($hotUrl, '/');
}
}
}
if ($hashOnly) {
# Extract hash: "file?id=hash" => "hash"
return explode('=', $this->manifestFiles[$manifest][$file])[1] ?? '';
}
return $this->manifestFiles[$manifest][$file];
}
if ($hashOnly) {
return '';
}
return $file;
}
/**
* Core file manifest version
* @param string $file
* @param array $options [hashOnly]
* @return string
*/
public function manifestCoreVersion(string $file, array $options = [])
{
return $this->manifestVersion(
$file,
$this->app->publicPath('/js/bff/' . static::MANIFEST_FILE),
$this->app->publicPath('/js/bff/hot'),
$options
);
}
/**
* Проверяем является ли указанный путь корректным URL
* @param string $path
* @return bool
*/
public function isUrl(string $path): bool
{
if (! preg_match('~^(#|//|https?://)~', $path)) {
return filter_var($path, FILTER_VALIDATE_URL) !== false;
}
return true;
}
/**
* View registered inline block
* @deprecated Use {@link View::block} instead
* @param string $id
* @param array $context
* @return string
*/
public function tag(string $id, $context = [])
{
return $this->app->tags()->view($id, $context);
}
/**
* View registered inline block
* @param string $id block id or Block class
* @param array $context view context data
* @return string
*/
public function block(string $id, $context = [])
{
if (class_exists($id) && is_a($id, Block::class, true)) {
return new $id($context);
}
return $this->app->tags()->view($id, $context);
}
/**
* Reset state
*/
public function reset()
{
$this->pageData = [];
$this->jsData = [];
$this->sharedData = [];
$this->layout = null;
$this->setLayoutTemplate('main');
$this->packages = [];
$this->scripts = [];
$this->scriptsInline = [];
$this->scriptsInlineOpen = [];
$this->scriptsInlineDefaultPosition = self::POS_FOOT;
$this->styles = [];
$this->manifestFiles = [];
}
}