$val)
* %uniq : A random string that identifies a specific loop or condition.
*/
protected static $_directives = [
'if' => ['if (%s):', 'endif;'],
'unless' => ['if (!(%s)):', 'endif;'],
'for' => ['for (%s):', 'endfor;'],
'while' => ['while (%s):', 'endwhile;'],
'switch' => ['switch (%s):', 'endswitch;'],
'foreach' => [
'$__tmp_%uniq = %array ?? []; $__loop_%uniq = $this->_v2_initLoopVar("%uniq", $__tmp_%uniq); foreach ($__tmp_%uniq as %remainder):',
'$this->_v2_incrLoopVar($__loop_%uniq); endforeach; $this->_v2_removeLoopVar($__loop_%uniq); unset($__loop_%uniq);',
],
'forelse' => [
'$__tmp_%uniq = %array ?? []; if($__tmp_%uniq): $__loop_%uniq = $this->_v2_initLoopVar("%uniq", $__tmp_%uniq); foreach ($__tmp_%uniq as %remainder):',
'$this->_v2_incrLoopVar($__loop_%uniq); endforeach; $this->_v2_removeLoopVar($__loop_%uniq); unset($__loop_%uniq); else:',
'endif;',
],
'once' => [
"if (!isset(\$GLOBALS['tplv2_once']['%uniq'])):",
"\$GLOBALS['tplv2_once']['%uniq'] = true; endif;",
],
'fragment' => [
'ob_start(); $__last_fragment_name = %s;',
'$this->_fragments[$__last_fragment_name] = ob_get_flush();',
],
'error' => [
'if ($this->_v2_errorExists(%s)):',
'endif;',
],
'push' => [
'ob_start(); if (!isset(self::$_stacks[%s])): self::$_stacks[%s] = []; endif;',
'array_push(self::$_stacks[%s], trim(ob_get_clean()));',
],
'pushif' => [
'list($__stack_cond, $__stack_name) = [%s]; if ($__stack_cond): ob_start(); if (!isset(self::$_stacks[$__stack_name])): self::$_stacks[$__stack_name] = []; endif;',
'array_push(self::$_stacks[$__stack_name], trim(ob_get_clean())); endif;',
],
'pushonce' => [
'ob_start(); if (!isset(self::$_stacks[%s])): self::$_stacks[%s] = []; endif;',
'$__tmp_%uniq = trim(ob_get_clean()); if (!in_array($__tmp_%uniq, self::$_stacks[%s])): array_push(self::$_stacks[%s], $__tmp_%uniq); endif;',
],
'prepend' => [
'ob_start(); if (!isset(self::$_stacks[%s])): self::$_stacks[%s] = []; endif;',
'array_unshift(self::$_stacks[%s], trim(ob_get_clean()));',
],
'prependif' => [
'list($__stack_cond, $__stack_name) = [%s]; if ($__stack_cond): ob_start(); if (!isset(self::$_stacks[$__stack_name])): self::$_stacks[$__stack_name] = []; endif;',
'array_unshift(self::$_stacks[$__stack_name], trim(ob_get_clean())); endif;',
],
'prependonce' => [
'ob_start(); if (!isset(self::$_stacks[%s])): self::$_stacks[%s] = []; endif;',
'$__tmp_%uniq = trim(ob_get_clean()); if (!in_array($__tmp_%uniq, self::$_stacks[%s])): array_unshift(self::$_stacks[%s], $__tmp_%uniq); endif;',
],
'isset' => ['if (isset(%s)):', 'endif;'],
'unset' => ['if (!isset(%s)):', 'endif;'],
'empty' => ['if (empty(%s)):', 'endif;'],
'admin' => ['if ($this->user->isAdmin()):', 'endif;'],
'auth' => ['if ($this->_v2_checkAuth(%s)):', 'endif;'],
'can' => ['if ($this->_v2_checkCapability(1, %s)):', 'endif;'],
'cannot' => ['if ($this->_v2_checkCapability(2, %s)):', 'endif;'],
'canany' => ['if ($this->_v2_checkCapability(3, %s)):', 'endif;'],
'guest' => ['if (!$this->user->isMember()):', 'endif;'],
'desktop' => ['if (!$this->_v2_isMobile()):', 'endif;'],
'mobile' => ['if ($this->_v2_isMobile()):', 'endif;'],
'env' => ['if (!empty($_ENV[%s])):', 'endif;'],
'else' => ['else:'],
'elseif' => ['elseif (%s):'],
'case' => ['case %s:'],
'default' => ['default:'],
'continue' => ['continue;'],
'break' => ['break;'],
];
/**
* Cache the compiled regexp for directives here.
*/
protected static $_directives_regexp;
/**
* Convert template code into PHP.
*
* @param string $content
* @param Template $template
* @return string
*/
public function convert(string $content, Template $template): string
{
// Store template info in instance property.
$this->template = $template;
// Preprocessing.
$content = $this->_preprocess($content);
// Apply conversions.
$content = $this->_addContextSwitches($content);
$content = $this->_removeComments($content);
$content = $this->_convertVerbatimSections($content);
$content = $this->_convertRelativePaths($content);
$content = $this->_convertPHPSections($content);
$content = $this->_convertFragments($content);
$content = $this->_convertClassAliases($content);
$content = $this->_convertIncludes($content);
$content = $this->_convertResource($content);
$content = $this->_convertLoopDirectives($content);
$content = $this->_convertInlineDirectives($content);
$content = $this->_convertMiscDirectives($content);
$content = $this->_convertEchoStatements($content);
$content = $this->_addDeprecationMessages($content);
// Postprocessing.
$content = $this->_postprocess($content);
return $content;
}
/**
* Preprocessing.
*
* @param string $content
* @return string
*/
protected function _preprocess(string $content): string
{
// Remove trailing whitespace.
$content = preg_replace('#[\x20\x09]+$#m', '', $content);
return $content;
}
/**
* Insert context switch points (HTML <-> JS).
*
* @param string $content
* @return string
*/
protected function _addContextSwitches(string $content): string
{
// Inline styles.
$content = preg_replace_callback('#(?<=\s)(style=")([^"]*?)"#i', function($match) {
return $match[1] . 'config->context = \'CSS\'; ?>' . $match[2] . 'config->context = \'HTML\'; ?>"';
}, $content);
// Inline scripts.
$content = preg_replace_callback('#(?<=\s)(href="javascript:|pattern="|on[a-z]+=")([^"]*?)"#i', function($match) {
return $match[1] . 'config->context = \'JS\'; ?>' . $match[2] . 'config->context = \'HTML\'; ?>"';
}, $content);
// config->context = \'HTML\'; ?>' . $match[1];
}
else
{
return $match[1] . 'config->context = \'CSS\'; ?>';
}
}, $content);
// config->context = \'HTML\'; ?>' . $match[1];
}
elseif (!str_contains($match[2] ?? '', 'src="'))
{
return $match[1] . 'config->context = \'JS\'; ?>';
}
else
{
return $match[0];
}
}, $content);
return $content;
}
/**
* Remove context switch points.
*
* @param string $content
* @return string
*/
protected static function _removeContextSwitches(string $content): string
{
return preg_replace('#<\?php \$this->config->context = \'[A-Z]+\'; \?>#', '', $content);
}
/**
* Remove comments that should not be visible in the output.
*
*
* {{-- Blade-style Comment --}}
*
* @param string $content
* @return string
*/
protected function _removeComments(string $content): string
{
return preg_replace([
'##',
'#\{\{--[^\n]+?--\}\}#',
], '', $content);
}
/**
* Convert relative paths to absolute paths.
*
* @param string $content
* @return string
*/
protected function _convertRelativePaths(string $content): string
{
// Get the base path for this template.
$basepath = \RX_BASEURL . $this->template->relative_dirname;
// Convert all src and srcset attributes.
$regexp = '#(<(?:img|audio|video|script|input|source|link)\s[^>]*)(?<=\s)(src|srcset|poster)="([^"]+)"#';
$content = preg_replace_callback($regexp, function($match) use ($basepath) {
if ($match[2] !== 'srcset')
{
$src = trim($match[3]);
return $match[1] . sprintf('%s="%s"', $match[2], $this->template->isRelativePath($src) ? $this->template->convertPath($src, $basepath) : $src);
}
else
{
$srcset = array_map('trim', explode(',', $match[3]));
$result = array_map(function($src) use($basepath) {
return $this->template->isRelativePath($src) ? $this->template->convertPath($src, $basepath) : $src;
}, array_filter($srcset, function($src) {
return !empty($src);
}));
return $match[1] . sprintf('srcset="%s"', implode(', ', $result));
}
}, $content);
// Convert relative paths in CSS url() function.
$regexp = ['#\b(style=")([^"]+)(")#', '#(