/** Datei: plugins/cards/library/ARUNOX/Tools/Cardnumber/v2.php **/ * @copyright 2023 Sparky@ARUNOX * @version 1.0.0 * @since 1.0.0 File is created */ namespace ARUNOX\Tools\Cardnumber; use ARUNOX\Tools\Cardnumber; class v2 extends BaseCalc { /* ### CONSTANTS ################################################ # */ /* ### VARIABLES ################################################ # */ protected static int $NumberBase = 36; protected static bool $DoubleRemove = TRUE; protected static int $ChecksumBase = 36; protected static int $ChecksumLevel = 2; /* ### PUBLIC STATIC FUNCTIONS ################################## # */ /* ### PUBLIC FUNCTIONS ######################################### # */ /* ### PROTECTED FUNCTIONS ####################################### # */ protected function calcValue( string $input, null|int $sourceBase = NULL, null|int $targetBase = NULL ) : int { $input = preg_replace('/[^0-9A-Z]/i', '', $input ); $input = strtoupper( $input ); $input = substr( $input, 0 , $this->getNumberLength() + 8 ); $parts = str_split( $input ); $parts = array_map( fn( $part ) => $this->decode( $part, $sourceBase ), $parts ); $acc = 0; foreach( $parts as $part ) $acc = ( $acc * 31 + $part ); return (int)$acc; } protected function parseChecksum( int $acc, null|int $length = NULL, null|int $base = NULL ) : string { $base = $this->getChecksumBase( $base ); $level = $this->getChecksumLevel() - 1; $length = $this->getChecksumLength( $length ); $bucket = Cardnumber::getBucketSize(); $max = pow( $base, $length ); if( $bucket * ( $level + 1 ) > $max ) { throw new InvalidArgumentException('Bucket passt nicht in den verfügbaren Raum.'); } $acc = ( ($acc % $bucket) + $bucket ) % $bucket; $acc = $acc + $bucket * $level; $code = $this->encode( $acc, $base ); $code = str_pad( (string)$code, $length, '0', STR_PAD_LEFT ); $code = strtoupper( $code ); return $code; } protected function parseValidation( int $acc, null|int $length = NULL, null|int $base = NULL ) : string { $base = $this->getValidationBase( $base ); $length = $this->getValidationLength( $length ); $max = pow( $base, $length ); $acc = $acc % $max; $code = $this->encode( $acc, $base ); $code = str_pad( (string)$code, $length, '0', STR_PAD_LEFT ); $code = strtoupper( $code ); return $code; } /* ### PRIVATE FUNCTIONS ######################################## # */ /* ### MAGIC FUNCTIONS ########################################## # */ } /** Datei: plugins/cards/library/ARUNOX/Tools/Cardnumber/Cardnumber.php **/ * @copyright 2023 Sparky@ARUNOX * @version 1.0.0 * @since 1.0.0 File is created */ namespace ARUNOX\Tools; use InvalidArgumentException; class Cardnumber { /* ### CONSTANTS ################################################ # */ /* ### VARIABLES ################################################ # */ protected static int $bucketSize = 256; protected static array $levelClasses = []; protected static null|int $defaultLevel = NULL; /* ################################################################ # */ /* ### PUBLIC STATIC FUNCTIONS ################################## # */ /* ################################################################ # */ /* ### CHECK FUNCTIONS ########################################## # */ public static function check( string $input, null|string $serial = NULL ) : array { $result = [ 500, "INVALID", "UNKNOWN" ]; if( preg_match( '/(([a-z0-9]{4})([a-z0-9]{2}))([0-9]{3})?/i', $input, $matches ) ) { [ $input, $number, $uuid, $checksum, $validation ] = array_pad( $matches, 5, NULL ); $acc = base_convert( $checksum,36, 10 ); $size = static::getBucketSize(); $level = ceil( $acc / $size ); $tool = static::getLevelObject( $level ); if( $tool ) { $result = $tool->check( $input, $serial ); } array_push( $result, $number ); } return $result; } /* ### BUCKET FUNCTIONS ######################################### # */ public static function setBucketSize( int $size ) : void { static::$bucketSize = $size; } public static function getBucketSize() : int { return static::$bucketSize; } /* ### DEFAULT FUNCTIONS ######################################## # */ public static function setDefaultLevel( int $level ) : bool { if( static::getLevelClass( $level ) ) { static::$defaultLevel = $level; return TRUE; } return FALSE; } public static function getDefaultLevel() : null|int { return static::$defaultLevel; } public static function getDefaultClass() : bool|string { if( $class = static::getLevelClass( static::$defaultLevel ) ) { return $class; } return FALSE; } public static function getDefaultObject() : bool|object { if( $class = static::getLevelClass( static::$defaultLevel ) ) { return new $class(); } return FALSE; } /* ### LEVEL FUNCTIONS ########################################## # */ public static function setLevelClass( int $level, string $class ) : bool { if( class_exists( $class ) ) { static::$levelClasses[ $level ] = $class; return TRUE; } return FALSE; } public static function getLevelClass( int $level ) : bool|string { if( key_exists( $level, static::$levelClasses ) ) { return static::$levelClasses[ $level ]; } return FALSE; } public static function getLevelObject( int $level ) : bool|object { if( $class = static::getLevelClass( $level ) ) { return new $class(); } return FALSE; } /* ### PUBLIC FUNCTIONS ######################################### # */ /* ### PROTECTED FUNCTIONS ####################################### # */ /* ### PRIVATE FUNCTIONS ######################################## # */ /* ### MAGIC FUNCTIONS ########################################## # */ public function __call( string $name, $args ) { } public static function __callStatic( string $name, $args ) { } } /** Datei: plugins/cards/library/ARUNOX/Tools/Cardnumber/v1.php **/ * @copyright 2023 Sparky@ARUNOX * @version 1.0.0 * @since 1.0.0 File is created */ namespace ARUNOX\Tools\Cardnumber; class v1 extends BaseCalc { /* ### CONSTANTS ################################################ # */ /* ### VARIABLES ################################################ # */ protected static int $ChecksumBase = 16; protected static int $ChecksumLevel = 1; protected static int $NumberBase = 16; /* ### PUBLIC STATIC FUNCTIONS ################################## # */ /* ### PUBLIC FUNCTIONS ######################################### # */ /* ### PROTECTED FUNCTIONS ####################################### # */ protected function calcValue( string $input, null|int $sourceBase = NULL, null|int $targetBase = NULL ) : int { $number = substr( $input, 0, 4 ); $serial = substr( $input, 4 );; if( $serial ) $checksum = $this->calcChecksumFromMac( $number, $serial ); else $checksum = $this->calcChecksumFromHex( $number ); // show( $number, $serial, $checksum, FALSE ); return $checksum; } protected function parseChecksum( int $acc, null|int $length = NULL, null|int $base = NULL ) : string { $code = $acc % pow( 16, $this->getChecksumLength() ); $code = dechex( $code ); $code = str_pad( (string)$code, $this->getChecksumLength(), '0', STR_PAD_LEFT ); $code = strtoupper( $code ); return $code; } protected function parseValidation( int $acc, null|int $length = NULL, null|int $base = NULL ) : string { $code = $acc * pow( $this->getValidationLength(), 2 ) % pow( 10, $this->getValidationLength() ); $code = str_pad( (string)$code, $this->getValidationLength(), '0', STR_PAD_LEFT ); $code = strtoupper( $code ); return $code; } /* ### PRIVATE FUNCTIONS ######################################## # */ // protected function calcNumber() { // if( $this->number ) return $this->number; // // $min = $this->number_min ? pow( 10, $this->number_length - 1 ) : 0; // // switch( $this->number_type ) { // case static::NumberTypeHex: do { $number = uuid("%0{$this->number_length}X"); } while( hexdec( $number ) < $min ); break; // case static::NumberTypeDec: $number = rand( $min, pow( 10, $this->number_length ) - 9 ); break; // } // // $number = str_pad( $number, $this->number_length, 0, STR_PAD_LEFT ); // $number = strtoupper( $number ); // // return $number; // } private function calcChecksumFromMac( string $number, null|string $serial ) { $checksum = explode( ":", $serial ); $checksum = array_map( "hexdec", $checksum ); $checksum = array_sum( $checksum ); $checksum = $checksum * $this->calcChecksumFromHex( $number ); return $checksum; } private function calcChecksumFromHex( string $number ) { $checksum = hexdec( $number ); $checksum = str_split( $checksum ); foreach( $checksum as $index => &$value ) { $value = $value * $index; } $checksum = array_sum( $checksum ); return $checksum; } /* ### MAGIC FUNCTIONS ########################################## # */ } /** Datei: plugins/cards/library/ARUNOX/Tools/Cardnumber/BaseCalc.php **/ * @copyright 2023 Sparky@ARUNOX * @version 1.0.0 * @since 1.0.0 File is created */ namespace ARUNOX\Tools\Cardnumber; use ARUNOX\Tools\Cardnumber; use InvalidArgumentException; class BaseCalc { /* ### CONSTANTS ################################################ # */ /* ### VARIABLES ################################################ # */ protected static int $NumberBase = 10; protected static int $NumberLength = 4; protected static int $DoubleCount = 2; protected static bool $DoubleRemove = FALSE; protected static int $ChecksumBase = 10; protected static int $ChecksumLevel = 0; protected static int $ChecksumLength = 2; protected static int $ValidationBase = 10; protected static int $ValidationLength = 3; protected int|null $number_base = NULL; protected int|null $number_length = NULL; protected int|null $double_count = NULL; protected bool|null $double_remove = NULL; protected int|null $checksum_level = NULL; protected int|null $checksum_length = NULL; protected int|null $validation_base = NULL; protected int|null $validation_length = NULL; /* ### PUBLIC STATIC FUNCTIONS ################################## # */ public static function setDefaultNumberBase( int $base ) { static::$NumberBase = $base; } public static function setDefaultNumberLength( int $length ) { static::$NumberLength = $length; } public static function setDefaultDoubleRemove( bool $remove ) { static::$DoubleRemove = $remove; } public static function setDefaultDoubleCount( int $count ) { static::$DoubleCount = $count; } public static function setDefaultChecksumBase( int $base ) { static::$ChecksumBase = $base; } public static function setDefaultChecksumLevel( int $level ) { static::$ChecksumLevel = $level; } public static function setDefaultChecksumLength( int $length ) { static::$ChecksumLength = $length; } public static function setDefaultValidationBase( int $base ) { static::$ValidationBase = $base; } public static function setDefaultValidationLength( int $length ) { static::$ValidationLength = $length; } /* ### PUBLIC FUNCTIONS ######################################### # */ public function getNumber() { return $this->__toString(); } public function getNumberBase( null|int $base = NULL ) { return $base ?? $this->number_base ?? static::$NumberBase; } public function getNumberLength( null|int $length = NULL ) { return $length ?? $this->number_length ?? static::$NumberLength; } public function setNumberBase( int $base ) { $this->number_base = $base; } public function setNumberLength( int $length ) { $this->number_length = $length; } public function calcNumber( null|int $length = NULL, null|int $base = NULL ) { $base = $this->getNumberBase( $base ); $length = $this->getNumberLength( $length ); $min = 0; $max = pow( $base, $length ) - 1; do { $number = random_int( $min, $max ); $number = $this->encode( $number, $base ); $number = str_pad( $number, $length, 0, STR_PAD_LEFT ); } while ( $this->hasDoubles( $number ) ); $number = strtoupper( $number ); return $number; } public function getDoubleRemove( null|bool $remove = NULL ) { return $remove ?? $this->double_remove ?? static::$DoubleRemove; } public function getDoubleCount( null|int $count = NULL ) { return $count ?? $this->double_count ?? static::$DoubleCount; } public function setDoubleRemove( bool $remove ) { $this->double_remove = $remove; } public function setDoubleCount( int $count ) { $this->double_count = $count; } public function calcDoubles( null|int $count = NULL, null|int $length = NULL, null|int $base = NULL ) { $count = $this->getDoubleCount( $count ); $base = $this->getNumberBase( $base ); $length = $this->getNumberLength( $length ); $total = gmp_pow( $base, $length ); $allowed = gmp_init(0 ); $k = array_fill(1, $count, 0 ); $factN = gmp_fact( $length ); $factR = []; for( $r = 0; $r <= $count; $r++ ) $factR[ $r ] = gmp_fact( $r ); $this->enumerateDoubles(1, $length, 0, $k, $count, $length, $base, $factN, $factR, $allowed ); $forbidden = gmp_sub( $total, $allowed ); $lossPct = gmp_strval( $forbidden ) * 100 / gmp_strval( $total ); return [ 'base' => $base, 'length' => $length, 'total' => gmp_strval( $total ), 'allowed' => gmp_strval( $allowed ), 'forbidden' => gmp_strval( $forbidden ), 'loss_pct' => number_format( $lossPct, 3 ), ]; } public function hasDoubles( string $number, null|int $count = NULL ) { if( !$this->getDoubleRemove() ) return FALSE; $input = str_split( $number ); $length = strlen( $number ); $count = $this->getDoubleCount( $count ); $counted = array_count_values( $input ); $filtered = array_filter( $counted, fn( $item ) => $item <= $count ); $newLength = array_sum( $filtered ); return $newLength != $length; } public function getChecksumBase( null|int $base = NULL ) { return $base ?? $this->checksum_base ?? static::$ChecksumBase; } public function getChecksumLevel( null|int $level = NULL ) { return $level ?? $this->checksum_level ?? static::$ChecksumLevel; } public function getChecksumLength( null|int $length = NULL ) { return $length ?? $this->checksum_length ?? static::$ChecksumLength; } public function setChecksumBase( int $base ) { $this->checksum_base = $base; } public function setChecksumLevel( int $level ) { $this->checksum_level = $level; } public function setChecksumLength( int $length ) { $this->checksum_length = $length; } public function calcChecksum( string $number, null|int $length = NULL, null|int $base = NULL, null|int $inputBase = NULL ) : string { $input = $this->getNumberBase( $inputBase ); $base = $this->getChecksumBase( $base ); $length = $this->getChecksumLength( $length ); $acc = $this->calcValue( $number, $input, $base ); $code = $this->parseChecksum( $acc, $length, $base ); // show( $input, $base, $length, $acc, $code, FALSE ); return $code; } public function getValidationBase( null|int $base = NULL ) { return $base ?? $this->validation_base ?? static::$ValidationBase; } public function getValidationLength( null|int $length = NULL ) { return $length ?? $this->validation_length ?? static::$ValidationLength; } public function setValidationBase( int $base ) { $this->validation_base = $base; } public function setValidationLength( int $length ) { $this->validation_length = $length; } public function calcValidation( string $number, null|int $length = NULL, null|int $base = NULL, null|int $inputBase = NULL ) : string { $input = $this->getNumberBase( $inputBase ); $base = $this->getValidationBase( $base ); $length = $this->getValidationLength( $length ); $acc = $this->calcValue( $number, $input, $base ); $code = $this->parseValidation( $acc, $length, $base ); // show($number, $base, $acc, $code, FALSE ); return $code; } public function check( string $input, null|string $serial = NULL ) : array { if( preg_match( '/(([a-z0-9]{4})([a-z0-9]{2}))([0-9]{3})?/i', $input, $matches ) ) { [ $input, $number, $uuid, $checksum, $cvc ] = array_pad( $matches, 5, NULL ); $numberLength = $this->getNumberLength() + $this->getChecksumLength(); $cvcLength = $this->getValidationLength(); if( strlen( $number ) < $numberLength ) return [ 100, "IDLENGTH" ]; if( $serial && $this->calcChecksum( $uuid . $serial ) == $checksum ) { return [ 200, "SERIAL" ]; } // show( $number, $numberLength, $uuid, $checksum, $this->calcChecksum( $uuid ), get_called_class(), FALSE ); if( $this->calcChecksum( $uuid ) == $checksum ) { return [ 200, "CHECKSUM" ]; } if( $cvc ) { if( strlen( $cvc ) < $cvcLength ) return [ 100, "CVCLENGTH" ]; if( $this->calcValidation( $number ) == $cvc ) { return [ 200, "CVC" ]; } } } return [ 500, "INVALID" ]; } /* ### PROTECTED FUNCTIONS ####################################### # */ protected function parseChecksum( int $acc, null|int $length = NULL, null|int $base = NULL ) : string { $base = $this->getChecksumBase( $base ); $level = $this->getChecksumLevel() - 1; $length = $this->getChecksumLength( $length ); $bucket = Cardnumber::getBucketSize(); $max = pow( $base, $length ); if( $bucket * ( $level + 1 ) > $max ) { throw new InvalidArgumentException('Bucket passt nicht in den verfügbaren Raum.'); } $acc = ( ($acc % $bucket) + $bucket ) % $bucket; $acc = $acc + $bucket * $level; $code = $this->encode( $acc, $base ); $code = str_pad( (string)$code, $length, '0', STR_PAD_LEFT ); $code = strtoupper( $code ); return $code; } protected function parseValidation( int $acc, null|int $length = NULL, null|int $base = NULL ) : string { $base = $this->getValidationBase( $base ); $length = $this->getValidationLength( $length ); $max = pow( $base, $length ); $acc = $acc % $max; $code = $this->encode( $acc, $base ); $code = str_pad( (string)$code, $length, '0', STR_PAD_LEFT ); $code = strtoupper( $code ); return $code; } protected function calcValue( string $input, null|int $sourceBase = NULL, null|int $targetBase = NULL ) : int { $input = preg_replace('/[^0-9A-F]/i', '', $input ); $input = strtoupper( $input ); $parts = str_split( $input ); $parts = array_map( fn( $part ) => $this->decode( $part, $sourceBase ), $parts ); $acc = 0; foreach( $parts as $index => $part ) $acc = ( $acc + $part * $index ); return $acc; } protected function encode( string $value, int $base ) : string { return base_convert( $value, 10, $base ); } protected function decode( string $value, int $base ) : string { // show( $value, $base, debug_backtrace(), FALSE ); return base_convert( $value, $base, 10 ); } /* ### PRIVATE FUNCTIONS ######################################## # */ private function enumerateDoubles( $r, $remaining, $usedSymbols, $k, $maxRepeat, $length, $base, $factN, $factR, &$allowed ) { if ($r === $maxRepeat) { // letzter Block: rest muss durch r teilbar sein if ($remaining % $r !== 0) return; $k[$r] = intdiv($remaining, $r); $symbolsNeeded = $usedSymbols + $k[$r]; if ($symbolsNeeded > $base) return; // Symbole wählen: Produkt der Binomiale (absteigend r) $waysPick = gmp_init(1); $left = $base; for ($rr = $maxRepeat; $rr >= 1; $rr--) { $kr = $k[$rr]; if ($kr > 0) { $waysPick = gmp_mul($waysPick, gmp_binomial($left, $kr)); $left -= $kr; } } // Anordnungen: n! / Π (r!)^{k_r} $den = gmp_init(1); for ($rr = 1; $rr <= $maxRepeat; $rr++) { $kr = $k[$rr]; if ($kr > 0) { // (r!)^kr $den = gmp_mul($den, gmp_pow($factR[$rr], $kr)); } } $perms = gmp_div_q($factN, $den); // Beitrag addieren $allowed = gmp_add($allowed, gmp_mul($waysPick, $perms)); return; } // Verteile k_r von 0..floor(remaining/r) $maxKr = intdiv($remaining, $r); for ($kr = 0; $kr <= $maxKr; $kr++) { $k[$r] = $kr; // früh abbrechen, wenn Symbolbedarf bereits > base wäre if ($usedSymbols + $kr > $base) break; $this->enumerateDoubles($r + 1, $remaining - $r * $kr, $usedSymbols + $kr, $k, $maxRepeat, $length, $base, $factN, $factR, $allowed); } } /* ### MAGIC FUNCTIONS ########################################## # */ public function __toString() { $number = $this->calcNumber(); return implode( "", [ $number, $this->calcChecksum( $number ), ]); } } /** Datei: plugins/cards/library/ARUNOX/Plugins/Locations/Cards/Model.php **/ [ "message" => "Card wurde Standort hinzugefügt", "origin_hid" => $current->card->hashid, "target_hids" => [ $current->origin->hashid ], ], "card_id" => [ "message" => "Card wurde Standort hinzugefügt", "origin_hid" => $current->origin->hashid, "target_hids" => [ $current->card->hashid ], ], ]; } /* ### MAGIC FUNCTIONS ########################################## # */ } /** Datei: plugins/cards/library/ARUNOX/Plugins/Locations/Cards/API/v1/Routes.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\Plugins\Locations\Cards\API\v1; use ARUNOX\Core\Hooks; use ARUNOX\Core\Router; use ARUNOX\Plugins\REST; class Routes { public static function register() : void { /* ### MAIN ROUTES ############################## # */ Router::addAll( Controller::class ); } } /** Datei: plugins/cards/library/ARUNOX/Plugins/Locations/Cards/API/v1/Controller.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\Plugins\Locations\Cards\API\v1; use ARUNOX\HTTP; use ARUNOX\Plugins; use ARUNOX\API\PHP; use ARUNOX\Core\Hooks; use ARUNOX\API\BaseController; use ARUNOX\Database\BaseEntity; use ARUNOX\Plugins\REST\Generator\Base\BaseGenerator; #[PHP\Attributes\Controller\Label( "Karte", "Karten" )] #[PHP\Attributes\Controller\Icon( Plugins\REST\Generator\Config\Icon::CreditCard2Front )] #[PHP\Attributes\Controller\Route( "cards", Plugins\Locations\API\v1\Controller::class )] #[PHP\Attributes\Controller\Notifications( "rolechanged.0", '{$LABEL.singular} geändert zu Default' )] #[PHP\Attributes\Controller\Notifications( "rolechanged.1", '{$LABEL.singular} geändert zu User' )] #[PHP\Attributes\Controller\Notifications( "rolechanged.2", '{$LABEL.singular} geändert zu Admin' )] #[PHP\Attributes\Database\Definitions\Model( Plugins\Locations\Cards\Model::class )] class Controller extends BaseController { /* ### PUBLIC GET FUNCTIONS ##################################### # */ /** * @api * @method GET * * @route.path /locations/{origin}/cards * @route.description Listet alle Logeinträge */ public static function getAll( string $origin ) : array { $origin = Plugins\Locations\Cards\Model::resolveHashIDPrefix( $origin ); return Plugins\Locations\Cards\Model::getAll([ "origin_id" => $origin->id ] ); } /** * @api * @method GET * * @route.path /locations/cards/{hashid} * @route.description Gibt einen einzelnen Logeintrag anhand der ID zurück */ public static function getPrepared( string $hashid ) : BaseEntity | HTTP\Response { return parent::getPrepared( $hashid ); } /* ### PUBLIC SAVE FUNCTIONS #################################### # */ /** * @api * @method PUT * * @route.path /locations/cards/{hashid} * @route.description Speichert Änderungen an einem bestehenden Termin */ public static function savePrepared( string $hashid, array $data ) : HTTP\Response { return parent::savePrepared( $hashid, $data ); } /** * @api * @method POST * * @route.path /locations/cards * @route.description Erstellt einen neuen Kalender */ public static function createPrepared( array $data ) : HTTP\Response { return parent::createPrepared( $data ); } /** * @api * @method DELETE * * @route.path /locations/cards/{hashid} * @route.description Löscht ein Logeintrag anhand der ID */ public static function deletePrepared( string $hashid ) : HTTP\Response { return parent::deletePrepared( $hashid ); } /** * @api * @method PATCH * * @route.path /locations/cards/{hashid}/role * @route.description Wechselt den Status (aktiv/inaktiv) eines Kalenders */ public static function toggleRole( string $hashid ) : HTTP\Response { return static::togglePrepared( "role", $hashid ); } /* ### PUBLIC VALIDATE FUNCTIONS ################################ # */ /* ### PUBLIC REST RESPONSE FUNCTIONS ########################### # */ /** * @internal rest * @method PUT * * @route.path /locations/cards/{hashid} * @route.description REST-Antwort nach dem Speichern eines Termins */ public static function restResponseSavePrepared( string $hashid, array $data ) : HTTP\Response { return parent::restResponseSavePrepared( $hashid, $data ); } /** * @internal rest * @method POST * * @route.path /locations/cards * @route.description REST-Antwort nach dem Erstellen eines Kalenders */ public static function restResponseCreate( array $data ) : HTTP\Response { $result = static::createPrepared( $data ); if( $result->isStatus( 200 ) ) { Plugins\REST\Response::closeModals(); Plugins\REST\Response::refresh( "#cards" ); } return static::restResponse( $result ); } /** * @internal rest * @method DELETE * * @route.path /locations/cards/{hashid} * @route.description REST-Antwort nach dem Löschen eines Logeintrag */ public static function restResponseDelete( string $hashid ) : HTTP\Response { Plugins\REST\Response::closeModals(); $result = static::deletePrepared( $hashid ); if( $result->isStatus( 200 ) ) { Plugins\REST\Response::redirect( "/locations/cards" ); } return static::restResponse( $result ); } /** * @internal rest * @method PATCH * * @route.path /locations/cards/{hashid}/status * @route.description REST-Antwort zur Status-Umschaltung eines Kalenders */ public static function restResponseToggleStatusPrepared( string $hashid ) : HTTP\Response { $card = Plugins\Locations\Cards\Model::resolveHashIDPrefix( $hashid ); $result = Plugins\Cards\API\v1\Controller::toggleStatusPrepared( $card->card->hashid ); $card = Plugins\Locations\Cards\Model::resolveHashIDPrefix( $hashid ); if( $result->isStatus( 200 ) ) { Plugins\REST\Response::addUpdater( [ "data/table/row", $hashid ], $card ); } return static::restResponse( $result ); } /** * @internal rest * @method PATCH * * @route.path /locations/cards/{hashid}/role * @route.description REST-Antwort zur Status-Umschaltung eines Kalenders */ public static function restResponseToggleRole( string $hashid ) : BaseGenerator { return Plugins\REST\Response::addModal( "details", Plugins\Locations\Cards\Role::getSingular( "%t ändern" ) ) ->get() ->setApi( static::getRoute( $hashid ) ) ->setAction( static::getRoute( $hashid ), "PUT" ) ->addButton(...Hooks::filter( "rest.generator::button.type->submit", [], "Speichern", static::getRoute() )) ->addField(...Hooks::filter( "rest.generator::control.type->radio", [ "options" => Plugins\Locations\Cards\Role::getAll(), ], "role", TRUE, TRUE, "locations/cards" )) ; } /* ### PUBLIC REST CONFIG FUNCTIONS ############################# # */ /** * @internal rest * @method GET * * @route.path /locations/cards * @route.description REST-Konfiguration für Logbuch-Tabelle */ public static function restConfigOverview() : BaseGenerator { return Plugins\REST\Response::addWrapper( "details", static::getPlural() )->get() ->addTab( "Übersicht", FALSE )->get() ->setIcon( static::getIcon() ) ->addHeadline( static::getPlural(), 2 ) ->addCallable( [ static::class, "restConfigTable" ] ) ->addButton(...Hooks::filter( "rest.generator::button.name->create", [], "locations" )) ->parent() ; } /** * @internal rest * @method GET * * @route.path /locations/{origin}/cards/{$hashid} * @route.description REST-Konfiguration für Detailansicht eines Logeintrags */ public static function restConfigDetails( string $origin, string $hashid ) : BaseGenerator { return Plugins\REST\Response::addModal( "details", static::getSingular( "%t bearbeiten" ) ) ->setSize( "equal" ) ->setWidth("lg") ->get() ->setApi( static::getRoute( $hashid ) ) ->setAction( static::getRoute( $hashid ), "PUT" ) ->addTab( static::getSingular() )->get() ->addHeadline( static::getSingular(), 2 ) ->addCallable( [ static::class, "restConfigOrigin" ], $origin, $hashid ) ->addCallable( [ static::class, "restConfigInputs" ], $origin, $hashid ) ->parent() ->addCallable( [ Plugins\Logs\API\v1\Controller::class, "restConfigTab" ], $hashid ) ->addTab( "Speichern" )->get() ->setColor( Plugins\REST\Generator\Config\Color::Primary ) ->setIcon( "bi bi-save" ) ->setType( "submit" ); } /** * @internal rest * @method GET * * @route.path /locations/{origin}/cards/create * @route.description REST-Konfiguration für das Erstellen eines Kalenders (Formular) */ public static function restConfigCreateByOriginPrepared( bool | string $origin = FALSE ) : BaseGenerator { return parent::restConfigCreateByOriginPrepared( $origin ); } /** * @internal rest * @method GET * * @route.path /locations/cards/{hashid}/delete * @route.description REST-Konfiguration für die Lösch-Bestätigung eines Logeintrags */ public static function restConfigDeletePrepared( string $hashid ) : BaseGenerator { return parent::restConfigDeletePrepared( $hashid ); } /* ### PUBLIC REST EXTENSION FUNCTIONS ########################## # */ public static function restConfigTab( BaseGenerator $rest, null|string $hashid = NULL ) : void { $rest ->addTab( static::getPlural(), "cards" )->get() ->setIcon( static::getIcon() ) ->addCallable( [ static::class, "restConfigTable" ], $hashid ) ; } public static function restConfigTable( BaseGenerator $rest, null|string $hashid = NULL ) : BaseGenerator { return $rest ->addTable()->get() ->setApi( Plugins\Locations\API\v1\Controller::class::getRoute( $hashid, "cards" ) ) ->setAction( Plugins\Locations\API\v1\Controller::class::getRoute( $hashid, "cards", "{hashid}") ) ->setOrder( "uuid", "asc" ) ->setSearch( TRUE ) ->addButton(...Hooks::filter( "rest.generator::button.name->create", [], "locations/{$hashid}/cards" )) ->addColumn(...Hooks::filter( "rest.generator::column.type->status", [ "label" => Plugins\Cards\Status::getSingular(), "icon" => Plugins\Cards\Status::getIcon(), "options" => Plugins\Cards\Status::getAll(), ], "status", [], "locations/cards", "hashid" )) ->addColumn(...Hooks::filter( "rest.generator::column.type->icon", [ "label" => Plugins\Cards\Type::getSingular(), "icon" => Plugins\Cards\Type::getIcon(), "options" => Plugins\Cards\Type::getAll(), ], "card.type", [], "locations/cards", "hashid" )) ->addColumn(...Hooks::filter( "rest.generator::column.type->status", [ "label" => Plugins\Locations\Cards\Role::getSingular(), "icon" => Plugins\Locations\Cards\Role::getIcon(), "options" => Plugins\Locations\Cards\Role::getAll(), ], "role", [], "locations/cards", "hashid" )) ->addColumn(...Hooks::filter( "rest.generator::column.type->text", [ "label" => "UUID", ], "card.uuid", TRUE, TRUE, TRUE, "locations/cards" )) ; } public static function restConfigInputs( BaseGenerator $rest, bool|string $origin, bool|string $hashid = FALSE ) : BaseGenerator { return $rest->addField(...Hooks::filter( "rest.generator::control.type->select", [ "options" => Plugins\Locations\Cards\Role::getAll(), ], "role", TRUE, TRUE, "locations/cards" )); } public static function restConfigOrigin( BaseGenerator $rest, bool|string $origin, bool|string $hashid = FALSE ) : BaseGenerator { $origin = Plugins\Locations\Cards\Model::resolveHashIDPrefix( $origin ); $current = Plugins\Locations\Cards\Model::resolveHashIDPrefix( $hashid ); $excluded = Plugins\Locations\Cards\Model::getAllValue([ "origin_id" => $origin->id ], "card_id" ); $rest->addField(...Hooks::filter( "rest.generator::control.type->hidden", [], "origin_id", $origin->id, "locations/cards" )); if( $hashid === FALSE ) { return $rest->addField(...Hooks::filter( "rest.generator::control.type->select", [ "options" => Plugins\Cards\Model::getAsOptions( "uuid", excluded: $excluded ) , ], "card_id", TRUE, TRUE, "locations/cards" )); } return $rest->addField(...Hooks::filter( "rest.generator::control.type->hidden", [], "card_id", $current->id, "locations/cards" )); } } /** Datei: plugins/cards/library/ARUNOX/Plugins/Locations/Cards/Role.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: plugins/cards/library/ARUNOX/Plugins/Locations/Cards/Entity.php **/ origin_id ); } public function getCard() : bool|BaseEntity { return Cards\Model::get( $this->card_id ); } /* ### MAGIC FUNCTIONS ########################################## # */ } /** Datei: plugins/cards/library/ARUNOX/Plugins/Cards/Model.php **/ Status::Lost ], $labelColumn, $valueColumn, $excluded ); } public static function verify( string $input, string|NULL $serial = NULL ) { return Cardnumber::check( $input, $serial ); } /* ### MAGIC FUNCTIONS ########################################## # */ } /** Datei: plugins/cards/library/ARUNOX/Plugins/Cards/API/v1/Routes.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\Plugins\Cards\API\v1; use ARUNOX\Core\Hooks; use ARUNOX\Core\Router; use ARUNOX\Tools\Cardnumber; class Routes { public static function register() : void { /* ### MAIN ROUTES ############################## # */ Router::addAll( Controller::class ); /* ### MENU ROUTES ############################## # */ Hooks::add('router::get->api.v1.rest.routes', function( $routes ) { $routes[] = [ "path" => Controller::getRoute(), "label" => Controller::getPlural(), "icon" => Controller::getIcon(), "menu" => "management", ]; return $routes; }, 50 ); } public static function init() : void { Cardnumber::setLevelClass( 1, Cardnumber\v1::class ); Cardnumber::setLevelClass( 2, Cardnumber\v2::class ); Cardnumber::setDefaultLevel( 2 ); } public static function redirect( $route ) : array { [ $uri, $method ] = $route; if( $uri === Controller::getRoute( "0:rest", "verify" ) ) { Hooks::remove( "LOGIN_REDIRECT" ); } return $route; } } /** Datei: plugins/cards/library/ARUNOX/Plugins/Cards/API/v1/Controller.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\Plugins\Cards\API\v1; use ARUNOX\HTTP; use ARUNOX\Plugins; use ARUNOX\API\PHP; use ARUNOX\Core\Hooks; use ARUNOX\API\BaseController; use ARUNOX\Database\BaseEntity; use ARUNOX\Plugins\REST\Generator\Base\BaseGenerator; #[PHP\Attributes\Controller\Label( "Karte", "Karten" )] #[PHP\Attributes\Controller\Icon( Plugins\REST\Generator\Config\Icon::CreditCard2Front )] #[PHP\Attributes\Controller\Route( "cards" )] #[PHP\Attributes\Controller\Notifications( "statuschanged.0", '{$LABEL.singular} wurde erfolgreich erstellt' )] #[PHP\Attributes\Controller\Notifications( "statuschanged.1", '{$LABEL.singular} wurde erfolgreich beschrieben' )] #[PHP\Attributes\Controller\Notifications( "statuschanged.2", '{$LABEL.singular} wurde erfolgreich aktiviert' )] #[PHP\Attributes\Controller\Notifications( "statuschanged.3", '{$LABEL.singular} wurde erfolgreich deaktiviert' )] #[PHP\Attributes\Controller\Notifications( "NOTFOUND", 'Card not Found' )] #[PHP\Attributes\Controller\Notifications( "FORBIDDEN", 'Card not allowed' )] #[PHP\Attributes\Controller\Notifications( "CHECKSUM", 'LOGIN: by CHECKSUM' )] #[PHP\Attributes\Controller\Notifications( "SERIAL", 'LOGIN: by SERIAL' )] #[PHP\Attributes\Controller\Notifications( "CVC", 'LOGIN: by CVC' )] #[PHP\Attributes\Controller\Notifications( "INVALID", 'LOGIN: FAILED' )] #[PHP\Attributes\Database\Definitions\Model( Plugins\Cards\Model::class )] class Controller extends BaseController { /* ### PUBLIC GET FUNCTIONS ##################################### # */ /** * @api * @method GET * * @route.path /cards * @route.description Listet alle Logeinträge */ public static function getAllPrepared() : array { return parent::getAllPrepared(); } /** * @api * @method GET * * @route.path /cards/{hashid} * @route.description Gibt einen einzelnen Logeintrag anhand der ID zurück */ public static function getPrepared( string $hashid ) : BaseEntity | HTTP\Response { return parent::getPrepared( $hashid ); } /** * @api * @method POST * * @route.path /cards/verify * @route.description Gibt einen einzelnen Logeintrag anhand der ID zurück */ public static function verify( $data ) : HTTP\Response { [ $code, $error, $number ] = Plugins\Cards\Model::verify( $data["cardid"], $data["serial"] ); if( $code == 200 ) { if( $data["action"] == "login" ) { if( $card = Plugins\Cards\Model::getByUUID( $number ) ) { if( $card->status->isActive() && $card->type->isLogin() ) { Plugins\Users\Model::setCurrent( $card->user ); } else [ $code, $error ] = [ "403", "FORBIDDEN" ]; } else [ $code, $error ] = [ "404", "NOTFOUND" ]; } } return Plugins\REST\Response::autoResponse( $code, static::getNotify( $error ), NULL, "NFC Scan" ); } /* ### PUBLIC SAVE FUNCTIONS #################################### # */ /** * @api * @method PUT * * @route.path /cards/{hashid} * @route.description Speichert Änderungen an einem bestehenden Termin */ public static function savePrepared( string $hashid, array $data ) : HTTP\Response { return parent::savePrepared( $hashid, $data ); } /** * @api * @method POST * * @route.path /cards * @route.description Erstellt einen neuen Kalender */ public static function createPrepared( array $data ) : HTTP\Response { return parent::createPrepared( $data ); } /** * @api * @method DELETE * * @route.path /cards/{hashid} * @route.description Löscht ein Logeintrag anhand der ID */ public static function deletePrepared( string $hashid ) : HTTP\Response { return parent::deletePrepared( $hashid ); } /** * @api * @method PATCH * * @route.path /cards/{hashid}/status * @route.description Wechselt den Status (aktiv/inaktiv) eines Kalenders */ public static function toggleStatusPrepared( string $hashid ) : HTTP\Response { return parent::toggleStatusPrepared( $hashid ); } /* ### PUBLIC VALIDATE FUNCTIONS ################################ # */ /* ### PUBLIC REST RESPONSE FUNCTIONS ########################### # */ /** * @internal rest * @method PUT * * @route.path /cards/{hashid} * @route.description REST-Antwort nach dem Speichern eines Termins */ public static function restResponseSavePrepared( string $hashid, array $data ) : HTTP\Response { return parent::restResponseSavePrepared( $hashid, $data ); } /** * @internal rest * @method POST * * @route.path /cards * @route.description REST-Antwort nach dem Erstellen eines Kalenders */ public static function restResponseCreate( array $data ) : HTTP\Response { $result = static::createPrepared( $data ); if( $result->isStatus( 200 ) ) { Plugins\REST\Response::closeModals(); Plugins\REST\Response::redirect( "/cards/{$result->getBody("data", "hashid")}" ); } return static::restResponse( $result ); } /** * @internal rest * @method DELETE * * @route.path /cards/{hashid} * @route.description REST-Antwort nach dem Löschen eines Logeintrag */ public static function restResponseDelete( string $hashid ) : HTTP\Response { Plugins\REST\Response::closeModals(); $result = static::deletePrepared( $hashid ); if( $result->isStatus( 200 ) ) { Plugins\REST\Response::redirect( "/cards" ); } return static::restResponse( $result ); } /** * @internal rest * @method PATCH * * @route.path /cards/{hashid}/status * @route.description REST-Antwort zur Status-Umschaltung eines Kalenders */ public static function restResponseToggleStatusPrepared( string $hashid ) : HTTP\Response { return parent::restResponseToggleStatusPrepared( $hashid ); } /** * @internal rest * @method POST * * @route.path /cards/verify * @route.description REST-Konfiguration für Benutzer Lougoin */ public static function restResponseVerify( array $data ) : HTTP\Response { return static::restResponse( static::verify( $data ), function(){ $referer = HTTP\Request::referer(); redirect( "/api/v1/rest/{$referer["path"]}" ); }); } /* ### PUBLIC REST CONFIG FUNCTIONS ############################# # */ /** * @internal rest * @method GET * * @route.path /cards * @route.description REST-Konfiguration für Logbuch-Tabelle */ public static function restConfigOverview() : BaseGenerator { return Plugins\REST\Response::addWrapper( "details", static::getPlural() )->get() ->addTab( "Übersicht", FALSE )->get() ->setIcon( static::getIcon() ) ->addHeadline( static::getPlural(), 2 ) ->addCallable( [ static::class, "restConfigTable" ] ) ->addButton(...Hooks::filter( "rest.generator::button.name->create", [], "cards" )) ; } /** * @internal rest * @method GET * * @route.path /cards/{hashid} * @route.description REST-Konfiguration für Detailansicht eines Logeintrags */ public static function restConfigDetails( string $hashid ) : BaseGenerator { return Plugins\REST\Response::addWrapper( "details", static::getSingular( "%t bearbeiten" ) ) ->setBacklink( static::getRoute() ) ->get() ->setApi( static::getRoute( $hashid ) ) ->setAction( static::getRoute( $hashid ), "PUT" ) ->addTab( static::getSingular() )->get() ->addHeadline( static::getSingular(), 2 ) ->addCallable( [ static::class, "restConfigInputs" ], $hashid ) ->parent() ->addCallable( [ Plugins\Logs\API\v1\Controller::class, "restConfigTab" ], $hashid ) ->addTab( "Speichern" )->get() ->setColor( Plugins\REST\Generator\Config\Color::Primary ) ->setIcon( "bi bi-save" ) ->setType( "submit" ); } /** * @internal rest * @method GET * * @route.path /cards/create * @route.description REST-Konfiguration für das Erstellen eines Kalenders (Formular) */ public static function restConfigCreatePrepared() : BaseGenerator { return parent::restConfigCreatePrepared(); } /** * @internal rest * @method GET * * @route.path /cards/{hashid}/delete * @route.description REST-Konfiguration für die Lösch-Bestätigung eines Logeintrags */ public static function restConfigDeletePrepared( string $hashid ) : BaseGenerator { return parent::restConfigDeletePrepared( $hashid ); } /* ### PUBLIC REST EXTENSION FUNCTIONS ########################## # */ public static function restConfigTable( BaseGenerator $rest ) : BaseGenerator { return $rest ->addTable()->get() ->setApi( static::getRoute() ) ->setAction( static::getRoute("{hashid}") ) ->setOrder( "uuid", "asc" ) ->setSearch( TRUE ) ->addCallable( [ static::class, "restConfigColumns" ] ) ; } public static function restConfigColumns( BaseGenerator $rest ) : BaseGenerator { return $rest ->addColumn(...Hooks::filter( "rest.generator::column.type->status", [ "label" => Plugins\Cards\Status::getSingular(), "icon" => Plugins\Cards\Status::getIcon(), "options" => Plugins\Cards\Status::getAll(), ], "status", [], "cards", "hashid" )) ->addColumn(...Hooks::filter( "rest.generator::column.type->icon", [ "label" => Plugins\Cards\Type::getSingular(), "icon" => Plugins\Cards\Type::getIcon(), "options" => Plugins\Cards\Type::getAll(), ], "type", [], "cards", "hashid" )) ->addColumn(...Hooks::filter( "rest.generator::column.type->text", [], "uuid", TRUE, TRUE, TRUE, "cards" )) ->addColumn(...Hooks::filter( "rest.generator::column.type->text", [ "label" => "Standort", ], "location.name", TRUE, TRUE, TRUE, "cards" )) ->addColumn(...Hooks::filter( "rest.generator::column.type->text", [ "label" => "Benutzer", ], "user.username", TRUE, TRUE, TRUE, "cards" )) ->addColumn(...Hooks::filter( "rest.generator::column.type->datetime", [], "date_edit", TRUE, FALSE, TRUE, "cards" )) ; } public static function restConfigInputs( BaseGenerator $rest ) : BaseGenerator { return $rest ->addField(...Hooks::filter( "rest.generator::control.type->text", [], "name", TRUE, "cards" )) // ->addField(...Hooks::filter( "rest.generator::control.type->text", [], "additional", FALSE, "cards" )) // ->addField(...Hooks::filter( "rest.generator::control.type->text", [], "address", FALSE, "cards" )) // ->addField(...Hooks::filter( "rest.generator::control.type->text", [], "zipcode", FALSE, "cards" )) // ->addField(...Hooks::filter( "rest.generator::control.type->text", [], "city", FALSE, "cards" )) ; } } /** Datei: plugins/cards/library/ARUNOX/Plugins/Cards/Type.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; } public function isLogin() : bool { return in_array( $this->value, [ static::User, static::Administrator ] ); } } /** Datei: plugins/cards/library/ARUNOX/Plugins/Cards/Entity.php **/ user_id ) { $location = Plugins\Locations\Users\Model::get([ "user_id" => $this->user_id, ]); } else { $location = Plugins\Locations\Cards\Model::get([ "card_id" => $this->id, ]); } return $location ? $location->origin : NULL; } public function getLocations() : array { if( $this->user_id ) { return Plugins\Locations\Users\Model::getAll([ "user_id" => $this->user_id, ]); } return Plugins\Locations\Cards\Model::getAll([ "card_id" => $this->id, ]); } public function getUser() : null|BaseEntity { return $this->user_id ? Plugins\Users\Model::get( $this->user_id ) : NULL; } /* ### MAGIC FUNCTIONS ########################################## # */ } /** Datei: plugins/cards/library/ARUNOX/Plugins/Cards/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; } }