Initial Version

This commit is contained in:
root
2025-12-21 10:09:54 -05:00
commit 2fbddd7dbc
366 changed files with 41999 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
<?php
namespace KTXF\Resource\Exceptions;
use Exception;
/**
* Exemption for unauthorized access
*/
class InvalidParameterException extends Exception {
/**
* @param string $message The exception message
* @param int $code The exception code
* @param Exception|null $previous The previous exception
*/
public function __construct($message = "Invalid Parameter", $code = 0, ?Exception $previous = null) {
parent::__construct($message, $code, $previous);
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace KTXF\Resource\Exceptions;
use Exception;
/**
* Exemption for unauthorized access
*/
class UnauthorizedException extends Exception {
/**
* @param string $message The exception message
* @param int $code The exception code
* @param Exception|null $previous The previous exception
*/
public function __construct($message = "Unauthorized access", $code = 0, ?Exception $previous = null) {
parent::__construct($message, $code, $previous);
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace KTXF\Resource\Exceptions;
use Exception;
/**
* Exemption for unauthorized access
*/
class UnsupportedException extends Exception {
/**
* @param string $message The exception message
* @param int $code The exception code
* @param Exception|null $previous The previous exception
*/
public function __construct($message = "Function is not supported", $code = 0, ?Exception $previous = null) {
parent::__construct($message, $code, $previous);
}
}

View File

@@ -0,0 +1,129 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace KTXF\Resource\Filter;
class Filter implements IFilter {
protected array $attributes = [];
protected array $conditions = [];
/**
* Constructor
*
* @since 2025.05.01
*
* @param array<string,string> $attributes List of attributes that can be used in the filter
*/
public function __construct(array $attributes) {
foreach ($attributes as $id => $value) {
// separate the value into components
[$type, $length, $comparatorDefault, $comparatorSupported] = explode(':', $value);
// validate the components
$this->attributes[$id]['type'] = match ($type) {
's' => 'string',
'i' => 'integer',
'b' => 'boolean',
'a' => 'array',
default => throw new \InvalidArgumentException("Invalid type '$type' for attribute '$id'."),
};
$this->attributes[$id]['length'] = (int)$length;
$this->attributes[$id]['comparatorDefault'] = FilterComparisonOperator::from((int)$comparatorDefault);
// comparatorSupported is a bitmask of supported comparators
if ($comparatorSupported !== '0') {
$comparators = FilterComparisonOperator::cases();
foreach ($comparators as $comparator) {
if (($comparatorSupported & $comparator->value) === $comparator->value) {
$this->attributes[$id]['comparatorSupported'][] = $comparator;
}
}
} else {
$this->attributes[$id]['comparatorSupported'] = [];
}
}
}
/**
* List of attributes that can be used in the filter
*
* @since 2025.05.01
*
* @return array<string>
*/
public function attributes(): array {
return $this->attributes;
}
/**
* List of comparison operators that can be used in the filter
*
* @since 2025.05.01
*/
public function comparators(): string {
return FilterComparisonOperator::class;
}
/**
* List of conjunction operators that can be used in the filter
*
* @since 2025.05.01
*/
public function conjunctions(): string {
return FilterConjunctionOperator::class;
}
/**
* Define a condition for the filter
*
* @since 2025.05.01
*/
public function condition(string $attribute, mixed $value, ?FilterComparisonOperator $comparator = null, ?FilterConjunctionOperator $conjunction = null): void {
// check if the attribute is defined in the filter
if (!isset($this->attributes[$attribute])) {
throw new \InvalidArgumentException("Attribute '$attribute' is not defined in the filter");
}
// check if comparator is valid and supported for the attribute
if ($comparator === null) {
$comparator = $this->attributes[$attribute]['comparatorDefault'];
}
if (!in_array($comparator, $this->attributes[$attribute]['comparatorSupported'], true)) {
throw new \InvalidArgumentException("Comparator '$comparator' is not supported for attribute '$attribute'");
}
// check if the value type is valid for the attribute
if ($this->attributes[$attribute]['type'] !== gettype($value)) {
throw new \InvalidArgumentException("Value for attribute '$attribute' must be of type '" . $this->attributes[$attribute]['type'] . "'");
}
// check if the value length is within the defined limit
if ($this->attributes[$attribute]['type'] === 'array' && $this->attributes[$attribute]['length'] <= count($value)) {
throw new \InvalidArgumentException("Value for attribute '$attribute' exceeds the maximum length of " . $this->attributes[$attribute]['length'] . " items");
}
if ($this->attributes[$attribute]['type'] === 'string' && $this->attributes[$attribute]['length'] <= mb_strlen($value)) {
throw new \InvalidArgumentException("Value for attribute '$attribute' exceeds the maximum length of " . $this->attributes[$attribute]['length'] . " characters");
}
$this->conditions[$attribute] = [
'attribute' => $attribute,
'value' => $value,
'comparator' => $comparator,
'conjunction' => $conjunction,
];
}
/**
* List of defined conditions
*
* @since 2025.05.01
*
* @return array<string, array{attribute:string, value:mixed, comparator:FilterComparisonOperator, conjunction:FilterConjunctionOperator}>
*/
public function conditions(): array {
return $this->conditions;
}
}

View File

@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace KTXF\Resource\Filter;
enum FilterComparisonOperator: int {
case EQ = 1;
case NEQ = 2;
case GT = 4;
case LT = 8;
case GTE = 16;
case LTE = 32;
case IN = 64;
case NIN = 128;
case LIKE = 256;
case NLIKE = 512;
}

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace KTXF\Resource\Filter;
enum FilterConjunctionOperator: string {
case NONE = '';
case AND = 'AND';
case OR = 'OR';
}

View File

@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace KTXF\Resource\Filter;
interface IFilter {
/**
* List of attributes that can be used in the filter
*
* @since 2025.05.01
*
* @return array<string>
*/
public function attributes(): array;
/**
* List of comparison operators that can be used in the filter
*
* @since 2025.05.01
*/
public function comparators(): string;
/**
* List of conjunction operators that can be used in the filter
*
* @since 2025.05.01
*/
public function conjunctions(): string;
/**
* Define a filter condition
*
* @since 2025.05.01
*/
public function condition(string $property, mixed $value, ?FilterComparisonOperator $comparator = null, ?FilterConjunctionOperator $conjunction = null): void;
/**
* list of defined conditions
*
* @since 2025.05.01
*
* @return array<int, array{attribute:string, value:mixed, comparator:FilterComparisonOperator, conjunction:FilterConjunctionOperator}>
*/
public function conditions(): array;
}

View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace KTXF\Resource\Provider;
/**
* Resource Provider Interface
*/
interface ProviderInterface
{
public const TYPE_AUTHENTICATION = 'authentication';
public const TYPE_PEOPLE = 'people';
public const TYPE_CHRONO = 'chrono';
public const TYPE_FILES = 'files';
public const TYPE_MAIL = 'mail';
/**
* Provider type (e.g., 'authentication', 'storage', 'notification')
*/
public function type(): string;
/**
* Unique identifier for this provider instance (e.g., UUID or 'password-auth')
*/
public function identifier(): string;
/**
* Human-friendly name (e.g., 'Password Authentication Provider', 'S3 Storage Provider')
*/
public function label(): string;
/**
* human-friendly description of this provider's functionality
*/
public function description(): string;
/**
* Icon representing this provider (e.g., FontAwesome class)
*/
public function icon(): string;
}

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace KTXF\Resource\Range;
interface IRange {
/**
* Gets the type of this range
*
* @since 1.0.0
*/
public function type(): RangeType;
}

View File

@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace KTXF\Resource\Range;
use DateTimeInterface;
interface IRangeDate extends IRange {
/**
*
* @since 1.0.0
*/
public function getStart(): DateTimeInterface;
/**
*
* @since 1.0.0
*/
public function setStart(DateTimeInterface $value): void;
/**
*
* @since 1.0.0
*/
public function getEnd(): DateTimeInterface;
/**
*
* @since 1.0.0
*/
public function setEnd(DateTimeInterface $value): void;
}

View File

@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace KTXF\Resource\Range;
interface IRangeTally extends IRange {
/**
* Gets the anchor type of the range
*
* @since 1.0.0
*/
public function getAnchor(): RangeAnchorType;
/**
* Sets the anchor type of the range
*
* @since 1.0.0
*/
public function setAnchor(RangeAnchorType $value): void;
/**
* Gets the start position of the range
*
* @since 1.0.0
*/
public function getPosition(): string|int;
/**
* Sets the start position of the range
*
* @since 1.0.0
*/
public function setPosition(string|int $value): void;
/**
* Gets the count of items in the range
*
* @since 1.0.0
*/
public function getTally(): int;
/**
* Sets the count of items in the range
*
* @since 1.0.0
*/
public function setTally(int $value): void;
}

View File

@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace KTXF\Resource\Range;
class Range implements IRange {
/**
* Returns the type of the range
*
* @since 1.0.0
*/
public function type(): RangeType {
return RangeType::NONE;
}
}

View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace KTXF\Resource\Range;
enum RangeAnchorType: string {
case RELATIVE = 'relative'; // A relative anchor is used to indicate a starting position based on a record identifier
case ABSOLUTE = 'absolute'; // A absolute anchor is used to indicate a starting position based on record count
}

View File

@@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace KTXF\Resource\Range;
use DateTime;
use DateTimeInterface;
class RangeDate extends Range implements IRangeDate {
protected DateTimeInterface $start;
protected DateTimeInterface $end;
/**
* Returns the type of the range
*
* @since 1.0.0
*/
public function type(): RangeType {
return RangeType::DATE;
}
/**
* Gets the start date of the range
*
* @since 1.0.0
*/
public function getStart(): DateTimeInterface {
return $this->start;
}
/**
* Sets the start date of the range
*
* @since 1.0.0
*/
public function setStart(DateTimeInterface $value): void {
$this->start = $value;
}
/**
* Gets the end date of the range
*
* @since 1.0.0
*/
public function getEnd(): DateTimeInterface {
return $this->end;
}
/**
* Sets the end date of the range
*
* @since 1.0.0
*/
public function setEnd(DateTimeInterface $value): void {
$this->end = $value;
}
}

View File

@@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace KTXF\Resource\Range;
class RangeTally extends Range implements IRangeTally {
protected RangeAnchorType $anchor = RangeAnchorType::ABSOLUTE;
protected string|int $position = 0;
protected int $tally = 32;
/**
* Returns the type of the range
*
* @since 1.0.0
*/
public function type(): RangeType {
return RangeType::TALLY;
}
/**
* Gets the anchor type of the range
*
* @since 1.0.0
*/
public function getAnchor(): RangeAnchorType {
return $this->anchor;
}
/**
* Sets the anchor type of the range
*
* @since 1.0.0
*/
public function setAnchor(RangeAnchorType $value): void {
$this->anchor = $value;
}
/**
* Gets the start position of the range
*
* @since 1.0.0
*/
public function getPosition(): string|int {
return $this->position;
}
/**
* Sets the start position of the range
*
* @since 1.0.0
*/
public function setPosition(string|int $value): void {
$this->position = $value;
}
/**
* Gets the count of items in the range
*
* @since 1.0.0
*/
public function getTally(): int {
return $this->tally;
}
/**
* Sets the count of items in the range
*
* @since 1.0.0
*/
public function setTally(int $value): void {
$this->tally = $value;
}
}

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace KTXF\Resource\Range;
enum RangeType: string {
case NONE = 'none';
case DATE = 'date';
case TALLY = 'tally';
}

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace KTXF\Resource\Selector;
/**
* Collection-level selector
*/
class CollectionSelector extends SelectorAbstract {
protected array $keyTypes = ['string', 'integer'];
protected array $valueTypes = ['boolean', EntitySelector::class];
protected string $nestedSelector = EntitySelector::class;
protected string $selectorName = 'CollectionSelector';
}

View File

@@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace KTXF\Resource\Selector;
/**
* Entity-level selector (leaf node)
*/
class EntitySelector extends SelectorAbstract {
protected array $keyTypes = ['string', 'integer'];
protected array $valueTypes = ['boolean', 'array', 'string', 'integer'];
protected string $selectorName = 'EntitySelector';
public function append($value): void {
if (!is_string($value) && !is_int($value)) {
throw new \InvalidArgumentException('EntitySelector values must be string or int');
}
parent::append($value);
}
public function offsetSet($key, $value): void {
if ($key !== null && !is_int($key)) {
throw new \InvalidArgumentException('EntitySelector does not support associative keys');
}
if (!is_string($value) && !is_int($value)) {
throw new \InvalidArgumentException('EntitySelector values must be string or int');
}
parent::offsetSet($key, $value);
}
/**
* Get all entity identifiers
* @return array<string|int>
*/
public function identifiers(): array {
return $this->getArrayCopy();
}
}

View File

@@ -0,0 +1,129 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace KTXF\Resource\Selector;
use JsonSerializable;
use KTXF\Json\JsonDeserializable;
/**
* Abstract base class for all selector types
* Provides common functionality for hierarchical selectors
*/
abstract class SelectorAbstract extends \ArrayObject implements JsonSerializable, JsonDeserializable {
protected const TYPE_STRING = 'string';
protected const TYPE_INT = 'int';
protected const TYPE_BOOL = 'bool';
/** @var array<mixed> Allowed key types: 'string', 'int' */
protected array $keyTypes = [];
/** @var array<mixed> Allowed scalar value types: 'string', 'int', 'bool' */
protected array $valueTypes = [];
/** @var class-string selector class for nested structures */
protected string $nestedSelector = SelectorAbstract::class;
/** @var string Human-readable name for this selector type */
protected string $selectorName = 'Selector';
/**
* Serialize to JSON-compatible array
*
* @return array
*/
public function jsonSerialize(): array {
$result = [];
foreach ($this as $key => $value) {
if ($value instanceof JsonSerializable) {
$result[$key] = $value->jsonSerialize();
} else {
$result[$key] = $value;
}
}
return $result;
}
/**
* Deserialize from JSON-compatible array
* @param array $data
* @return void
* @throws \InvalidArgumentException
*/
public function jsonDeserialize(array|string $data): static {
if (is_string($data)) {
$data = json_decode($data, true);
}
foreach ($data as $key => $value) {
if ($this->nestedSelector !== null && is_array($value)) {
$selector = new $this->nestedSelector();
$selector->jsonDeserialize($value);
$this->offsetSet($key, $selector);
} else {
$this->offsetSet($key, $value);
}
}
return $this;
}
/**
* Validate if a key is of the correct type for this selector
*
* @param mixed $key
* @return bool
*/
protected function validateKey(mixed $key): bool {
return in_array(gettype($key), $this->keyTypes, true);
}
/**
* Validate if a value is of the correct type for this selector
*
* @param mixed $value
* @return bool
*/
protected function validateValue(mixed $value): bool {
if ($this->nestedSelector !== null && $value instanceof $this->nestedSelector) {
return true;
}
return in_array(gettype($value), $this->valueTypes, true);
}
/**
* Override offsetSet to enforce type checking
*
* @param mixed $key
* @param mixed $value
* @return void
* @throws \InvalidArgumentException
*/
#[\Override]
public function offsetSet($key, $value): void {
if (!$this->validateKey($key)) {
throw new \InvalidArgumentException("{$this->selectorName} keys must be one of [" . implode(', ', $this->keyTypes) . "], got " . gettype($key));
}
if (!$this->validateValue($value)) {
throw new \InvalidArgumentException("{$this->selectorName} values must be one of [" . implode(', ', $this->valueTypes) . "], got " . gettype($value));
}
parent::offsetSet($key, $value);
}
/**
* Get all identifiers (keys or values depending on selector type)
*
* @return array
*/
public function identifiers(): array {
return array_keys($this->getArrayCopy());
}
}

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace KTXF\Resource\Selector;
/**
* Service-level selector
*/
class ServiceSelector extends SelectorAbstract {
protected array $keyTypes = ['string', 'int'];
protected array $valueTypes = ['boolean', CollectionSelector::class];
protected string $nestedSelector = CollectionSelector::class;
protected string $selectorName = 'CollectionSelector';
}

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace KTXF\Resource\Selector;
/**
* Top-level selector for sources
*/
class SourceSelector extends SelectorAbstract {
protected array $keyTypes = ['string'];
protected array $valueTypes = ['boolean', ServiceSelector::class];
protected string $nestedSelector = ServiceSelector::class;
protected string $selectorName = 'ProviderSelector';
}

View File

@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace KTXF\Resource\Sort;
interface ISort {
/**
* List of available attributes
*
* @since 1.0.0
*
* @return array<string,bool>
*/
public function attributes(): array;
/**
* Define sort condition
*
* @since 1.0.0
*
* @param string $attribute attribute name
* @param bool $direction true for ascending, false for descending
*/
public function condition(string $property, bool $direction): void;
/**
* List of sort conditions
*
* @since 1.0.0
*
* @return array<string,array{attribute:string,direction:bool}>
*/
public function conditions(): array;
}

View File

@@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace KTXF\Resource\Sort;
class Sort implements ISort {
protected array $attributes = [];
protected array $conditions = [];
public function __construct(array $attributes) {
$this->attributes = $attributes;
}
/**
*
* @since 1.0.0
*
* @return array<string,bool>
*/
public function attributes(): array {
return $this->attributes;
}
/**
*
* @since 1.0.0
*
* @param string $attribute attribute name
* @param bool $direction true for ascending, false for descending
*/
public function condition(string $attribute, bool $direction): void {
if (isset($this->attributes[$attribute])) {
$this->conditions[$attribute] = [
'attribute' => $attribute,
'direction' => $direction,
];
}
}
/**
*
* @since 1.0.0
*
* @return array<string,array{attribute:string, direction:bool}>
*/
public function conditions(): array {
return $this->conditions;
}
}