/** Datei: library/ARUNOX/HTTP/Request.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\HTTP; class Request { private array $query; private array $body; private array $files; private array $headers; private string $method; private ?string $contentType; private ?array $referer; private string $uri; private string $pathname; public function __construct() { $this->headers = getallheaders() ?: []; $this->method = $_SERVER['REQUEST_METHOD'] ?? 'GET'; $this->contentType = $_SERVER["CONTENT_TYPE"] ?? NULL; $this->query = $_GET ?? []; $this->uri = strtok($_SERVER['REQUEST_URI'] ?? '/', '?'); // URI ohne Query-String $this->referer = parse_url_full( $this->getHeaders("Referer") ); $this->pathname = $this->parsePathname(); [ $this->body, $this->files ] = $this->parseBody(); } /** * Gibt die Query-Parameter zurück. * * @return array */ public function getQueryParams( ?string $name = NULL, mixed $default = NULL ): null|array|string { return $name ? ( $this->query[ $name ] ?? $default ) : $this->query; } /** * Gibt die Body-Parameter zurück (JSON). * * @return array */ public function getBodyParams(): array { return $this->method == "GET" ? $this->query : $this->body; } /** * Gibt die Header der Anfrage zurück. * * @return array */ public function getHeaders( ?string $name = NULL ) : null|array|string { return $name ? ( $this->headers[ $name ] ?? "" ) : $this->headers; } public function getReferer( ?string $name = NULL ) : array|string { return $name ? $this->referer[ $name ] : $this->referer; } /** * Gibt die HTTP-Methode zurück. * * @return string */ public function getMethod(): string { return $this->method; } /** * Gibt die angeforderte URI ohne Query-String zurück. * * @return string */ public function getUri( string $prefix = "" ): string { return $this->uri; } public function getPathname(): string { return $this->pathname; } private function parsePathname(): string { if( preg_match( "/(\/api\/v[0-9]+)\/?(.*)/", $this->uri, $matches ) ) { return $matches[2]; } return $this->uri; } /** * Parst den Anfrage-Body. * * @return array */ private function parseBody(): array { if( !empty( $_POST ) || !empty( $_FILES ) ) { return [ $_POST, $_FILES ]; } if( $this->method === "GET" || !$this->contentType ) return [ [], [] ]; if( $this->contentType === "application/json" ) { $input = file_get_contents('php://input'); $decoded = json_decode( $input, true ); return [ $decoded, [] ]; } return [ $_POST, $_FILES ] = request_parse_body(); } public static function __callStatic( string $name, array $args ) { $that = new self(); return $that->{"get{$name}"}( ...$args ); } } /** Datei: library/ARUNOX/HTTP/Response.php **/ status = $status; $this->headers = $headers; $this->body = $body; } /* ### SETTERS ### */ public function withStatus(int $status): self { $this->status = $status; return $this; } public function withHeader(string $name, string $value): self { $this->headers[$name] = $value; return $this; } public function withBody(mixed $body): self { $this->body = $body; return $this; } /* ### GETTERS ### */ public function hasError() : bool { return $this->status >= 400; } public function isStatus( int $code ) : bool { return $this->status === $code; } public function getStatus() : int { return $this->status; } public function getHeaders(): array { return $this->headers; } public function getBody( ...$names ) : mixed { $data = $this->body; if( $names[0] == "*" ) { return json_decode( $data, TRUE ); } foreach( $names as $name ) { $data = $this->getData( $name, $data ); } return $data; } private function getData( string $name, mixed $data ) : mixed { if( is_json( $data ) ) $data = json_decode( $data ); if( is_array( $data ) ) return $data[ $name ] ?? NULL; if( is_object( $data ) ) return $data->{$name} ?? NULL; return $data; } /* ### OUTPUT ### */ public function send(): void { http_response_code($this->status); foreach ($this->headers as $name => $value) { header("{$name}: {$value}"); } if( $this->body !== null ) { if( is_scalar( $this->body ) ) { echo $this->body; } else { echo json_encode( $this->body ); } } exit; } } /** Datei: library/ARUNOX/API/Response/Response.php **/ * @copyright 2025 ARUNOX * @version 1.0.0 */ namespace ARUNOX\API; class Response { public static function json(array $data, int $status = 200): void { http_response_code($status); header('Content-Type: application/json'); echo json_encode($data); exit; } } /** Datei: library/ARUNOX/API/PHP/Token/T_DOC_COMMENT.php **/ * @copyright 2024 Sparky@ARUNOX * @version 1.0.0 * @since 1.0.0 File is created */ namespace ARUNOX\API\PHP\Token; use stdClass; use ARUNOX\API\PHP; class T_DOC_COMMENT extends PHP\Token { /* ### VARIABLES ################################################ # */ protected static $AUTODETECT = [ "name" => "0", "description" => "1+", ], $HEADER = [ "name" => "Plugin Name", "description" => "Description", ], $PHPDOC = [ "author" => "@author", "ignore" => "@ignore", "preload" => "@preload", "copyright" => "@copyright", "dependencies" => "@dependencies", "version" => "@version", ], $PHPDOC_MULTI = [ "hooks" => "@hook", "since" => "@since", ]; protected $data = NULL; /* ### PUBLIC FUNCTIONS ######################################### # */ /* ### PROTECTED FUNCTIONS ###################################### # */ protected function construct() { $this->data = new stdClass(); preg_match_all( "/\*[*\s]+(.*)/", $this->text, $comments ); $comments = array_filter( $comments[1] ); $autodetect = array_reverse( static::$AUTODETECT ); foreach( $comments as $index => $comment ) { foreach( static::$HEADER as $name => $regex ) { $regex = preg_quote( $regex ) . ":?\s+(.*)"; if( preg_match( "/{$regex}/", $comment, $header ) ) { $this->data->{$name} = trim( $header[1] ); continue 2; } } foreach( static::$PHPDOC as $name => $regex ) { $regex = preg_quote( $regex ) . ":?\s+(.*)"; if( preg_match( "/{$regex}/", $comment, $header ) ) { $this->data->{$name} = trim( $header[1] ); continue 2; } } foreach( static::$PHPDOC_MULTI as $name => $regex ) { $regex = preg_quote( $regex ) . ":?\s+(.*)"; if( preg_match( "/{$regex}/", $comment, $header ) ) { $this->data->{$name}[] = trim( $header[1] ); continue 2; } } if( preg_match( "/@([a-zA-Z0-9]+)(-([a-zA-Z0-9]+))?:?\s+(.*)/", $comment, $matches ) ) { $name = $matches[1]; $subname = $matches[3]; $value = $matches[4]; if( !$this->parse( $name, $value ) ) { if( $subname ) $this->data->{$name}[ $subname ] = trim( $value ); else $this->data->{$name} = trim( $value ); } continue; } foreach( $autodetect as $name => $line ) { preg_match( "/(\d+)(\+?)/", $line, $line ); if( $line[1] == $index ) { $this->data->{$name} = trim( $comment ); continue 2; } if( !$line[2] ) continue; if( $line[1] < $index ) { if( is_string( $this->data->{$name} ) ) { $this->data->{$name} = (array)trim( $this->data->{$name} ); } if( is_array( $this->data->{$name} ) ) { $this->data->{$name}[] = trim( $comment ); } $this->data->{$name} = implode( "; ", $this->data->{$name} ); } } } } /* ### PRIVATE FUNCTIONS ######################################## # */ private function parse( string $name, string $value ) { if( method_exists( $this, "parse{$name}") ) { return $this->{"parse{$name}"}( $value ); } return FALSE; } private function parseParam( string $value ) { if( !property_exists( $this->data, "params" ) ) $this->data->params = []; preg_match( "/([a-z|]+)\s+(\\\$[a-zA-Z0-9_]+)\s+(.*)\s*/", $value, $param ); return $this->data->params[ $param[2] ] = "({$param[1]}) {$param[3]}"; } /* ### MAGIC FUNCTIONS ########################################## # */ } /** Datei: library/ARUNOX/API/PHP/Token/T_USE.php **/ * @copyright 2024 Sparky@ARUNOX * @version 1.0.0 * @since 1.0.0 File is created */ namespace ARUNOX\API\PHP\Token; use stdClass; use ARUNOX\API\PHP; class T_USE extends PHP\Token { /* ### VARIABLES ################################################ # */ protected $row = NULL; protected $nextLine = NULL; /* ### PUBLIC FUNCTIONS ######################################### # */ public function __toString() { return $this->text . implode( "", $this->row ); } /* ### PROTECTED FUNCTIONS ###################################### # */ protected function construct( array $tokens, int $index ) { $row = array_slice( $tokens, $index ); $search = array_column( $row, "text" ); $end = array_search( ";", $search ); $this->row = array_slice( $row, 1, ++$end ); $this->nextLine = $row[ $end ]->line; } /* ### PRIVATE FUNCTIONS ######################################## # */ /* ### MAGIC FUNCTIONS ########################################## # */ } /** Datei: library/ARUNOX/API/PHP/Token/Token.php **/ * @copyright 2024 Sparky@ARUNOX * @version 1.0.0 * @since 1.0.0 File is created */ namespace ARUNOX\API\PHP; #[\AllowDynamicProperties] class Token { /* ### VARIABLES ################################################ # */ protected $id = NULL, $pos = NULL, $line = NULL, $text = NULL, $name = NULL; /* ### PUBLIC FUNCTIONS ######################################### # */ public function getArgs() { return $this->ARGS ?? NULL; } /* ### PROTECTED FUNCTIONS ###################################### # */ /* ### PRIVATE FUNCTIONS ######################################## # */ private function getReflectedValue( $name ) { $reflection = new \ReflectionClass( $this ); $property = $reflection->getProperty( $name ); return $property->getValue( $this ); } /* ### MAGIC FUNCTIONS ########################################## # */ public function __construct( $token, ...$args ) { $this->id = $token->id; $this->pos = $token->pos; $this->text = $token->text; $this->line = $token->line; $this->name = token_name( $this->id ); if( method_exists( $this, "construct" ) ) $this->construct( ...$args ); } public function &__get( string $name ) { $function = "get" . ucfirst( $name ); if( method_exists( $this, $function ) ) return $this->{$function}(); if( property_exists( $this, $name ) ) return $this->getReflectedValue( $name ); if( method_exists( $this, $name ) ) return $this->{$name}(); if( isset( $this->ARGS[$name] ) ) return $this->ARGS[$name]; if( isset( $this->NODES[$name] ) ) return $this->NODES[$name]; if( $this instanceof \Iterator ) return $this->values()[ $name ] ?? NULL; return NULL; } } /** Datei: library/ARUNOX/API/PHP/Attributes/Attribute.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\API\PHP\Attributes; abstract class Attribute { /* ################################################################ # */ /* ### ATTRIBUTE CONFIG ######################################### # */ /* ################################################################ # */ /* ### VARIABLES ################################################ # */ protected static null|string $__NAME = NULL; protected static null|string $__DEFAULT = NULL; /* ### PUBLIC FUNCTIONS ######################################### # */ public function getName() : string { if( static::$__NAME ) return static::$__NAME; $name = explode( "\\", static::class ); $name = end( $name ); $name = strtolower( $name ); return $name; } public function get() : mixed { if( static::$__DEFAULT ) return static::{static::$__DEFAULT}(); return NULL; } /* ################################################################ # */ /* ### CACHE SUPPORT ########################################### # */ /* ################################################################ # */ /* ### VARIABLES ################################################ # */ protected static array $__CACHE = []; protected mixed $CONFIG = NULL; /* ### PROTECTED FUNCTIONS ###################################### # */ protected function getCache() { return static::$__CACHE[ static::class ][ $this->PARENT ]; } protected function getCacheAll() { return static::$__CACHE[ static::class ]; } protected function addCache( mixed $config ) { static::$__CACHE[ static::class ][ $this->PARENT ] = $config; } /* ################################################################ # */ /* ### PARENT SUPPORT ########################################### # */ /* ################################################################ # */ /* ### VARIABLES ################################################ # */ protected null|string $PARENT = NULL; /* ### PUBLIC FUNCTIONS ######################################### # */ public function setParentClass( string $class ){ $this->PARENT = $class; $this->addCache( $this->CONFIG ); } /* ### PROTECTED FUNCTIONS ###################################### # */ protected function getParentClass() : string { return $this->PARENT; } /* ################################################################ # */ /* ### NAMESPACE SUPPORT ######################################## # */ /* ################################################################ # */ /* ### VARIABLES ################################################ # */ protected null|string $NAMESPACE = NULL; /* ### PUBLIC FUNCTIONS ######################################### # */ public function setNamespace( string $namespace ){ $this->NAMESPACE = $namespace; } /* ### PROTECTED FUNCTIONS ###################################### # */ protected function getNamespace() : string { return $this->NAMESPACE; } /* ################################################################ # */ /* ### MAGIC FUNCTIONS ########################################## # */ /* ################################################################ # */ public function __construct( mixed $config ) { $this->CONFIG = $config; } } /** Datei: library/ARUNOX/API/PHP/Attributes/Magic/Get/Exceptions/PatternFailed.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\API\PHP\Attributes\Magic\Get\Exceptions; use Exception; class PatternFailed extends Exception {} /** Datei: library/ARUNOX/API/PHP/Attributes/Magic/Get/Exceptions/FallbackFailed.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\API\PHP\Attributes\Magic\Get\Exceptions; use Exception; class FallbackFailed extends Exception {} /** Datei: library/ARUNOX/API/PHP/Attributes/Magic/Get/Get.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\API\PHP\Attributes\Magic; use Attribute; use ARUNOX\API\PHP; #[Attribute( Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE )] class Get { use PHP\Attributes\Magic\Get\Helper; /* ### VARIABLES ################################################ # */ /* ### PROTECTED FUNCTIONS ###################################### # */ /* ### MAGIC FUNCTIONS ########################################## # */ public function __construct( protected null|string $match = NULL, protected bool $fallback = FALSE, protected int $priority = 10 ){} } /** Datei: library/ARUNOX/API/PHP/Attributes/Magic/Get/Helper.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\API\PHP\Attributes\Magic\Get; use ARUNOX\API\PHP; use ReflectionClass; use ReflectionMethod; trait Helper { /* ### VARIABLES ################################################ # */ protected static array $__CACHED_GETTER = []; /* ### PUBLIC STATIC FUNCTIONS ################################## # */ public static function getMagicGetter() : array { $class = static::class; if( !isset( static::$__CACHED_GETTER[ $class ] ) ) { static::$__CACHED_GETTER[ $class ] = static::resolveMagicGettersFromClass(); } return static::$__CACHED_GETTER[ $class ]; } /* ### PRIVATE STATIC FUNCTIONS ################################# # */ private static function resolveMagicGettersFromClass() : array { $ref = new ReflectionClass( static::class ); $result = [ "exact" => [], "pattern" => [], "fallback" => [], ]; foreach( $ref->getMethods(ReflectionMethod::IS_PUBLIC ) as $method ) { foreach( $method->getAttributes(PHP\Attributes\Magic\Get::class ) as $attr ) { $meta = $attr->newInstance(); $target = $method->getName(); if( $meta->fallback || $meta->match === NULL || $meta->match === "*" ) { $result["fallback"][ $meta->priority ][] = $target; continue; } if( @preg_match("/^{$meta->match}$/", "" ) !== FALSE ) { $result["pattern"][ $meta->priority ][] = [ "/^{$meta->match}$/", $target ]; continue; } $result["exact"][ $meta->priority ][ $meta->match ] = $target; } } foreach( $result as &$type ) { ksort( $type ); $type = array_reduce_recursive( $type ); $type = array_values( $type ); } return $result; } /* ### MAGIC FUNCTIONS ########################################## # */ public function __get( string $name ) : mixed { $map = self::getMagicGetter(); // 1. exact if( isset( $map["exact"][ $name ] ) ) { return $this->{$map["exact"][ $name ]}(); } // 2. pattern foreach( $map["pattern"] as [ $regex, $method ]) { if( preg_match( $regex, $name ) ) { try { return $this->{$method}( $name ); } catch( Exceptions\PatternFailed $e ) {} } } // 3. fallback foreach( $map["fallback"] as $method ) { try { return $this->{$method}( $name ); } catch( Exceptions\FallbackFailed $e ) {} } // 4. method getXyz() if( method_exists( $this, "get{$name}" ) ) { return $this->{"get{$name}"}(); } // 5. property access if( property_exists( $this, $name ) ) { return $this->{$name}; } throw new \BadMethodCallException("Unbekannte dynamische Eigenschaft: {$name}"); } } /** Datei: library/ARUNOX/API/PHP/Attributes/Magic/Call/Exceptions/PatternFailed.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\API\PHP\Attributes\Magic\Call\Exceptions; use Exception; class PatternFailed extends Exception {} /** Datei: library/ARUNOX/API/PHP/Attributes/Magic/Call/Exceptions/FallbackFailed.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\API\PHP\Attributes\Magic\Call\Exceptions; use Exception; class FallbackFailed extends Exception {} /** Datei: library/ARUNOX/API/PHP/Attributes/Magic/Call/Call.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\API\PHP\Attributes\Magic; use Attribute; use ARUNOX\API\PHP; #[Attribute( Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE )] class Call { use PHP\Attributes\Magic\Get\Helper; /* ### VARIABLES ################################################ # */ /* ### PROTECTED FUNCTIONS ###################################### # */ /* ### MAGIC FUNCTIONS ########################################## # */ public function __construct( protected null|string $match = NULL, protected bool $fallback = FALSE ){} } /** Datei: library/ARUNOX/API/PHP/Attributes/Magic/Call/Helper.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\API\PHP\Attributes\Magic\Call; use ARUNOX\API\PHP; use ReflectionClass; use ReflectionMethod; trait Helper { /* ### VARIABLES ################################################ # */ protected static array $__CACHED_CALLER = []; /* ### PUBLIC STATIC FUNCTIONS ################################## # */ public static function getMagicCaller( bool $static = FALSE ) : array { $class = static::class; $type = $static ? "static" : "normal"; if( !isset( static::$__CACHED_CALLER[ $class ] ) ) { static::$__CACHED_CALLER[ $class ] = static::resolveMagicCallerFromClass(); } return static::$__CACHED_CALLER[ $class ][ $type ]; } /* ### PRIVATE STATIC FUNCTIONS ################################# # */ private static function resolveMagicCallerFromClass() : array { $ref = new ReflectionClass( static::class ); $result = [ "exact" => [], "pattern" => [], "fallback" => [], ]; $result = [ "static" => $result, "normal" => $result, ]; foreach( $ref->getMethods(ReflectionMethod::IS_PUBLIC ) as $method ) { foreach( $method->getAttributes(PHP\Attributes\Magic\Call::class ) as $attr ) { $meta = $attr->newInstance(); $target = $method->getName(); $type = $method->isStatic() ? "static" : "normal"; // show( $meta, $method, $type, $target ); if( $meta->fallback || $meta->match === NULL || $meta->match === "*" ) { $result[ $type ]["fallback"][] = $target; continue; } if( @preg_match("/^{$meta->match}$/", "" ) !== FALSE ) { $result[ $type ]["pattern"][] = [ "/^{$meta->match}$/", $target ]; continue; } $result[ $type ]["exact"][ $meta->match ] = $target; } } return $result; } /* ### MAGIC FUNCTIONS ########################################## # */ public function __call( string $name, array $args ) : mixed { $map = self::getMagicCaller(); // if( $name == "getIcon" ) show( $name, $args, $map, FALSE ); // 1. exact if( isset( $map["exact"][ $name ] ) ) { return $this->{$map["exact"][ $name ]}( ...$args ); } // 2. pattern foreach( $map["pattern"] as [ $regex, $method ]) { if( preg_match( $regex, $name, $matches ) ) { try { return $this->{$method}( $matches, ...$args ); } catch( Exceptions\PatternFailed $e ) {} } } // 3. fallback foreach( $map["fallback"] as $method ) { try { return $this->{$method}( $name, ...$args ); } catch( Exceptions\FallbackFailed $e ) {} } return NULL; // throw new \BadMethodCallException("Unbekannte dynamische Methode: {$name}"); } public static function __callStatic( string $name, array $args ) : mixed { $map = self::getMagicCaller( TRUE ); // show( static::class, FALSE ); // 1. exact if( isset( $map["exact"][ $name ] ) ) { return static::{$map["exact"][ $name ]}( ...$args ); } // 2. pattern foreach( $map["pattern"] as [ $regex, $method ]) { if( preg_match( $regex, $name, $matches ) ) { try { return static::{$method}( $matches, ...$args ); } catch( Exceptions\PatternFailed $e ) {} } } // 3. fallback foreach( $map["fallback"] as $method ) { try { return static::{$method}( $name, ...$args ); } catch( Exceptions\FallbackFailed $e ) {} } return NULL; // throw new \BadMethodCallException("Unbekannte dynamische Methode: {$name}"); } } /** Datei: library/ARUNOX/API/PHP/Attributes/Magic/Set/Set.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\API\PHP\Attributes\Magic; use Attribute; use ARUNOX\API\PHP; #[Attribute( Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE )] class Set { use PHP\Attributes\Magic\Get\Helper; /* ### VARIABLES ################################################ # */ /* ### PROTECTED FUNCTIONS ###################################### # */ /* ### MAGIC FUNCTIONS ########################################## # */ public function __construct( protected null|string $match = NULL, protected bool $fallback = FALSE ){} } /** Datei: library/ARUNOX/API/PHP/Attributes/Magic/Set/Helper.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\API\PHP\Attributes\Magic\Set; use ARUNOX\API\PHP; use ReflectionClass; use ReflectionMethod; trait Helper { /* ### VARIABLES ################################################ # */ protected static array $__CACHED_SETTER = []; /* ### PUBLIC STATIC FUNCTIONS ################################## # */ public static function getMagicSetter() : array { $class = static::class; if( !isset( static::$__CACHED_SETTER[ $class ] ) ) { static::$__CACHED_SETTER[ $class ] = static::resolveMagicSettersFromClass(); } return static::$__CACHED_SETTER[ $class ]; } /* ### PRIVATE STATIC FUNCTIONS ################################# # */ private static function resolveMagicSettersFromClass() : array { $ref = new ReflectionClass( static::class ); $result = [ "exact" => [], "pattern" => [], "fallback" => [], ]; foreach( $ref->getMethods(ReflectionMethod::IS_PUBLIC ) as $method ) { foreach( $method->getAttributes(PHP\Attributes\Magic\Set::class ) as $attr ) { $meta = $attr->newInstance(); $target = $method->getName(); if( $meta->fallback || $meta->match === NULL || $meta->match === "*" ) { $result["fallback"][] = $target; continue; } if( @preg_match("/^{$meta->match}$/", "" ) !== FALSE ) { $result["pattern"][] = [ "/^{$meta->match}$/", $target ]; continue; } $result["exact"][ $meta->match ] = $target; } } return $result; } /* ### MAGIC FUNCTIONS ########################################## # */ public function __set( string $name, mixed $value ) : void { $map = self::getMagicSetter(); // 1. exact if( isset( $map["exact"][ $name ] ) ) { // show( "Case 1", $map["exact"], $name, $value, FALSE ); $this->{$map["exact"][ $name ]}( $value ); return; } // 2. pattern foreach( $map["pattern"] as [ $regex, $method ]) { if( preg_match( $regex, $name ) ) { // show( "Case 2", $method, $name, $value, FALSE ); $this->{$method}( $name, $value ); return; } } // 3. fallback foreach( $map["fallback"] as $method ) { // show( "Case 3", $method, $name, $value, FALSE ); $this->{$method}( $name, $value ); return; } // 4. method getXyz() if( method_exists( $this, "set{$name}" ) ) { // show( "Case 4", "set{$name}", $value, FALSE ); $this->{"set{$name}"}( $value ); return; } // 5. property access if( property_exists( $this, $name ) ) { // show( "Case 5", $name, $value, FALSE ); $this->{$name} = $value; return; } throw new \BadMethodCallException("Unbekannte dynamische Eigenschaft: {$name}"); } } /** Datei: library/ARUNOX/API/PHP/Attributes/Database/Attributes/Attributes.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\API\PHP\Attributes\Database; use Attribute; use ARUNOX\API\PHP; use ARUNOX\Database; #[Attribute( Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE )] class Attributes { use PHP\Attributes\Magic\Get\Helper; /* ### VARIABLES ################################################ # */ protected static array $ATTRIBUTES = [ "status" => Database\Attribute\Status::class, "date_create" => Database\Attribute\DateTime::class, "date_edit" => Database\Attribute\DateTime::class, ]; /* ### PROTECTED FUNCTIONS ###################################### # */ protected function getClass() { return $this->class ?? ( static::$ATTRIBUTES[ $this->column ] ?? NULL ); } /* ### MAGIC FUNCTIONS ########################################## # */ public function __construct( protected string $column, protected null|string $class = NULL ){ if( !$class ) $this->class = $this->getClass( $this->column ); } } /** Datei: library/ARUNOX/API/PHP/Attributes/Database/Attributes/Resolve/Helper.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\API\PHP\Attributes\Database\Attributes\Resolve; use ARUNOX\API\PHP; use ReflectionClass; use ARUNOX\Interfaces\Supports; trait Helper { /* ### VARIABLES ################################################ # */ protected static array $__CACHED_RESOLVES = []; /* ### PUBLIC FUNCTIONS ######################################### # */ #[PHP\Attributes\Magic\Get( fallback: TRUE, priority: 5 )] public function handleGetResolve( string $column ) { if( static::existsResolve( $column ) ) { $resolves = static::getResolve( $column ); $result = NULL; foreach( $resolves as $resolve ) { if( preg_match( '/\$\{[a-z]+\}/', $resolve ) ) { $result = preg_replace_callback( '/\$\{([a-z]+)\}/', fn( $m ) => $this->attributes[ $m[1] ], $resolve ); } else if( is_string( $resolve ) ) $result = $this->attributes[ $resolve ]; if( preg_replace( "/\s+/", "", $result ) != NULL ) return $result; } return ""; } throw new PHP\Attributes\Magic\Get\Exceptions\FallbackFailed(); } /* ### PUBLIC STATIC FUNCTIONS ################################## # */ public static function existsResolve( string $column ) : bool { $resolves = static::getResolves(); return isset( $resolves[ $column ] ); } public static function getResolve( string $column ) : array { return static::resolveResolvesFromClass( $column ); } public static function getResolves() : array { return static::resolveResolvesFromClass(); } /* ### PRIVATE STATIC FUNCTIONS ################################# # */ private static function resolveResolvesFromClass( null|string $column = NULL ) : string|array { $reflection = new ReflectionClass( static::class ); $attributes = []; if( !isset( static::$__CACHED_RESOLVES[ static::class ] ) ) { do { foreach( $reflection->getAttributes( PHP\Attributes\Database\Attributes\Resolve::class ) as $attribute ) { $instance = $attribute->newInstance(); if( !empty( $attributes ) ) break 2; $attributes[ $instance->column ] = $instance->resolves; } $reflection = $reflection->getParentClass(); } while ($reflection); static::$__CACHED_RESOLVES[ static::class ] = $attributes; } if( $column ) { return static::$__CACHED_RESOLVES[ static::class ][ $column ] ?? ""; } return static::$__CACHED_RESOLVES[ static::class ] ?? ""; } } /** Datei: library/ARUNOX/API/PHP/Attributes/Database/Attributes/Resolve/Resolve.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\API\PHP\Attributes\Database\Attributes; use Attribute; use ARUNOX\API\PHP; #[Attribute( Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE )] class Resolve { use PHP\Attributes\Magic\Get\Helper; /* ### VARIABLES ################################################ # */ protected array $resolves = []; /* ### PROTECTED FUNCTIONS ###################################### # */ /* ### MAGIC FUNCTIONS ########################################## # */ public function __construct( protected string $column, ...$resolves ){ $this->resolves = (array)$resolves; } } /** Datei: library/ARUNOX/API/PHP/Attributes/Database/Attributes/Helper.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\API\PHP\Attributes\Database\Attributes; use ARUNOX\API\PHP; use ReflectionClass; use ARUNOX\Interfaces\Supports; trait Helper { /* ### VARIABLES ################################################ # */ protected static array $ATTRIBUTES = []; protected static array $__CACHED_ATTRS = []; protected static array $__CALLABLE_ATTRS = [ "toggle" => Supports\Toggle::class, ]; /* ### PUBLIC FUNCTIONS ######################################### # */ #[PHP\Attributes\Magic\Call( fallback: TRUE )] public function handleAttributesCall( string $name, ...$args ) { $name = preg_split('/([[:upper:]][[:lower:]]+)/', $name, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY ); if( is_array( $name ) ) { $name = array_map( "strtolower", $name ); if( key_exists( $name[0], static::$__CALLABLE_ATTRS ) ) { $class = static::$__CALLABLE_ATTRS[ $name[0] ]; if( isset( $this->{$name[1]} ) ) { if( $this->{$name[1]} instanceof $class ) { if( method_exists( $this->{$name[1]}, "setParent" ) ) { $this->{$name[1]}->setParent( $this ); } return $this->{$name[1]}->{$name[0]}( ...$args ); } } } } throw new PHP\Attributes\Magic\Call\Exceptions\FallbackFailed(); } /* ### PUBLIC STATIC FUNCTIONS ################################## # */ public static function existsAttribute( string $name ) : bool { $attrs = static::getAttributes(); return isset( $attrs[ $name ] ); } public static function getAttribute( string $name ) : mixed { $attrs = static::getAttributes(); return $attrs[ $name ] ?? NULL; } public static function getAttributes() : array { $class = static::class; if( !isset( static::$__CACHED_ATTRS[ $class ] ) ) { $attrs = static::resolveAttributesFromClass(); $attrs = static::getMergedAttributes( $attrs ); static::$__CACHED_ATTRS[ $class ] = $attrs; } return static::$__CACHED_ATTRS[ $class ]; } /* ### PRIVATE STATIC FUNCTIONS ################################# # */ private static function getMergedAttributes( null|array $defaults = []) : array { $result = $defaults; $class = static::class; do { if( property_exists( $class, "ATTRIBUTES" ) ) { $ref = new ReflectionClass( $class ); if( $ref->hasProperty("ATTRIBUTES") ) { $prop = $ref->getProperty("ATTRIBUTES"); if( $prop->isStatic() ) { $prop->setAccessible( TRUE ); $attrs = $prop->getValue(); if( is_array( $attrs ) ) { $result = array_merge( $attrs, $result ); } } } } } while( $class = get_parent_class( $class ) ); return $result; } private static function resolveAttributesFromClass() : array { $ref = new ReflectionClass( static::class ); $result = []; foreach( $ref->getAttributes( PHP\Attributes\Database\Attributes::class ) as $attr ) { $inst = $attr->newInstance(); $result[ $inst->column ] = $inst->class; } return $result; } } /** Datei: library/ARUNOX/API/PHP/Attributes/Database/Definitions/Model.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\API\PHP\Attributes\Database\Definitions; use Attribute; use ARUNOX\API\PHP; #[Attribute( Attribute::TARGET_CLASS )] class Model extends PHP\Attributes\Attribute { /* ### VARIABLES ################################################ # */ /* ### PUBLIC STATIC FUNCTIONS ################################## # */ public function getModelClass() : string|bool { return $this->class; } public function callModelClass( string $method, ...$args ) { return $this->getModelClass()::{$method}( ...$args ); } /* ### PROTECTED FUNCTIONS ###################################### # */ /* ### MAGIC FUNCTIONS ########################################## # */ public function __construct( protected string $class ){ parent::__construct( $class ); } } /** Datei: library/ARUNOX/API/PHP/Attributes/Database/Definitions/HashID.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\API\PHP\Attributes\Database\Definitions; use Attribute; use ARUNOX\API\PHP; use Hashids\Hashids; use ARUNOX\Core\Hooks; #[Attribute( Attribute::TARGET_CLASS )] class HashID extends PHP\Attributes\Attribute { /* ### VARIABLES ################################################ # */ /* ### PUBLIC FUNCTIONS ######################################### # */ public function getHashIDPrefix() : string { return $this->prefix; } public function getHashID( int $primary ) : string { $prefix = $this->getHashIDPrefix() ?? $this->getTable(); $hashids = new Hashids( $this->getTable(), 16 ); $numbers = [ $primary ]; $numbers = Hooks::filterWithNames( "model::hashids", $this->getTable(), $numbers, $this->getTable() ); $encoded = $hashids->encode( $numbers ); $hashid = "{$this->prefix}_{$encoded}"; return $hashid; } public function resolveHashID( string $hashid, null|int $index = 0 ) : int|bool|array { $prefix = $this->getHashIDPrefix() ?? $this->getTable(); $hashid = explode( "_", $hashid ); if( $hashid[0] !== $prefix ) return FALSE; $sqids = new Hashids( $this->getTable(), 16 ); $decoded = $sqids->decode( $hashid[1] ); if( !$decoded ) return FALSE; return $index === NULL ? $decoded : $decoded[ $index ]; } public function resolveHashIDPrefix( string $hashid, null|string $column = NULL ) { $split = explode( "_", $hashid ); $class = array_search( $split[0], $this->getCacheAll() ); if( !$class ) return NULL; $data = $class::get( $hashid ); if( $column ) return $data->{$column} ?? NULL; return $data; } /* ### PROTECTED FUNCTIONS ###################################### # */ /* ### PRIVATE FUNCTIONS ######################################## # */ private function getTable() : string { return $this->getParentClass()::getTable(); } /* ### MAGIC FUNCTIONS ########################################## # */ public function __construct( protected string $prefix ){ parent::__construct( $prefix ); } } /** Datei: library/ARUNOX/API/PHP/Attributes/Database/Definitions/Table.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\API\PHP\Attributes\Database\Definitions; use Attribute; use ARUNOX\API\PHP; #[Attribute( Attribute::TARGET_CLASS )] class Table extends PHP\Attributes\Attribute { /* ### VARIABLES ################################################ # */ /* ### PUBLIC FUNCTIONS ######################################### # */ public function getTable( null|string $suffix = NULL ) { return implode( "_", array_filter( [ $this->table, $suffix ] ) ); } public function resolveTable( string $table ) { $class = array_search( $table, $this->getCacheAll() ); if( !$class ) return NULL; return $class; } /* ### PROTECTED FUNCTIONS ###################################### # */ /* ### MAGIC FUNCTIONS ########################################## # */ public function __construct( protected string $table, ){ parent::__construct( $table ); } } /** Datei: library/ARUNOX/API/PHP/Attributes/Database/Definitions/Entity.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\API\PHP\Attributes\Database\Definitions; use Attribute; use ARUNOX\API\PHP; #[Attribute( Attribute::TARGET_CLASS )] class Entity extends PHP\Attributes\Attribute { /* ### VARIABLES ################################################ # */ /* ### PUBLIC STATIC FUNCTIONS ################################## # */ public function getEntityClass() : string|bool { return $this->class; } public function callEntityClass( string $method, ...$args ) { return $this->getEntityClass()::{$method}( ...$args ); } /* ### PROTECTED FUNCTIONS ###################################### # */ /* ### MAGIC FUNCTIONS ########################################## # */ public function __construct( protected string $class ){ parent::__construct( $class ); } } /** Datei: library/ARUNOX/API/PHP/Attributes/Database/Definitions/Origin.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\API\PHP\Attributes\Database\Definitions; use Attribute; use ARUNOX\API\PHP; use ARUNOX\Autoloader; #[Attribute( Attribute::TARGET_CLASS )] class Origin extends PHP\Attributes\Attribute { /* ### VARIABLES ################################################ # */ /* ### PUBLIC STATIC FUNCTIONS ################################## # */ public function getOriginClass() : string|bool { return $this->class; } public function callOriginClass( string $method, ...$args ) { return $this->getOriginClass()::{$method}( ...$args ); } public function getControllerClass() { return Autoloader::find( $this->getNamespace(), "controller" ); } public function callControllerClass( string $method, ...$args ) { return $this->getControllerClass()::{$method}( ...$args ); } /* ### PROTECTED FUNCTIONS ###################################### # */ /* ### MAGIC FUNCTIONS ########################################## # */ public function __construct( protected string $class ){ parent::__construct( $class ); } } /** Datei: library/ARUNOX/API/PHP/Attributes/Database/Options/Label.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\API\PHP\Attributes\Database\Options; use Attribute; use ARUNOX\API\PHP; #[Attribute( Attribute::TARGET_CLASS | Attribute::TARGET_CLASS_CONSTANT )] class Label extends PHP\Attributes\Attribute { /* ### VARIABLES ################################################ # */ protected static null|string $__DEFAULT = "getSingular"; /* ### PUNLIC FUNCTIONS ######################################### # */ public function getLabel() : array { return [ "singular" => $this->singular, "plural" => $this->plural, "group" => $this->group, ]; } public function getSingular( null|string $label = NULL, ...$tokens ) : string { return $this->getLabelSingular( $label, ...$tokens ); } public function getLabelSingular( null|string $label = NULL, ...$tokens ) : string { return $this->resolveLabel( "singular", $label, "Entry", ...$tokens ); } public function getPlural( null|string $label = NULL, ...$tokens ) : string { return $this->getLabelPlural( $label, ...$tokens ); } public function getLabelPlural( null|string $label = NULL, ...$tokens ) : string { return $this->resolveLabel( "plural", $label, "Entries", ...$tokens ); } public function getLabelGroup( null|string $label = NULL, ...$tokens ) : string { return $this->resolveLabel( "group", $label, "Overview", ...$tokens ); } /* ### PROTECTED FUNCTIONS ###################################### # */ /* ### PRIVATE FUNCTIONS ######################################## # */ private function resolveLabel( null|string $column = NULL, null|string $label = NULL, null|string $fallback = NULL, ...$tokens ) : string|array { if( $column ) { if( !$label ) return __( $this->getLabel()[ $column ] ?? $fallback ); return __( $label, $this->getLabel()[ $column ] ?? $fallback, ...$tokens ); } else return $this->getLabel() ?? []; } /* ### MAGIC FUNCTIONS ########################################## # */ public function __construct( protected string $singular, protected null|string $plural = NULL, protected null|string $group = NULL ){ if( !$plural ) $this->plural = $singular; if( !$group ) $this->group = $this->plural; } } /** Datei: library/ARUNOX/API/PHP/Attributes/Database/Options/Color.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\API\PHP\Attributes\Database\Options; use Attribute; use ARUNOX\API\PHP; use ARUNOX\Plugins\REST; #[Attribute( Attribute::TARGET_CLASS | Attribute::TARGET_CLASS_CONSTANT )] class Color extends PHP\Attributes\Attribute { /* ### VARIABLES ################################################ # */ protected static null|string $__DEFAULT = "getColor"; /* ### PUBLIC FUNCTIONS ######################################### # */ public function getColor() { return $this->CONFIG; } /* ### PROTECTED FUNCTIONS ###################################### # */ /* ### MAGIC FUNCTIONS ########################################## # */ } /** Datei: library/ARUNOX/API/PHP/Attributes/Database/Options/Toggle.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\API\PHP\Attributes\Database\Options; use Attribute; use ARUNOX\API\PHP; use ARUNOX\Plugins\REST; #[Attribute( Attribute::TARGET_CLASS_CONSTANT )] class Toggle extends PHP\Attributes\Attribute { /* ### VARIABLES ################################################ # */ protected static null|string $__DEFAULT = "getToggle"; /* ### PUBLIC FUNCTIONS ######################################### # */ public function getToggle() { return $this->CONFIG; } /* ### PROTECTED FUNCTIONS ###################################### # */ /* ### MAGIC FUNCTIONS ########################################## # */ } /** Datei: library/ARUNOX/API/PHP/Attributes/Database/Options/Icon.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\API\PHP\Attributes\Database\Options; use Attribute; use ARUNOX\API\PHP; use ARUNOX\Plugins\REST; #[Attribute( Attribute::TARGET_CLASS | Attribute::TARGET_CLASS_CONSTANT )] class Icon extends PHP\Attributes\Attribute { /* ### VARIABLES ################################################ # */ protected static null|string $__DEFAULT = "getIcon"; /* ### PUBLIC FUNCTIONS ######################################### # */ public function getIcon() { return $this->CONFIG; } /* ### PROTECTED FUNCTIONS ###################################### # */ /* ### MAGIC FUNCTIONS ########################################## # */ } /** Datei: library/ARUNOX/API/PHP/Attributes/Helper.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\API\PHP\Attributes; use Attribute; use ARUNOX\API\PHP; use ReflectionClass; use ReflectionAttribute; use ReflectionClassConstant; trait Helper { /* ### VARIABLES ################################################ # */ protected static array $__CACHED_ATTRIBUTES = []; protected static array $__CACHED_CONSTANTS = []; /* ### PUBLIC FUNCTIONS ######################################### # */ #[PHP\Attributes\Magic\Call( fallback: TRUE )] public static function handleDynamicAttributesCallStatic( string $name, ...$args ) { $objects = static::getDynamicAttributes(); // if( $name == "getIcon" ) show( $name, $args, get_called_class(), get_class(), static::class, $objects, FALSE ); foreach( $objects as $object ) { if( method_exists( $object, $name ) ) { return $object->{$name}( ...$args ); } } // show( $name, $args, $objects, FALSE ); throw new PHP\Attributes\Magic\Call\Exceptions\FallbackFailed(); } #[PHP\Attributes\Magic\Call( fallback: TRUE )] public function handleDynamicAttributesCall( string $name, ...$args ) { return static::handleDynamicAttributesCallStatic( $name, ...$args ); } /* ### PUBLIC STATIC FUNCTIONS ################################## # */ public static function getDynamicAttributes() : array { return static::resolveDynamicAttributesFromClass(); } /* ### PRIVATE STATIC FUNCTIONS ################################# # */ private static function resolveDynamicAttributesFromReflection( $reflection ) : array { $attributes = []; do { foreach( $reflection->getAttributes( PHP\Attributes\Attribute::class, ReflectionAttribute::IS_INSTANCEOF ) as $attribute ) { $attrName = $attribute->getName(); $attrInstance = $attribute->newInstance(); $attrArguments = $attribute->getArguments(); $attrReflection = new ReflectionClass( $attribute->getName() ); $attrAttributes = $attrReflection->getAttributes( Attribute::class ); $attrRepeatable = Attribute::IS_REPEATABLE & $attrAttributes[0]->getArguments()[0]; if( method_exists( $reflection, "getNamespaceName" ) ) { $attrInstance->setNamespace( $reflection->getNamespaceName() ); } $attrInstance->setParentClass( static::class ); if( !key_exists( $attrName, $attributes ) ) { $attributes[ $attrName ] = $attrInstance; } if( !$attrRepeatable ) continue; $attributes[ $attrName ]->add( ...$attrArguments ); } if( !method_exists( $reflection, "getParentClass" ) ) break; $reflection = $reflection->getParentClass(); } while( $reflection ); return array_filter( $attributes ); } private static function resolveDynamicAttributesFromClass() : array { $reflection = new ReflectionClass( static::class ); if( !isset( static::$__CACHED_ATTRIBUTES[ static::class ] ) ) { static::$__CACHED_ATTRIBUTES[ static::class ] = static::resolveDynamicAttributesFromReflection( $reflection ); } return static::$__CACHED_ATTRIBUTES[ static::class ] ?? []; } private static function resolveDynamicAttributesFromConstants() : array { $reflection = new ReflectionClass( static::class ); $constants = []; if( !isset( static::$__CACHED_CONSTANTS[ static::class ] ) ) { foreach( $reflection->getReflectionConstants( ReflectionClassConstant::IS_PUBLIC ) as $constant ) { $attributes = static::resolveDynamicAttributesFromReflection( $constant ); if( key_exists( $constant->getValue(), $constants ) ) continue; $constants[ $constant->getValue() ] = [ "name" => $constant->getName(), "value" => $constant->getValue(), "attributes" => $attributes, ]; } static::$__CACHED_CONSTANTS[ static::class ] = array_filter( $constants ); // if( static::class == \ARUNOX\Plugins\Calendars\Entries\Participants\Status::class ) { // show( static::class, $constants, $reflection->getReflectionConstants( ReflectionClassConstant::IS_PUBLIC ) ); // } } return static::$__CACHED_CONSTANTS[ static::class ] ?? []; } } /** Datei: library/ARUNOX/API/PHP/Attributes/Controller/Label.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\API\PHP\Attributes\Controller; use Attribute; use ARUNOX\API\PHP; #[Attribute( Attribute::TARGET_CLASS )] class Label extends PHP\Attributes\Database\Options\Label {} /** Datei: library/ARUNOX/API/PHP/Attributes/Controller/Notifications/Helper.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\API\PHP\Attributes\Controller\Notifications; use Attribute; use ARUNOX\API\PHP; use ReflectionClass; trait Helper { /* ### VARIABLES ################################################ # */ protected static array $__CACHED_NOTIFIES = []; /* ### PUBLIC STATIC FUNCTIONS ################################## # */ public static function getNotify( string|array $type, null|string $label = NULL ) : string|bool { return static::getNotification( $type, $label ); } public static function getNotification( string|array $type, null|string|array $label = NULL ) : string|bool { $notify = static::resolveNotificationsFromClass( ...(array)$type ); if( $label ) { $label = new Label( ...(array)$label ); $label = $label->getLabel() ?? NULL; } if( $label == NULL ) { $label = static::getLabel(); } if( $label ) { $notify = preg_replace_callback( '/\{\$LABEL\.([a-z]+)\}/', fn( $match ) => $label[ $match[1] ], $notify ); } return $notify; } public static function getNotifications() : array { return static::resolveNotificationsFromClass(); } /* ### PRIVATE STATIC FUNCTIONS ################################# # */ private static function resolveNotificationsFromClass( ...$type ) : string|array { $reflection = new ReflectionClass( static::class ); $attributes = []; $type = implode( ".", $type ); if( !isset( static::$__CACHED_NOTIFIES[ static::class ] ) ) { do { foreach( $reflection->getAttributes( PHP\Attributes\Controller\Notifications::class ) as $attribute ) { $instance = $attribute->newInstance(); if( key_exists( $instance->type, $attributes ) ) continue; $attributes[ $instance->type ] = $instance->message; } $reflection = $reflection->getParentClass(); } while ($reflection); static::$__CACHED_NOTIFIES[ static::class ] = $attributes; } if( $type ) return static::$__CACHED_NOTIFIES[ static::class ][ $type ] ?? "Unbekannter Fehler!"; else return static::$__CACHED_NOTIFIES[ static::class ] ?? []; } } /** Datei: library/ARUNOX/API/PHP/Attributes/Controller/Notifications/Notifications.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\API\PHP\Attributes\Controller; use Attribute; use ARUNOX\API\PHP; #[Attribute( Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE )] class Notifications { use PHP\Attributes\Magic\Get\Helper; /* ### VARIABLES ################################################ # */ /* ### PROTECTED FUNCTIONS ###################################### # */ /* ### MAGIC FUNCTIONS ########################################## # */ public function __construct( protected string $type, protected string $message, protected bool $default = FALSE ){} } /** Datei: library/ARUNOX/API/PHP/Attributes/Controller/Icon.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\API\PHP\Attributes\Controller; use Attribute; use ARUNOX\API\PHP; #[Attribute( Attribute::TARGET_CLASS )] class Icon extends PHP\Attributes\Database\Options\Icon {} /** Datei: library/ARUNOX/API/PHP/Attributes/Controller/Route/Route.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\API\PHP\Attributes\Controller; use Attribute; use ARUNOX\API\PHP; use ARUNOX\API\BaseController; #[Attribute( Attribute::TARGET_CLASS )] class Route { use PHP\Attributes\Magic\Get\Helper; /* ### VARIABLES ################################################ # */ protected string $route = "/"; /* ### PUNLIC FUNCTIONS ######################################### # */ public function get() { return $this->route; } /* ### MAGIC FUNCTIONS ########################################## # */ public function __construct( ... $route ){ $segments = []; foreach( $route as $item ) { if( class_exists( $item ) ) { if( method_exists( $item, "getRoute" ) ) { array_unshift( $segments, $item::getRoute() ?? NULL ); } continue; } if( is_string( $item ) ) array_unshift( $segments, $item ); } $segments = array_filter( $segments ); $segments = implode( "/", $segments ); $segments = preg_replace( "/\/+/", "/", $segments ); $segments = strtolower( $segments ); $this->route = $segments; } } /** Datei: library/ARUNOX/API/PHP/Attributes/Controller/Route/Helper.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\API\PHP\Attributes\Controller\Route; use ARUNOX\API\PHP; use ReflectionClass; trait Helper { /* ### VARIABLES ################################################ # */ protected static array $__CACHED_ROUTES = []; /* ### PUBLIC STATIC FUNCTIONS ################################## # */ public static function getRoute( ...$suffixes ) : string { $route = static::resolveRouteFromClass(); $route = preg_replace( "/\\/+/", "/", $route ); $route = preg_replace( "/\\/#/", "#", $route ); $route = explode( "/", $route ); $route = array_filter( $route ); foreach( $suffixes as $suffix ) { if( preg_match( "/([0-9]+):(.*)/", $suffix, $matches ) ) { $pos = $matches[1] * -1; $prev = array_slice( $route, 0, $pos ); $next = array_slice( $route, $pos ); $route = [ ...$prev, $matches[2], ...$next ]; continue; } $route[] = $suffix; } $route = array_filter( $route ); $route = implode( "/", $route ); $route = "/{$route}"; return $route; } /* ### PRIVATE STATIC FUNCTIONS ################################# # */ private static function resolveRouteFromClass() : string { $reflection = new ReflectionClass( static::class ); $attributes = ""; if( !isset( static::$__CACHED_ROUTES[ static::class ] ) ) { do { foreach( $reflection->getAttributes( PHP\Attributes\Controller\Route::class ) as $attribute ) { $instance = $attribute->newInstance(); if( !empty( $attributes ) ) break 2; $attributes = $instance->get(); } $reflection = $reflection->getParentClass(); } while ($reflection); static::$__CACHED_ROUTES[ static::class ] = $attributes; } return static::$__CACHED_ROUTES[ static::class ] ?? ""; } } /** Datei: library/ARUNOX/API/PHP/Traits/Helpers/Controller.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\API\PHP\Traits\Helpers; use ReflectionClass; use ARUNOX\Autoloader; trait Controller { /* ### PUBLIC STATIC FUNCTIONS ################################## # */ public static function getControllerClass() : string { return Autoloader::find( static::getNamespace(), "controller" ); } public static function callControllerClass( string $method, ...$args ) : mixed { return static::getControllerClass()::{$method}( ...$args ); } /* ### PRIVAT STATIC FUNCTIONS ################################## # */ private static function getNamespace() : string { $reflection = new ReflectionClass( static::class ); return $reflection->getNamespaceName(); } } /** Datei: library/ARUNOX/API/PHP/Traits/Enums/AsOptions.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\API\PHP\Traits\Enums; trait AsOptions { /* ### PUBLIC STATIC FUNCTIONS ################################## # */ public static function getAsOptions() : array { $options = []; foreach( static::cases() as $color ) { $options[ $color->value ] = [ "label" => $color->name, "value" => $color->value, ]; } return $options; } } /** Datei: library/ARUNOX/API/PHP/PHP.php **/ * @copyright 2024 Sparky@ARUNOX * @version 1.0.0 * @since 1.0.0 File is created */ namespace ARUNOX\API; use PhpToken; #[\AllowDynamicProperties] class PHP { /* ### VARIABLES ################################################ # */ protected $filename = NULL; /* ### PUBLIC FUNCTIONS ######################################### # */ public function content() { return $this->content = file_get_contents( $this->filename ); } public function tokens() { $this->tokens = PhpToken::tokenize( $this->content ); // preg_match('/.*\/\*\*.*\*\//Us', $this->content, $matches ); // $this->tokens = PhpToken::tokenize( $matches[0] ); // show( $this->tokens ); foreach( $this->tokens as $index => &$token ) { $name = token_name( $token->id ); $class = "ARUNOX\\API\\PHP\\Token\\{$name}"; if( !class_exists( $class ) ) { $class = "ARUNOX\\API\\PHP\\Token"; } $token = new $class( $token, $this->tokens, $index ); } return $this->tokens; } public function getAll( $token_type ) { return array_filter( $this->tokens, fn( $item ) => $item->id == $token_type ); } public function get( $token_type ) { return current( $this->getAll( $token_type ) ); } public function getArgs() { return $this->ARGS ?? NULL; } /* ### PROTECTED FUNCTIONS ###################################### # */ /* ### PRIVATE FUNCTIONS ######################################## # */ private function getReflectedValue( $name ) { $reflection = new \ReflectionClass( $this ); $property = $reflection->getProperty( $name ); return $property->getValue( $this ); } /* ### MAGIC FUNCTIONS # ######################################### # */ public function __construct( $filename ) { $this->filename = $filename; } public function &__get( string $name ) { $function = "get" . ucfirst( $name ); if( method_exists( $this, $function ) ) return $this->{$function}(); if( property_exists( $this, $name ) ) return $this->getReflectedValue( $name ); if( method_exists( $this, $name ) ) return $this->{$name}(); if( isset( $this->ARGS[$name] ) ) return $this->ARGS[$name]; if( isset( $this->NODES[$name] ) ) return $this->NODES[$name]; if( $this instanceof \Iterator ) return $this->values()[ $name ] ?? NULL; return NULL; } } /** Datei: library/ARUNOX/API/Method.php **/ [ "label" => "GET", "color" => REST\Generator\Config\Color::Primary, "icon" => "bi bi-telephone-forward" ], "PUT" => [ "label" => "PUT", "color" => REST\Generator\Config\Color::Primary, "icon" => "bi bi-telephone-forward" ], "POST" => [ "label" => "POST", "color" => REST\Generator\Config\Color::Primary, "icon" => "bi bi-telephone-forward" ], "PATCH" => [ "label" => "PATCH", "color" => REST\Generator\Config\Color::Primary, "icon" => "bi bi-telephone-forward" ], "DELETE" => [ "label" => "DELETE", "color" => REST\Generator\Config\Color::Primary, "icon" => "bi bi-telephone-forward" ], ]; /* ### PUBLIC FUNCTIONS ######################################### # */ } /** Datei: library/ARUNOX/API/BaseController.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\API; use ARUNOX\HTTP; use ARUNOX\Core\Hooks; use ARUNOX\Core\Errors; use ARUNOX\Database\BaseEntity; use ARUNOX\Plugins\REST; use ARUNOX\Plugins\REST\Generator\Base\BaseGenerator; #[PHP\Attributes\Controller\Label( "Eintrag" )] #[PHP\Attributes\Controller\Notifications( "notfound", '{$LABEL.singular} nicht gefunden' )] #[PHP\Attributes\Controller\Notifications( "saved", '{$LABEL.singular} wurde erfolgreich gespeichert' )] #[PHP\Attributes\Controller\Notifications( "deleted", '{$LABEL.singular} wurde erfolgreich gelöscht' )] #[PHP\Attributes\Controller\Notifications( "created", '{$LABEL.singular} wurde erfolgreich erstellt' )] #[PHP\Attributes\Controller\Notifications( "statuschanged.0", '{$LABEL.singular} wurde erfolgreich deaktiviert' )] #[PHP\Attributes\Controller\Notifications( "statuschanged.1", '{$LABEL.singular} wurde erfolgreich aktiviert' )] #[PHP\Attributes\Controller\Notifications( "notcreated", '{$LABEL.singular} konnte nicht erstellt werden' )] #[PHP\Attributes\Controller\Notifications( "notadded", '{$LABEL.singular} konnte nicht hinzugefügt werden' )] #[PHP\Attributes\Controller\Notifications( "notsaved", '{$LABEL.singular} konnte nicht gespeichert werden' )] class BaseController { use PHP\Attributes\Helper; use PHP\Attributes\Magic\Call\Helper; use PHP\Attributes\Controller\Route\Helper; use PHP\Attributes\Controller\Notifications\Helper; /* ### VARIABLES ################################################ # */ protected static string $PLUGIN = ""; protected HTTP\Request $request; protected HTTP\Response $response; protected array $class; /* ### PUBLIC FUNCTIONS ######################################### # */ public function jsonResponse( array|BaseEntity|BaseGenerator|\stdClass $data, int $status = 200 ) : HTTP\Response { $this->response ->withHeader('Content-Type', 'application/json') ->withStatus( $status ) ->withBody( $data ); return Hooks::filterWithNames("controller::response", $this->class, $this->response, $this->request ); } /* ### PUBLIC STATIC FUNCTIONS ################################## # */ public static function getPluginName( ?string $prefix = NULL ) { $pluginName = [ $prefix, static::$PLUGIN ]; $pluginName = array_filter( $pluginName ); $pluginName = implode( "/", $pluginName ); return $pluginName; } /* ################################################################ # */ /* ### PREPARED FUNCTIONS ####################################### # */ /* ################################################################ # */ /* ### PUBLIC GET FUNCTIONS ##################################### # */ public static function getAllPrepared() : array { return static::callModelClass( "getAll" ); } public static function getByOriginPrepared( string $origin ) : array { return static::callModelClass( "getByOrigin", $origin ); } public static function getPrepared( string $hashid ) : BaseEntity | HTTP\Response { // show( static::callModelClass( "get", $hashid ) ); if( $result = static::callModelClass( "get", $hashid ) ) return $result; return REST\Response::autoResponse( 404, static::getNotify("notfound") ); } /* ### PUBLIC SAVE FUNCTIONS #################################### # */ public static function savePrepared( string $hashid, array $data ) : HTTP\Response { if( $result = static::callModelClass( "get", $hashid ) ) { if( $result->save( $data ) ) { return REST\Response::autoResponse( 200, static::getNotify("saved") ); } return REST\Response::autoResponse( 500, static::getNotify("notsaved") ); } return REST\Response::autoResponse( 404, static::getNotify("notfound") ); } public static function createPrepared( array $data ) : HTTP\Response { if( $hashid = static::callModelClass( "save", $data ) ) { $result = static::callModelClass( "get", $hashid ); return REST\Response::autoResponse( 200, static::getNotify("created"), [ "data" => $result ] ); } return REST\Response::autoResponse( 500, static::getNotify("notcreated") ); } public static function deletePrepared( string $hashid ) : HTTP\Response { if( static::callModelClass( "delete", $hashid ) ) { return REST\Response::autoResponse( 200, static::getNotify("deleted") ); } return REST\Response::autoResponse( 500, static::getNotify("notfound") ); } public static function togglePrepared( string $column, string $hashid ) : HTTP\Response { if( $result = static::callModelClass( "get", $hashid ) ) { $column = strtolower( $column ); $toggle = "toggle" . ucfirst( $column ); $result->{$toggle}(); $result->save(); return REST\Response::autoResponse( 200, static::getNotify("{$column}changed.{$result->{$column}->getValue()}" ), [ "data" => $result ], __( "Toggle %T", $column ) ); } return REST\Response::autoResponse( 404, static::getNotify("notfound") ); } public static function toggleStatusPrepared( string $hashid ) : HTTP\Response { return static::togglePrepared( "status", $hashid ); } public static function createByOriginPrepared( string $origin, array $data ) : HTTP\Response { if( $data["origin_id"] = static::callModelClass( "resolveHashIDPrefix", $origin, "id" ) ) { return static::createPrepared( $data ); } return REST\Response::autoResponse( 500, static::getNotify("notfound", "Termin") ); } /* ### PUBLIC REST RESPONSE FUNCTIONS ########################### # */ public static function restResponse( HTTP\Response $response, null|callable $success = NULL, bool|callable $error = FALSE ) : HTTP\Response { if( $response->getBody("message" ) ) { Errors::add(...[ "code" => $response->getStatus(), "title" => $response->getBody("title") ?? NULL, "message" => $response->getBody("message"), ]); } $status = $response->isStatus( 200 ); $error = $error === TRUE ? $success : $error; if( $status && is_callable( $success ) ) call_user_func( $success, $response ); if( !$status && is_callable( $error ) ) call_user_func( $error, $response ); if( Errors::has() ) { foreach( Errors::getAll() as $error ) { REST\Response::addNotify(...[ "type" => $error["color"], "message" => $error["message"], "title" => $error["title"], "icon" => $error["icon"], ]); } } return REST\Response::jsonResponse(); } public static function restResponseSavePrepared( string $hashid, array $data ) : HTTP\Response { $result = static::savePrepared( $hashid, $data ); if( $result->isStatus( 200 ) ) { $data = static::callModelClass( "get", $hashid ); REST\Response::closeModals(); REST\Response::addUpdater( [ "data/table/row", $hashid ], $data ); } return static::restResponse( $result ); } public static function restResponseDeletePrepared( string $hashid ) : HTTP\Response { REST\Response::closeModals(); $result = static::deletePrepared( $hashid ); if( $result->isStatus( 200 ) ) { REST\Response::redirect( static::getRoute() ); } return static::restResponse( $result ); } public static function restResponseTogglePrepared( string $column, string $hashid ) : HTTP\Response { $result = static::togglePrepared( $column, $hashid ); // REST\Response::addUpdater( [ "tr", $hashid ], $result->getBody("data"), false ); REST\Response::addUpdater( [ "tr", $hashid ], $result->getBody("data"), $result->isStatus( 200 ) ); return static::restResponse( $result ); } public static function restResponseToggleStatusPrepared( string $hashid ) : HTTP\Response { return static::restResponseTogglePrepared( "status", $hashid ); } /* ### PUBLIC REST CONFIG FUNCTIONS ############################# # */ public static function restConfigCreatePrepared() : REST\Generator\Base\BaseGenerator { return static::restConfigCreateByOriginPrepared( FALSE ); } public static function restConfigCreateByOriginPrepared( bool|string $origin = FALSE ) : REST\Generator\Base\BaseGenerator { $rest = REST\Response::addModal( "details", static::getSingular( "%t erstellen" ) ) ->get() ->setAction( static::getRoute(), "POST" ) ->addButton(...Hooks::filter( "rest.generator::button.type->submit", [], "Erstellen", static::getRoute() )) ->addCallable( [ static::class, "restConfigOrigin" ], $origin ) ->addCallable( [ static::class, "restConfigInputs" ], $origin ) ; // $rest->addChildren( static::restConfigOrigin( $origin ) ); // $rest->addChildren( static::restConfigInputs( $origin ) ); return $rest; } public static function restConfigAddPrepared() : REST\Generator\Base\BaseGenerator { return static::restConfigAddByOriginPrepared( FALSE ); } public static function restConfigAddByOriginPrepared( bool|string $origin = FALSE ) : REST\Generator\Base\BaseGenerator { $rest = REST\Response::addModal( "details", static::getSingular( "%t hinzufügen" ) ) ->get() ->setAction( static::getRoute( "1:{$origin}" ), "POST" ) ->addButton(...Hooks::filter( "rest.generator::button.type->submit", [], "Hinzufügen", static::getRoute() )) ; if( method_exists( static::class, "restConfigOrigin" ) ) { $rest->addChildren( static::restConfigOrigin( $origin ) ); } $rest->addChildren( static::restConfigInputs( $origin ) ); return $rest; } public static function restConfigDeletePrepared( string $hashid ) : REST\Generator\Base\BaseGenerator { return REST\Response::addModal( "details", static::getSingular( "%t löschen" ) ) ->setSize( REST\Wrapper\Modal\Size::sm ) ->get() ->setAction( static::getRoute( $hashid ), "DELETE" ) ->addText( "Möchtest du wirklich den Datensatz (#{$hashid}) löschen? " ) ->addButton(...Hooks::filter( "rest.generator::button.type->cancel", [], NULL, static::getRoute() )) ->addButton(...Hooks::filter( "rest.generator::button.type->delete", [], NULL, static::getRoute() )); } /* ### PUBLIC REST EXTENSION FUNCTIONS ########################## # */ public static function restConfigTabPrepared( REST\Generator\Base\BaseGenerator $rest, null|string $hashid = NULL ) : void { $rest->addTab( static::getLabelGroup(), static::getPlural() )->get() ->setIcon( static::getIcon() ) ->addContent( static::restConfigTable( $hashid ) ) ; } /* ### MAGIC FUNCTIONS ########################################## # */ public function __construct( HTTP\Request $request, HTTP\Response $response ) { $this->request = $request; $this->response = $response; $this->class = array_slice( explode('\\', strtolower( static::class ) ), 2, -3 ); Hooks::applyWithNames("controller::before", $this->class, $this->request ); } } /** Datei: library/ARUNOX/API/Filesystem/Filesystem.php **/ path, $rootUrl, $filename ); } public static function basename( string $filename ) : bool|string { return pathinfo( $filename, PATHINFO_BASENAME ); } public static function extension( string $filename ) : bool|string { return pathinfo( $filename, PATHINFO_EXTENSION ); } public static function filesize( string $filename ) : bool|string { return filesize( $filename ); } public static function filesizes( string $filename ) : array { $info = getimagesize( $filename ); return [ "w" => $info[0], "h" => $info[1] ]; } public static function filehash( string $filename ) : bool|string { return hash_file("sha256", $filename ); } public static function created( string $filename ) : int { $stat = stat( $filename ); return $stat['ctime'] ?? 0; } public static function modified( string $filename ) : int { $stat = stat( $filename ); return $stat['mtime'] ?? 0; } public static function permissions( string $filename ) : int { $stat = stat( $filename ); return $stat['mode'] ?? 0; } public static function upload( array $file, string $destination, null|string $filename = NULL ) : null|File { if( !isset( $file["tmp_name"], $file["name"]) || !is_uploaded_file( $file["tmp_name"] ) ) { return NULL; } $extension = static::extension( $file["name"] ); $filename = static::filename( $filename ?? $file["name"] ); $destination = rtrim( $destination, DIRECTORY_SEPARATOR ); $target = $destination . DIRECTORY_SEPARATOR . $filename . "." . $extension; if( !static::existsDir( $destination ) ) static::createDir( $destination ); // show( $filename, $extension, $destination, $target, "STOP "); if( move_uploaded_file( $file["tmp_name"], $target ) ) { return static::getFile( $target ); } return NULL; } /* ### PUBLIC STATIC DIRECTORY FUNCTIONS ######################## # */ public static function isDir( string $directory ) : bool { return is_dir( $directory ); } public static function existsDir( string $directory ) : bool { return file_exists( $directory ); } public static function createDir( string $directory, int $mode = 0777, bool $recursive = TRUE ) : bool { return @mkdir( $directory, $mode, $recursive ) !== FALSE; } public static function readDir( string $directory ) : array { $list = []; foreach( new DirectoryIterator( $directory ) as $item ) { if( !$item->isDot() ) { $list[] = $directory . DIRECTORY_SEPARATOR . $item->getFilename(); } } return $list; } public static function listDirs( string $directory, bool $recursive = FALSE ) : array { $list = []; foreach( new DirectoryIterator( $directory ) as $item ) { if( !$item->isDot() && $item->isDir()) { $list[] = $directory . DIRECTORY_SEPARATOR . $item->getFilename(); } } return $list; } public static function listFiles( string $directory, bool $recursive = FALSE ) : array { $list = []; foreach( new DirectoryIterator( $directory ) as $item ) { if( !$item->isDot() && $item->isFile() ) { $list[] = $directory . DIRECTORY_SEPARATOR . $item->getFilename(); } if( $recursive && !$item->isDot() && $item->isDir() ) { $files = static::listFiles( $directory . DIRECTORY_SEPARATOR . $item->getFilename(), TRUE ); array_push( $list, ...$files ); } } return $list; } public static function removeDir( string $directory ) : bool { return rmdir( $directory ); } public static function moveDir( string $directory, string $destination ) : bool { return rename( $directory, $destination ); } public static function copyDir( string $directory, string $destination ) : bool { return copy( $directory, $destination ); } public static function getDir( string $directory ) : null|Directory { return file_exists( $directory ) ? new Directory( realpath( $directory ), static::class ) : NULL; } public static function putDir( string $directory, int $mode = 0777, bool $recursive = TRUE ) : bool|Directory { if( static::createDir( $directory, $mode, $recursive ) !== FALSE ) { return static::getDir( $directory ); } return FALSE; } public static function dirname( string $directory ) : bool|string { return pathinfo( $directory, PATHINFO_DIRNAME ); } } /** Datei: library/ARUNOX/API/Filesystem/Directory.php **/ files(), $this->directories() ) ); } public function files( bool $recursive = FALSE ) : array { if( $this->files === NULL || $this->fileRecursive != $recursive ) { $this->fileRecursive = $recursive; $this->loadFiles( $recursive ); } return $this->files; } public function directories( bool $recursive = FALSE ) : array { if( $this->directories === NULL || $this->dirsRecursive != $recursive ) { $this->dirsRecursive = $recursive; $this->loadDirs( $recursive ); } return $this->directories; } /* ### PROTECTED FUNCTIONS ###################################### # */ protected function loadFiles( bool $recursive = FALSE ) : void { $this->files = $this->filesystem::listFiles( $this->path, $recursive ); foreach( $this->files as &$file ) { $file = $this->filesystem::getFile( $file ); } } protected function loadDirs( bool $recursive = FALSE ) : void { $this->directories = $this->filesystem::listDirs( $this->path, $recursive ); foreach( $this->directories as &$dir ) { $dir = $this->filesystem::getDir( $dir ); } } /* ### PUBLIC MAGIC FUNCTIONS ################################### # */ public function __construct( string $directory, string $filesystem ) { if( !class_exists( $filesystem ) ) throw new RuntimeException( "Filesystem `{$filesystem}` ist missing." ); if( !$filesystem::isDir( $directory ) ) throw new RuntimeException( "Directory `{$directory}` not found." ); $this->path = $directory; $this->filesystem = $filesystem; $this->name = $filesystem::basename( $directory ); } public function __get( string $name ) { if( method_exists( $this, $name ) ) { return $this->{$name}(); } } public function __toString() : string { return $this->path; } public static function __callStatic( string $name, array $args ) { if( method_exists( Filesystem::class, "{$name}Dir" ) ) { return Filesystem::{"{$name}Dir"}( ...$args ); } } } /** Datei: library/ARUNOX/API/Filesystem/File.php **/ content === NULL ) { $this->content = $this->filesystem::readFile( $this->path ); } return $this->content; } public function versioned() : string { return "{$this->url}?_={$this->modified()}"; } public function size() : int { if( $this->size === NULL ) { $this->size = $this->filesystem::filesize( $this->path ); } return $this->size; } public function sizes() : array { if( $this->sizes === NULL ) { $this->sizes = $this->filesystem::filesizes( $this->path ); } return $this->sizes; } public function mime() : string { if( $this->mime === NULL ) { $this->mime = $this->filesystem::mimetype( $this->path ); } return $this->mime; } public function hash() : string { if( $this->hash === NULL ) { $this->hash = $this->filesystem::filehash( $this->path ); } return $this->hash; } public function created() : int { if( $this->created === NULL ) { $this->created = $this->filesystem::created( $this->path ); } return $this->created; } public function modified() : int { if( $this->modified === NULL ) { $this->modified = $this->filesystem::modified( $this->path ); } return $this->modified; } public function permissions() : int { if( $this->permissions === NULL ) { $this->permissions = $this->filesystem::permissions( $this->path ); } return $this->permissions; } /* ### PROTECTED FUNCTIONS ###################################### # */ /* ### PUBLIC MAGIC FUNCTIONS ################################### # */ public function __construct( string $filename, string $filesystem ) { if( !class_exists( $filesystem ) ) throw new RuntimeException( "Filesystem `{$filesystem}` ist missing." ); if( !$filesystem::isFile( $filename ) ) throw new RuntimeException( "File `{$filename}` not found." ); $this->path = $filename; $this->filesystem = $filesystem; $this->basename = $filesystem::basename( $filename ); $this->extension = $filesystem::extension( $filename ); $this->filename = $filesystem::filename( $filename ); $this->dirname = $filesystem::dirname( $filename ); $this->url = $filesystem::fileurl( $filename ); } public function __get( string $name ) { if( method_exists( $this, $name ) ) { return $this->{$name}(); } } } /** Datei: library/ARUNOX/API/API.php **/ * @copyright 2023 Sparky@ARUNOX * @version 1.0.0 * @since 1.0.0 File is created */ namespace ARUNOX; class API { /* ### VARIABLES ################################################ # */ private static $ERROR = NULL, $ERRNO = NULL, $RESULT = NULL, $RESPONSE = NULL; protected static $CONTENTTYPE = "application/json", $ACCEPTTYPE = "application/json", $AUTHTYPE = "Bearer", $AUTHTOKEN = NULL, $APIURL = NULL; /* ### PUBLIC FUNCTIONS ######################################### # */ public static function setApiUrl( string $APIURL ) : void { static::$APIURL = $APIURL; } public static function setAuthType( string $AUTHTYPE ) : void { static::$AUTHTYPE = $AUTHTYPE; } public static function setAuthToken( string $AUTHTOKEN ) : void { static::$AUTHTOKEN = $AUTHTOKEN; } public static function setAcceptType( string $ACCEPTTYPE ) : void { static::$ACCEPTTYPE = $ACCEPTTYPE; } public static function setContentType( string $CONTENTTYPE ) : void { static::$CONTENTTYPE = $CONTENTTYPE; } /* ### PUBLIC REQUEST FUNCTIONS ################################# # */ public static function get( string $url, string|array $args = [], array|string|bool $headers = [] ) { return static::request( "GET", $url, $args, $headers ); } public static function post( string $url, string|array $args = [], array|string|bool $headers = [] ) { return static::request( "POST", $url, $args, $headers ); } public static function put( string $url, string|array $args = [], array|string|bool $headers = [] ) { return static::request( "PUT", $url, $args, $headers ); } public static function patch( string $url, string|array $args = [], array|string|bool $headers = [] ) { return static::request( "PATCH", $url, $args, $headers ); } public static function delete( string $url, string|array $args = [], array|string|bool $headers = [] ) { return static::request( "DELETE", $url, $args, $headers ); } public static function getResult() : string|array|object { return static::$RESULT; } public static function getResponse() : int { return static::$RESPONSE; } public static function hasError() : bool { return !!static::$ERROR; } public static function getError() : mixed { return static::$ERROR; } public static function getErrNo() : string { return static::$ERRNO; } public static function getApiUrl() { return static::$TOKEN; } public static function getAuthType() { return static::$AUTHTYPE; } public static function getAuthToken() { return static::$AUTHTOKEN; } public static function getContenType() { return static::$CONTENTTYPE; } public static function getAcceptType() { return static::$ACCEPTTYPE; } public static function hasApiUrl() { return !!static::getApiUrl(); } public static function hasAuth() { return !!static::getAuthType() && !!static::getAuthToken() ; } public static function hasAuthType() { return !!static::getAuthType(); } public static function hasAuthToken() { return !!static::getAuthToken(); } public static function hasContenType() { return !!static::getContenType(); } public static function hasAcceptType() { return !!static::getAcceptType(); } public static function parseUrl( string $url, string|array $args, string $method ) { if( !filter_var( $url, FILTER_VALIDATE_URL) ) { $url = static::$APIURL . "/" . trim( $url, "/" ); } if( $method == "GET" ) { if( is_array( $args ) ) { $args = static::parseArgs( $args, $method ); } $query = preg_match( "/\?/", $url ) ? "&" : "?"; $url = "{$url}{$query}{$args}"; } return $url; } protected static function parseArgs(string|array $args, string $method, array|bool|null $headers = NULL) { // JSON-Requests: Daten als JSON serialisieren if( $method != "GET" && isset( $headers["Content-Type"] ) && $headers["Content-Type"] === "application/json") { $jsonArgs = json_encode((array)$args); if ($jsonArgs === false) { throw new \Exception("JSON encoding failed: " . json_last_error_msg()); } return $jsonArgs; } // Datei-Uploads: Array unverändert zurückgeben if (self::containsFile($args)) { return $args; // multipart/form-data erfordert Rohdaten } // Standard: Query-String für GET oder andere Anfragen return http_build_query( (array)$args ); } /* ### PROTECTED FUNCTIONS ###################################### # */ protected static function setError( mixed $error ) { static::$ERROR = $error; } protected static function request( string $method, string $url, string|array $args = [], array|string|bool $headers = [] ) { $header = []; $headers = static::getHeaders( $headers, $header, $args ); $args = static::parseArgs( $args, $method, $headers ); $url = static::parseUrl( $url, $args, $method ); // show( $method, $url, $args, $headers, $header ); $curl = curl_init(); curl_setopt( $curl, CURLOPT_URL, $url ); curl_setopt( $curl, CURLOPT_CUSTOMREQUEST, $method ); curl_setopt( $curl, CURLOPT_SSL_VERIFYPEER, TRUE ); curl_setopt( $curl, CURLOPT_RETURNTRANSFER, TRUE ); curl_setopt( $curl, CURLOPT_FOLLOWLOCATION, TRUE ); curl_setopt( $curl, CURLOPT_AUTOREFERER, TRUE ); curl_setopt( $curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2 ); curl_setopt( $curl, CURLOPT_TIMEOUT, 10 ); if( $method != "GET" ) curl_setopt( $curl, CURLOPT_POSTFIELDS, $args ); if( $header ) curl_setopt( $curl, CURLOPT_HTTPHEADER, $header ); static::$RESULT = curl_exec( $curl ); static::$ERROR = curl_error( $curl ); static::$ERRNO = curl_errno( $curl ); static::$RESPONSE = curl_getinfo( $curl, CURLINFO_HTTP_CODE ); curl_close($curl); if( is_json( static::$RESULT ) ) { static::$RESULT = json_decode( static::$RESULT ); } return static::$RESULT; } protected static function getHeaders( array|string|bool $headers = [], array &$parsed = [], array|string|null $args = NULL ) { if( $headers === FALSE ) return NULL; if( is_json( $headers ) ) { $entries = json_decode( $headers, TRUE ); $headers = []; foreach( $entries as $header ) { $header = preg_split( "/:/", $header ); $header = array_map( "trim", $header ); $headers[ $header[0] ] = $header[1]; } } if( !is_array( $headers ) ) return NULL; if( !isset( $headers["Authorization"] ) && static::hasAuth() ) { $headers["Authorization"] = static::getAuthType() . " " . static::getAuthToken(); } if( !isset( $headers["Content-Type"] ) && static::hasContenType() && !self::containsFile($args) ) { $headers["Content-Type"] = static::getContenType(); } if( !isset( $headers["Accept"] ) && static::hasAcceptType() ) { $headers["Accept"] = static::getAcceptType(); } foreach( $headers as $name => &$header ) { $parsed[] = "{$name}: {$header}"; } return $headers; } protected static function containsFile( array|string|null $args ) : bool { if( is_array( $args ) ) { foreach( $args as $value ) { if( $value instanceof \CURLFile ) return true; } } return false; } /* ### PRIVATE FUNCTIONS ######################################## # */ /* ### MAGIC FUNCTIONS ########################################## # */ } /** Datei: library/ARUNOX/Migrations/BaseMigration.php **/ initTable(); } /** * Runs all pending migrations. */ public static function run(): void { $applied = self::$entity->getApplied(); $files = self::getFiles(); foreach ($files as $file) { $name = basename($file, '.php'); if (!in_array($name, $applied)) { self::execute($file, $name); } } } /** * Finds all migration files in the specified directories. * * @return array */ private static function getFiles(): array { $files = []; foreach (self::$directories as $dir) { if (is_dir($dir)) { $files = array_merge($files, glob($dir . '/*.php')); } } return $files; } /** * Executes a single migration file. * * @param string $file * @param string $name */ private static function execute(string $file, string $name): void { try { Database::begin(); require_once $file; if (!class_exists($name)) { throw new Exception("Migration class {$name} not found in {$file}"); } $migration = new $name(); if (!$migration instanceof Migration) { throw new Exception("Migration class {$name} must extend the abstract Migration class"); } $migration->up(); self::$entity->record($name); Database::commit(); echo "Migration {$name} executed successfully.\n"; } catch (Exception $e) { Database::rollback(); echo "Migration {$name} failed: " . $e->getMessage() . "\n"; } } } /** Datei: library/ARUNOX/Migrations/Entity.php **/ table} ( id INT AUTO_INCREMENT PRIMARY KEY, migration VARCHAR(255) NOT NULL UNIQUE, executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP )")->execute(); } /** * Retrieves the list of applied migrations. * * @return array */ public function getApplied(): array { return Database::query("SELECT migration FROM {$this->table}") ->fetchAll(\PDO::FETCH_COLUMN); } /** * Records a migration as executed. * * @param string $migration */ public function record(string $migration): void { Database::query("INSERT INTO {$this->table} (migration) VALUES (:migration)") ->bind(['migration' => $migration]) ->execute(); } } /** Datei: library/ARUNOX/Tools/Debug/Debug.php **/ * @copyright 2024 Sparky@ARUNOX * @version 1.0.0 * @since 1.0.0 File is created */ namespace ARUNOX\Tools; use DateTime; class Debug { /* ### VARIABLES ################################################ # */ protected static bool $STATUS = FALSE; protected static float $STARTTIME = 0; protected static array $LASTLOGTIME = []; protected static array $EXCLUDED_URLS = []; /* ### PRIVATE STATIC FUNCTIONS ################################# # */ private static function parseNames( null|string|array $names = NULL ) { $names = $names ?? "default"; $names = implode( ",", (array)$names ); $names = explode( ",", "{$names},all" ); $names = array_unique( $names ); $names = array_filter( $names ); return $names; } private static function parseEntry( array $entry ) : string { $durationAll = number_format( $entry["duration"]["all"], 3, ",", "." ); $durationLast = number_format( $entry["duration"]["last"], 3, ",", "." ); return "{$durationAll} » {$durationLast} » {$entry["text"]}"; } private static function parseUrl( null|string $url = NULL, null|string $method = NULL ) : string { $url ??= $_SERVER["REQUEST_URI"]; $method ??= $_SERVER["REQUEST_METHOD"]; return "{$method} » {$url}"; } /* ### PUBLIC STATIC FUNCTIONS ################################## # */ public static function setDebugging( bool $setting ) : void { static::$STATUS = $setting; } public static function startDebugging() : void { static::$STATUS = TRUE; } public static function stopDebugging() : void { static::$STATUS = FALSE; } public static function isDebugging() : bool { return static::$STATUS; } public static function addExcludedUrl( null|string $url = NULL, null|string $method = NULL ) : void { static::$EXCLUDED_URLS[] = static::parseUrl( $url, $method ); static::$EXCLUDED_URLS = array_filter( static::$EXCLUDED_URLS ); static::$EXCLUDED_URLS = array_unique( static::$EXCLUDED_URLS ); } public static function isUrlAllowed( null|string $url = NULL, null|string $method = NULL ) : bool { return !in_array( static::parseUrl( $url, $method ), static::$EXCLUDED_URLS ); } public static function setStartTime( null|float $startTime = NULL ) : float { static::$STARTTIME = $startTime ?? microtime( TRUE ); return static::setLastLogTime( NULL, static::$STARTTIME ); } public static function getStartTime( null|float $startTime = NULL ) : float { return $startTime ?? static::$STARTTIME ?? microtime( TRUE ); } public static function getDurationStart( null|float $startTime = NULL ) : float { return microtime(TRUE ) - static::getStartTime( $startTime ); } public static function setLastLogTime( null|string|array $names = NULL, null|float $lastLogTime = NULL ) : float { $time = $lastLogTime ?? microtime( TRUE ); $names = static::parseNames( $names ); foreach( $names as $name ) static::$LASTLOGTIME[ $name ] = $time; return $time; } public static function getLastLogTime( null|string|array $name = NULL, null|float $lastLogTime = NULL ) : float { $time = $lastLogTime ?? static::$LASTLOGTIME ?? microtime( TRUE ); $name = static::parseNames( $name ); $name = current( $name ); if( !key_exists( $name, static::$LASTLOGTIME ) ) { static::setLastLogTime( $name, static::getLastLogTime() ); } return static::$LASTLOGTIME[ $name ] ?? $time; } public static function getDurationLast( null|string|array $name = NULL, null|float $lastLogTime = NULL ) : float { return microtime(TRUE ) - static::getLastLogTime( $name, $lastLogTime ); } public static function log( string $text, null|string|array $names = NULL ) : void { if( !static::isDebugging() ) return; if( !static::isUrlAllowed() ) return; $names = static::parseNames( $names ); $url = static::parseUrl(); $start = date( "Y-m-d H:i:s", (int)static::getStartTime() ); $entry = [ "text" => $text, "logtime" => microtime( TRUE ), "duration" => [ "all" => static::getDurationStart(), "last" => static::getDurationLast( $names ), ], ]; foreach( $names as $name ) { static::setLastLogTime( $name ); $_SESSION["debug"]["log"][ $name ][ "{$start} - {$url}" ][] = $entry; } } public static function clearLog() : void { $_SESSION["debug"]["log"] = []; } public static function getLog( string $name ) : array { $name = static::parseNames( $name ); $name = current( $name ); return $_SESSION["debug"]["log"][ $name ] ?? []; } public static function showLog( $clear = FALSE, $name = "default", $exit = TRUE ) { if( empty( $_SESSION["debug"]["log"] ) ) return FALSE; if( key_exists( $name, $_SESSION["debug"]["log"] ) ) { $details = static::getLog( $name ); $details = array_map( fn( $group ) => implode( "\n", array_map( [ static::class, "parseEntry" ], $group ) ), $details ); show( "DEBUGGING", // "» " . str_replace( getcwd(), "", $debug["file"] ) . ":" . $debug["line"] . "", $details, "NEXT", FALSE ); } if( $clear ) static::clearLog(); if( $exit ) exit; } public static function logTime( $startTime, $echo = FALSE, $title = NULL ) { $duration = microtime(TRUE) - $startTime; $_SESSION["debug"]["logtime"][] = $duration; if( $echo ) { echo ""; } } public static function showLogTime( $title = NULL ) { static::logTime( StartTime, TRUE, $title ); } public static function getClassName( $class_or_object, $split_by = "\_-" ) { if( is_object( $class_or_object ) ) $class_name = get_class( $class_or_object ); elseif( is_string( $class_or_object ) ) $class_name = $class_or_object; if( !isset( $class_name ) ) return FALSE; $split_by = preg_quote( $split_by ); $class_name = preg_split( "/[{$split_by}]/", $class_name ); $class_name = array_map_ucwords( $class_name ); return $class_name; } public static function getClassTitle( $class_or_object, $namespace = "ARUNOX", $replace = "Module" ) { $class_name = static::getClassName( $class_or_object ); $replace = implode( "\\", [ $namespace, $replace ] ); $replace = preg_quote( $replace ); $title = preg_replace( "/{$replace}/", "", $class_name ); $title = trim( $title, "\\" ); $title = preg_replace( "/(\\\)/", " \\ ", $title ); return $title; } public static function getClassHook( $class_or_object, $namespace = "ARUNOX", $replace = "Module" ) { $class_name = static::getClassName( $class_or_object ); $replace = implode( "\\", [ $namespace, $replace ] ); $replace = preg_quote( $replace ); $hook = preg_replace( "/{$replace}/", "", $class_name ); $hook = trim( $hook, "\\" ); return $hook; } public static function getClassPath( $class_or_object, $namespace = "ARUNOX", $replace = "Module" ) { $hook = static::getClassHook( $class_or_object, $namespace, $replace ); $path = preg_replace( "/\\\\/", "/", "\\{$hook}" ); return $path; } public static function getClassUri( $class_or_object, $namespace = "ARUNOX", $replace = "Module" ) { $path = static::getClassPath( $class_or_object, $namespace, $replace ); $path = strtolower( $path ); return $path; } } /** Datei: library/ARUNOX/Database/BaseAttribute.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\Database; use JsonSerializable; use ARUNOX\Plugins\REST; abstract class BaseAttribute implements JsonSerializable { /* ### VARIABLES ################################################ # */ protected static ?string $TEXTDOMAIN = NULL; protected static ?string $DEFAULT_LABEL = NULL; protected static ?string $DEFAULT_ICON = NULL; protected static mixed $DEFAULT_COLOR = REST\Generator\Config\Color::Default; protected ?BaseEntity $parent = NULL; protected mixed $value = NULL; /* ### PUBLIC FUNCTIONS ######################################### # */ public function defaultColor() { return static::color(); } public function defaultLabel() { return static::label(); } public function defaultIcon() { return static::icon(); } public function is( mixed $value ) { return $this->value === $value; } public function getValue() : mixed { return $this->value; } public function setValue( mixed $value) : void { $this->value = $value; } public function getParent() : ?BaseEntity { return $this->parent; } public function setParent( BaseEntity $parent) : void { $this->parent = $parent; } public function jsonSerialize() : mixed { return $this->value; } /* ### PUBLIC STATIC FUNCTIONS ################################## # */ public static function color() { return static::$DEFAULT_COLOR; } public static function label() { return static::$DEFAULT_LABEL; } public static function icon() { return static::$DEFAULT_ICON; } public static function getConstant( mixed $value ) { return array_search( $value, static::getConstants() ); } public static function getConstants() { $class = get_called_class(); $object = new \ReflectionClass( $class ); return $object->getConstants(); } public static function getByConstant( ?string $name = NULL, mixed $default = NULL ) { if( $name === NULL ) return $default; $name = preg_replace( "/[\s_-]+/", "", $name ); $constants = static::getConstants(); $constants = array_change_key_case( $constants, CASE_LOWER ); return $constants[ strtolower( $name ) ] ?? $default; } /* ### MAGIC FUNCTIONS ########################################## # */ public function __construct( mixed $value, ?BaseEntity $parent = NULL ) { $this->value = $value; $this->parent = $parent; } public function __toString() : string { return $this->value; } public function __get( string $name ) { if( method_exists( $this, $name ) ) { return $this->{$name}(); } } } /** Datei: library/ARUNOX/Database/Attribute/Options.php **/ getName() ] = $attribute->get(); } unset( $option["attributes"] ); } $hookName = Hooks::parseName( get_called_class() ); $options = array_replace( $options, static::$OPTIONS ); $options = Hooks::filterWithNames( "database.attribute.options::getAll", $hookName, $options ); foreach( $options as $value => &$option ) { $option = array_merge( $option, [ "value" => $value ] ); $option = Hooks::filterWithNames( "database.attribute.options::get", $hookName, $option ); } // if( static::class == \ARUNOX\Plugins\Websites\Connections\Status::class ) { // show($options, FALSE); // } return $options; } /* ### MAGIC FUNCTIONS ########################################## # */ public function __construct( mixed $value, $parent = NULL ) { if( is_bool( $value ) ) $value = (int)$value; if( is_a( $value, self::class ) ) $value = $value->getValue(); parent::__construct( $value, $parent ); $options = static::getOptions(); if( !$options ) return; // if( !is_numeric($value) ) show( $value, get_class( $value ), self::class, FALSE ); if( !isset( $options[ $value ] ) ) return; $this->value = $value; $this->icon = $options[ $value ]["icon"]; $this->color = $options[ $value ]["color"]; $this->label = $options[ $value ]["label"]; foreach( $options[ $this->value ] as $key => $value ) { if( property_exists( $this, $key ) ) { $this->{$key} = $options[ $this->value ][ $key ]; } else { $this->dynamic[ $key ] = $options[ $this->value ][ $key ]; } } } #[PHP\Attributes\Magic\Call( "is([A-Z][a-zA-Z0-9]+)" )] public function handleOptionsIsCall( array $matches, ...$args ) { if( NULL !== $value = static::getByConstant( $matches[1] ) ) { return $this->is( $value ); } throw new PHP\Attributes\Magic\Call\Exceptions\PatternFailed(); } // #[PHP\Attributes\Magic\Call( "get([A-Z][a-zA-Z0-9]+)" )] // public static function handleOptionsGetCall( array $matches, ...$args ) { // return $matches[0]; // } // #[PHP\Attributes\Magic\Call( "get([A-Z][a-zA-Z0-9]+)" )] // public function handleOptionsGetCall( array $matches, ...$args ) { // $key = strtolower( $matches[1] ); // // if( property_exists( $this, $key ) ) return $this->{$key}; // if( key_exists( $key, $this->dynamic ) ) return $this->dynamic[ $key ]; // // throw new PHP\Attributes\Magic\Call\Exceptions\PatternFailed(); // } } /** Datei: library/ARUNOX/Database/Attribute/DateTime.php **/ format( $format ); } public function time( $format = FORMAT_TIME ) { return $this->format( $format ); } public function datetime( $format = FORMAT_DATETIME ) { return $this->format( $format ); } public function format( $format ) { return $this->value ? dateformat( $format, strtotime( $this->value ), FORMAT_TIMEZONE, FORMAT_LOCALE ) : NULL; } public function timestamp() { return $this->value ? strtotime( $this->value ) : NULL; } public function setTimestamp( $timestamp ) { return $this->value = dateformat( "Y-m-d H:i:s", $timestamp, FORMAT_TIMEZONE, FORMAT_LOCALE ); } public function addTimestamp( $timestamp ) { return $this->value = dateformat( "Y-m-d H:i:s", $this->timestamp() + $timestamp, FORMAT_TIMEZONE, FORMAT_LOCALE ); } /* ### MAGIC FUNCTIONS ########################################## # */ public function __construct( mixed $value, ?BaseEntity $parent = NULL ) { if( $value === TRUE ) $value = time(); if( is_string( $value ) ) $value = strtotime( $value ); if( is_numeric( $value ) ) $value = dateformat( "Y-m-d H:i:s", $value ); parent::__construct( $value, $parent ); } public function __toString() : string { return $this->datetime(); } } /** Datei: library/ARUNOX/Database/Attribute/File.php **/ __toString(); } /* ### MAGIC FUNCTIONS ########################################## # */ public function __construct( mixed $value, ?Database\BaseEntity $parent = NULL ) { if( API\Filesystem::isFile( STORAGE_DIR . $value ) ) { $value = API\Filesystem::getFile( STORAGE_DIR . $value ); } parent::__construct( $value, $parent ); } public function __toString() : string { return (string)$this->url; } public function __get( string $name ) { if( $this->value ) { return $this->value?->{$name} ?? NULL; } return parent::__get( $name ); } } /** Datei: library/ARUNOX/Database/Attribute/Status.php **/ isset( $item["toggle"] ) && $item["toggle"] ); $options = array_map( fn( $item ) => $item["name"], $options ); ksort( $options ); $this->value++; if( $this->value > array_key_last( $options ) ) { $this->value = array_key_first( $options ); } return $this->value; } } /** Datei: library/ARUNOX/Database/BaseModelWithMeta.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created * @since 1.1.0 Modified get() and delete() to accept string values */ namespace ARUNOX\Database; use ARUNOX\Database; abstract class BaseModelWithMeta extends BaseModel { /* ### PUBLIC STATIC FUNCTIONS ################################## # */ public static function hasMeta( ?string $meta_key = NULL ) : bool { if( !Database::existsTable( static::getTable( "meta" ) ) ) { return false; } if( $meta_key === null ) { return true; } return QueryBuilder::select(static::getTable( "meta" ), ["meta_key"], [ "meta_key" => $meta_key, ])->fetch() !== false; } public static function get( int|string|array $value, string|array $columns = ["*"] ) : BaseEntity|false { $record = parent::get( $value, $columns ); if( !in_array( "*", (array)$columns ) ) { return $record; } if( !$record || !static::hasMeta() ) { return $record; } $metaRecords = static::getAllMeta($record->{static::$PRIMARY_KEY}); foreach( $metaRecords as $key => $value ) { $record->__set( $key, $value ); } return $record; } public static function getAll( null|array|int|string $where = [], string|array $columns = ['*'] ) : array { $records = parent::getAll( $where, $columns ); if( !$records || !static::hasMeta() ) { return $records; } foreach( $records as $record ) { $metaRecords = static::getAllMeta( $record->{static::$PRIMARY_KEY} ); foreach( $metaRecords as $key => $value ) { $record->__set( $key, $value ); } } return $records; } public static function save( array $data ) : mixed { $columns = static::getColumns(); $columns = array_flip( $columns ); $mainData = array_intersect_key( $data, $columns ); $metaData = array_diff_key( $data, $columns ); $return = parent::save( $mainData ); $primid = parent::getValue( $return, static::$PRIMARY_KEY ); if( !empty( $metaData ) ) { // show( $id, $metaData ); static::saveAllMeta( $primid, $metaData ); } return $return; } public static function delete( int|string|array $value): int { // Hauptdatensatz löschen $deletedRows = parent::delete( $value ); // Meta-Daten löschen, falls die Tabelle existiert if( static::hasMeta() ) { if( is_array( $value ) ) { $value = $value["origin_id"]; } QueryBuilder::delete( static::getTable( "meta" ), [ 'origin_id' => $value, ])->execute(); } return $deletedRows; } /* ### PUBLIC STATIC META FUNCTIONS ############################# # */ public static function getMeta(int $origin, string $meta_key): mixed { $result = QueryBuilder::select( static::getTable( "meta" ), ['meta_value'], [ 'origin_id' => $origin, 'meta_key' => $meta_key, ])->fetch(); return $result['meta_value'] ?? null; } public static function getAllMeta(int $origin): array { $metaRecords = QueryBuilder::select( static::getTable( "meta" ), ['meta_key', 'meta_value'], [ 'origin_id' => $origin, ])->fetchAll(); return array_column($metaRecords, 'meta_value', 'meta_key'); } public static function saveMeta( int $origin, string $meta_key, mixed $meta_value ): void { if( $meta_value instanceof BaseAttribute ) { $meta_value = (string)$meta_value; } QueryBuilder::insert( static::getTable( "meta" ), [ 'origin_id' => $origin, 'meta_key' => $meta_key, 'meta_value' => is_scalar( $meta_value ) ? $meta_value : json_encode( $meta_value ), ])->onDuplicateKeyUpdate([ 'meta_value' => is_scalar( $meta_value ) ? $meta_value : json_encode( $meta_value ), ])->execute(); } public static function saveAllMeta(int $origin, array $meta_key_value): void { foreach( $meta_key_value as $key => $value ) { static::saveMeta( $origin, $key, $value ); } } public static function deleteMeta(int $origin, string $meta_key): void { QueryBuilder::delete( static::getTable( "meta" ), [ 'origin_id' => $origin, 'meta_key' => $meta_key, ])->execute(); } public static function deleteAllMeta(int $origin): void { QueryBuilder::delete( static::getTable( "meta" ), [ 'origin_id' => $origin, ])->execute(); } } /** Datei: library/ARUNOX/Database/QueryBuilder.php **/ type( "select" ) ->table( $table ) ->columns( (array)$columns ) ->where( $where ); } public static function insert( ?string $table = NULL, array $data = [] ): self { return (new self()) ->type( "insert" ) ->table( $table ) ->data( $data ); } public static function update( ?string $table = NULL, array $data = [], array $where = [] ): self { return (new self()) ->type( "update" ) ->table( $table ) ->data( $data ) ->where( $where ); } public static function delete( ?string $table = NULL, array $where = [] ): self { return (new self()) ->type( "delete" ) ->table( $table ) ->where( $where ); } public static function truncate( ?string $table = NULL ) : self { return (new self()) ->type( "truncate" ) ->table( $table ); } /* ### PUBLIC CHAIN FUNCTIONS ################################### # */ public function type( string $type ) : self { $this->type = strtolower( $type ); return $this; } public function table( ?string $table ) : self { if( $table ) { $this->table = $table; } return $this; } public function columns( array|string $columns = [] ) : self { if( $columns ) { $this->columns = explode( ",", implode( ",", (array)$columns ) ); } return $this; } public function data( ?array $data = [] ) : self { if( $data ) { foreach( $data as $name => $value ) { $this->addData( $name, $value ); } } return $this; } public function addData( string $name, mixed $value ) : self { $this->data[ $name ] = $value; $this->bindings[":{$name}"] = $value; return $this; } public function where( ?array $conditions = [] ) : self { if( $conditions ) { $this->where = Filters::buildWhereClause( $conditions, $this->bindings, $this->table, [], $this->database ); if( key_exists( "ORDERBY", $conditions ) ) { $this->addOrderBy( $conditions["ORDERBY"] ); } if( key_exists( "LIMIT", $conditions ) ) { $this->limit( $conditions["LIMIT"] ); } // if( $this->table == "logs" ) // show( $conditions, $this->where, $this->bindings, FALSE ); } return $this; } public function addWhere( array $conditions = [] ) : self { if( $conditions ) { $this->where = Filters::buildWhereClause( $conditions, $this->bindings, $this->table, $this->where, $this->database ); } return $this; } public function addOrderBy( array|string $orderby ) { $orderby = explode( ",", $orderby ); $orderby = array_map( fn( $item ) => trim( $item ), $orderby ); $orderby = array_filter( $orderby ); if( !$orderby ) return $this; $orderby = array_map( fn( $item ) => explode( " ", $item ), $orderby ); $orderby = array_map( fn( $item ) => array_pad( $item, 2, "asc" ), $orderby ); // show( $orderby ); foreach( $orderby as [ $column, $direction ] ) { $this->orderBy[] = implode(" ", [ $column, strtoupper( $direction ) ] ); } // show( $this->orderBy ); return $this; } public function orderBy( ?string $column, ?string $direction = 'ASC' ) : self { if( $column ) { $this->addOrderBy( $column, $direction ); } return $this; } public function limit( ?int $rowCount = NULL, ?int $offset = NULL ) : self { if( $rowCount ) { $this->limit = $rowCount; $this->offset = $offset; } return $this; } public function onDuplicateKeyUpdate( array $updateValues, array $exclude = [] ) : self { $updateParts = []; foreach ($updateValues as $key => $value) { if (in_array($key, $exclude)) continue; $updateParts[] = "{$key} = :update_{$key}"; $this->updateBindings[":update_{$key}"] = $value; } $this->bindings = array_merge($this->bindings, $this->updateBindings); // Binde Update-Werte $this->bindings[':onDuplicateKeyUpdate'] = implode(', ', $updateParts); // Speichere SQL-Teile in `data` return $this; } public function using( string $name ) : self { if( Connections::has( $name ) ) { $this->database = Connections::get( $name ); } return $this; } /* ### PUBLIC EXECUTE FUNCTIONS ################################# # */ public function execute() : mixed { try { $this->database->query( $this->getSql(), $this->parseBindung()->bindings ); if( $this->type === "insert" ) return $this->database->lastInsertId(); return TRUE; } catch( \Exception $e ) { return FALSE; } } public function fetch( string|bool $entityClass = TRUE ) { return $this->database->fetch( $this->getSql(), $this->bindings, $entityClass ); } public function fetchAll( string|bool $entityClass = TRUE ) : array|false|BaseEntity|\stdClass { return $this->database->fetchAll( $this->getSql(), $this->bindings, $entityClass ); } public function fetchColumn( string|bool $entityClass = TRUE ) : mixed { return $this->database->fetchColumn( $this->getSql(), $this->bindings ); } public function fetchColumns( string|bool $entityClass = TRUE ) : array|false|BaseEntity|\stdClass { return $this->database->fetchColumns( $this->getSql(), $this->bindings ); } /* ### PUBLIC GETTER FUNCTIONS ################################## # */ public function getBindings() : array { return $this->bindings; } public function getData( ?string $name = NULL ) : mixed { return $name ? $this->data[ $name ] ?? NULL : $this->data; } public function getSql(): string { $sql = ''; switch ($this->type) { case 'select': $columns = implode(', ', $this->columns ?: ['*']); $sql = "SELECT {$columns} FROM `{$this->table}`"; break; case 'insert': $columns = implode(', ', array_map( function( $item ) { return "`{$item}`"; }, array_keys( $this->data ) ) ); $placeholders = ':' . implode(', :', array_keys($this->data)); $sql = "INSERT INTO `{$this->table}` ({$columns}) VALUES ({$placeholders})"; // Falls onDuplicateKeyUpdate definiert wurde if (!empty($this->bindings[':onDuplicateKeyUpdate'])) { $sql .= " ON DUPLICATE KEY UPDATE {$this->bindings[':onDuplicateKeyUpdate']}"; unset( $this->bindings[':onDuplicateKeyUpdate'] ); } break; case 'update': $set = implode(', ', array_map(fn($key) => "`{$key}` = :{$key}", array_keys($this->data))); $sql = "UPDATE `{$this->table}` SET {$set}"; break; case 'delete': $sql = "DELETE FROM `{$this->table}`"; break; case 'truncate': $sql = "TRUNCATE TABLE `{$this->table}`"; break; } if (!empty($this->where)) { $sql .= ' WHERE ' . implode(' AND ', $this->where); } if ($this->type === 'select' && !empty( $this->orderBy ) ) { $sql .= " ORDER BY " . implode( ",", $this->orderBy ); } if ($this->type === 'select' && $this->limit !== null) { $sql .= $this->offset !== null ? " LIMIT {$this->offset}, {$this->limit}" : " LIMIT {$this->limit}"; } return $sql; } /* ### PRIVATE FUNCTIONS ######################################## # */ private function parseBindung() { foreach( $this->bindings as &$binding ) { if( is_array( $binding ) ) { $binding = json_encode( $binding ); } } return $this; } /* ### MAGIC FUNCTIONS ########################################## # */ public function __construct() { $this->database = Connections::current(); } public function __get( $name ) { if( property_exists( $this, $name ) ) return $this->{$name}; return NULL; } } /** Datei: library/ARUNOX/Database/Database.php **/ * @copyright 2025 ARUNOX * @version 1.0.0 * * @since 1.0.0 File is created */ namespace ARUNOX; use PDO; use PDOException; use ARUNOX\Database\BaseEntity; use ARUNOX\Database\Connections; use ARUNOX\Database\QueryBuilder; class Database { /* ### VARIABLES ################################################ # */ public ?PDO $connection = NULL; private array $config = []; /* ### PUBLIC STATIC FUNCTIONS ################################## # */ public static function connect( array $config ) : Database { return new self( $config ); } public static function parseSQLArgs( array $args, array &$params = [] ) : string { $query = QueryBuilder::select() ->table( $args['table'] ) ->columns( $args['fields'] ?? "*" ) ->where( $args['where'] ?? NULL ) ->orderBy( $args['orderby'] ?? NULL, $args['order'] ?? NULL ) ->limit( $args['limit'] ?? NULL ); $params = $query->getBindings(); $sql = $query->getSql(); return $sql; } public static function getDatabase() : array { $current = Connections::current(); try { $stmt = $current->query("SHOW DATABASES"); return $stmt->fetchAll( PDO::FETCH_COLUMN ); } catch (PDOException $e) { return false; } } public static function existsDatabase( string $database ) : bool { $current = Connections::current(); try { $stmt = $current->query("SHOW DATABASES LIKE :database", [ "database" => $database ]); return !!$stmt->fetch(); } catch (PDOException $e) { return false; } } /* ### PSEUDO STATIC FUNCTIONS ################################## # */ private function pseudoFetchColumn( string|array $sql, array $params = [] ) : mixed { $stmt = $this->query( $sql, $params ); return $stmt->fetchColumn(); } private function pseudoFetchColumns( string|array $sql, array $params = [] ) : array|false { $stmt = $this->query( $sql, $params ); return $stmt->fetchAll( PDO::FETCH_COLUMN ); } private function pseudoFetchAll( string|array $sql, array $params = [], string|bool $entityClass = FALSE ) : array|false { $stmt = $this->query( $sql, $params ); if( $entityClass === FALSE ) return $stmt->fetchAll( PDO::FETCH_ASSOC ); if( $entityClass === TRUE ) return $stmt->fetchAll( PDO::FETCH_OBJ ); return $stmt->fetchAll( PDO::FETCH_CLASS, $entityClass ); } private function pseudoFetch( string|array $sql, array $params = [], string|bool $entityClass = FALSE ) { $stmt = $this->query( $sql, $params ); if( $entityClass === FALSE ) return $stmt->fetch( PDO::FETCH_ASSOC ); if( $entityClass === TRUE ) return $stmt->fetch( PDO::FETCH_OBJ ); $stmt->setFetchMode(\PDO::FETCH_CLASS, $entityClass ); return $stmt->fetch(); } private function pseudoExistsTable( string $table ) : bool { try { $stmt = $this->query( "SHOW TABLES LIKE :table", [ "table" => $table ] ); return !!$stmt->fetch(); } catch (PDOException $e) { return false; } } private function pseudoGetTables( ?string $where = NULL) : array { try { if( $where ) { $stmt = $this->query( "SHOW TABLES LIKE :where", [ "where" => $where ] ); } else $stmt = $this->query( "SHOW TABLES" ); return $stmt->fetchAll( PDO::FETCH_COLUMN ); } catch (PDOException $e) { return false; } } private function pseudoExistsColumn( string $table, string $column ) : bool { try { $stmt = $this->query( " SELECT * FROM `information_schema`.`columns` WHERE `TABLE_SCHEMA` = :schema AND `TABLE_NAME` = :table AND `COLUMN_NAME` = :column ", [ "table" => $table, "column" => $column, "schema" => $this->getConfig("dbname"), ]); return !!$stmt->fetch(); } catch (PDOException $e) { return false; } } private function pseudoGetColumns( string $table ) : array { try { $stmt = $this->query( " SELECT `COLUMN_NAME` FROM information_schema.columns WHERE `TABLE_SCHEMA` = :schema AND `TABLE_NAME` = :table ", [ "table" => $table, "schema" => $this->getConfig("dbname"), ]); return $stmt->fetchAll( PDO::FETCH_COLUMN ); } catch (PDOException $e) { return false; } } private function pseudoGetSchema( string $table ) : array { $schema = [ 'columns' => [], 'primary' => [], 'indexes' => [], ]; // 🔹 1. Spalten lesen $columns = $this->fetchAll(" SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_DEFAULT, EXTRA FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = '{$table}' ORDER BY ORDINAL_POSITION "); foreach ($columns as $col) { $def = strtoupper($col['COLUMN_TYPE']); if ($col['IS_NULLABLE'] === 'NO') { $def .= ' NOT NULL'; } // DEFAULT-Wert if ($col['COLUMN_DEFAULT'] !== null) { if ($this->isSqlLiteral( $col['COLUMN_DEFAULT'] )) { $def .= " DEFAULT {$col['COLUMN_DEFAULT']}"; } else { $def .= " DEFAULT '" . addslashes($col['COLUMN_DEFAULT']) . "'"; } } // ON UPDATE / AUTO_INCREMENT etc. if (!empty($col['EXTRA'])) { $extra = strtoupper(str_replace('DEFAULT_GENERATED', '', $col['EXTRA'])); $def .= ' ' . trim($extra); } $schema['columns'][$col['COLUMN_NAME']] = trim($def); } // 🔹 2. Indexe + Primärschlüssel $indexes = $this->fetchAll(" SELECT INDEX_NAME, COLUMN_NAME, NON_UNIQUE FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = '{$table}' ORDER BY INDEX_NAME, SEQ_IN_INDEX "); foreach ($indexes as $idx) { if ($idx['INDEX_NAME'] === 'PRIMARY') { $schema['primary'][] = $idx['COLUMN_NAME']; } else { $schema['indexes'][$idx['INDEX_NAME']][] = $idx['COLUMN_NAME']; } } // 🔹 3. Engine + Charset $tableInfo = $this->fetchRow(" SELECT ENGINE, TABLE_COLLATION FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = '{$table}' "); if ($tableInfo) { $schema['engine'] = $tableInfo['ENGINE']; if (str_contains($tableInfo['TABLE_COLLATION'], '_')) { $schema['charset'] = explode('_', $tableInfo['TABLE_COLLATION'])[0]; } } return $schema; } private function pseudoInstallSchema( array $schema ) : bool { foreach( $schema as $table => &$definition) { if( $this->existsTable( $table ) ) continue; $definition = $this->createTable( $table, $definition ); } $schema = array_filter( $schema ); return !!$schema; } private function pseudoUpdateSchema( array $schema ) : bool { foreach( $schema as $table => &$definition) { if( !$this->existsTable( $table ) ) { $definition = $this->createTable( $table, $definition ); continue; } $definition = $this->updateTable( $table, $definition ); } $schema = array_filter( $schema ); return !!$schema; } private function pseudoCreateTable( string $table, array $schema ) : bool { if (empty($schema['columns'])) return false; try { $fields = []; foreach ($schema['columns'] as $name => $definition) { $fields[] = "`{$name}` {$definition}"; } if (!empty($schema['primary'])) { $columns = implode('`, `', $schema['primary']); $fields[] = "PRIMARY KEY (`{$columns}`)"; } if (!empty($schema['indexes'])) { foreach ($schema['indexes'] as $index => $cols) { $colList = implode('`, `', (array)$cols); $fields[] = "KEY `{$index}` (`{$colList}`)"; } } $fields = implode(",\n ", $fields ); $engine = $schema["engine"] ?? "InnoDB"; $charset = $schema["charset"] ?? "utf8mb4"; $this->query( " CREATE TABLE IF NOT EXISTS `{$table}` ( {$fields} ) ENGINE={$engine} DEFAULT CHARSET={$charset}; ")->execute(); return TRUE; } catch( \Throwable $e ) { return FALSE; } } private function pseudoUpdateTable( string $table, array $schema ) : bool { if (empty($schema['columns'])) return false; try { $current = $this->pseudoGetSchema($table); if (!$current || empty($current['columns'])) return false; $alters = []; // 🔍 Spalten prüfen foreach ($schema['columns'] as $col => $definition) { if (!isset($current['columns'][$col])) { // ➕ Spalte fehlt $alters[] = "ADD COLUMN `{$col}` {$definition}"; } elseif (trim(strtoupper($current['columns'][$col])) !== trim(strtoupper($definition))) { // 🔄 Spaltendefinition unterscheidet sich $alters[] = "MODIFY COLUMN `{$col}` {$definition}"; } } // 🔍 Primary Key prüfen if (!empty($schema['primary']) && $schema['primary'] !== $current['primary']) { $alters[] = "DROP PRIMARY KEY"; $alters[] = "ADD PRIMARY KEY (`" . implode('`, `', $schema['primary']) . "`)"; } // 🔍 Indexe prüfen (nur hinzufügen – kein remove) foreach ($schema['indexes'] as $key => $cols) { if (!isset($current['indexes'][$key])) { $colList = implode('`, `', (array)$cols); $alters[] = "ADD KEY `{$key}` (`{$colList}`)"; } } if (empty($alters)) return true; $this->query("ALTER TABLE `{$table}`\n" . implode(",\n", $alters) . ";")->execute(); return TRUE; } catch( \Throwable $e ) { return FALSE; } } private function isSqlLiteral( string $default ) : bool { static $keywords = [ 'CURRENT_TIMESTAMP', 'CURRENT_DATE', 'CURRENT_TIME', 'NOW()', 'SYSDATE()', 'NULL' ]; return in_array(strtoupper($default), $keywords, true); } /* ### PUBLIC FUNCTIONS ######################################### # */ public function query( string|array $sql, array $params = [] ) : \PDOStatement { if( is_array( $sql ) ) { $sql = static::parseSQLArgs( $sql, $params ); } $stmt = $this->connection->prepare( $sql ); $stmt->execute( $params ); return $stmt; } public function lastInsertId() : int { return $this->connection->lastInsertId(); } public function getConfig( ?string $name = NULL ) : string|array { return $name ? $this->config[ $name ] : $this->config; } /* ### MAGIC FUNCTIONS ########################################## # */ public function __construct( array $config, string $name = "default" ) { $dsn = "mysql:host={$config['host']};dbname={$config['dbname']};charset={$config['charset']}"; $this->config = $config; try { $this->connection = new PDO($dsn, $config['user'], $config['pass'], [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]); } catch (PDOException $e) { throw new PDOException('Database connection failed: ' . $e->getMessage()); } } public function __call( string $name, $args ) { if( method_exists( $this, "pseudo{$name}" ) ) { return $this->{"pseudo{$name}"}( ...$args ); } return NULL; } public static function __callStatic( string $name, $args ) { $current = Connections::current(); if( method_exists( $current, "pseudo{$name}" ) ) { return $current->{"pseudo{$name}"}( ...$args ); } return NULL; } } /** Datei: library/ARUNOX/Database/Filters.php **/ * @copyright 2025 ARUNOX * @version 1.0.0 * * @since 1.0.0 File is created */ namespace ARUNOX\Database; use ARUNOX\Database; class Filters { /** * Builds SQL WHERE clauses from the provided conditions array. * * @param array $conditions The conditions to build the WHERE clause. * @param array &$params The parameters for prepared statements. * @return array The list of SQL conditions. */ public static function buildWhereClause( array $conditions, array &$params, string $table, array $whereClauses = [], ?Database $database = NULL ): array { $columns = $database->getColumns( $table ) ?? []; // if( $table == "logs" ) show( $conditions ); foreach( $conditions as $key => $value ) { if( strpos( $key, "__" ) !== FALSE ) { [ $field, $operator ] = explode('__', $key); $whereClauses[] = match( strtoupper( $operator ) ) { "SQL" => self::handleSQLLogic( $field, $value, $params ), "DATE" => self::handleDateLogic( $field, $value, $params ), "TIME" => self::handleTimeLogic( $field, $value, $params ), "DATETIME" => self::handleDateTimeLogic( $field, $value, $params ), "JSON" => self::handleJsonLogic( $field, $value, $params) , "QUERY" => self::handleQueryLogic ($field, $value, $params ), "META" => self::handleMetaLogic( $field, $value, $params, $table ), default => self::handleDefaultLogic( $operator, $columns, $field, $value, $params ), }; continue; } if( is_array( $value ) ) { $relation = "AND" ; $subConds = &$value; if( isset( $subConds["RELATION"] ) ) { $relation = $subConds["RELATION"]; unset( $subConds["RELATION"] ); } if( isset( $subConds["CONDITIONS"] ) ) { $subConds = $subConds["CONDITIONS"]; } // show( $subConds, FALSE ); $relation = strtoupper( $relation ); $subConds = self::buildWhereClause( $subConds, $params, $table, [], $database ); $whereClauses[] = "(" . implode(" {$relation} ", $subConds ) . ")"; // show( $subConds, FALSE ); continue; } if( $columns && !in_array( $key, $columns ) ) continue; // show( $key, $value, $params, FALSE ); $paramKey = ":{$key}_" . count($params); $whereClauses[] = "{$key} = {$paramKey}"; $params[$paramKey] = $value; } return array_filter( $whereClauses, fn( $item ) => $item !== NULL ); } private static function handleMetaLogic( string $field, string $value, array &$params, string $table ) : string { $metaTable = "{$table}_meta"; // Haupttabelle bestimmen $paramKeyValue = ":meta_value_" . count($params); $paramKeyKey = ":meta_key_" . count($params); $params[$paramKeyKey] = str_replace('__meta', '', $field); // Meta-Schlüssel extrahieren $params[$paramKeyValue] = $value; return "EXISTS (SELECT 1 FROM {$metaTable} WHERE {$metaTable}.origin_id = {$table}.id AND {$metaTable}.meta_key = {$paramKeyKey} AND {$metaTable}.meta_value = {$paramKeyValue})"; } private static function handleDateLogic( string $field, array $value, array &$params ) : string { $clauses = []; foreach ($value as $datePart => $dateValue) { $negation = str_starts_with($datePart, '!'); $datePart = ltrim($datePart, '!'); $paramKey = ":{$field}_{$datePart}_" . count($params); $clause = "EXTRACT({$datePart} FROM {$field})"; $clause .= $negation ? " != {$paramKey}" : " = {$paramKey}"; $clauses[] = $clause; $params[$paramKey] = $dateValue; } return '(' . implode(' AND ', $clauses) . ')'; } private static function handleTimeLogic( string $field, array $value, array &$params ) : string { $clauses = []; foreach ($value as $timePart => $timeValue) { $negation = str_starts_with($timePart, '!'); $timePart = ltrim($timePart, '!'); $paramKey = ":{$field}_{$timePart}_" . count($params); $clause = "EXTRACT({$timePart} FROM {$field})"; $clause .= $negation ? " != {$paramKey}" : " = {$paramKey}"; $clauses[] = $clause; $params[$paramKey] = $timeValue; } return '(' . implode(' AND ', $clauses) . ')'; } private static function handleJsonLogic( string $field, mixed $value, array &$params ) : string { if( is_string( $value ) ) { return "JSON_CONTAINS( `{$field}`, '\"{$value}\"')"; } return "JSON_OVERLAPS( `{$field}`, '" . json_encode( $value ) . "')"; } private static function handleDateTimeLogic( string $field, array $value, array &$params ) : string { return self::handleDateLogic($field, $value, $params) . ' AND ' . self::handleTimeLogic($field, $value, $params); } private static function handleSQLLogic( string $field, string $sql, array &$params ) : string { return $sql; } private static function handleQueryLogic( string $field, array $value, array &$params ) : string { $joinTable = $value['table'] ?? null; $onCondition = $value['on'] ?? null; $queryConditions = $value['conditions'] ?? []; if (!$joinTable || !$onCondition) { throw new \InvalidArgumentException("Invalid query logic for {$field}."); } $subParams = []; $subClauses = self::buildWhereClause($queryConditions, $subParams, $joinTable ); $params = array_merge($params, $subParams); return "EXISTS (SELECT 1 FROM {$joinTable} WHERE {$onCondition} AND " . implode(' AND ', $subClauses) . ")"; } private static function handleDefaultLogic( string $operator, array $columns, string $field, mixed $value, array &$params ) : null|string { [ $name, $operator, $replace ] = self::mapOperator( $operator ); if( $replace ) { $key = preg_replace( "/__{$operator}/", "__{$name}", $key ); } // show( $key, $replace, $field, $value, $params, $name, $operator ); if( in_array( $operator, [ 'IN', 'NOT IN' ] ) ) { $value = (array)$value; } if( $columns && !in_array( $field, $columns ) ) return NULL; $clause = "{$field} {$operator} "; if( is_array( $value ) ) { $clause .= "( "; foreach( (array)$value as $subValue ) { $paramKey = ":{$field}_" . count($params); $params[$paramKey] = $subValue; $clause .= "{$paramKey}, "; } $clause = trim( $clause, ", " ); $clause .= " )"; } else { $paramKey = ":{$field}_" . count($params); $clause .= $paramKey; $params[$paramKey] = $value; } // if( $field == "date_create" ) show( $field, $value, $clause, $params, FALSE ); return $clause; } private static function mapOperator(string $operator) : array { $operator = strtolower( $operator ); $result = []; $operators = [ "in" => "IN", "not_in" => "NOT IN", "gt" => ">", "gte" => ">=", "lt" => "<", "lte" => "<=", "ne" => "!=", "not" => "!=", "like" => "LIKE", "not_like" => "NOT LIKE", "exists" => "EXISTS", "not_exists" => "NOT EXISTS", ]; if( key_exists( $operator, $operators ) ) { $result[] = $operator; $result[] = $operators[ $operator ]; $result[] = FALSE; } if( in_array( $operator, $operators ) ) { $result[] = array_search( $operator, $operators ); $result[] = $operator; $result[] = TRUE; } if( !$result ) { $result[] = ""; $result[] = "="; $result[] = FALSE; } return $result; } } /** Datei: library/ARUNOX/Database/Connections.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.1.0 Hinzufügen von 'switch' und merken der aktuellen Verbindung * @since 1.0.0 File is created */ namespace ARUNOX\Database; use PDOException; use ARUNOX\Database; class Connections { /* ### VARIABLES ################################################ # */ protected static array $CONNECTIONS = []; private static ?Database $CONNECTION = NULL; /* ### PUBLIC STATIC FUNCTIONS ################################## # */ public static function connect( array $config, string $name = "default", bool $reconnect = FALSE ) : Database { if( !$reconnect && isset( self::$CONNECTIONS[ $name ] ) ) { throw new PDOException("Connection '{$name}' already exists."); } self::$CONNECTIONS[ $name ] = Database::connect( array_merge( $config, [ "name" => $name ] ) ); return static::switch( $name ); } public static function switch( string $name = "default" ) : Database { return self::$CONNECTION = self::get( $name ); } public static function current() : Database { if( !isset( self::$CONNECTION ) ) { throw new PDOException("Current Connection not exists."); } return self::$CONNECTION; } public static function has( string $name ) : bool { return isset( self::$CONNECTIONS[ $name ] ); } public static function get( string $name ) : Database { if( !isset( self::$CONNECTIONS[ $name ] ) ) { throw new PDOException("Connection '{$name}' not found."); } return self::$CONNECTIONS[$name]; } public static function getAll() : array { return self::$CONNECTIONS; } public static function remove( string $name ) : void { unset( self::$CONNECTIONS[ $name ] ); } public static function clear() : void { self::$CONNECTIONS = []; } /* ### CHAT GPT FUNCTIONS ####################################### # */ } /** Datei: library/ARUNOX/Database/BaseEntity.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\Database; use ARUNOX\API\PHP; use JsonSerializable; abstract class BaseEntity implements JsonSerializable { use PHP\Traits\Helpers\Controller; use PHP\Attributes\Helper; use PHP\Attributes\Magic\Get\Helper; use PHP\Attributes\Magic\Set\Helper; use PHP\Attributes\Magic\Call\Helper; use PHP\Attributes\Database\Attributes\Helper; use PHP\Attributes\Database\Attributes\Resolve\Helper; /* ### VARIABLES ################################################ # */ protected array $attributes = []; protected static array $COMMANDS = [ "array" => [ "LENGTH" => "count" ], ]; /* ### PUBLIC STATIC FUNCTIONS ################################## # */ /* ### PUBLIC FUNCTIONS ######################################### # */ public function getHashID() : string { return static::callModelClass( "getHashID", $this->id ) ?? $this->id; } public function save( array $data = [] ): mixed { if( $data ) { foreach( $data as $key => $value ) { $this->{$key} = $value; } $primaryKey = static::callModelClass("getPrimaryKey"); if( !isset( $data[ $primaryKey ] ) ) { $data[ $primaryKey ] = $this->{$primaryKey}; } } else $data = json_decode( json_encode( $this ), TRUE ); return static::callModelClass( "save", $data ); } public function delete(): mixed { return static::callModelClass( "delete", $this->{static::callModelClass("getPrimaryKey")} ); } public function jsonSerialize() : array { return $this->toArray(); } public function toArray() : array { foreach( static::getAttributes() as $name => $void ) { $this->loadAttributeValue( $name ); } // if( $this->id == 31 ) show( $this->attributes ); return $this->attributes; } /* ### PRIVATE FUNCTIONS ######################################## # */ private function loadAttributeValue( string $name ) { if( !static::existsAttribute( $name ) ) return; $config = static::getAttribute( $name ); if( class_exists( $config ) ) return; if( method_exists( $this, "get{$config}" ) ) return $this->{$name} = $this->{"get{$config}"}(); if( isset( $this->attributes[ $name ] ) && $this->attributes[ $name ] !== NULL ) return; if( $this->isAttributeChain( $config ) ) { $this->{$name} = $this->loadAttributeChain( $config ); } } private function loadAttributeChain( string $chain ) { if( !$this->isAttributeChain( $chain ) ) return NULL; $segments = preg_split( "/(->|\.|\(\))/", $chain ); $attribute = array_shift( $segments ); $attribute = $this->{$attribute}; $callable = $segments && end( $segments ) == ""; $segments = array_filter( $segments, fn( $value ) => $value !== NULL ); // if( static::class == \ARUNOX\Plugins\Cards\Entity::class ) show( $segments, $callable, $attribute, "STOP" ); if( $callable ) { $callable = array_pop( $segments ); } // if( $attribute && $chain == "target.name" ) show( $chain, $segments, $attribute ); if( $attribute ) { // show( $chain, $segments, $attribute, FALSE ); foreach( $segments as $segment ) { $type = gettype( $attribute ); $method = static::$COMMANDS[ $type ][ $segment ] ?? NULL; if( is_callable( $method ) ) { return $method( $attribute ); } if( is_array( $attribute ) ) { $attribute = (object)$attribute; } // if( static::class == \ARUNOX\Plugins\Cards\Entity::class ) show( $segment, $type, $method ); // show( $segment, $attribute, FALSE ); if( property_exists( $attribute, $segment ) ) { // if( $attribute && $chain == "target.name" ) show("X"); $attribute = $attribute->{$segment}; continue; } if( method_exists( $attribute, "__get" ) ) { // if( $attribute && $chain == "target.name" ) show( $chain, $attribute, $attribute->{$segment}, "STOP" ); if( $attribute->{$segment} !== NULL ) { $attribute = $attribute->{$segment}; } } } // if( $attribute && $chain == "target.name" ) show("STOP"); if( $callable ) { if( method_exists( $attribute, $callable ) ) { $attribute = $attribute->{$callable}(); } } return $attribute; } return NULL; } private function isAttributeChain( string $chain ) { return preg_match( "/(->|\.|\(\))/", $chain ); } /* ### MAGIC FUNCTIONS ########################################## # */ public function __construct() {} #[PHP\Attributes\Magic\Get( fallback: TRUE )] public function getFromHashID( $name ) : mixed { $name = "{$name}_hid"; if( isset( $this->{$name} ) ) { if( $this->{$name} === NULL ) return NULL; return static::callModelClass( "resolveHashIDPrefix", $this->{$name} ); } throw new PHP\Attributes\Magic\Get\Exceptions\FallbackFailed(); } #[PHP\Attributes\Magic\Set( fallback: TRUE )] public function setToAttributes( $name, $value ) : void { if( static::existsAttribute( $name ) ) { if( class_exists( static::getAttribute( $name ) ) ) { $value = new (static::getAttribute( $name ))( $value ); } } if( method_exists( $this, "set{$name}" ) ) { $this->{"set{$name}"}( $value ); } else $this->attributes[ $name ] = $value; } #[PHP\Attributes\Magic\Get( fallback: TRUE )] public function getFromAttributes( $name ) : mixed { if( $this->isAttributeChain( $name ) ) { return $this->loadAttributeChain( $name ); } if( static::existsAttribute( $name ) ) { $this->loadAttributeValue( $name ); } if( key_exists( $name, $this->attributes ) ) { return $this->attributes[ $name ]; } if( method_exists( $this, "get{$name}" ) ) { // if( $name == "plugins" ) show($name, $this->attributes, FALSE); return $this->attributes[ $name ] = $this->{"get{$name}"}(); } $alternate = str_replace( "_", "", $name ); if( method_exists( $this, "get{$alternate}" ) ) { return $this->attributes[ $name ] = $this->{"get{$alternate}"}(); } return NULL; } public function __isset( $name ) : bool { return key_exists( $name, $this->attributes ); } } /** Datei: library/ARUNOX/Database/BaseModel.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created * @since 1.1.0 Modified get() and delete() to accept string values */ namespace ARUNOX\Database; use ARUNOX\API; use ARUNOX\Database; use ARUNOX\Core\Hooks; abstract class BaseModel { use API\PHP\Attributes\Helper; use API\PHP\Attributes\Magic\Call\Helper; use API\PHP\Traits\Helpers\Controller; /* ### VARIABLES ################################################ # */ protected static ?string $PRIMARY_KEY = "id"; /* ### PUBLIC STATIC FUNCTIONS ################################## # */ public static function get( int|string|array $value, string|array $columns = ["*"] ) : array|false|BaseEntity { if( is_numeric( $value ) ) $value = (int)$value; if( is_string( $value ) ) $value = static::resolveHashID( $value ); if( is_numeric( $value ) ) $value = [ static::$PRIMARY_KEY => (int)$value ]; if( $value === FALSE ) return FALSE; // if( static::getTable() == "projects_types" ) show( $value, is_numeric( $value ), $columns ); // show( static::class, static::getTable(), $columns ); $query = QueryBuilder::select( static::getTable(), $columns )->where( $value ); $query = Hooks::filterWithNames( "model::query", static::getTable(), $query, static::getTable() ); $query = Hooks::filterWithNames( "model::get->query", static::getTable(), $query, static::getTable() ); $result = $query->fetch( static::getEntityClass() ); $result = Hooks::filterWithNames( "model::fetched", static::getTable(), $result, static::getTable(), $query ); return $result; } private static function getRawData( array $where ) : array|false { return QueryBuilder::select( static::getTable() )->where( $where )->fetch( FALSE ); } public static function getAll( null|array|int|string $where = [], string|array $columns = ['*'] ) : array { if( $where === NULL ) $where = []; if( is_string( $where ) || is_numeric( $where ) ) { $where = [ static::$PRIMARY_KEY => $where ]; } $columns = (array)$columns; $columns = empty( $columns ) ? ["*"] : $columns; $columns = array_intersect( $columns, array_merge( static::getColumns(), ["*"] ) ); $columns[] = static::$PRIMARY_KEY; $query = QueryBuilder::select( static::getTable(), $columns, $where ); $query = Hooks::filterWithNames( "model::query", static::getTable(), $query, static::getTable() ); $query = Hooks::filterWithNames( "model::getall->query", static::getTable(), $query, static::getTable() ); $result = $query->fetchAll( static::getEntityClass() ); $result = Hooks::filterWithNames( "model::fetchedAll", static::getTable(), $result, static::getTable(), $query ); return $result; } public static function getCount( null|array|int|string $where = [], string|array $columns = ['*'] ) { return count( self::getAll( $where, $columns ) ); } public static function getValue( int|string|array $value, null|string $column = NULL ) : mixed { $column = $column ?? static::$PRIMARY_KEY; $result = static::get( $value, $column ); // if( static::getTable() == "agents" ) show( $result, $value, $column ); return $result?->{$column} ?? NULL; } public static function getAllValue( null|array|int|string $where = [], null|string $column = NULL ) : array { $column = $column ?? static::$PRIMARY_KEY; $where = $where ?? []; if( is_scalar( $where ) ) $where = [ static::$PRIMARY_KEY => $where ]; $query = QueryBuilder::select( static::getTable(), $column, $where ); $result = $query->fetchColumns(); return $result; } public static function getByColumn( string $column, int|string $value, string|array $columns = ["*"] ) : array|false|BaseEntity { return static::get([ $column => $value ]); } public static function getAllByColumn( string $column, $value, string|array $columns = ['*'] ) : array { return static::getAll([ $column => $value ], $columns ); } public static function getAsOptions( string|array $labelColumn = "name", null|string $valueColumn = NULL, array $excluded = [] ) : array { $entity = static::getEntityClass(); $status = $entity::getAttribute( "status" ); $where = $status ? [ "status" => $status::Active ] : []; return static::getAsOptionsArray( $where, $labelColumn, $valueColumn, $excluded ); } public static function getColumns() { return Database::getColumns( static::getTable() ); } public static function existsColumn( string $column ) { return Database::existsColumn( static::getTable(), $column ); } public static function getPrimaryKey() { return static::$PRIMARY_KEY; } public static function fileUplaodAll( array $data = [] ) : array { foreach( $_FILES as $column => $file ) { if( !$file["tmp_name"] ) continue; if( isset( $data[ $column ] ) ) { $current = STORAGE_DIR . $data[ $column ]; if( $current && API\Filesystem::existsFile( $current ) ) { API\Filesystem::deleteFile( $current ); } } $fileObject = API\Filesystem::upload( $file, UPLOADS_DIR . "/database/", guid() ); $data[ $column ] = $fileObject?->url ?? NULL; } return $data; } public static function save( array $data ) : mixed { $data = static::fileUplaodAll( $data ); $columns = static::getColumns(); $columns = array_flip( $columns ); $newData = array_intersect_key( $data, $columns ); $oldData = []; // if( static::getTable() == "pwa_images" ) show( "STOP", FALSE ); if( isset( $data[ static::$PRIMARY_KEY ] ) ) { $finalHook = "updated"; $queryHook = "update"; $where = [ static::$PRIMARY_KEY => $data[ static::$PRIMARY_KEY ] ]; $oldData = static::getRawData( $where ); $oldData = json_encode( $oldData ); $oldData = json_decode( $oldData, TRUE ); $oldData = array_intersect_key( $oldData, $newData ); // if( static::getTable() == "pwa_images" ) show( $oldData, FALSE ); $query = QueryBuilder::update( static::getTable(), $newData, $where ); } else { $finalHook = "created"; $queryHook = "insert"; $query = QueryBuilder::insert( static::getTable(), $newData ); // ->onDuplicateKeyUpdate([ static::$PRIMARY_KEY ]); } $query = Hooks::filterWithNames( "model::query", static::getTable(), $query, static::getTable() ); $query = Hooks::filterWithNames( "model::save->query", static::getTable(), $query, static::getTable() ); $query = Hooks::filterWithNames( "model::{$queryHook}->query", static::getTable(), $query, static::getTable() ); $result = $query->execute(); $changed = array_diff_assoc( $newData, $oldData ); // if( static::getTable() == "pwa_images" ) show( $query, FALSE ); if( $result === TRUE ) $result = $data[ static::$PRIMARY_KEY ]; $result = Hooks::filterWithNames( "model::{$finalHook}", static::getTable(), $result, static::getTable(), $query, $finalHook, $changed, static::class ); $result = Hooks::filterWithNames( "model::saved", static::getTable(), $result, static::getTable(), $query, $changed, static::class ); return $result; } public static function delete( int|string|array $value ) : int { if( is_string( $value ) ) $value = static::resolveHashID( $value ); if( $value === FALSE ) return FALSE; if( is_numeric( $value ) ) $value = [ static::$PRIMARY_KEY => (int)$value ]; // if( is_string( $value ) ) $value = [ "hashid" => $value ]; $query = QueryBuilder::delete( static::getTable(), $value ); $query = Hooks::filterWithNames( "model::query", static::getTable(), $query, static::getTable() ); $query = Hooks::filterWithNames( "model::delete->query", static::getTable(), $query, static::getTable() ); $result = $query->execute(); $result = Hooks::filterWithNames( "model::deleted", static::getTable(), $result, static::getTable(), $query ); return $result; } /* ### PROTECTED STATIC FUNCTIONS ############################### # */ protected static function getAsOptionsArray( string|array $where, string|array $labelColumn = "name", null|string $valueColumn = NULL, array $excluded = [], null|callable $walker = NULL ) : array { $labels = (array)$labelColumn; $options = []; $primary = $valueColumn ?? static::getPrimaryKey(); $results = static::getAll( $where ); foreach( $results as $item ) { if( in_array( $item->{$primary}, $excluded, ) ) continue; $optionLabel = []; foreach( $labels as $labelColumn ) { $optionLabel[] = $item->{$labelColumn}; } $option = [ "value" => $item->{$primary}, "label" => implode( " ", $optionLabel ), "icon" => $item->icon ?? $item->icon_name ?? NULL, "color" => $item->color ?? $item->icon_color ?? NULL, ]; if( is_callable( $walker ) ) { $option = call_user_func( $walker, $option, $item ); } $options[] = $option; } $options = array_column( $options, NULL, "value" ); $options = array_filter( $options, fn( $item ) => $item["label"] ); return $options; } } /** Datei: library/ARUNOX/Database/BaseEntityWithMeta.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\Database; abstract class BaseEntityWithMeta extends BaseEntity { /* ### PUBLIC FUNCTIONS ######################################### # */ /* ### PUBLIC META FUNCTIONS #################################### # */ /* ### MAGIC FUNCTIONS ########################################## # */ } /** Datei: library/ARUNOX/Interfaces/Supports/Toggle.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\Interfaces\Supports; interface Toggle { /* ### FUNCTIONS ################################################ # */ public function toggle() : mixed; } /** Datei: library/ARUNOX/Interfaces/Supports/AsOptions.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\Interfaces\Supports; interface AsOptions { /* ### FUNCTIONS ################################################ # */ public static function getAsOptions() : array; } /** Datei: library/ARUNOX/Autoloader.php **/ str_replace( "*", $path, $dir ), $dirs ); $dirs = array_map( fn( $dir ) => "{$dir}/library/ARUNOX/{$type}/{$name}", $dirs ); $dirs = array_map( fn( $dir ) => str_replace( "\\", "/", $dir ), $dirs ); // show( $dirs, $path, $class, $type ); $dirs = array_map( fn( $dir ) => realpath( $dir ), $dirs ); $dirs = array_filter( $dirs ); foreach( $dirs as $dir ) { $files = glob_recursive( "{$dir}/{$class}.php" ); // show( $files ); foreach( $files as &$file ) { $file = str_replace( "{$dir}/", "", $file ); $file = preg_replace( "/\.php$/", "", $file ); $file = explode( "/", $file ); $file = array_merge( array_slice( $file, 0, -2 ), array_unique( array_slice( $file, -2 ) ) ); // show( $file ); } usort( $files, fn( $a, $b ) => count( $a ) < count( $b ) ? -1 : 1 ); $current = current( $files ); if( !$current ) continue; $current = implode( "/", $current ); $current = "ARUNOX/{$type}/{$name}/{$current}"; $current = str_replace( "/", "\\", $current ); $current = preg_replace( '/\\\+/', '\\', $current ); if( class_exists( $current ) ) return $current; } return FALSE; } public static function exists( string $class, null|string $type = NULL, null|string $subclass = NULL ) : bool|string { $class = preg_split( "/\\\|_/", $class ); if( $class[0] == "arunox" ) array_shift( $class ); if( $type && $class[0] != $type ) array_unshift( $class, $type ); $class = array_map( "ucfirst", $class ); $class = implode( "\\", $class ); $class = "ARUNOX\\{$class}"; if( $subclass ) { $path = str_replace( "\\", DIRECTORY_SEPARATOR, $class ); $path = "*/{$path}/"; $files = glob_recursive( $path ); show( $files, $path, $class ); array_push( $class, ucfirst( $subclass ) ); } show( $class ); if( class_exists( $class ) ) return $class; return FALSE; } public static function register() : void { spl_autoload_register([__CLASS__, 'autoload']); // Add core, plugins, and themes to the search map self::addLocation( realpath( ROOT_DIR ) ); self::addLocation(realpath( PLUGINS_DIR ) . "/*" ); self::addLocation(realpath( THEMES_DIR ) . "/*" ); } public static function addLocation(string $baseDir) : void { self::$locationMap = array_merge( self::$locationMap, glob( rtrim( $baseDir, '/' ) . "/library" ) ); } public static function autoload( string $class ) : bool { $class = explode('\\', $class ); $name = end($class ); $path = implode('/', $class ); foreach( self::$locationMap as $baseDir ) { $file = "{$baseDir}/{$path}.php"; if( file_exists( $file ) ) { require_once $file; return TRUE; } } /* ### FALLBACK ############################################# # */ foreach( self::$locationMap as $baseDir ) { $file = "{$baseDir}/{$path}/{$name}.php"; if( file_exists( $file ) ) { require_once $file; return TRUE; } } return FALSE; } } Autoloader::register(); /** Datei: library/ARUNOX/Core/Cache.php **/ isFile() && filemtime( $f->getPathname() ) > $cacheTime ) { return FALSE; } } } elseif( is_file( $item ) && filemtime( $item ) > $cacheTime ) { return FALSE; } } return TRUE; } /* ### PUBLIC STATIC CACHE FUNCTIONS ############################ # */ public static function setData( string $key, mixed $data, string|array $name = "unknown" ) : bool { return static::saveFile( $key, serialize( $data ), $name ); } public static function getData( string $key, string|array $watchFilesOrDirs = "unknown" ) : mixed { if( $content = static::getFile( $key, $watchFilesOrDirs ) ) { return unserialize( $content ); } return NULL; } public static function clear( string $key, string|array $watchFilesOrDirs = "unknown" ) : bool { if( $filename = self::getFilename( $key, $watchFilesOrDirs ) ) { if( file_exists( $filename ) ) { return unlink( $filename ); } } return TRUE; } } /** Datei: library/ARUNOX/Core/Hooks.php **/ * @copyright 2025 ARUNOX * @version 1.0.0 * @since 1.0.0 File is created */ namespace ARUNOX\Core; use ARUNOX\API\PHP; use ARUNOX\Tools\Debug; class Hooks { /* ### VARIABLES ################################################ # */ private static array $HOOKIDS = []; private static array $HOOKS = []; private static array $REGEX = []; public static function registerNEW( $pluginName ) : bool { $root = PLUGINS_DIR . "/{$pluginName}/hooks"; $hooks = Cache::fetch( "hooks", function() use( $root ) { $hooks = []; if( !$root = realpath( $root ) ) return $hooks; $files = glob_recursive( "{$root}/*.php" ); foreach( $files as $filename ) { $files = Cache::fetch( "hooks", function() use( $filename ) { $phpfile = new PHP( $filename ); $comment = $phpfile->get( T_DOC_COMMENT ); $header = (array)$comment->data; if( key_exists( "hooks", $header ) ) { $files = []; $uses = $phpfile->getAll( T_USE ); $last = end( $uses ); $uses = implode( "", $uses ); $count = explode( "\n", $comment->text ); $count = count( $count ) + $comment->line; $lines = explode( "\n", $phpfile->content ); $lines = array_slice( $lines, $last->nextLine ?? ++$count ); $lines = implode( "\n", $lines ); $prio = $header["priority"] ?? 10; $params = $header["params"] ?? []; $params = array_keys( $params ); $params = implode( ", ", $params ); foreach( $header["hooks"] as $hook ) { $name = "{$filename}_{$hook}"; $file = Cache::getFilename( "hooks/files", $name ); if( $file == " /home2/www/b90/arunox/dev.arunox/tools/framework/storage/cache/hooks/files/3c93c461128506fac2a18d7009240cf8.cache.php" ) { show( $uses, $lines, "STOP" ); } $php = implode( "\n", [ 'get( T_DOC_COMMENT ); $header = (array)$comment->data; return $header; }, $filename ); $hooks[] = [ "filename" => $filename, "hooks" => $header["hooks"] ?? [], "params" => array_keys( $header["params"] ?? [] ), "priority" => $header["priority"] ?? 10, ]; } return $hooks; }, $root ); foreach( $hooks as $file ) { if( $file["hooks"] ) { foreach( $file["hooks"] as $hook ) { static::addFile( $hook, $file["filename"], $file["params"], $file["priority"] ); } continue; } @include_once $file["filename"]; } // show( $path, $hooks, false ); return !!$hooks; } public static function addFile( string $hook, string $file, array $params, int $priority = 10 ) : void { $params = array_filter( $params ); $params = array_values( $params ); $params = array_map( "trim", $params ); static::add( $hook, function( ...$ARGS ) use( $file, $params, $hook ) { foreach( $params as $index => &$name ) { $name = str_replace( "$", "", $name ); ${$name} = $ARGS[ $index ] ?? NULL; } // if( $hook != "database.attribute.options::get" ) echo "{$hook}
"; unset( $name ); return include $file; }, $priority, hashid( "HOOK-", $file ) ); } public static function add( string $name, callable|string $callback, int $priority = 10, string|bool|null $uuid = NULL ) : void { $name = strtolower( $name ); $uuid = $uuid === TRUE ? $name : $uuid; $hook = $name; if( is_string( $callback ) ) { if( preg_match( "/__file_([^|]*)(\|(.*))?/", $callback, $matches ) ) { $matches[3] = explode( ",", $matches[3] ); $matches[3] = array_filter( $matches[3] ); $matches[3] = array_values( $matches[3] ); $matches[3] = array_map( "trim", $matches[3] ); static::addFile( $name, $matches[1], $matches[3], $priority ); return; } else if( preg_match( "/__return_(.*)/", $callback, $matches ) ) { $callback = function() use( $matches ){ return match ( $matches[1] ) { "false" => FALSE, "true" => TRUE, "null" => NULL, default => $matches[1], }; }; } else if( preg_match( "/__push_(.*)/", $callback, $matches ) ) { $callback = function( $values ) use( $matches ){ $values[] = $matches[1]; return $values; }; } } $uuid = $uuid ?? uniqid_index( static::$HOOKIDS, "HOOK-" ); if( preg_match( '/\*/', $name ) ) { $name = preg_quote( $name ); $name = preg_replace( '/(\\\\\*)/', "([a-z0-9._-]+)", $name ); static::$REGEX[ $name ][ $priority ][ $uuid ] = $callback; static::$HOOKIDS[ $uuid ] = &static::$REGEX[ $name ][ $priority ][ $uuid ]; ksort(static::$REGEX[ $name ] ); return; } static::$HOOKS[ $name ][ $priority ][ $uuid ] = $callback; static::$HOOKIDS[ $uuid ] = &static::$HOOKS[ $name ][ $priority ][ $uuid ]; ksort(static::$HOOKS[ $name ] ); } public static function list( $name = NULL ) { $name = strtolower( $name ); if( $name ) return static::$HOOKS[ $name ]; else return array_keys( static::$HOOKS ); } public static function remove( $uuid ) { static::$HOOKIDS[ $uuid ] = NULL; } public static function exists( $uuid ) { return !!static::$HOOKIDS[ $uuid ]; } public static function apply(string $name, ...$args): void { $name = strtolower( $name ); Debug::log( $name, "hooks" ); if (!isset(self::$HOOKS[$name])) { return; } foreach( self::$HOOKS[$name] as $priority => $callbacks ) { foreach( $callbacks as $callback ) { if( is_callable( $callback ) ) { call_user_func_array($callback, $args); } } } } public static function applyWithNames( string $hook, array|string $names, ...$args) : void { static::apply( "{$hook}", ...$args ); // show("{$hook}", FALSE ); $name = []; foreach( (array)$names as $item ) { $name[] = $item; static::apply("{$hook}->" . implode( ".", $name ), ...$args ); // show("{$hook}->" . implode( ".", $name ), FALSE ); } } public static function filter( string $name, mixed $value = NULL, ...$args ) : mixed { $name = strtolower( $name ); Debug::log( $name, "hooks" ); if( static::$REGEX ) { foreach( static::$REGEX as $regex => $hooks ) { if( preg_match( "/{$regex}/", $name, $matches ) ) { array_shift( $matches ); foreach( $hooks as $priority => $callbacks ) { foreach( $callbacks as $callback ) { $value = call_user_func_array( $callback, array_merge( [ $value ], $args, [ $matches ] ) ); } } } } } if( !isset( self::$HOOKS[ $name ] ) ) { return $value; } // if( $name == "router::resolve->before" ) show( $name, self::$HOOKS[ $name ] ); foreach( self::$HOOKS[ $name ] as $priority => $callbacks ) { foreach( $callbacks as $callback ) { if( is_callable( $callback ) ) { $value = call_user_func_array( $callback, [ $value, ...$args ] ); } } } return $value; } public static function filterWithNames( string $hook, array|string $names, mixed $value, ...$args) : mixed { // if( $hook == "model::updated" ) show("{$hook}", $names, FALSE ); $value = static::filter("{$hook}", $value, ...$args ); $name = []; foreach( (array)$names as $item ) { $name[] = $item; // if( $hook == "model::updated" ) show("{$hook}->" . implode( ".", $name ), FALSE ); // show("{$hook}->" . implode( ".", $name ), FALSE ); $value = static::filter("{$hook}->" . implode( ".", $name ), $value, ...$args ); } return $value; } public static function parseName( string $name ) { if( class_exists( $name ) ) { $name = preg_replace( '/ARUNOX\\\[A-Z][a-z]+\\\/', "", $name ); } $name = strtolower( $name ); $name = preg_replace('/(\/\{[a-z_]+\})/', '', $name );; $name = preg_replace( '/(\/|\\\)/', ".", $name ); return $name; } } /** Datei: library/ARUNOX/Core/Errors/Errors.php **/ * @copyright 2025 ARUNOX * @version 1.0.0 * @since 1.0.0 File is created */ namespace ARUNOX\Core; class Errors { /* ### CONSTANTS ################################################ # */ /* ### VARIABLES ################################################ # */ protected static array $ERRORS = []; protected static array $CODES = [ "100" => "Continue", "200" => "OK", "208" => "Already Reported", "304" => "Not Modified", "400" => "Bad Request", "401" => "Unauthorized", "402" => "Payment Required", "403" => "Forbidden", "404" => "Not Found", "405" => "Method Not Allowed", "409" => "Conflict", "500" => "Internal Server Error", ]; /* ################################################################ # */ /* ### PUBLIC FUNCTIONS ######################################### # */ /* ################################################################ # */ public static function init() : void { if( !key_exists( "errors", $_SESSION ) ) { $_SESSION["errors"] = []; } static::$ERRORS = &$_SESSION["errors"]; } public static function reset( int $index = -1 ) : void { $index = static::getIndex( $index ); unset( static::$ERRORS[ $index ] ); } public static function resetAll() : void { static::$ERRORS = []; } public static function get( bool $reset = TRUE, int $index = -1 ) : array { $index = static::getIndex( $index ); $error = static::$ERRORS[ $index ]; if( $reset ) static::reset( $index ); return $error; } public static function getAll( bool $reset = TRUE ) : array { $errors = static::$ERRORS; if( $reset ) static::resetAll(); return $errors; } public static function getIndex( int $index = -1 ) : int { $errors = array_keys( static::$ERRORS ); $errors = array_slice( $errors, $index, 1 ); return current( $errors ); } public static function has( bool|string $includes = "all" ) : bool { if( $includes === "all" ) return !!static::$ERRORS; if( $includes === TRUE ) return !!array_filter( static::$ERRORS, fn( $item ) => $item["code"] < 300 ); if( $includes === FALSE ) return !!array_filter( static::$ERRORS, fn( $item ) => $item["code"] >= 300 ); if( is_numeric( $includes ) ) return !!array_filter( static::$ERRORS, fn( $item ) => $item["code"] == $includes ); if( preg_match( '/([0-9])([0-9]{2})(s|\+|-)/', $includes, $matches ) ) { if( $matches[3] == "s" ) $range = [ "start" => "{$matches[1]}00", "end" => "{$matches[1]}99" ]; if( $matches[3] == "+" ) $range = [ "start" => "{$matches[1]}00", "end" => "999" ]; if( isset( $range ) ) return !!array_filter( static::$ERRORS, fn( $item ) => $item["code"] >= $range["start"] && $item["code"] <= $range["end"] ); } return FALSE; } public static function add( int $code, null|string $message = NULL, null|string $title = NULL, null|string $icon = NULL, null|string $color = NULL ) { static::$ERRORS[] = static::parse(...[ "code" => $code, "message" => $message, "title" => $title, "icon" => $icon, "color" => $color, ]); } /* ### PARSING FUNCTIONS ####################################### # */ public static function parseCode( int $code, null|string $value = NULL ) { if( $code >= 100 ) $info = [ "color" => "primary", "beep" => "short", "title" => __( "Information" ), "icon" => "bi bi-info-lg" ]; if( $code >= 200 ) $info = [ "color" => "success", "beep" => "short", "title" => __( "Erfolg" ), "icon" => "bi bi-check-lg" ]; if( $code >= 300 ) $info = [ "color" => "warning", "beep" => "alert", "title" => __( "Warnung" ), "icon" => "bi bi-exclamation-lg" ]; if( $code >= 400 ) $info = [ "color" => "danger", "beep" => "alert", "title" => __( "Fehler" ), "icon" => "bi bi-shield-exclamation" ]; if( $code >= 500 ) $info = [ "color" => "danger", "beep" => "long", "title" => __( "Unbekannter Fehler" ), "icon" => "bi bi-exclamation-diamond" ]; $info["code"] = $code; $info["origin"] = static::$CODES[ $code ] ?? NULL; if( $value ) return $info[ $value ] ?? NULL; return $info; } /* ################################################################ # */ /* ### PROTECTED FUNCTIONS ###################################### # */ /* ################################################################ # */ protected static function parse( int $code, null|string $message = NULL, null|string $title = NULL, null|string $icon = NULL, null|string $color = NULL ) { return [ "code" => $code, "message" => __( $message ), "title" => __( $title ?? static::parseCode( $code, "title" )), "icon" => $icon, "color" => $color ?? static::parseCode( $code, "color" ), ]; } } /** Datei: library/ARUNOX/Core/Plugins/Plugins.php **/ $config ) { // static::checkDependencies( $plugin, $config->dependencies ); } } public static function checkDependencies( string $plugin, array $dependencies ) : bool { foreach( $dependencies as $dependency ) { if( !( static::$allPlugins[ $dependency ]->active ?? FALSE ) ) { throw new \Exception("Missing dependency: {$dependency} for plugin {$plugin}"); } } return TRUE; } public static function get( string $plugin, bool $force = FALSE ) : ?Entity { if( empty( static::$allPlugins ) || $force ) self::loadAll( $force ); return static::$allPlugins[ $plugin ] ?? NULL; } public static function getAll( bool $force = FALSE ) : array { if( empty( static::$allPlugins ) || $force ) self::loadAll( $force ); return static::$allPlugins; } public static function getActive( bool $force = FALSE ) : array { static::getAll( $force ); return array_filter( static::$allPlugins, function( $plugin ) { return !$plugin->ignore && $plugin->active; } ); } public static function getPreload( bool $force = FALSE ) : array { static::getAll( $force ); return array_filter( static::$allPlugins, function( $plugin ) { return !$plugin->ignore && $plugin->preload; } ); } } /** Datei: library/ARUNOX/Core/Plugins/Entity.php **/ active = TRUE; } public function deactivate() { $this->active = FALSE; } public function setStatus( int|bool $status ) { if( $status ) $this->activate(); else $this->deactivate(); } public function checkDependencies() : bool { return Plugins::checkDependencies( $this->name, $this->dependencies ); } /* ### PROTECTED FUNCTIONS ###################################### # */ protected function load( $filename ) { $phpfile = new PHP( $filename ); $comment = $phpfile->get( T_DOC_COMMENT ); $data = (array)$comment->data; return (object)$data; } /* ### MAGIC FUNCTIONS ########################################## # */ public function __construct( $filename ) { $header = $this->load( $filename ); foreach( $header as $name => $value ) { $this->{$name} = $value; } $this->path = dirname( $filename ); $this->id = basename( $this->path ); $this->label = $this->name; $this->name = strtolower( $this->name ?? "" ); $this->plugin = $this->name; $this->ignore = isset( $this->ignore ); $this->active = filter_var( $this->active, FILTER_VALIDATE_BOOLEAN ); $this->preload = filter_var( $this->preload, FILTER_VALIDATE_BOOLEAN ); $this->dependencies = array_filter( preg_split( "/,\s/", $this->dependencies ?? "" ) ); } } /** Datei: library/ARUNOX/Core/Auth.php **/ time(); } public static function createHash( string $value, string $salt = AUTH_HASH_SALT ) { return hash_hmac("sha256", strtolower( trim( $value ) ), $salt ); } } /** Datei: library/ARUNOX/Core/Launcher.php **/ path; $file = $ref->getFileName(); $name = str_replace( $root, "", $file ); $name = preg_replace( '/library\/.*/', "", $name ); $name = trim( $name, '/' ); return Directory::get( "{$root}/{$name}" ); } public static function getUrl() : null|string { $root = static::getRootUrl(); $name = static::getName(); return "{$root}/{$name}"; } public static function getName() : string { $dir = static::getDir(); return $dir->name; } /* ### PRIVATE FUNCTIONS ######################################## # */ /* ### MAGIC FUNCTIONS ########################################## # */ public function __get( string $name ) { if( method_exists( $this, "get{$name}" ) ) { return $this->{"get{$name}"}(); } } public function __toString() : string { return static::getDir(); } } /** Datei: library/ARUNOX/Core/Router.php **/ {$subClassName}", "registerAPIs" ); } static::$REGISTERED[] = $className; break 2; } } } } public static function handleRequest(Request $request) : void { $uri = $request->getUri(); $method = $request->getMethod(); foreach (self::$ROUTES as $route) { if ($method === $route['method'] && $uri === $route['path']) { call_user_func($route['action'], $request); return; } } http_response_code(404); echo json_encode(['error' => 'Route not found']); } public static function add( callable | array $action ) : array { if( !is_callable( $action ) ) return []; if( is_array( $action ) ) $ref = new \ReflectionMethod( ...$action ); else $ref = new \ReflectionFunction( $action ); if( $docComment = $ref->getDocComment() ) { $docComment = static::parseDocTags( $docComment ); $routePath = trim( $docComment["route"]["path"] ?? "", "/" ); $routePath = array_filter([ $docComment["internal"] ?? NULL, $routePath ]); $routePath = implode( "/", $routePath ); $routeData = [ "method" => $docComment["method"] ?? "GET", "path" => "/{$routePath}", "action" => $action, "config" => $docComment, ]; // if( preg_match( '/.*\/logs$/', $routeData["path"] ) ) show( $routeData, FALSE ); return static::addRoute( ...$routeData ); } } public static function addAll( string $class ) : array { $refClass = new \ReflectionClass( $class ); $routes = Cache::fetch( "routes", function() use( $refClass, $class ) { $methods = $refClass->getMethods(\ReflectionMethod::IS_STATIC | \ReflectionMethod::IS_PUBLIC ); $routes = []; foreach( $methods as $method ) { $doc = $method->getDocComment() ?: ''; if( !preg_match('/@route\.path\s+([^\s]+)/', $doc, $pathMatch ) ) continue; if( !preg_match('/@method\s+([A-Z]+)/', $doc, $httpMatch ) ) continue; $routes[] = static::add([ $class, $method->getName() ]); } return array_filter( $routes ); }, $refClass->getFileName() ); self::$ROUTES = array_merge( self::$ROUTES, $routes ); static::sortRoutes(); return $routes; } public static function any( string $path, callable | array $action ) : void { self::addRoute("*", $path, $action ); } public static function get( string $path, callable | array $action ) : void { self::addRoute("GET", $path, $action ); } public static function put( string $path, callable | array $action ) : void { self::addRoute("PUT", $path, $action ); } public static function post( string $path, callable | array $action ) : void { self::addRoute("POST", $path, $action ); } public static function patch( string $path, callable | array $action ) : void { self::addRoute("PATCH", $path, $action ); } public static function delete( string $path, callable | array $action ) : void { self::addRoute("DELETE", $path, $action ); } public static function getRoutes() { return self::$ROUTES; } public static function resolve( string $method, string $uri ) : array|bool{ $result = FALSE; $method = strtoupper( $method ); $uri = preg_replace( "/(\/api\/v[0-9]+)\/?/", "/", $uri ); $hook = trim( $uri, "/" ); $hook = preg_replace( "/\//", ".", $hook ); $hook = strtolower( "{$method}.{$hook}"); [ $uri, $method ] = Hooks::filterWithNames("router::resolve->before", $hook, [ $uri, $method ] ); // show( $uri, $method, self::$ROUTES ); foreach( self::$ROUTES as $route ) { if( !in_array( $route["method"], [ $method, "*" ] ) ) continue; if( !preg_match( "/^{$route["pattern"]}$/", $uri, $matches ) ) continue; if( $matches ) array_shift( $matches ); $route["matches"] = $matches; $result = $route; break; } return Hooks::filterWithNames("router::resolve->after", $hook, $result ); } public static function dispatch( string $method, string $uri, ?array $route = NULL ) : mixed { if( $route = $route ?? static::resolve( $method, $uri ) ) { if( !$route['isDynamic'] ) { $action = $route['action']; $request = new Request(); $response = new Response(); $data = $request->getBodyParams(); $arguments = []; Hooks::filterWithNames("router::dispatch->before", $route["hook"], $route, $request ); if( is_callable( $action ) ) { $arguments[] = $data; $arguments[] = $request; $arguments[] = $response; $result = call_user_func( $action, ...$arguments ); } else { $result = call_user_func( [ new $action[0]( $request, $response ), $action[1] ], $data ); } $route["request"] = $request; $route["response"] = $response; return Hooks::filterWithNames("router::dispatch->after", $route["hook"], $result ?? NULL, $route, $data ); } if( $route['isDynamic'] ) { if( $route["matches"] ) { $action = $route["action"]; $request = new Request(); $response = new Response(); $matches = $route["matches"]; if( is_array( $action ) && method_exists( $action[0], $action[1] ) ) { $refMethod = new \ReflectionMethod( $action[0], $action[1] ); $params = $refMethod->getParameters(); foreach( $params as $i => $param ) { if( !isset( $matches[ $i ] ) ) continue; $value = $matches[ $i ]; $type = $param->getType(); if( $type instanceof \ReflectionUnionType ) { $allowedTypes = array_map(fn($t) => $t->getName(), $type->getTypes()); } elseif( $type instanceof \ReflectionNamedType ) { $allowedTypes = [ $type->getName() ]; } else { $allowedTypes = []; } // Verhindere numerische Werte für string-typisierte IDs if( in_array("string", $allowedTypes ) && !in_array("int", $allowedTypes ) && is_numeric( $value ) ) { http_response_code(400); echo json_encode([ "error" => "Numeric ID not allowed here. Use hashid." ]); return null; } // Wenn int erwartet, aber String übergeben wurde if ( in_array("int", $allowedTypes ) && !in_array("string", $allowedTypes ) && is_string( $value ) && is_numeric( $value ) === false ) { http_response_code(400); echo json_encode([ "error" => "Invalid ID format. Expected numeric ID." ]); return null; } } } $matches[] = $data = $request->getBodyParams(); $matches[] = &$request; Hooks::applyWithNames("router::dispatch->before", $route["hook"], $route, $request ); $result = is_callable( $action ) ? call_user_func_array( $action, $matches ) : call_user_func_array([ new $action[0]( $request, $response ), $action[1] ], $matches ); $route["request"] = &$request; $route["response"] = &$response; return Hooks::filterWithNames("router::dispatch->after", $route["hook"], $result, $route, $data, ...$matches ); } } } // show( $method, $uri, $route, static::resolve( $method, $uri ), self::$ROUTES, "STOP" ); Hooks::apply('router::dispatch->error', $uri, $method ); http_response_code(404); return ['error' => 'Route not found']; } public static function sortRoutes( null|array $routes = NULL ) : array { $routes = $routes ?? self::$ROUTES; usort($routes, function( $a, $b ) { if( $a["isDynamic"] !== $b["isDynamic"] ) { return $a["isDynamic"] <=> $b["isDynamic"]; } $aPos = array_search( "{*}", explode( "/", $a["path"] ) ); $bPos = array_search( "{*}", explode( "/", $b["path"] ) ); $aPos = $aPos === FALSE ? PHP_INT_MAX : $aPos; $bPos = $bPos === FALSE ? PHP_INT_MAX : $bPos; if( $aPos !== $bPos ) { return $bPos <=> $aPos; } $aCount = substr_count( $a["path"], "{*}" ); $bCount = substr_count( $b["path"], "{*}" ); return $aCount <=> $bCount; }); return self::$ROUTES = $routes; } /* ### PRIVATE FUNCTIONS ######################################## # */ private static function addRoute( string $method, string $path, callable | array $action, array $config = [] ) : array { $route = static::parseRoute([ "method" => strtoupper($method), "path" => $path, "action" => Hooks::filter('router::addRoute->action', $action, $method, $path ), "config" => $config, ]); self::$ROUTES[] = $route; static::sortRoutes(); return $route; } private static function parseRoute( array $route ) : array { $route["method"] = strtoupper( $route["method"] ); if( preg_match( "/(\/api\/v[0-9]+)\/?(.*)/", $route["path"], $matches ) ) { $route["path"] = $matches[2]; } $route["path"] = trim( $route["path"], "/" ); $route["path"] = "/{$route["path"]}"; $route["isDynamic"] = preg_match_all('/(\{[^\}]+\}|\*)/', $route["path"] , $matches ); // $route["matches"] = $matches[0]; $route["pattern"] = $route["path"]; $route["pattern"] = preg_replace( '/\//', '\/', $route["pattern"] ); $route["pattern"] = preg_replace( '/\{\*\}|\*/', '(.*)', $route["pattern"] ); // $route["pattern"] = preg_replace( '/\{\*\}/', '([a-zA-Z0-9_-\]+)', $route["pattern"] ); $route["pattern"] = preg_replace( '/\{[^\}]+\}/', '([a-zA-Z0-9_-]+)', $route["pattern"] ); $route["hook"] = trim( $route["path"], "/" ); $route["hook"] = preg_replace( "/\//", ".", $route["hook"] ); $route["hook"] = strtolower( "{$route["method"]}.{$route["hook"]}"); // if( preg_match( '/.*\/logs$/', $route["path"] ) ) show( $route, FALSE ); return $route; } private static function parseDocTags( string $docComment ) : array { $tags = []; $lines = explode("\n", $docComment ); foreach( $lines as $line ) { $line = trim( $line, " \t\n\r\0\x0B*/" ); if( $line === "" ) continue; if( preg_match('/^@([a-zA-Z0-9_.-]+)(?:\s+(.*))?$/', $line, $match ) ) { $value = isset( $match[2] ) ? trim( $match[2] ) : true; $names = explode( ".", $match[1] ); $current = &$tags; foreach( $names as $name ) { if( !key_exists( $name, $current ) ) { $current[ $name ] = []; } $current = &$current[ $name ]; } $current = $value; } } return $tags; } }