PATH:
home
/
vcomplcotr
/
www
/
wp-content
/
plugins
/
akeebabackupwp
/
app
/
vendor
/
akeeba
/
awf
/
src
/
Mvc
/
Compiler
<?php /** * @package awf * @copyright Copyright (c)2014-2026 Nicholas K. Dionysopoulos / Akeeba Ltd * @license GNU GPL version 3 or later */ /** @noinspection PhpUnused */ namespace Awf\Mvc\Compiler; use Awf\Container\Container; use Awf\Container\ContainerAwareInterface; use Awf\Container\ContainerAwareTrait; use Awf\Text\Text; use Awf\Utils\HashHelper; require_once __DIR__ . '/../../Utils/helpers.php'; /** * Blade templates compiler into regular PHP code * * @since 1.0.0 */ class Blade implements CompilerInterface, ContainerAwareInterface { use ContainerAwareTrait; /** * Are the results of this engine cacheable? * * @var bool */ protected $isCacheable = true; /** * The extension of the template files supported by this compiler * * @var string */ protected $fileExtension = 'blade.php'; /** * All the registered compiler extensions. * * @var array */ protected $extensions = []; /** * The file currently being compiled. * * @var string */ protected $path; /** * All the available compiler functions. Each one is called against every HTML block in the template. * * @var array */ protected $compilers = [ 'Extensions', 'Statements', 'Comments', 'Echos', ]; /** * Array of opening and closing tags for escaped echos. * * @var array */ protected $contentTags = ['{{', '}}']; /** * Array of opening and closing tags for escaped echos. * * @var array */ protected $escapedTags = ['{{{', '}}}']; /** * Array of footer lines to be added to template. * * @var array */ protected $footer = []; /** * Counter to keep track of nested forelse statements. * * @var int */ protected $forelseCounter = 0; /** * Should I use the PHP Tokenizer extension to compile Blade templates? Default is true and is preferable. We expect * this to be false only on bad quality hosts. It can be overridden with Reflection for testing purposes. * * @var bool */ protected $usingTokenizer = false; /** * Maps the name of `repeatable` blocks to the method names generated for them, therefore allowing overrides. * * @var array * @since 1.2.0 */ protected $repeatableMap = []; /** * List of `repeatableOverride` sections which prevent future `repeatable` statements from overriding them. * * @var array * @since 1.2.0 */ protected $repeatableOverrides = []; /** * Constructor. * * @param Container $container The application container. * * @since 1.0.0 */ public function __construct(Container $container) { $this->setContainer($container); $this->conditionallyEnableTokenizer(); } /** * Report if the PHP Tokenizer extension is being used * * @return bool * @since 1.0.0 */ public function isUsingTokenizer(): bool { return $this->usingTokenizer; } /** * Are the results of this compiler engine cacheable? If the engine makes use of the forcedParams it must return * false. * * @return bool * @since 1.0.0 */ public function isCacheable(): bool { return $this->isCacheable; } /** * Compile a view template into PHP and HTML * * @param string $path The absolute filesystem path of the view template * @param array $forceParams Any parameters to force (only for engines returning raw HTML) * * @return string * @since 1.0.0 */ public function compile(string $path, array $forceParams = []): string { $this->footer = []; $fileData = @file_get_contents($path); if ($path) { $this->setPath($path); } return $this->compileString($fileData); } /** * Get the path currently being compiled. * * @return string * @since 1.0.0 */ public function getPath(): string { return $this->path; } /** * Set the path currently being compiled. * * @param string $path * * @return void * @since 1.0.0 */ public function setPath(string $path) { $this->path = $path; } /** * Compile the given Blade template contents. * * @param string|null $value The string to compile * * @return string * @since 1.0.0 */ public function compileString(?string $value): string { $value = $value ?? ''; $result = ''; if ($this->usingTokenizer) { /** * Here we will loop through all the tokens returned by the Zend lexer and parse each one into the * corresponding, valid PHP. */ foreach (token_get_all($value) as $token) { $result .= is_array($token) ? $this->parseToken($token) : $token; } } else { foreach ($this->compilers as $type) { $value = $this->{"compile{$type}"}($value); } $result .= $value; } /** * If there are any footer lines that need to get added to a template we will add them here at the end of the * template. This gets used mainly for the template inheritance via the extends keyword that should be appended. */ if (count($this->footer) > 0) { $result = ltrim($result, PHP_EOL) . PHP_EOL . implode(PHP_EOL, array_reverse($this->footer)); } return $result; } /** * Compile the default values for the echo statement. * * @param string $value * * @return string * @since 1.0.0 */ public function compileEchoDefaults($value) { return preg_replace('/^(?=\$)(.+?)(?:\s+or\s+)(.+?)$/s', 'isset($1) ? $1 : $2', $value); } /** * Register a custom Blade compiler. * * @param callable $compiler * * @return void * @since 1.0.0 */ public function extend(callable $compiler) { $this->extensions[] = $compiler; } /** * Get the regular expression for a generic Blade function. * * @param string $function * * @return string * @since 1.0.0 */ public function createMatcher(string $function): string { return '/(?<!\w)(\s*)@' . $function . '(\s*\(.*\))/'; } /** * Get the regular expression for a generic Blade function. * * @param string $function * * @return string * @since 1.0.0 */ public function createOpenMatcher(string $function): string { return '/(?<!\w)(\s*)@' . $function . '(\s*\(.*)\)/'; } /** * Create a plain Blade matcher. * * @param string $function * * @return string * @since 1.0.0 */ public function createPlainMatcher(string $function): string { return '/(?<!\w)(\s*)@' . $function . '(\s*)/'; } /** * Sets the escaped content tags used for the compiler. * * @param string $openTag * @param string $closeTag * * @return void * @since 1.0.0 */ public function setEscapedContentTags(string $openTag, string $closeTag): void { $this->setContentTags($openTag, $closeTag, true); } /** * Gets the content tags used for the compiler. * * @return array * @since 1.0.0 */ public function getContentTags(): array { return $this->getTags(); } /** * Sets the content tags used for the compiler. * * @param string $openTag * @param string $closeTag * @param bool $escaped * * @return void * @since 1.0.0 */ public function setContentTags(string $openTag, string $closeTag, bool $escaped = false): void { $property = ($escaped === true) ? 'escapedTags' : 'contentTags'; $this->{$property} = [preg_quote($openTag), preg_quote($closeTag)]; } /** * Gets the escaped content tags used for the compiler. * * @return array * @since 1.0.0 */ public function getEscapedContentTags(): array { return $this->getTags(true); } /** * Returns the file extension supported by this compiler * * @return string * @since 1.0.0 */ public function getFileExtension(): string { return $this->fileExtension; } /** * Parse the tokens from the template. * * @param array $token * * @return string * @since 1.0.0 */ protected function parseToken(array $token): string { [$id, $content] = $token; if ($id == T_INLINE_HTML) { foreach ($this->compilers as $type) { $content = $this->{"compile{$type}"}($content); } } return $content; } /** * Execute the user defined extensions. * * @param string $value * * @return string * @since 1.0.0 */ protected function compileExtensions(string $value): string { foreach ($this->extensions as $compiler) { $value = call_user_func($compiler, $value, $this); } return $value; } /** * Compile Blade comments into valid PHP. * * @param string $value * * @return string * @since 1.0.0 */ protected function compileComments(string $value): string { $pattern = sprintf('/%s--((.|\s)*?)--%s/', $this->contentTags[0], $this->contentTags[1]); return preg_replace($pattern, '<?php /*$1*/ ?>', $value); } /** * Compile Blade echos into valid PHP. * * @param string $value * * @return string * @since 1.0.0 */ protected function compileEchos(string $value): string { $difference = strlen($this->contentTags[0]) - strlen($this->escapedTags[0]); if ($difference > 0) { return $this->compileEscapedEchos($this->compileRegularEchos($value)); } return $this->compileRegularEchos($this->compileEscapedEchos($value)); } /** * Compile Blade Statements that start with "@" * * @param string $value * * @return string * @since 1.0.0 */ protected function compileStatements(string $value): string { return preg_replace_callback( '/\B@(\w+)([ \t]*)(\( ( (?>[^()]+) | (?3) )* \))?/x', [$this, 'compileStatementsCallback'], $value ); } /** * Callback for compileStatements, since $this is not allowed in Closures under PHP 5.3. * * @param array $match * * @return string * @since 1.0.0 */ protected function compileStatementsCallback(array $match): string { if (method_exists($this, $method = 'compile' . ucfirst($match[1]))) { $match[0] = $this->$method(akeeba_array_get($match, 3)); } return isset($match[3]) ? $match[0] : $match[0] . $match[2]; } /** * Compile the "regular" echo statements. * * @param string $value * * @return string * @since 1.0.0 */ protected function compileRegularEchos(string $value): string { $pattern = sprintf('/(@)?%s\s*(.+?)\s*%s(\r?\n)?/s', $this->contentTags[0], $this->contentTags[1]); return preg_replace_callback($pattern, [$this, 'compileRegularEchosCallback'], $value); } /** * Callback for compileRegularEchos, since $this is not allowed in Closures under PHP 5.3. * * @param array $matches * * @return string * @since 1.0.0 */ protected function compileRegularEchosCallback(array $matches): string { $whitespace = empty($matches[3]) ? '' : $matches[3] . $matches[3]; return $matches[1] ? substr($matches[0], 1) : '<?php echo ' . $this->compileEchoDefaults($matches[2]) . '; ?>' . $whitespace; } /** * Compile the escaped echo statements. * * @param string $value * * @return string * @since 1.0.0 */ protected function compileEscapedEchos(string $value): string { $pattern = sprintf('/%s\s*(.+?)\s*%s(\r?\n)?/s', $this->escapedTags[0], $this->escapedTags[1]); return preg_replace_callback($pattern, [$this, 'compileEscapedEchosCallback'], $value); } /** * Callback for compileEscapedEchos, since $this is not allowed in Closures under PHP 5.3. * * @param array $matches * * @return string * @since 1.0.0 */ protected function compileEscapedEchosCallback(array $matches): string { $whitespace = empty($matches[2]) ? '' : $matches[2] . $matches[2]; return '<?php echo $this->escape(' . $this->compileEchoDefaults($matches[1]) . '); ?>' . $whitespace; } /** * Compile the `each` statements into valid PHP. * * @param string $expression * * @return string * @since 1.0.0 */ protected function compileEach(string $expression): string { return "<?php echo \$this->renderEach{$expression}; ?>"; } /** * Compile the yield statements into valid PHP. * * @param string $expression * * @return string * @since 1.0.0 */ protected function compileYield(string $expression): string { return "<?php echo \$this->yieldContent{$expression}; ?>"; } /** * Compile the show statements into valid PHP. * * @param string|null $expression * * @return string * @since 1.0.0 */ protected function compileShow(?string $expression): string { return "<?php echo \$this->yieldSection(); ?>"; } /** * Compile the section statements into valid PHP. * * @param string $expression * * @return string * @since 1.0.0 */ protected function compileSection(string $expression): string { return "<?php \$this->startSection{$expression}; ?>"; } /** * Compile the append statements into valid PHP. * * @param string|null $expression * * @return string * @since 1.0.0 */ protected function compileAppend(?string $expression): string { return "<?php \$this->appendSection(); ?>"; } /** * Compile the end-section statements into valid PHP. * * @param string|null $expression * * @return string * @since 1.0.0 */ protected function compileEndsection(?string $expression): string { return "<?php \$this->stopSection(); ?>"; } /** * Compile the stop statements into valid PHP. * * @param string|null $expression * * @return string * @since 1.0.0 */ protected function compileStop(?string $expression): string { return "<?php \$this->stopSection(); ?>"; } /** * Compile the overwrite statements into valid PHP. * * @param string|null $expression * * @return string * @since 1.0.0 */ protected function compileOverwrite(?string $expression): string { return "<?php \$this->stopSection(true); ?>"; } /** * Compile the unless statements into valid PHP. * * @param string|null $expression * * @return string * @since 1.0.0 */ protected function compileUnless(?string $expression): string { return "<?php if ( ! $expression): ?>"; } /** * Compile the end unless statements into valid PHP. * * @param string|null $expression * * @return string * @since 1.0.0 */ protected function compileEndunless(?string $expression): string { return "<?php endif; ?>"; } /** * Compile the repeatable statements into valid PHP. * * @param string $expression * * @return string * @since 1.0.0 */ protected function compileRepeatable(string $expression, bool $isOverride = false): string { $expression = trim($expression, '()'); $parts = explode(',', $expression, 2); $argumentsList = $parts[1] ?? ''; $key = HashHelper::md5(strtolower(trim($parts[0]))); if ($isOverride) { $this->repeatableOverrides[] = $key; } if (!$isOverride && in_array($key, $this->repeatableOverrides)) { $key = "SKIP_" . $key; } return "<?php @\$this->repeatableMap['$key'] = function($argumentsList) { ?>"; } /** * Compile the repeatableOverride statements into valid PHP. * * @param string $expression * * @return string * @since 1.2.0 */ protected function compileRepeatableOverride(string $expression): string { return $this->compileRepeatable($expression, true); } /** * Compile the end endRepeatable statements into valid PHP. * * @param string|null $expression * * @return string * @since 1.0.0 */ protected function compileEndRepeatable(?string $expression): string { return "<?php }; ?>"; } /** * Compile the end yieldRepeatable statements into valid PHP. * * @param string $expression * * @return string * @since 1.0.0 */ protected function compileYieldRepeatable(string $expression): string { $expression = trim($expression, '()'); $parts = explode(',', $expression, 2); $argumentsList = $parts[1] ?? ''; $key = HashHelper::md5(strtolower(trim($parts[0]))); return "<?php try { \$this->repeatableMap['$key']($argumentsList); } catch (\Throwable \$e) { throw new \RuntimeException(sprintf('Error calling repeatable \"%s\"', {$parts[0]}), 500, \$e); } ?>"; } /** * Compile the lang statements into valid PHP. * * @param string $expression * * @return string * @since 1.0.0 */ protected function compileLang(string $expression): string { return "<?php echo \$this->getLanguage()->text$expression; ?>"; } /** * Compile the sprintf statements into valid PHP. * * @param string $expression * * @return string * @since 1.0.0 */ protected function compileSprintf(string $expression): string { return "<?php echo \$this->getLanguage()->sprintf$expression; ?>"; } /** * Compile the plural statements into valid PHP. * * e.g. `@plural('COM_FOOBAR_N_ITEMS_SAVED', $countItemsSaved)` * * @param string $expression * * @return string * @since 1.0.0 * @see Text::plural() * */ protected function compilePlural(string $expression): string { return "<?php echo \$this->getLanguage()->plural$expression; ?>"; } /** * Compile the token statements into valid PHP. * * @param string|null $expression * * @return string * @since 1.0.0 */ protected function compileToken(?string $expression): string { return "<?php echo \$this->container->session->getCsrfToken()->getValue(); ?>"; } /** * Compile the else statements into valid PHP. * * @param string|null $expression * * @return string * @since 1.0.0 */ protected function compileElse(?string $expression): string { return "<?php else: ?>"; } /** * Compile the for statements into valid PHP. * * @param string $expression * * @return string * @since 1.0.0 */ protected function compileFor(string $expression): string { return "<?php for{$expression}: ?>"; } /** * Compile the foreach statements into valid PHP. * * @param string $expression * * @return string * @since 1.0.0 */ protected function compileForeach(string $expression): string { return "<?php foreach{$expression}: ?>"; } /** * Compile the forelse statements into valid PHP. * * @param string $expression * * @return string * @since 1.0.0 */ protected function compileForelse(string $expression): string { $empty = '$__empty_' . ++$this->forelseCounter; return "<?php {$empty} = true; foreach{$expression}: {$empty} = false; ?>"; } /** * Compile the if statements into valid PHP. * * @param string $expression * * @return string * @since 1.0.0 */ protected function compileIf(string $expression): string { return "<?php if{$expression}: ?>"; } /** * Compile the else-if statements into valid PHP. * * @param string $expression * * @return string * @since 1.0.0 */ protected function compileElseif(string $expression): string { return "<?php elseif{$expression}: ?>"; } /** * Compile the forelse statements into valid PHP. * * @param string|null $expression * * @return string * @since 1.0.0 */ protected function compileEmpty(?string $expression): string { $empty = '$__empty_' . $this->forelseCounter--; return "<?php endforeach; if ({$empty}): ?>"; } /** * Compile the while statements into valid PHP. * * @param string $expression * * @return string * @since 1.0.0 */ protected function compileWhile(string $expression): string { return "<?php while{$expression}: ?>"; } /** * Compile the end-while statements into valid PHP. * * @param string|null $expression * * @return string * @since 1.0.0 */ protected function compileEndwhile(?string $expression): string { return "<?php endwhile; ?>"; } /** * Compile the end-for statements into valid PHP. * * @param string|null $expression * * @return string * @since 1.0.0 */ protected function compileEndfor(?string $expression): string { return "<?php endfor; ?>"; } /** * Compile the end-for-each statements into valid PHP. * * @param string|null $expression * * @return string * @since 1.0.0 */ protected function compileEndforeach(?string $expression): string { return "<?php endforeach; ?>"; } /** * Compile the end-if statements into valid PHP. * * @param string|null $expression * * @return string * @since 1.0.0 */ protected function compileEndif(?string $expression): string { return "<?php endif; ?>"; } /** * Compile the end-for-else statements into valid PHP. * * @param string|null $expression * * @return string * @since 1.0.0 */ protected function compileEndforelse(?string $expression): string { return "<?php endif; ?>"; } /** * Compile the extends statements into valid PHP. * * @param string $expression * * @return string * @since 1.0.0 */ protected function compileExtends(string $expression): string { if (akeeba_starts_with($expression, '(')) { $expression = substr($expression, 1, -1); } $data = "<?php echo \$this->loadAnyTemplate($expression); ?>"; $this->footer[] = $data; return ''; } /** * Compile the include statements into valid PHP. * * @param string $expression * * @return string * @since 1.0.0 */ protected function compileInclude(string $expression): string { if (akeeba_starts_with($expression, '(')) { $expression = substr($expression, 1, -1); } return "<?php echo \$this->loadAnyTemplate($expression); ?>"; } /** * Compile the stack statements into the content * * @param string $expression * * @return string * @since 1.0.0 */ protected function compileStack(string $expression): string { return "<?php echo \$this->yieldContent{$expression}; ?>"; } /** * Compile the push statements into valid PHP. * * @param string $expression * * @return string * @since 1.0.0 */ protected function compilePush(string $expression): string { return "<?php \$this->startSection{$expression}; ?>"; } /** * Compile the endpush statements into valid PHP. * * @param string|null $expression * * @return string * @since 1.0.0 */ protected function compileEndpush(?string $expression): string { return "<?php \$this->appendSection(); ?>"; } /** * Compile the route statements into valid PHP. * * @param string $expression * * @return string * @since 1.0.0 */ protected function compileRoute(string $expression): string { return "<?php echo \$this->container->router->route{$expression}; ?>"; } /** * Compile the css statements into valid PHP. * * @param string $expression * * @return string * @since 1.0.0 */ protected function compileCss(string $expression): string { return "<?php \\Awf\\Utils\\Template::addCss{$expression}; ?>"; } /** * Compile the inlineCss statements into valid PHP. * * @param string $expression * * @return string * @since 1.0.0 */ protected function compileInlineCss(string $expression): string { return "<?php \$this->container->application->getDocument()->addStyleDeclaration{$expression}; ?>"; } /** * Compile the inlineJs statements into valid PHP. * * @param string $expression * * @return string * @since 1.0.0 */ protected function compileInlineJs(string $expression): string { return "<?php \$this->container->application->getDocument()->addScriptDeclaration{$expression}; ?>"; } /** * Compile the js statements into valid PHP. * * @param string $expression * * @return string * @since 1.0.0 */ protected function compileJs(string $expression): string { return "<?php \\Awf\\Utils\\Template::addJs{$expression}; ?>"; } /** * Compile the jhtml statements into valid PHP. * * @param string $expression * * @return string * @since 1.0.0 */ protected function compileJhtml(string $expression): string { return '<' . '?php echo $this->getContainer()->html->get' . $expression . '; ?' . '>'; } /** * Compile the html statements into valid PHP. * * @param string $expression * * @return string * @since 1.0.0 */ protected function compileHtml(string $expression): string { return $this->compileJhtml($expression); } /** * Compile the media statements into valid PHP. * * @param string $expression * * @return string * @since 1.0.0 */ protected function compileMedia(string $expression): string { return "<?php echo \\Awf\\Utils\\Template::parsePath{$expression}; ?>"; } /** * Gets the tags used for the compiler. * * @param bool $escaped * * @return array * @since 1.0.0 */ protected function getTags(bool $escaped = false): array { $tags = $escaped ? $this->escapedTags : $this->contentTags; return array_map('stripcslashes', $tags); } /** * Enable the PHP Tokenizer if it is enabled and found to be working correctly * * @return void * @since 1.1.0 * @see https://www.akeeba.com/support/akeeba-backup-wordpress/39513-user-interface-shows-code-instead-of-control-element-texts.html */ private function conditionallyEnableTokenizer(): void { $this->usingTokenizer = function_exists('token_get_all') && defined('T_INLINE_HTML'); if (!$this->usingTokenizer) { return; } $uncompiledSource = "@lang('TEST')"; $actual = trim($this->compileString($uncompiledSource)); $expected = trim($this->compileLang("('TEST')")); $this->usingTokenizer = $actual === $expected && $actual !== $uncompiledSource; } }
[+]
..
[-] CompilerInterface.php
[edit]
[-] Blade.php
[edit]