/** 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;
}
}
/** Datei: plugins/cards/hooks/pwa.php **/
* @copyright 2025 ARUNOX
* @version 1.0.0
* @since 1.0.0 File is created
*/
use ARUNOX\Core\Hooks;
/** Datei: plugins/cards/hooks/init.php **/
* @copyright 2025 ARUNOX
* @version 1.0.0
* @since 1.0.0 File is created
*/
use ARUNOX\Core\Hooks;
use ARUNOX\Plugins\Cards;
use ARUNOX\Plugins\Locations;
Hooks::add('public::api->init', [ Cards\API\v1\Routes::class, "register" ] );
Hooks::add('public::api->init', [ Locations\Cards\API\v1\Routes::class, "register" ] );
Hooks::add('config::plugins->loaded', [ Cards\API\v1\Routes::class, "init" ] );
Hooks::add('router::resolve->before', [ Cards\API\v1\Routes::class, "redirect" ] );
Hooks::add('theme::scripts->before', function() {
?>config.OLD', function( config ) {
config.content ??= {};
config.content.message ??= [];
if( ARUNOX.Tools?.NFC.exists() ) {
config.content.message.push('scan access card');
}
return config;
});
ARUNOX.Hooks.register( 'template.login->afterRender.OLD', function() {
ARUNOX.Hooks.once( 'wrapper.dynamic->afterRender.OLD', function() {
const section = this.instance;
const parent = this.instance.parent;
if( ARUNOX.Tools?.NFC.exists() ) {
const NFCConfig = Object.assignRecursive( {}, section.element.ARX.storage.config, {
color : 'danger',
header : {
after : false,
before : 'Permission',
},
aside : {
side : 'left',
},
footer : {
after : {
content : [
ARUNOX.Theme.htmlTag.config( 'button', {
id : 'NFC_grant_authorization',
icon : 'bi-check-lg',
label : 'Berechtigung erteilen',
onClick : () => ARUNOX.Tools.NFC.read(),
style : {
width : '100%',
maxWidth : '100%',
},
}),
],
},
},
});
NFCConfig.content = [
ARUNOX.Theme.htmlTag.config( 'div', {
classes : 'error__wrapper',
content : [
ARUNOX.Theme.htmlTag.config( 'div', {
classes : 'error',
content : [
ARUNOX.Theme.htmlTag.config( 'div', {
classes : 'error__icon',
innerHTML : ``,
}),
ARUNOX.Theme.htmlTag.config( 'div', {
classes : 'error__text',
content : [
ARUNOX.Theme.htmlTag.config( 'h1', {
classes : 'error__caption',
textNode : 'NFC Gerät',
}),
ARUNOX.Theme.htmlTag.config( 'div', {
classes : 'error__message',
textNode : 'Fehlende Berechtigung',
}),
],
}),
],
}),
],
})
];
const NFCQuery = parent.render( 'layout/section', NFCConfig );
const NFCHide = () => {
NFCQuery.element.remove();
parent.append( section );
};
const NFCShow = ( denied ) => {
section.element.remove();
parent.append( NFCQuery )
if( denied ) NFCQuery.elements.footer.elements.after.remove();
};
ARUNOX.Tools.NFC.onDenied = () => NFCShow( true );
if( typeof ARUNOX.Tools.NFCReadLogin === 'function' ) {
ARUNOX.Tools.NFC.onRead = ARUNOX.Tools.NFCReadLogin;
}
ARUNOX.Tools.NFC.onGranted = NFCHide;
ARUNOX.Tools.NFC.onPrompt = () => NFCShow( false );
ARUNOX.Tools.NFC.init();
}
if( ARUNOX.Tools?.Keyboard ) {
const loginForm = section.element.querySelector('form');
const Keyboard = parent.render( 'layout/section', {
name : 'keyboard',
color : 'primary',
style : {
order : 2,
flexBasis : '200%',
},
header : {
after : '#',
},
aside : {
side : 'right',
},
footer : {
before : 'Keyboard',
}
});
const KBCopen = section.element.ARX.elements.header.addRight( true );
const KBCclose = Keyboard.element.ARX.elements.footer.addRight( true );
const KBCnumber = Keyboard.element.ARX.elements.header.addAfter( true );
const KBCback = Keyboard.element.ARX.elements.header.addBefore( true );
const KBCclear = Keyboard.element.ARX.elements.header.addRight( true );
const KBBopen = KBCopen.replace( 'button', {
htmlTag : 'a',
icon : 'bi bi-keyboard',
title : 'Keyboard',
color : { color: 'default', target: 'button' },
});
const KBBclose = KBCclose.replace( 'button', {
htmlTag : 'a',
icon : 'bi bi-caret-down-fill',
title : 'Schließen',
});
const KBBclear = KBCclear.replace( 'button', {
htmlTag : 'a',
color : 'danger',
icon : 'bi bi-trash',
title : 'Zurücksetzen',
});
const KBBback = KBCback.replace( 'button', {
htmlTag : 'a',
color : 'secondary',
icon : 'bi bi-backspace',
title : 'Löschen',
});
const KBEnumber = KBCnumber.element.querySelector('.frame__title');
KBCopen.reset();
ARUNOX.Tools.Keyboard.init({
visible : false,
type : 'base36',
closeBtn : KBBclose,
openBtn : KBBopen,
backBtn : KBBback,
clearBtn : KBBclear,
number : KBEnumber.ARX,
content : Keyboard.elements.content,
onHide : () => {
loginForm.style.display = '';
KBCopen.replace( KBBopen );
Keyboard.element.remove();
},
onShow : () => {
loginForm.style.display = 'none';
KBCopen.reset();
parent.append( Keyboard )
},
onBackHide : () => {
KBCback.reset();
},
onBackShow : () => {
KBCback.replace( KBBback );
},
onClearHide : () => {
KBCclear.reset();
},
onClearShow : () => {
KBCclear.replace( KBBclear );
},
onRefresh : ( value ) => ARUNOX.Tools.NFCManuelLogin( value ),
});
}
});
});
/** Datei: plugins/cards/assets/js/arunox/hooks/templates/_general.js **/
/**
* cards/arunox/hooks/templates/v.js
*
* Some Javascript codes without further description
*
* @author Sparky
* @copyright 2025 ARUNOX
* @version 1.0.0
* @since 1.0.0 File is created
*/
ARUNOX.Hooks.register( 'template->beforeRender.OLD', function() {
if( ARUNOX.Tools?.NFC.exists() ) {
ARUNOX.Tools.NFC.onRead = null;
ARUNOX.Tools.NFC.onError = null;
ARUNOX.Tools.NFC.onError = null;
ARUNOX.Tools.NFC.onPrompt = null;
ARUNOX.Tools.NFC.onDenied = null;
ARUNOX.Tools.NFC.onGranted = null;
ARUNOX.Tools.NFC.onWritten = null;
ARUNOX.Tools.NFC.init();
}
}, 0 );
ARUNOX.Hooks.register( 'wrapper->beforeRender.OLD', function() {
if( this.instance.storage.config.title == 'PWAs' ) {
// console.log( 'wrapper->beforeRender', this.instance.element, this.instance.storage.config );
}
}, 0 );
/** Datei: plugins/cards/assets/js/arunox/nfc.js **/
window.ARUNOX ??= {};
ARUNOX.Tools ??= {};
ARUNOX.Tools.NFC = {
ndef : null,
abort : null,
onRead : null,
onAbort : null,
onError : null,
onPrompt : null,
onDenied : null,
onGranted : null,
onWritten : null,
log : function() {
// console.log( 'NFC Reader', ...arguments );
},
stop : function() {
this.log( 'Stop' );
this.abort.abort();
},
exists : function() { return typeof NDEFReader !== 'undefined'; },
write : async function( records, onSuccess, onAbort ) {
this.ndef = new NDEFReader();
this.abort = new AbortController();
this.abort.signal.onabort = event => {
this.log( 'OnAbortWrite', event );
this._call( onAbort || this.onAbort, event );
};
try {
await this.ndef.write({ records: records }, { signal: this.abort.signal })
.then( () => {
let data = [];
for( let record of records ) {
data.push({
serial : null,
type : record.recordType || null,
media : record.mediaType || null,
data : record.data || null,
lang : record.lang || null,
encoding: record.encoding || null,
});
}
this.log( 'Start Write', data );
return this._call( onSuccess || this.onWritten );;
});
}
catch( error ) {
this.log( 'onErrorWrite', error );
this._call( onError || this.onError, error );
}
},
read : async function( onSuccess, onAbort, onError ) {
this.ndef = new NDEFReader();
this.abort = new AbortController();
this.abort.signal.onabort = event => {
this.log( 'OnAbortRead', event );
this._call( onAbort || this.onAbort, event );
};
this.log( 'Read' );
try {
const decoder = new TextDecoder();
await this.ndef.scan({ signal: this.abort.signal });
this.log( 'Waiting' );
this.ndef.addEventListener( 'readingerror', event => {
this.log( 'onReadingError', 'Argh! Cannot read data from the NFC tag. Try another one?', event );
this._call( onError || this.onError, event );
});
this.ndef.addEventListener( 'reading', ({ message, serialNumber }) => {
this.log( 'Reading' );
let records = [];
for( let [ index, record ] of Object.entries( message.records ) ) {
if( record.data ) {
let data = decoder.decode( record.data );
records.push({
serial : serialNumber,
type : record.recordType,
media : record.mediaType,
data : data,
lang : record.lang,
encoding: record.encoding,
});
}
else {
records.push({
serial : serialNumber,
type : record.recordType,
media : record.mediaType,
lang : record.lang,
encoding: record.encoding,
});
}
}
return this._call( onSuccess || this.onRead, records, serialNumber );;
});
}
catch( error ) {
this.log( 'onErrorRead', error );
this._call( onError || this.onError, error );
}
},
init : function( onGranted, onPrompt, onDenied ) {
navigator.permissions.query({ name: 'nfc' }).then( permissionStatus => {
if( permissionStatus.state === 'prompt' ) this._call( onPrompt || this.onPrompt );
else if( permissionStatus.state === 'denied' ) this._call( onDenied || this.onDenied );
else if( permissionStatus.state === 'granted' ) {
this._call( onGranted || this.onGranted );
if( this._init ) return;
this._init = true;
this.read();
}
permissionStatus.onchange = _ => {
if( permissionStatus.state == 'granted' ) this._call( onGranted || this.onGranted );
else if( permissionStatus.state === 'denied' ) this._call( onDenied || this.onDenied );
};
});
},
_init : false,
_call : function( callback, ...args ) {
if( typeof callback === 'function' ) {
return callback.apply( this, args );
}
return false;
},
};
/** Datei: plugins/cards/assets/js/cards.js **/
/**
* cards/cards.js
*
* Some Javascript codes without further description
*
* @author Sparky
* @copyright 2025 ARUNOX
* @version 1.0.0
* @since 1.0.0 File is created
*/
window.ARUNOX = window.ARUNOX || {};
ARUNOX.Tools ??= {};
ARUNOX.Tools.NFCReadLogin = function( records, serial ) {
for( const record of records ) {
const matches = record.data?.match(/ARXCID=(.*)/ );
if( matches ) {
ARUNOX.Router.handle({
route : `/cards/verify?redirect=/rest${location.pathname}`,
method : 'POST',
}, {
action : 'login',
cardid : matches[1],
serial : serial,
}, {}, 'rest' );
return;
}
}
ARUNOX.Theme.wrapper.render('notifications', {
content : [
{
type : 'generator',
generator : 'components/notification',
config : {
type : 'warning',
icon : 'bi bi-exclamation-triangle-fill',
duration : null,
title : 'Not recognized',
message : 'Unknown card scanned',
content : []
}
}
]
});
}
ARUNOX.Tools.NFCManuelLogin = async function( value ) {
if( value?.length == 9 ) {
const result = await ARUNOX.Router.handle({
route : `/cards/verify?redirect=/rest${location.pathname}`,
method : 'POST',
}, {
action : 'login',
cardid : value,
serial : null,
}, {}, 'rest' );
ARUNOX.Tools.Keyboard.clear();
return;
}
if( value?.length > 9 ) {
ARUNOX.Theme.wrapper.render('notifications', {
content : [
{
type : 'generator',
generator : 'components/notification',
config : {
type : 'warning',
icon : 'bi bi-exclamation-triangle-fill',
duration : null,
title : 'Card Number',
message : 'Maximum length reached',
content : []
}
}
]
});
ARUNOX.Tools.Keyboard.clear();
return;
}
}
/** Datei: plugins/cards/config.php **/
* @copyright 2025 ARUNOX
* @version 0.1.0
*
* @since 0.1.0 initial version
*/