/** 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"] = '
[$content!]
'; $shortcodes["button"] ='[$content!]'; $shortcodes["bi/"] = ''; $shortcodes["strong"] = '[$content]'; $shortcodes["spacer/"] = '
'; $shortcodes["mediatext"] = function( $attrs, $content, $data ) { $attrs["img"] = $attrs["img"] ?? NULL; $attrs["assets"] = explode( ",", $attrs["asset"] ?? "" ); // [asset id=$asset class="[$attrs.asset-class-{$index} ?? $attrs.asset-class ?? \'\']" style="[$attrs..asset-style-{$index} ?? $attrs.asset-style ?? \'\']"] return Templater::render('
[if $attrs.img || $attrs.assets ]
[if $attrs.assets ] [foreach each=$attrs.assets as=$asset] [asset id=$asset class="$attrs.asset-class-0 ?? \'\'"] [/foreach] [elseif $attrs.img] [/if]
[/if] [if $content]
[if $attrs.title]

[$attrs.title!]

[/if] [$content!]
[/if]
', array_merge( $data, [ "attrs" => $attrs, "content" => $content, ])); }; return $shortcodes; }); /** Datei: plugins/templater/config.php **/ * @copyright 2025 ARUNOX * @version 0.0.0 * * @preload TRUE * * @since 1.0.0 File is created */