%PDF- %PDF-
| Direktori : /www/varak.net/losik.varak.net/vendor/latte/latte/src/Latte/ |
| Current File : /www/varak.net/losik.varak.net/vendor/latte/latte/src/Latte/Engine.php |
<?php
/**
* This file is part of the Latte (https://latte.nette.org)
* Copyright (c) 2008 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Latte;
/**
* Templating engine Latte.
*/
class Engine
{
use Strict;
public const VERSION = '2.11.6';
public const VERSION_ID = 21106;
/** Content types */
public const
CONTENT_HTML = 'html',
CONTENT_XHTML = 'xhtml',
CONTENT_XML = 'xml',
CONTENT_JS = 'js',
CONTENT_CSS = 'css',
CONTENT_ICAL = 'ical',
CONTENT_TEXT = 'text';
/** @var callable[] */
public $onCompile = [];
/** @internal */
public $probe;
/** @var Parser|null */
private $parser;
/** @var Compiler|null */
private $compiler;
/** @var Loader|null */
private $loader;
/** @var Runtime\FilterExecutor */
private $filters;
/** @var \stdClass */
private $functions;
/** @var mixed[] */
private $providers = [];
/** @var string */
private $contentType = self::CONTENT_HTML;
/** @var string|null */
private $tempDirectory;
/** @var bool */
private $autoRefresh = true;
/** @var bool */
private $strictTypes = false;
/** @var Policy|null */
private $policy;
/** @var bool */
private $sandboxed = false;
public function __construct()
{
$this->filters = new Runtime\FilterExecutor;
$this->functions = new \stdClass;
$this->probe = function () {};
$defaults = new Runtime\Defaults;
foreach ($defaults->getFilters() as $name => $callback) {
$this->filters->add($name, $callback);
}
foreach ($defaults->getFunctions() as $name => $callback) {
$this->functions->$name = $callback;
}
}
/**
* Renders template to output.
* @param object|mixed[] $params
*/
public function render(string $name, $params = [], ?string $block = null): void
{
$template = $this->createTemplate($name, $this->processParams($params));
$template->global->coreCaptured = false;
($this->probe)($template);
$template->render($block);
}
/**
* Renders template to string.
* @param object|mixed[] $params
*/
public function renderToString(string $name, $params = [], ?string $block = null): string
{
$template = $this->createTemplate($name, $this->processParams($params));
$template->global->coreCaptured = true;
($this->probe)($template);
return $template->capture(function () use ($template, $block) { $template->render($block); });
}
/**
* Creates template object.
* @param mixed[] $params
*/
public function createTemplate(string $name, array $params = []): Runtime\Template
{
$class = $this->getTemplateClass($name);
if (!class_exists($class, false)) {
$this->loadTemplate($name);
}
$this->providers['fn'] = $this->functions;
return new $class($this, $params, $this->filters, $this->providers, $name, $this->sandboxed ? $this->policy : null);
}
/**
* Compiles template to PHP code.
*/
public function compile(string $name): string
{
if ($this->sandboxed && !$this->policy) {
throw new \LogicException('In sandboxed mode you need to set a security policy.');
}
foreach ($this->onCompile ?: [] as $cb) {
(Helpers::checkCallback($cb))($this);
}
$this->onCompile = [];
$source = $this->getLoader()->getContent($name);
$comment = preg_match('#\n|\?#', $name) ? null : "source: $name";
try {
$tokens = $this->getParser()
->setContentType($this->contentType)
->parse($source);
$code = $this->getCompiler()
->setContentType($this->contentType)
->setFunctions(array_keys((array) $this->functions))
->setFilters($this->filters->_origNames)
->setPolicy($this->sandboxed ? $this->policy : null)
->compile($tokens, $this->getTemplateClass($name), $comment, $this->strictTypes);
} catch (\Throwable $e) {
if (!$e instanceof CompileException) {
$e = new CompileException($e instanceof SecurityViolationException ? $e->getMessage() : "Thrown exception '{$e->getMessage()}'", 0, $e);
}
$line = isset($tokens)
? $this->getCompiler()->getLine()
: $this->getParser()->getLine();
throw $e->setSource($source, $line, $name);
}
return $code;
}
/**
* Compiles template to cache.
* @throws \LogicException
*/
public function warmupCache(string $name): void
{
if (!$this->tempDirectory) {
throw new \LogicException('Path to temporary directory is not set.');
}
$class = $this->getTemplateClass($name);
if (!class_exists($class, false)) {
$this->loadTemplate($name);
}
}
private function loadTemplate(string $name): void
{
if (!$this->tempDirectory) {
$code = $this->compile($name);
if (@eval(substr($code, 5)) === false) { // @ is escalated to exception, substr removes <?php
throw (new CompileException('Error in template: ' . error_get_last()['message']))
->setSource($code, error_get_last()['line'], "$name (compiled)");
}
return;
}
// Solving atomicity to work everywhere is really pain in the ass.
// 1) We want to do as little as possible IO calls on production and also directory and file can be not writable
// so on Linux we include the file directly without shared lock, therefore, the file must be created atomically by renaming.
// 2) On Windows file cannot be renamed-to while is open (ie by include), so we have to acquire a lock.
$file = $this->getCacheFile($name);
$lock = defined('PHP_WINDOWS_VERSION_BUILD')
? $this->acquireLock("$file.lock", LOCK_SH)
: null;
if (!$this->isExpired($file, $name) && (@include $file) !== false) { // @ - file may not exist
return;
}
if ($lock) {
flock($lock, LOCK_UN); // release shared lock so we can get exclusive
}
$lock = $this->acquireLock("$file.lock", LOCK_EX);
// while waiting for exclusive lock, someone might have already created the cache
if (!is_file($file) || $this->isExpired($file, $name)) {
$code = $this->compile($name);
if (file_put_contents("$file.tmp", $code) !== strlen($code) || !rename("$file.tmp", $file)) {
@unlink("$file.tmp"); // @ - file may not exist
throw new RuntimeException("Unable to create '$file'.");
}
if (function_exists('opcache_invalidate')) {
@opcache_invalidate($file, true); // @ can be restricted
}
}
if ((include $file) === false) {
throw new RuntimeException("Unable to load '$file'.");
}
}
/**
* @return resource
*/
private function acquireLock(string $file, int $mode)
{
$dir = dirname($file);
if (!is_dir($dir) && !@mkdir($dir) && !is_dir($dir)) { // @ - dir may already exist
throw new RuntimeException("Unable to create directory '$dir'. " . error_get_last()['message']);
}
$handle = @fopen($file, 'w'); // @ is escalated to exception
if (!$handle) {
throw new RuntimeException("Unable to create file '$file'. " . error_get_last()['message']);
} elseif (!@flock($handle, $mode)) { // @ is escalated to exception
throw new RuntimeException('Unable to acquire ' . ($mode & LOCK_EX ? 'exclusive' : 'shared') . " lock on file '$file'. " . error_get_last()['message']);
}
return $handle;
}
private function isExpired(string $file, string $name): bool
{
return $this->autoRefresh && $this->getLoader()->isExpired($name, (int) @filemtime($file)); // @ - file may not exist
}
public function getCacheFile(string $name): string
{
$hash = substr($this->getTemplateClass($name), 8);
$base = preg_match('#([/\\\\][\w@.-]{3,35}){1,3}$#D', $name, $m)
? preg_replace('#[^\w@.-]+#', '-', substr($m[0], 1)) . '--'
: '';
return "$this->tempDirectory/$base$hash.php";
}
public function getTemplateClass(string $name): string
{
$key = serialize([
$this->getLoader()->getUniqueId($name),
self::VERSION,
array_keys((array) $this->functions),
$this->sandboxed,
$this->contentType,
]);
return 'Template' . substr(md5($key), 0, 10);
}
/**
* Registers run-time filter.
* @return static
*/
public function addFilter(?string $name, callable $callback)
{
if ($name === null) {
trigger_error('For dynamic filters, use the addFilterLoader() where you pass a callback as a parameter that returns the filter callback.', E_USER_DEPRECATED);
} elseif (!preg_match('#^[a-z]\w*$#iD', $name)) {
throw new \LogicException("Invalid filter name '$name'.");
}
$this->filters->add($name, $callback);
return $this;
}
/**
* Registers filter loader.
* @return static
*/
public function addFilterLoader(callable $callback)
{
$this->filters->add(null, function ($name) use ($callback) {
if ($filter = $callback($name)) {
$this->filters->add($name, $filter);
}
});
return $this;
}
/**
* Returns all run-time filters.
* @return string[]
*/
public function getFilters(): array
{
return $this->filters->getAll();
}
/**
* Call a run-time filter.
* @param mixed[] $args
* @return mixed
*/
public function invokeFilter(string $name, array $args)
{
return ($this->filters->$name)(...$args);
}
/**
* Adds new macro.
* @return static
*/
public function addMacro(string $name, Macro $macro)
{
$this->getCompiler()->addMacro($name, $macro);
return $this;
}
/**
* Registers run-time function.
* @return static
*/
public function addFunction(string $name, callable $callback)
{
if (!preg_match('#^[a-z]\w*$#iD', $name)) {
throw new \LogicException("Invalid function name '$name'.");
}
$this->functions->$name = $callback;
return $this;
}
/**
* Call a run-time function.
* @param mixed[] $args
* @return mixed
*/
public function invokeFunction(string $name, array $args)
{
if (!isset($this->functions->$name)) {
$hint = ($t = Helpers::getSuggestion(array_keys((array) $this->functions), $name))
? ", did you mean '$t'?"
: '.';
throw new \LogicException("Function '$name' is not defined$hint");
}
return ($this->functions->$name)(...$args);
}
/**
* Adds new provider.
* @param mixed $value
* @return static
*/
public function addProvider(string $name, $value)
{
if (!preg_match('#^[a-z]\w*$#iD', $name)) {
throw new \LogicException("Invalid provider name '$name'.");
}
$this->providers[$name] = $value;
return $this;
}
/**
* Returns all providers.
* @return mixed[]
*/
public function getProviders(): array
{
return $this->providers;
}
/** @return static */
public function setPolicy(?Policy $policy)
{
$this->policy = $policy;
return $this;
}
/** @return static */
public function setExceptionHandler(callable $callback)
{
$this->providers['coreExceptionHandler'] = $callback;
return $this;
}
/** @return static */
public function setSandboxMode(bool $on = true)
{
$this->sandboxed = $on;
return $this;
}
/** @return static */
public function setContentType(string $type)
{
$this->contentType = $type;
return $this;
}
/**
* Sets path to temporary directory.
* @return static
*/
public function setTempDirectory(?string $path)
{
$this->tempDirectory = $path;
return $this;
}
/**
* Sets auto-refresh mode.
* @return static
*/
public function setAutoRefresh(bool $on = true)
{
$this->autoRefresh = $on;
return $this;
}
/**
* Enables declare(strict_types=1) in templates.
* @return static
*/
public function setStrictTypes(bool $on = true)
{
$this->strictTypes = $on;
return $this;
}
public function getParser(): Parser
{
if (!$this->parser) {
$this->parser = new Parser;
}
return $this->parser;
}
public function getCompiler(): Compiler
{
if (!$this->compiler) {
$this->compiler = new Compiler;
Macros\CoreMacros::install($this->compiler);
Macros\BlockMacros::install($this->compiler);
}
return $this->compiler;
}
/** @return static */
public function setLoader(Loader $loader)
{
$this->loader = $loader;
return $this;
}
public function getLoader(): Loader
{
if (!$this->loader) {
$this->loader = new Loaders\FileLoader;
}
return $this->loader;
}
/**
* @param object|mixed[] $params
* @return mixed[]
*/
private function processParams($params): array
{
if (is_array($params)) {
return $params;
} elseif (!is_object($params)) {
throw new \InvalidArgumentException(sprintf('Engine::render() expects array|object, %s given.', gettype($params)));
}
$methods = (new \ReflectionClass($params))->getMethods(\ReflectionMethod::IS_PUBLIC);
foreach ($methods as $method) {
if ((PHP_VERSION_ID >= 80000 && $method->getAttributes(Attributes\TemplateFilter::class))
|| (strpos((string) $method->getDocComment(), '@filter'))
) {
$this->addFilter($method->name, [$params, $method->name]);
}
if ((PHP_VERSION_ID >= 80000 && $method->getAttributes(Attributes\TemplateFunction::class))
|| (strpos((string) $method->getDocComment(), '@function'))
) {
$this->addFunction($method->name, [$params, $method->name]);
}
}
return array_filter((array) $params, function ($key) { return $key[0] !== "\0"; }, ARRAY_FILTER_USE_KEY);
}
}