/** Datei: plugins/templater/library/ARUNOX/Compiler/Templater/Token/Variable.php **/
$name,
"alternate" => $alternate
]);
}
/* ### PRIVATE STATIC FUNCTIONS ################################# # */
/* ### MAGIC FUNCTIONS ########################################## # */
}
/** Datei: plugins/templater/library/ARUNOX/Compiler/Templater/Token/Text.php **/
$text,
]);
}
/* ### PRIVATE STATIC FUNCTIONS ################################# # */
/* ### MAGIC FUNCTIONS ########################################## # */
}
/** Datei: plugins/templater/library/ARUNOX/Compiler/Templater/Token/Shortcode.php **/
strtolower( $matches[2] ),
"attrs" => Templater::parseAttributes( $matches[3] ),
"raw" => $matches[0],
"closing" => $matches[1] === "/",
"selfclosing" => Templater::isSelfClosingTag( $matches[2] ),
]);
}
/* ### PRIVATE STATIC FUNCTIONS ################################# # */
/* ### MAGIC FUNCTIONS ########################################## # */
}
/** Datei: plugins/templater/library/ARUNOX/Compiler/Templater/Token/HTML.php **/
]*)>/iA';
protected static array $SELFCLOSING = [ 'area','base','br','col','embed','hr','img','input','link','meta','param','source','track','wbr' ];
protected static array $HTMLTAGS = [
'a','abbr','address','area','article','aside','audio','b','base','bdi','bdo','blockquote','body','br','button','canvas',
'caption','cite','code','col','colgroup','data','datalist','dd','del','details','dfn','dialog','div','dl','dt','em',
'embed','fieldset','figcaption','figure','footer','form','h1','h2','h3','h4','h5','h6','head','header','hr','html',
'i','iframe','img','input','ins','kbd','label','legend','li','link','main','map','mark','meta','meter','nav','noscript',
'object','ol','optgroup','option','output','p','param','picture','pre','progress','q','rp','rt','ruby','s','samp','script',
'section','select','small','source','span','strong','style','sub','summary','sup','table','tbody','td','template','textarea',
'tfoot','th','thead','time','title','tr','track','u','ul','var','video','wbr'
];
protected static ?string $TYPE = self::TYPE_HTML;
protected ?string $name = NULL;
protected array $attrs = [];
protected array $children = [];
protected bool $closing = FALSE;
protected bool $selfclosing = FALSE;
/* ### PUBLIC STATIC FUNCTIONS ################################## # */
public static function create( array $matches ) : false|Token {
if( !in_array( $matches[2], static::$HTMLTAGS ) ) return FALSE;
return new self([
"name" => strtolower( $matches[2] ),
"attrs" => Templater::parseAttributes( $matches[3] ),
"closing" => $matches[1] === "/",
"selfclosing" => in_array( strtolower( $matches[2] ), static::$SELFCLOSING ),
]);
}
/* ### PRIVATE STATIC FUNCTIONS ################################# # */
/* ### MAGIC FUNCTIONS ########################################## # */
}
/** Datei: plugins/templater/library/ARUNOX/Compiler/Templater/Templater.php **/
' . $rendered );
return ob_get_clean();
}
public static function parseVariables( string $tpl, array $data ) : string {
// 1) Raw Variables [$var!]
$tpl = preg_replace_callback(
'/\[\$([a-z0-9_\.]+)!]/i',
function( $m ) {
return '';
},
$tpl
);
// 2) Variables with fallback [$var ?? "Fallback"]
$tpl = preg_replace_callback(
'/\[\$([a-z0-9_\.]+)\s*\?\?\s*["\']([^"\']*)["\']\]/i',
function( $m ) {
return '';
},
$tpl
);
// 3) Escaped Variables [$var]
$tpl = preg_replace_callback(
'/\[\$([a-z0-9_\.]+)\]/i',
function( $m ) {
return '';
},
$tpl
);
return $tpl;
}
public static function tokenize( string $template ) : array {
$shortcodes = static::getShortcodes();
$tokens = [];
$length = strlen( $template );
$position = 0;
$iteration = 0;
while( $position < $length ) {
$iteration++;
if( $iteration > 100000 ) throw new \RuntimeException("Infinite loop detected" );
if( Token\Variable::match( $template, $matches, 0, $position ) ) {
$tokens[] = Token\Variable::create( $matches );
$position += strlen( $matches[0] );
continue;
}
if( Token\Shortcode::match( $template, $matches, 0, $position ) ) {
$tokens[] = Token\Shortcode::create( $matches );
$position += strlen( $matches[0] );
continue;
}
if( Token\HTML::match( $template, $matches, 0, $position ) ) {
$tokens[] = Token\HTML::create( $matches );
$position += strlen( $matches[0] );
continue;
}
if( preg_match( '/([^\[<]*)([\[<])/iA', $template, $matches, 0, $position ) ) {
$tokens[] = Token\Text::create( $matches[1] );
$position += strlen( $matches[1] );
continue;
}
show( htmlentities($template), $matches, $position );
// Match plain text
$strings = [ "[", "<" ];
foreach( $strings as $string ) {
$nextTag = strpos( $template, $string, $position );
if( $nextTag === false ) {
$nextTag = $length;
continue;
}
break;
}
show($nextTag, $position, $string, htmlentities( substr( $template, $position )));
if( $nextTag === $position ) {
$tokens[] = Token\Text::create( $string );
$position++;
} else {
}
}
return $tokens;
}
public static function parse( array $tokens ) : array {
$output = new Templater\Token\HTML([ "name" => "div" ]);
static::parseRecursive( $tokens, $output );
return $output->children;
}
public static function getShortcodes( null|string $name = NULL) {
if( empty( static::$shortcodes ) ) {
foreach( Hooks::filter('templater::shortcodes', [] ) as $name => $config ) {
$shortcode = [
"type" => static::SHORTCODE_TYPE_UNKNOWN,
"closing" => FALSE,
];
if( is_array( $config ) ) $shortcode = $config;
else $shortcode["code"] = $config;
if( is_callable( $config ) ) $shortcode["type"] = static::SHORTCODE_TYPE_FUNCTION;
elseif( is_string( $config ) ) $shortcode["type"] = static::SHORTCODE_TYPE_STRING;
if( preg_match( "/\/$/", $name ) ) {
$shortcode["closing"] = TRUE;
}
$name = trim( $name, "/" );
static::$shortcodes[ $name ] = $shortcode;
}
}
return $name ? static::$shortcodes[ $name ] ?? FALSE : static::$shortcodes;
}
public static function resolveVariable( string $name, array $data, bool $raw = FALSE ) {
$isRaw = false;
if( str_ends_with($name, '!' ) ) {
$isRaw = true;
$name = substr($name, 0, -1);
}
$value = null;
if (strpos($name, '.') !== false) {
$parts = explode('.', $name);
$v = $data;
foreach ($parts as $part) {
if (is_array($v) && array_key_exists($part, $v)) {
$v = $v[$part];
}
elseif (is_object($v) && isset($v->$part)) {
$v = $v->$part;
} else {
return ['value' => null, 'raw' => $isRaw];
}
}
$value = $v;
} else {
if (array_key_exists($name, $data)) {
$value = $data[$name];
}
elseif (is_object($data) && isset($data->$name)) {
$value = $data->$name;
}
}
return $raw ? $value : ['value' => $value, 'raw' => $isRaw];
}
public static function evaluateCondition( string $expr, array $data ) : bool {
$cond = explode( " ", $expr );
$cond = array_map( "trim", $cond );
foreach( $cond as &$condPart ) {
if( !preg_match( '/^\$/', $condPart ) ) continue;
$parts = explode( ".", trim( $condPart, "$" ) );
$name = "\$data";
$value = $data;
foreach( $parts as $part ) {
if( $value === NULL ) break;
if( is_array( $value ) ) {
$name = "{$name}[\"{$part}\"]";
$value = $value[ $part ] ?? NULL;
continue;
}
if( is_object( $value ) ) {
$name = "{$name}->{$part}";
$value = $value->{$part} ?? NULL;
continue;
}
$value = NULL;
}
$condPart = "( {$name} ?? NULL )";
}
$cond = implode( " ", $cond );
return (bool)eval( "return {$cond};" );
}
public static function isSelfClosingTag( string $name ) : bool {
if( $tag = static::getShortcodes( strtolower( $name ) ) ) {
return (bool)$tag["closing"];
}
return FALSE;
}
public static function parseAttributes( string $attrString ) : array {
$attrs = [];
$attrString = trim( $attrString );
if( $attrString === "" ) return $attrs;
if( $attrString === "/" ) return $attrs;
// 1. Alle key="value"
preg_match_all('/([a-z0-9_-]+)\s*=\s*"([^"]*)"/i', $attrString, $matches, PREG_SET_ORDER );
foreach( $matches as $m ) $attrs[$m[1]] = $m[2];
// Entferne erkannte key="value"
$cleaned = preg_replace('/([a-z0-9_-]+)\s*=\s*"([^"]*)"/i', '', $attrString );
$cleaned = trim( $cleaned );
// 2. Alle key=value (unquoted)
if( $cleaned !== '' ) {
preg_match_all('/([a-z0-9_-]+)\s*=\s*([^\s]+)/i', $cleaned, $matches2, PREG_SET_ORDER );
foreach( $matches2 as $m ) $attrs[$m[1]] = $m[2];
// Entferne erkannte key=value
$cleaned = preg_replace('/([a-z0-9_-]+)\s*=\s*([^\s]+)/i', '', $cleaned);
$cleaned = trim($cleaned);
// 3. Falls noch was übrig ist = einzelnes Argument
if( $cleaned !== "" ) $attrs[] = $cleaned;
}
return $attrs;
}
/* ### PRIVATE STATIC FUNCTIONS ################################# # */
private static function parseRecursive( array &$tokens, Token &$output, null|string $expectedClose = null ) {
while( !empty( $tokens ) ) {
$token = array_shift($tokens );
if( $token->isText() ) {
$output->addChild( $token );
continue;
}
if( $token->isVar() ) {
$output->addChild( $token );
continue;
}
if( $token->isHtml() ) {
if( $token->isClosing() ) {
if( $expectedClose ) {
if( $token->name !== $expectedClose ) {
array_unshift($tokens, $token );
}
return;
}
continue;
}
if( $token->isSelfClosing() ) {
$output->addChild( $token );
continue;
}
static::parseRecursive($tokens, $token, $token->name );
$output->addChild( $token );
continue;
}
if( $token->isShortcode() ) {
if( $token->isClosing() ) {
if( $expectedClose ) {
if( $token->name !== $expectedClose ) {
array_unshift($tokens, $token );
}
return;
}
continue;
}
if( $token->isSelfClosing() ) {
$output->addChild( $token );
continue;
}
static::parseRecursive($tokens, $token, $token->name );
$output->addChild( $token );
continue;
}
}
}
private static function renderFromTree( array $tree, array $data ) : string {
$output = '';
// show( $tree, FALSE );
foreach( $tree as $node ) {
if( $node->isText() ) {
$output .= $node->content;
continue;
}
if( $node->isVar() ) {
$resolved = static::resolveVariable( $node->name, $data );
$value = $resolved["value"];
if( !$resolved["raw"] ) {
foreach( (array)$node->alternate as $alternate ) {
if( $value !== NULL ) break;
if( preg_match( '/^\$/', $alternate ) ) {
$alternate = trim( $alternate, "$" );
$alternate = static::resolveVariable( $alternate, $data );
$value = $alternate["value"];
continue;
}
if( preg_match( '/^["\'](.*)["\']$/', $alternate, $m ) ) {
$value = $m[1];
break;
}
}
$output .= htmlspecialchars( $value ?? "" );
} else $output .= static::render( $value ?? "" , $data );
continue;
}
if( $node->isHtml() ) {
show( $node );
}
if( $node->isShortcode() ) {
if( !$shortcode = static::getShortcodes( $node['name'] ) ) {
$output .= '[' . $node['name'];
foreach ($node['attrs'] as $k => $v) {
$output .= ' ' . $k . '="' . htmlspecialchars($v) . '"';
}
$output .= ']';
$output .= static::render( $node['children'], $data );
$output .= '[/' . $node['name'] . ']';
continue;
}
// show( $shortcode, $node, FALSE );
if( $shortcode["type"] === static::SHORTCODE_TYPE_STRING ) {
$template = $shortcode['code'];
$scope = array_merge( $data, [
"attrs" => $node['attrs'],
'content' => static::render( $node['children'], $data ),
]);
$template = static::render( $template, $scope );
$output .= $template;
continue;
}
if( $shortcode["type"] === static::SHORTCODE_TYPE_FUNCTION ) {
$rawContent = static::rebuildContent($node['children']);
$template = call_user_func( $shortcode["code"], $node['attrs'], $rawContent, $data);
$template = static::render( $template, $data );
$output .= $template;
continue;
}
continue;
}
if( $node["type"] === static::TOKEN_TYPE_SELF_CLOSING ) {
if( !$shortcode = static::getShortcodes( $node['name'] ) ) {
$output .= '[' . $node['name'];
foreach ($node['attrs'] as $k => $v) {
$output .= ' ' . $k . '="' . htmlspecialchars($v) . '"';
}
$output .= ']';
}
// show( $shortcode, $node );
if( $shortcode["type"] === static::SHORTCODE_TYPE_STRING ) {
$template = $shortcode['code'];
$scope = array_merge( $data, [
"attrs" => $node['attrs'],
'content' => NULL,
]);
$template = static::render( $template, $scope );
$output .= Templater::parseVariables( $template, $scope );
continue;
}
if( $shortcode["type"] === static::SHORTCODE_TYPE_FUNCTION ) {
$template = call_user_func( $shortcode["code"], $node['attrs'], '', $data);
$template = static::render( $template, $data );
$output .= Templater::parseVariables( $template, $data );
continue;
}
continue;
}
}
return $output;
}
private static function rebuildContent( array $children ) : string {
$text = '';
foreach( $children as $node ) {
if( $node["type"] === static::TOKEN_TYPE_TEXT ) {
$text .= $node["text"];
continue;
}
if( $node["type"] === static::TOKEN_TYPE_VAR ) {
$text .= '[$' . $node['name'] . ']';
continue;
}
if( $node["type"] === static::TOKEN_TYPE_NODE ) {
$text .= '[' . $node['name'];
foreach ($node['attrs'] as $k => $v) {
$text .= ' ' . $k . '="' . htmlspecialchars($v) . '"';
}
$text .= ']';
$text .= static::rebuildContent($node['children']);
$text .= '[/' . $node['name'] . ']';
continue;
}
if( $node["type"] === static::TOKEN_TYPE_SELF_CLOSING ) {
$text .= '[' . $node['name'];
foreach ($node['attrs'] as $k => $v) {
$text .= ' ' . $k . '="' . htmlspecialchars($v) . '"';
}
$text .= ']';
continue;
}
}
return $text;
}
private static function buildPhpVariable( string $path ) : string {
$parts = explode('.', $path);
$expr = '$' . array_shift($parts);
foreach ($parts as $p) {
$expr .= '["' . $p . '"]';
}
return $expr;
}
/* ### MAGIC FUNCTIONS ########################################## # */
}
/** Datei: plugins/templater/library/ARUNOX/Compiler/Templater/Token.php **/
children[] = $child;
}
return FALSE;
}
/* ### PUBLIC STATIC FUNCTIONS ################################## # */
public static function match( string $subject, null|array &$matches = NULL, int $flags = 0, int $offset = 0 ) : false|int {
return preg_match(static::$REGEX, $subject, $matches, $flags, $offset );
}
/* ### PRIVATE STATIC FUNCTIONS ################################# # */
/* ### MAGIC FUNCTIONS ########################################## # */
public function __construct( array $attributes = [] ) {
foreach( $attributes as $name => $attribute ) {
if( property_exists( $this, $name ) ) {
$this->{$name} = $attribute;
}
}
$this->type = static::$TYPE ?? $attributes["type"];
}
public function __get( string $name ) : mixed {
if( property_exists( $this, $name ) ) {
return $this->{$name};
}
return NULL;
}
public function __call( string $name, array $args ) : mixed {
if( preg_match( '/^is([A-Z][a-zA-Z]*)/', $name, $matches ) ) {
$name = strtolower( $matches[1] );
$type = strtoupper( $name );
$constants = new \ReflectionClass(static::class);
if( $type = $constants->getConstant("TYPE_{$type}" ) ) {
return $this->type === $type;
}
if( property_exists( $this, $name ) ) {
return $this->{$name};
}
return FALSE;
}
}
}
/** Datei: plugins/templater/hooks/templater/render.php **/
* @copyright 2023 Sparky@ARUNOX
* @version 1.0.0
* @since 1.0.0 File is created
*/
use ARUNOX\Core\Hooks;
use ARUNOX\Compiler\Templater;
Hooks::add( 'templater.render::controlRules', function( $rules ) {
$rules[] = [
'pattern' => '/\[foreach\s+each="(.*?)"\s+as="(.*?)"\]/i',
'replace' => ''
];
$rules[] = [
'pattern' => '/\[\/foreach\]/i',
'replace' => ''
];
$rules[] = [
'pattern' => '/\[foreach\s+([^\s]+)\s+as\s+([^\]]+)\]/i',
'replace' => ''
];
$rules[] = [
'pattern' => '/\[if\s+condition="(.*?)"\]/i',
'replace' => ''
];
$rules[] = [
'pattern' => '/\[elseif\s+condition="(.*?)"\]/i',
'replace' => ''
];
$rules[] = [
'pattern' => '/\[else\]/i',
'replace' => ''
];
$rules[] = [
'pattern' => '/\[\/if\]/i',
'replace' => ''
];
$rules[] = [
'pattern' => '/\[while\s+condition="(.*?)"\]/i',
'replace' => ''
];
$rules[] = [
'pattern' => '/\[\/while\]/i',
'replace' => ''
];
return $rules;
});
Hooks::add( 'templater::shortcodes', function( $shortcodes ) {
$shortcodes["asset/"] = function( $attrs, $content, $data ) {
if( $attrs["id"] ) {
$attrs["img"] = "https://safespace.plus/uploads/ssp_trofast.png?_=1722332752";
}
return '';
};
return $shortcodes;
});
Hooks::add( 'templater::shortcodes', function( $shortcodes ) {
$shortcodes["foreach"] = function( $attrs, $content, $data ) {
$each = $attrs["each"] ?? null;
$as = $attrs["as"] ?? null;
// show( htmlentities($content), $attrs );
if( !$each || !$as ) return "";
$as = ltrim( $as, "$" );
$each = ltrim( $each, "$" );
$items = Templater::resolveVariable( $each, $data, TRUE );
// show( $each, $items, $attrs );
if( !is_array( $items ) ) return "";
$output = "";
foreach( $items as $item ) {
$scope = array_merge( $data, [ $as => $item ] );
$output .= Templater::render( $content, $scope );
}
return $output;
};
$shortcodes["if"] = function( $attrs, $content, $data ) {
$mainExpr = $attrs['condition'] ?? $attrs[0] ?? NULL;
if( !$mainExpr ) return "";
// show( $content );
$blocks = [];
$content = Templater::tokenize( $content );
$content = Templater::parse( $content );
if( !function_exists( "templaterShortcodeFilterContent" ) ) {
function templaterShortcodeFilterContent( $nodes ) {
return array_filter( $nodes, fn( $item ) => !key_exists( "name", $item ) || !in_array( $item["name"], ["elseif", "else"] ) );
}
}
if( !function_exists( "templaterShortcodeCreateBlcoks" ) ) {
function templaterShortcodeCreateBlcoks( $nodes, &$blocks = [] ) {
$nodes = array_filter( $nodes, fn( $item ) => key_exists( "name", $item ) && in_array( $item["name"], ["elseif", "else"] ) );
foreach( $nodes as $node ) {
if( $node["type"] !== Templater::TOKEN_TYPE_NODE ) continue;
$blocks[] = [
"type" => $node["name"],
"expr" => implode( " ", $node["attrs"] ),
"content" => templaterShortcodeFilterContent( $node["children"] ),
];
$blocks = templaterShortcodeCreateBlcoks( $node["children"], $blocks );
}
return $blocks;
}
}
$blocks[] = [
"type" => "if",
"expr" => implode( " ", $attrs ),
"content" => templaterShortcodeFilterContent( $content ),
];
$blocks = templaterShortcodeCreateBlcoks( $content, $blocks );
foreach( $blocks as $block ) {
if( $block["type"] === 'else' ) {
return Templater::render( $block['content'], $data);
}
if( Templater::evaluateCondition( $block['expr'], $data) ) {
return Templater::render($block['content'], $data);
}
}
};
$shortcodes["blockquote"] = '