/** 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;
}
}
/** Datei: library/vendor/composer/installed.php **/
array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'reference' => '22d9bde0e93598d7cdec0bd8f5188e7f26c5be2e',
'name' => '__root__',
'dev' => true,
),
'versions' => array(
'__root__' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'reference' => '22d9bde0e93598d7cdec0bd8f5188e7f26c5be2e',
'dev_requirement' => false,
),
'hashids/hashids' => array(
'pretty_version' => '5.0.2',
'version' => '5.0.2.0',
'type' => 'library',
'install_path' => __DIR__ . '/../hashids/hashids',
'aliases' => array(),
'reference' => '197171016b77ddf14e259e186559152eb3f8cf33',
'dev_requirement' => false,
),
),
);
/** Datei: library/vendor/composer/ClassLoader.php **/
* Jordi Boggiano
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier
* @author Jordi Boggiano
* @see https://www.php-fig.org/psr/psr-0/
* @see https://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
/** @var ?string */
private $vendorDir;
// PSR-4
/**
* @var array[]
* @psalm-var array>
*/
private $prefixLengthsPsr4 = array();
/**
* @var array[]
* @psalm-var array>
*/
private $prefixDirsPsr4 = array();
/**
* @var array[]
* @psalm-var array
*/
private $fallbackDirsPsr4 = array();
// PSR-0
/**
* @var array[]
* @psalm-var array>
*/
private $prefixesPsr0 = array();
/**
* @var array[]
* @psalm-var array
*/
private $fallbackDirsPsr0 = array();
/** @var bool */
private $useIncludePath = false;
/**
* @var string[]
* @psalm-var array
*/
private $classMap = array();
/** @var bool */
private $classMapAuthoritative = false;
/**
* @var bool[]
* @psalm-var array
*/
private $missingClasses = array();
/** @var ?string */
private $apcuPrefix;
/**
* @var self[]
*/
private static $registeredLoaders = array();
/**
* @param ?string $vendorDir
*/
public function __construct($vendorDir = null)
{
$this->vendorDir = $vendorDir;
}
/**
* @return string[]
*/
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
}
return array();
}
/**
* @return array[]
* @psalm-return array>
*/
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
/**
* @return array[]
* @psalm-return array
*/
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
/**
* @return array[]
* @psalm-return array
*/
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
/**
* @return string[] Array of classname => path
* @psalm-var array
*/
public function getClassMap()
{
return $this->classMap;
}
/**
* @param string[] $classMap Class to filename map
* @psalm-param array $classMap
*
* @return void
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param string[]|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*
* @return void
*/
public function add($prefix, $paths, $prepend = false)
{
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
(array) $paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
(array) $paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
(array) $paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param string[]|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
(array) $paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
(array) $paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
(array) $paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param string[]|string $paths The PSR-0 base directories
*
* @return void
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param string[]|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*
* @return void
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*
* @return void
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*
* @return void
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*
* @return void
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
if (null === $this->vendorDir) {
return;
}
if ($prepend) {
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
} else {
unset(self::$registeredLoaders[$this->vendorDir]);
self::$registeredLoaders[$this->vendorDir] = $this;
}
}
/**
* Unregisters this instance as an autoloader.
*
* @return void
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
if (null !== $this->vendorDir) {
unset(self::$registeredLoaders[$this->vendorDir]);
}
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return true|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
includeFile($file);
return true;
}
return null;
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
/**
* Returns the currently registered loaders indexed by their corresponding vendor directories.
*
* @return self[]
*/
public static function getRegisteredLoaders()
{
return self::$registeredLoaders;
}
/**
* @param string $class
* @param string $ext
* @return string|false
*/
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
return false;
}
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*
* @param string $file
* @return void
* @private
*/
function includeFile($file)
{
include $file;
}
/** Datei: library/vendor/composer/autoload_real.php **/
= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
if ($useStaticLoader) {
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInitb7ad182a7d0eb255503e79bfdfb240f0::getInitializer($loader));
} else {
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
}
$map = require __DIR__ . '/autoload_psr4.php';
foreach ($map as $namespace => $path) {
$loader->setPsr4($namespace, $path);
}
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
}
$loader->register(true);
return $loader;
}
}
/** Datei: library/vendor/composer/autoload_psr4.php **/
array($vendorDir . '/hashids/hashids/src'),
);
/** Datei: library/vendor/composer/autoload_static.php **/
array (
'Hashids\\' => 8,
),
);
public static $prefixDirsPsr4 = array (
'Hashids\\' =>
array (
0 => __DIR__ . '/..' . '/hashids/hashids/src',
),
);
public static $classMap = array (
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInitb7ad182a7d0eb255503e79bfdfb240f0::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInitb7ad182a7d0eb255503e79bfdfb240f0::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInitb7ad182a7d0eb255503e79bfdfb240f0::$classMap;
}, null, ClassLoader::class);
}
}
/** Datei: library/vendor/composer/InstalledVersions.php **/
* Jordi Boggiano
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer;
use Composer\Autoload\ClassLoader;
use Composer\Semver\VersionParser;
/**
* This class is copied in every Composer installed project and available to all
*
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
*
* To require its presence, you can require `composer-runtime-api ^2.0`
*/
class InstalledVersions
{
/**
* @var mixed[]|null
* @psalm-var array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array}|array{}|null
*/
private static $installed;
/**
* @var bool|null
*/
private static $canGetVendors;
/**
* @var array[]
* @psalm-var array}>
*/
private static $installedByVendor = array();
/**
* Returns a list of all package names which are present, either by being installed, replaced or provided
*
* @return string[]
* @psalm-return list
*/
public static function getInstalledPackages()
{
$packages = array();
foreach (self::getInstalled() as $installed) {
$packages[] = array_keys($installed['versions']);
}
if (1 === \count($packages)) {
return $packages[0];
}
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
}
/**
* Returns a list of all package names with a specific type e.g. 'library'
*
* @param string $type
* @return string[]
* @psalm-return list
*/
public static function getInstalledPackagesByType($type)
{
$packagesByType = array();
foreach (self::getInstalled() as $installed) {
foreach ($installed['versions'] as $name => $package) {
if (isset($package['type']) && $package['type'] === $type) {
$packagesByType[] = $name;
}
}
}
return $packagesByType;
}
/**
* Checks whether the given package is installed
*
* This also returns true if the package name is provided or replaced by another package
*
* @param string $packageName
* @param bool $includeDevRequirements
* @return bool
*/
public static function isInstalled($packageName, $includeDevRequirements = true)
{
foreach (self::getInstalled() as $installed) {
if (isset($installed['versions'][$packageName])) {
return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']);
}
}
return false;
}
/**
* Checks whether the given package satisfies a version constraint
*
* e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
*
* Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
*
* @param VersionParser $parser Install composer/semver to have access to this class and functionality
* @param string $packageName
* @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
* @return bool
*/
public static function satisfies(VersionParser $parser, $packageName, $constraint)
{
$constraint = $parser->parseConstraints($constraint);
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
return $provided->matches($constraint);
}
/**
* Returns a version constraint representing all the range(s) which are installed for a given package
*
* It is easier to use this via isInstalled() with the $constraint argument if you need to check
* whether a given version of a package is installed, and not just whether it exists
*
* @param string $packageName
* @return string Version constraint usable with composer/semver
*/
public static function getVersionRanges($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
$ranges = array();
if (isset($installed['versions'][$packageName]['pretty_version'])) {
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
}
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
}
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
}
if (array_key_exists('provided', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
}
return implode(' || ', $ranges);
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['version'])) {
return null;
}
return $installed['versions'][$packageName]['version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getPrettyVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
return null;
}
return $installed['versions'][$packageName]['pretty_version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
*/
public static function getReference($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['reference'])) {
return null;
}
return $installed['versions'][$packageName]['reference'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
*/
public static function getInstallPath($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @return array
* @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}
*/
public static function getRootPackage()
{
$installed = self::getInstalled();
return $installed[0]['root'];
}
/**
* Returns the raw installed.php data for custom implementations
*
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
* @return array[]
* @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array}
*/
public static function getRawData()
{
@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = include __DIR__ . '/installed.php';
} else {
self::$installed = array();
}
}
return self::$installed;
}
/**
* Returns the raw data of all installed.php which are currently loaded for custom implementations
*
* @return array[]
* @psalm-return list}>
*/
public static function getAllRawData()
{
return self::getInstalled();
}
/**
* Lets you reload the static array from another file
*
* This is only useful for complex integrations in which a project needs to use
* this class but then also needs to execute another project's autoloader in process,
* and wants to ensure both projects have access to their version of installed.php.
*
* A typical case would be PHPUnit, where it would need to make sure it reads all
* the data it needs from this class, then call reload() with
* `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
* the project in which it runs can then also use this class safely, without
* interference between PHPUnit's dependencies and the project's dependencies.
*
* @param array[] $data A vendor/composer/installed.php data set
* @return void
*
* @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array} $data
*/
public static function reload($data)
{
self::$installed = $data;
self::$installedByVendor = array();
}
/**
* @return array[]
* @psalm-return list}>
*/
private static function getInstalled()
{
if (null === self::$canGetVendors) {
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
}
$installed = array();
if (self::$canGetVendors) {
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir];
} elseif (is_file($vendorDir.'/composer/installed.php')) {
$installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
self::$installed = $installed[count($installed) - 1];
}
}
}
}
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = require __DIR__ . '/installed.php';
} else {
self::$installed = array();
}
}
$installed[] = self::$installed;
return $installed;
}
}
/** Datei: library/vendor/composer/autoload_namespaces.php **/
$vendorDir . '/composer/InstalledVersions.php',
);
/** Datei: library/vendor/sqids/sqids/src/Math/MathInterface.php **/
0;
}
public function intval($a)
{
return gmp_intval($a);
}
public function strval($a)
{
return gmp_strval($a);
}
public function get($a)
{
return gmp_init($a);
}
}
/** Datei: library/vendor/sqids/sqids/src/Math/BCMath.php **/
0;
}
public function intval($a)
{
return intval($a);
}
public function strval($a)
{
return $a;
}
public function get($a)
{
return $a;
}
}
/** Datei: library/vendor/sqids/sqids/src/Sqids.php **/
'[i1]',
'o' => '[o0]',
'l' => '[l1]',
];
protected MathInterface $math;
protected ?string $blocklistRegex;
/** @throws InvalidArgumentException */
public function __construct(
protected string $alphabet = self::DEFAULT_ALPHABET,
protected int $minLength = self::DEFAULT_MIN_LENGTH,
protected array $blocklist = self::DEFAULT_BLOCKLIST,
) {
$this->math = $this->getMathExtension();
if ($alphabet == '') {
$alphabet = self::DEFAULT_ALPHABET;
}
if (mb_strlen($alphabet) != strlen($alphabet)) {
throw new InvalidArgumentException('Alphabet cannot contain multibyte characters');
}
if (strlen($alphabet) < 3) {
throw new InvalidArgumentException('Alphabet length must be at least 3');
}
if (preg_match('/(.).*\1/', $alphabet)) {
throw new InvalidArgumentException('Alphabet must contain unique characters');
}
if ($minLength < 0 || $minLength > self::MIN_LENGTH_LIMIT) {
throw new InvalidArgumentException('Minimum length has to be between 0 and ' . self::MIN_LENGTH_LIMIT);
}
$this->blocklistRegex = $this->buildBlocklistRegex();
$this->alphabet = $this->shuffle($alphabet);
}
/**
* Encodes an array of unsigned integers into an ID
*
* These are the cases where encoding might fail:
* - One of the numbers passed is smaller than 0 or greater than `maxValue()`
* - An n-number of attempts has been made to re-generated the ID, where n is alphabet length + 1
*
* @param array $numbers Non-negative integers to encode into an ID
* @return string Generated ID
*/
public function encode(array $numbers): string
{
if (count($numbers) == 0) {
return '';
}
foreach ($numbers as $n) {
if ($n < 0 || $n > self::maxValue()) {
throw new InvalidArgumentException(
'Encoding supports numbers between 0 and ' . self::maxValue(),
);
}
}
return $this->encodeNumbers($numbers);
}
/**
* Internal function that encodes an array of unsigned integers into an ID
*
* @param array $numbers Non-negative integers to encode into an ID
* @param int $increment An internal number used to modify the `offset` variable in order to re-generate the ID
* @return string Generated ID
*/
protected function encodeNumbers(array $numbers, int $increment = 0): string
{
if ($increment > strlen($this->alphabet)) {
throw new InvalidArgumentException('Reached max attempts to re-generate the ID');
}
$offset = count($numbers);
foreach ($numbers as $i => $v) {
$offset += ord($this->alphabet[$v % strlen($this->alphabet)]) + $i;
}
$offset %= strlen($this->alphabet);
$offset = ($offset + $increment) % strlen($this->alphabet);
$alphabet = substr($this->alphabet, $offset) . substr($this->alphabet, 0, $offset);
$prefix = $alphabet[0];
$alphabet = strrev($alphabet);
$id = $prefix;
for ($i = 0; $i != count($numbers); $i++) {
$num = $numbers[$i];
$id .= $this->toId($num, substr($alphabet, 1));
if ($i < count($numbers) - 1) {
$id .= $alphabet[0];
$alphabet = $this->shuffle($alphabet);
}
}
if ($this->minLength > strlen($id)) {
$id .= $alphabet[0];
while ($this->minLength - strlen($id) > 0) {
$alphabet = $this->shuffle($alphabet);
$id .= substr($alphabet, 0, min($this->minLength - strlen($id), strlen($this->alphabet)));
}
}
if ($this->isBlockedId($id)) {
$id = $this->encodeNumbers($numbers, $increment + 1);
}
return $id;
}
/**
* Decodes an ID back into an array of unsigned integers
*
* These are the cases where the return value might be an empty array:
* - Empty ID / empty string
* - Non-alphabet character is found within the ID
*
* @param string $id Encoded ID
* @return array Array of unsigned integers
*/
public function decode(string $id): array
{
$ret = [];
if ($id == '') {
return $ret;
}
if (!preg_match('/^[' . preg_quote($this->alphabet, '/') . ']+$/', $id)) {
return $ret;
}
$prefix = $id[0];
$offset = strpos($this->alphabet, $prefix);
$alphabet = substr($this->alphabet, $offset) . substr($this->alphabet, 0, $offset);
$alphabet = strrev($alphabet);
$id = substr($id, 1);
while (strlen($id) > 0) {
$separator = $alphabet[0];
$chunks = explode($separator, $id, 2);
if (array_key_exists(0, $chunks)) {
if ($chunks[0] == '') {
return $ret;
}
$ret[] = $this->toNumber($chunks[0], substr($alphabet, 1));
if (array_key_exists(1, $chunks)) {
$alphabet = $this->shuffle($alphabet);
}
}
$id = $chunks[1] ?? '';
}
return $ret;
}
protected function shuffle(string $alphabet): string
{
for ($i = 0, $j = strlen($alphabet) - 1; $j > 0; $i++, $j--) {
$r = ($i * $j + ord($alphabet[$i]) + ord($alphabet[$j])) % strlen($alphabet);
[$alphabet[$i], $alphabet[$r]] = [$alphabet[$r], $alphabet[$i]];
}
return $alphabet;
}
protected function toId(int $num, string $alphabet): string
{
$id = '';
do {
$id = $alphabet[$this->math->intval($this->math->mod($num, strlen($alphabet)))] . $id;
$num = $this->math->divide($num, strlen($alphabet));
} while ($this->math->greaterThan($num, 0));
return $id;
}
protected function toNumber(string $id, string $alphabet): int
{
$number = 0;
for ($i = 0; $i < strlen($id); $i++) {
$number = $this->math->add(
$this->math->multiply($number, strlen($alphabet)),
strpos($alphabet, $id[$i]),
);
}
return $this->math->intval($number);
}
protected function isBlockedId(string $id): bool
{
return $this->blocklistRegex !== null && preg_match($this->blocklistRegex, $id);
}
protected static function maxValue(): int
{
return PHP_INT_MAX;
}
/**
* Get BC Math or GMP extension.
* @throws \RuntimeException
*/
protected function getMathExtension(): MathInterface
{
if (extension_loaded('gmp')) {
return new Gmp();
}
if (extension_loaded('bcmath')) {
return new BCMath();
}
throw new RuntimeException('Missing math extension for Sqids, install either bcmath or gmp.');
}
protected function buildBlocklistRegex(): ?string
{
$wordsMatchingExactly = [];
$wordsMatchingBeginningOrEnd = [];
$wordMatchingAnywhere = [];
foreach ($this->blocklist as $word) {
$word = (string) $word;
if (strlen($word) <= 3) {
$wordsMatchingExactly[] = preg_quote($word, '/');
} else {
$word = preg_quote($word, '/');
$leet = strtr($word, self::LEET);
if (!preg_match('/\d/', $word)) {
$wordMatchingAnywhere[] = $word;
} elseif ($leet === $word) {
$wordsMatchingBeginningOrEnd[] = $word;
}
if ($leet !== $word) {
$wordsMatchingBeginningOrEnd[] = $leet;
}
}
}
$regexParts = [];
if ($wordsMatchingExactly) {
$regexParts[] = '^(' . implode('|', $wordsMatchingExactly) . ')$';
}
if ($wordsMatchingBeginningOrEnd) {
$regexParts[] = '^(' . implode('|', $wordsMatchingBeginningOrEnd) . ')';
$regexParts[] = '(' . implode('|', $wordsMatchingBeginningOrEnd) . ')$';
}
if ($wordMatchingAnywhere) {
$regexParts[] = '(' . implode('|', $wordMatchingAnywhere) . ')';
}
if ($regexParts) {
return '/(' . implode('|', $regexParts) . ')/i';
}
return null;
}
}
/** Datei: library/vendor/sqids/sqids/src/SqidsInterface.php **/
$numbers
* @return string
*/
public function encode(array $numbers): string;
/**
* Decode an ID back to integers.
* @param string $id
* @return array
*/
public function decode(string $id): array;
}
/** Datei: library/vendor/hashids/hashids/src/Math/MathInterface.php **/
0;
}
public function intval($a)
{
return gmp_intval($a);
}
public function strval($a)
{
return gmp_strval($a);
}
public function get($a)
{
return gmp_init($a);
}
}
/** Datei: library/vendor/hashids/hashids/src/Math/BCMath.php **/
0;
}
public function intval($a)
{
return intval($a);
}
public function strval($a)
{
return $a;
}
public function get($a)
{
return $a;
}
}
/** Datei: library/vendor/hashids/hashids/src/Hashids.php **/
salt = mb_convert_encoding($salt, 'UTF-8', mb_detect_encoding($salt));
$this->minHashLength = $minHashLength;
$alphabet = mb_convert_encoding($alphabet, 'UTF-8', mb_detect_encoding($alphabet));
$this->alphabet = implode('', array_unique($this->multiByteSplit($alphabet)));
$this->math = $this->getMathExtension();
if (mb_strlen($this->alphabet) < 16) {
throw new InvalidArgumentException('The Hashids alphabet must contain at least 16 unique characters.');
}
if (false !== mb_strpos($this->alphabet, ' ')) {
throw new InvalidArgumentException('The Hashids alphabet can\'t contain spaces.');
}
$alphabetArray = $this->multiByteSplit($this->alphabet);
$sepsArray = $this->multiByteSplit($this->seps);
$this->seps = implode('', array_intersect($sepsArray, $alphabetArray));
$this->alphabet = implode('', array_diff($alphabetArray, $sepsArray));
$this->seps = $this->shuffle($this->seps, $this->salt);
if (!$this->seps || (mb_strlen($this->alphabet) / mb_strlen($this->seps)) > self::SEP_DIV) {
$sepsLength = (int) ceil(mb_strlen($this->alphabet) / self::SEP_DIV);
if ($sepsLength > mb_strlen($this->seps)) {
$diff = $sepsLength - mb_strlen($this->seps);
$this->seps .= mb_substr($this->alphabet, 0, $diff);
$this->alphabet = mb_substr($this->alphabet, $diff);
}
}
$this->alphabet = $this->shuffle($this->alphabet, $this->salt);
$guardCount = (int) ceil(mb_strlen($this->alphabet) / self::GUARD_DIV);
if (mb_strlen($this->alphabet) < 3) {
$this->guards = mb_substr($this->seps, 0, $guardCount);
$this->seps = mb_substr($this->seps, $guardCount);
} else {
$this->guards = mb_substr($this->alphabet, 0, $guardCount);
$this->alphabet = mb_substr($this->alphabet, $guardCount);
}
}
public function encode(...$numbers): string
{
$ret = '';
if (1 === count($numbers) && is_array($numbers[0])) {
$numbers = $numbers[0];
}
if (!$numbers) {
return $ret;
}
foreach ($numbers as $number) {
$isNumber = ctype_digit((string) $number);
if (!$isNumber) {
return $ret;
}
}
$alphabet = $this->alphabet;
$numbersSize = count($numbers);
$numbersHashInt = 0;
foreach ($numbers as $i => $number) {
$numbersHashInt += $this->math->intval($this->math->mod($number, $i + 100));
}
$lottery = $ret = mb_substr($alphabet, $numbersHashInt % mb_strlen($alphabet), 1);
foreach ($numbers as $i => $number) {
$alphabet = $this->shuffle($alphabet, mb_substr($lottery . $this->salt . $alphabet, 0, mb_strlen($alphabet)));
$ret .= $last = $this->hash($number, $alphabet);
if ($i + 1 < $numbersSize) {
$number %= (mb_ord($last, 'UTF-8') + $i);
$sepsIndex = $this->math->intval($this->math->mod($number, mb_strlen($this->seps)));
$ret .= mb_substr($this->seps, $sepsIndex, 1);
}
}
if (mb_strlen($ret) < $this->minHashLength) {
$guardIndex = ($numbersHashInt + mb_ord(mb_substr($ret, 0, 1), 'UTF-8')) % mb_strlen($this->guards);
$guard = mb_substr($this->guards, $guardIndex, 1);
$ret = $guard . $ret;
if (mb_strlen($ret) < $this->minHashLength) {
$guardIndex = ($numbersHashInt + mb_ord(mb_substr($ret, 2, 1), 'UTF-8')) % mb_strlen($this->guards);
$guard = mb_substr($this->guards, $guardIndex, 1);
$ret .= $guard;
}
}
$halfLength = (int) (mb_strlen($alphabet) / 2);
while (mb_strlen($ret) < $this->minHashLength) {
$alphabet = $this->shuffle($alphabet, $alphabet);
$ret = mb_substr($alphabet, $halfLength) . $ret . mb_substr($alphabet, 0, $halfLength);
$excess = mb_strlen($ret) - $this->minHashLength;
if ($excess > 0) {
$ret = mb_substr($ret, (int) ($excess / 2), $this->minHashLength);
}
}
return $ret;
}
public function decode(string $hash): array
{
$ret = [];
if (!($hash = trim($hash))) {
return $ret;
}
$alphabet = $this->alphabet;
$hashBreakdown = str_replace($this->multiByteSplit($this->guards), ' ', $hash);
$hashArray = explode(' ', $hashBreakdown);
$i = 3 === count($hashArray) || 2 === count($hashArray) ? 1 : 0;
$hashBreakdown = $hashArray[$i];
if ('' !== $hashBreakdown) {
$lottery = mb_substr($hashBreakdown, 0, 1);
$hashBreakdown = mb_substr($hashBreakdown, 1);
$hashBreakdown = str_replace($this->multiByteSplit($this->seps), ' ', $hashBreakdown);
$hashArray = explode(' ', $hashBreakdown);
foreach ($hashArray as $subHash) {
$alphabet = $this->shuffle($alphabet, mb_substr($lottery . $this->salt . $alphabet, 0, mb_strlen($alphabet)));
$result = $this->unhash($subHash, $alphabet);
if ($this->math->greaterThan($result, PHP_INT_MAX)) {
$ret[] = $this->math->strval($result);
} else {
$ret[] = $this->math->intval($result);
}
}
if ($this->encode($ret) !== $hash) {
$ret = [];
}
}
return $ret;
}
public function encodeHex(string $str): string
{
if (!ctype_xdigit($str)) {
return '';
}
$numbers = trim(chunk_split($str, 12, ' '));
$numbers = explode(' ', $numbers);
foreach ($numbers as $i => $number) {
$numbers[$i] = hexdec('1' . $number);
}
return $this->encode(...$numbers);
}
public function decodeHex(string $hash): string
{
$ret = '';
$numbers = $this->decode($hash);
foreach ($numbers as $number) {
$ret .= mb_substr(dechex($number), 1);
}
return $ret;
}
/** Shuffle alphabet by given salt. */
protected function shuffle(string $alphabet, string $salt): string
{
$key = $alphabet . ' ' . $salt;
if (isset($this->shuffledAlphabets[$key])) {
return $this->shuffledAlphabets[$key];
}
$saltLength = mb_strlen($salt);
$saltArray = $this->multiByteSplit($salt);
if (!$saltLength) {
return $alphabet;
}
$alphabetArray = $this->multiByteSplit($alphabet);
for ($i = mb_strlen($alphabet) - 1, $v = 0, $p = 0; $i > 0; $i--, $v++) {
$v %= $saltLength;
$p += $int = mb_ord($saltArray[$v], 'UTF-8');
$j = ($int + $v + $p) % $i;
$temp = $alphabetArray[$j];
$alphabetArray[$j] = $alphabetArray[$i];
$alphabetArray[$i] = $temp;
}
$alphabet = implode('', $alphabetArray);
$this->shuffledAlphabets[$key] = $alphabet;
return $alphabet;
}
/** Hash given input value. */
protected function hash(string $input, string $alphabet): string
{
$hash = '';
$alphabetLength = mb_strlen($alphabet);
do {
$hash = mb_substr($alphabet, $this->math->intval($this->math->mod($input, $alphabetLength)), 1) . $hash;
$input = $this->math->divide($input, $alphabetLength);
} while ($this->math->greaterThan($input, 0));
return $hash;
}
/** Unhash given input value. */
protected function unhash(string $input, string $alphabet): int|string
{
$number = 0;
$inputLength = mb_strlen($input);
if ($inputLength && $alphabet) {
$alphabetLength = mb_strlen($alphabet);
$inputChars = $this->multiByteSplit($input);
foreach ($inputChars as $char) {
$position = mb_strpos($alphabet, $char);
$number = $this->math->multiply($number, $alphabetLength);
$number = $this->math->add($number, $position);
}
}
return $number;
}
/**
* Get BC Math or GMP extension.
* @throws \RuntimeException
*/
protected function getMathExtension(): MathInterface
{
if (extension_loaded('gmp')) {
return new Gmp();
}
if (extension_loaded('bcmath')) {
return new BCMath();
}
throw new RuntimeException('Missing math extension for Hashids, install either bcmath or gmp.');
}
/**
* Replace simple use of $this->multiByteSplit with multi byte string.
* @return array
*/
protected function multiByteSplit(string $string): array
{
return preg_split('/(?!^)(?=.)/u', $string) ?: [];
}
}
/** Datei: library/vendor/hashids/hashids/src/HashidsInterface.php **/
...$numbers
*/
public function encode(...$numbers): string;
/**
* Decode a hash to the original parameter values.
* @return array|array{}
*/
public function decode(string $hash): array;
/** Encode hexadecimal values and generate a hash string. */
public function encodeHex(string $str): string;
/** Decode a hexadecimal hash. */
public function decodeHex(string $hash): string;
}
/** Datei: library/vendor/autoload.php **/