diff --git a/core/lib/Controllers/InitController.php b/core/lib/Controllers/InitController.php index 65a9d74..1df5c07 100644 --- a/core/lib/Controllers/InitController.php +++ b/core/lib/Controllers/InitController.php @@ -30,18 +30,17 @@ class InitController extends ControllerAbstract // modules - filter by permissions $configuration['modules'] = []; foreach ($this->moduleManager->list() as $module) { - if ($module instanceof ModuleBrowserInterface === false) { - continue; - } - // Check if user has permission to view this module // Allow access if user has: {module_handle}, {module_handle}.*, or * permission $handle = $module->handle(); if (!$this->hasModuleViewPermission($handle)) { continue; } - - $configuration['modules'][$handle] = $module->registerBI(); + + $integrations = $module->registerBI(); + if ($integrations !== null) { + $configuration['modules'][$handle] = $integrations; + } } // tenant diff --git a/core/lib/Module/ModuleManager.php b/core/lib/Module/ModuleManager.php index bf9c490..653a4a1 100644 --- a/core/lib/Module/ModuleManager.php +++ b/core/lib/Module/ModuleManager.php @@ -252,6 +252,29 @@ class ModuleManager $this->repository->deposit($moduleEntry); } + /** + * Boot all enabled modules (must be called after container is ready). + */ + public function modulesBoot(): void + { + // Only load modules that are enabled in the database + $modules = $this->list(); + $this->logger->debug('Booting enabled modules', ['count' => count($modules)]); + foreach ($modules as $module) { + $handle = $module->handle(); + try { + $module->boot(); + $this->logger->debug('Module booted', ['handle' => $handle]); + } catch (Exception $e) { + $this->logger->error('Module boot failed: ' . $handle, [ + 'exception' => $e, + 'message' => $e->getMessage(), + 'code' => $e->getCode(), + ]); + } + } + } + /** * Scan filesystem for module directories and return module instances * @@ -315,29 +338,6 @@ class ModuleManager return $modules; } - - /** - * Boot all enabled modules (must be called after container is ready). - */ - public function modulesBoot(): void - { - // Only load modules that are enabled in the database - $modules = $this->list(); - $this->logger->debug('Booting enabled modules', ['count' => count($modules)]); - foreach ($modules as $module) { - $handle = $module->handle(); - try { - $module->boot(); - $this->logger->debug('Module booted', ['handle' => $handle]); - } catch (Exception $e) { - $this->logger->error('Module boot failed: ' . $handle, [ - 'exception' => $e, - 'message' => $e->getMessage(), - 'code' => $e->getCode(), - ]); - } - } - } public function moduleInstance(string $handle, ?string $namespace = null): ?ModuleInstanceInterface { diff --git a/core/lib/Module/ModuleObject.php b/core/lib/Module/ModuleObject.php index 37dd68f..7257a77 100644 --- a/core/lib/Module/ModuleObject.php +++ b/core/lib/Module/ModuleObject.php @@ -4,6 +4,8 @@ namespace KTXC\Module; use JsonSerializable; use KTXC\Module\Store\ModuleEntry; +use KTXF\Module\ModuleBrowserInterface; +use KTXF\Module\ModuleConsoleInterface; use KTXF\Module\ModuleInstanceInterface; /** @@ -97,6 +99,11 @@ class ModuleObject implements JsonSerializable return '0.0.0'; } + public function permissions(): array + { + return $this->instance?->permissions() ?? []; + } + // ===== Computed properties ===== public function needsUpgrade(): bool @@ -153,14 +160,20 @@ class ModuleObject implements JsonSerializable $this->instance?->upgrade(); } - public function bootUi(): array | null + public function registerBI(): array | null { - return $this->instance?->bootUi() ?? null; + if ($this->instance instanceof ModuleBrowserInterface) { + return $this->instance->registerBI(); + } + return null; } - public function permissions(): array + public function registerCI(): array | null { - return $this->instance?->permissions() ?? []; + if ($this->instance instanceof ModuleConsoleInterface) { + return $this->instance->registerCI(); + } + return null; } } diff --git a/core/src/composables/useSnackbar.ts b/core/src/composables/useSnackbar.ts new file mode 100644 index 0000000..5b4a3c9 --- /dev/null +++ b/core/src/composables/useSnackbar.ts @@ -0,0 +1,38 @@ +/** + * Simple snackbar/toast notification composable + * Uses Vuetify's snackbar component + */ +import { ref } from 'vue' + +export interface SnackbarOptions { + message: string + color?: 'success' | 'error' | 'warning' | 'info' | string + timeout?: number +} + +const snackbarVisible = ref(false) +const snackbarMessage = ref('') +const snackbarColor = ref('info') +const snackbarTimeout = ref(3000) + +export function useSnackbar() { + const showSnackbar = (options: SnackbarOptions) => { + snackbarMessage.value = options.message + snackbarColor.value = options.color || 'info' + snackbarTimeout.value = options.timeout || 3000 + snackbarVisible.value = true + } + + const hideSnackbar = () => { + snackbarVisible.value = false + } + + return { + snackbarVisible, + snackbarMessage, + snackbarColor, + snackbarTimeout, + showSnackbar, + hideSnackbar, + } +} diff --git a/core/src/types/moduleTypes.ts b/core/src/types/moduleTypes.ts index 991da3a..13bd7ef 100644 --- a/core/src/types/moduleTypes.ts +++ b/core/src/types/moduleTypes.ts @@ -13,9 +13,21 @@ export interface ModuleObject { export type ModuleCollection = Record; -// Module-side integration types (before prefixing by the loader) -export interface ModuleIntegrationItem { +// Module integration types + +/** + * Base interface for module integrations + */ +export interface ModuleIntegrationBase { id: string; + [key: string]: any; +} + +/** + * UI Navigation integration item + * Represents a single clickable entry in a menu or navigation structure + */ +export interface ModuleIntegrationItem extends ModuleIntegrationBase { type?: 'item'; label: string; caption?: string; @@ -30,8 +42,11 @@ export interface ModuleIntegrationItem { meta?: Record; } -export interface ModuleIntegrationGroup { - id: string; +/** + * UI Navigation integration group + * Represents a group of related navigation items, potentially with its own label and icon + */ +export interface ModuleIntegrationGroup extends ModuleIntegrationBase { type: 'group'; label?: string; caption?: string; @@ -47,5 +62,6 @@ export type ModuleIntegrationEntry = ModuleIntegrationItem | ModuleIntegrationGr export interface ModuleIntegrations { system_menu?: ModuleIntegrationEntry[]; user_menu?: ModuleIntegrationEntry[]; - [key: string]: ModuleIntegrationEntry[] | undefined; + + [key: string]: ModuleIntegrationEntry[] | ModuleIntegrationBase[] | undefined; } diff --git a/package-lock.json b/package-lock.json index 8bff72d..cd08f54 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@tsconfig/node20": "20.1.4", "@typescript-eslint/parser": "^8.18.2", "@vue/compiler-sfc": "^3.5.16", + "dompurify": "^3.3.1", "pinia": "2.3.0", "vee-validate": "^4.15.1", "vite-plugin-vuetify": "2.0.4", @@ -27,6 +28,7 @@ }, "devDependencies": { "@eslint/js": "^9.17.0", + "@types/dompurify": "^3.0.5", "@types/node": "22.10.2", "@vitejs/plugin-vue": "5.2.1", "@vue/eslint-config-prettier": "10.1.0", @@ -747,6 +749,7 @@ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" @@ -758,6 +761,7 @@ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "devOptional": true, "license": "MIT", + "peer": true, "engines": { "node": ">=6.0.0" } @@ -768,6 +772,7 @@ "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" @@ -785,6 +790,7 @@ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -1123,6 +1129,16 @@ "integrity": "sha512-sqgsT69YFeLWf5NtJ4Xq/xAF8p4ZQHlmGW74Nu2tD4+g5fAsposc4ZfaaPixVu4y01BEiDCWLRDCvDM5JOsRxg==", "license": "MIT" }, + "node_modules/@types/dompurify": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz", + "integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/trusted-types": "*" + } + }, "node_modules/@types/eslint": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", @@ -1141,6 +1157,7 @@ "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/eslint": "*", "@types/estree": "*" @@ -1164,11 +1181,17 @@ "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.20.0" } }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "devOptional": true, + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.44.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.44.0.tgz", @@ -1214,7 +1237,6 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.44.0.tgz", "integrity": "sha512-VGMpFQGUQWYT9LfnPcX8ouFojyrZ/2w3K5BucvxL/spdNehccKhB4jUyB1yBCXpr2XFm0jkECxgrpXBW2ipoAw==", "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.44.0", "@typescript-eslint/types": "8.44.0", @@ -1714,6 +1736,7 @@ "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2" @@ -1724,21 +1747,24 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.13.2", @@ -1746,6 +1772,7 @@ "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.13.2", "@webassemblyjs/helper-api-error": "1.13.2", @@ -1757,7 +1784,8 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.14.1", @@ -1765,6 +1793,7 @@ "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -1778,6 +1807,7 @@ "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" } @@ -1788,6 +1818,7 @@ "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@xtuc/long": "4.2.2" } @@ -1797,7 +1828,8 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.14.1", @@ -1805,6 +1837,7 @@ "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -1822,6 +1855,7 @@ "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", @@ -1836,6 +1870,7 @@ "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -1849,6 +1884,7 @@ "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-api-error": "1.13.2", @@ -1864,6 +1900,7 @@ "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" @@ -1874,21 +1911,22 @@ "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", "dev": true, - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "peer": true }, "node_modules/@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true, - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1902,6 +1940,7 @@ "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10.13.0" }, @@ -1923,7 +1962,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -1941,6 +1979,7 @@ "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ajv": "^8.0.0" }, @@ -1959,6 +1998,7 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -1975,7 +2015,8 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/ajv-keywords": { "version": "3.5.2", @@ -2041,6 +2082,7 @@ "integrity": "sha512-wrH5NNqren/QMtKUEEJf7z86YjfqW/2uw3IL3/xpqZUC95SSVIFXYQeeGjL6FT/X68IROu6RMehZQS5foy2BXw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "baseline-browser-mapping": "dist/cli.js" } @@ -2145,7 +2187,8 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "devOptional": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/callsites": { "version": "3.1.0", @@ -2175,7 +2218,8 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "CC-BY-4.0" + "license": "CC-BY-4.0", + "peer": true }, "node_modules/chalk": { "version": "4.1.2", @@ -2237,6 +2281,7 @@ "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=6.0" } @@ -2264,7 +2309,8 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "devOptional": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/concat-map": { "version": "0.0.1", @@ -2350,12 +2396,22 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "license": "MIT" }, + "node_modules/dompurify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz", + "integrity": "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.222", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.222.tgz", "integrity": "sha512-gA7psSwSwQRE60CEoLz6JBCQPIxNeuzB2nL8vE03GK/OHxlvykbLyeiumQy1iH5C2f3YbRAZpGCMT12a/9ih9w==", "dev": true, - "license": "ISC" + "license": "ISC", + "peer": true }, "node_modules/emojis-list": { "version": "3.0.0", @@ -2373,6 +2429,7 @@ "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -2398,7 +2455,8 @@ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/esbuild": { "version": "0.24.2", @@ -2446,6 +2504,7 @@ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=6" } @@ -2467,7 +2526,6 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz", "integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==", "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -2528,7 +2586,6 @@ "integrity": "sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -2749,6 +2806,7 @@ "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.8.x" } @@ -2821,7 +2879,8 @@ "url": "https://opencollective.com/fastify" } ], - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "peer": true }, "node_modules/fastq": { "version": "1.19.1", @@ -2976,7 +3035,8 @@ "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", "dev": true, - "license": "BSD-2-Clause" + "license": "BSD-2-Clause", + "peer": true }, "node_modules/glob/node_modules/brace-expansion": { "version": "1.1.12", @@ -3219,6 +3279,7 @@ "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -3234,6 +3295,7 @@ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -3267,7 +3329,8 @@ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/json-schema-traverse": { "version": "0.4.1", @@ -3335,6 +3398,7 @@ "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=6.11.5" } @@ -3396,7 +3460,8 @@ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/merge2": { "version": "1.4.1", @@ -3426,6 +3491,7 @@ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -3436,6 +3502,7 @@ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "mime-db": "1.52.0" }, @@ -3513,7 +3580,8 @@ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz", "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/normalize-path": { "version": "3.0.0", @@ -3818,7 +3886,6 @@ "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -3877,6 +3944,7 @@ "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "safe-buffer": "^5.1.0" } @@ -3912,6 +3980,7 @@ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -4044,7 +4113,8 @@ "url": "https://feross.org/support" } ], - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/sass": { "version": "1.77.1", @@ -4052,7 +4122,6 @@ "integrity": "sha512-OMEyfirt9XEfyvocduUIOlUSkWOXS/LAt6oblR/ISXCTukyavjex+zQNm51pPCOiFKY1QpWvEH1EeCkgyV3I6w==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -4143,6 +4212,7 @@ "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, "license": "BSD-3-Clause", + "peer": true, "dependencies": { "randombytes": "^2.1.0" } @@ -4192,6 +4262,7 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "devOptional": true, "license": "BSD-3-Clause", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -4211,6 +4282,7 @@ "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -4296,6 +4368,7 @@ "integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=6" }, @@ -4330,6 +4403,7 @@ "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", @@ -4383,6 +4457,7 @@ "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3" }, @@ -4395,7 +4470,8 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/terser-webpack-plugin/node_modules/schema-utils": { "version": "4.3.2", @@ -4403,6 +4479,7 @@ "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -4458,7 +4535,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -4519,7 +4595,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -4599,6 +4674,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" @@ -4653,7 +4729,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.6.tgz", "integrity": "sha512-NSjmUuckPmDU18bHz7QZ+bTYhRR0iA72cs2QAxCqDpafJ0S6qetco0LB3WW2OxlMHS0JmAv+yZ/R3uPmMyGTjQ==", "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.24.2", "postcss": "^8.4.49", @@ -4745,7 +4820,6 @@ "resolved": "https://registry.npmjs.org/vite-plugin-vuetify/-/vite-plugin-vuetify-2.0.4.tgz", "integrity": "sha512-A4cliYUoP/u4AWSRVRvAPKgpgR987Pss7LpFa7s1GvOe8WjgDq92Rt3eVXrvgxGCWvZsPKziVqfHHdCMqeDhfw==", "license": "MIT", - "peer": true, "dependencies": { "@vuetify/loader-shared": "^2.0.3", "debug": "^4.3.3", @@ -4772,7 +4846,6 @@ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz", "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==", "license": "MIT", - "peer": true, "dependencies": { "@vue/compiler-dom": "3.5.13", "@vue/compiler-sfc": "3.5.13", @@ -4989,7 +5062,6 @@ "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.7.6.tgz", "integrity": "sha512-lol0Va5HtMIqZfjccSD5DLv5v31R/asJXzc6s7ULy51PHr1DjXxWylZejhq0kVpMGW64MiV1FmA/p8eYQfOWfQ==", "license": "MIT", - "peer": true, "engines": { "node": "^12.20 || >=14.13" }, @@ -5021,6 +5093,7 @@ "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -5085,6 +5158,7 @@ "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10.13.0" } @@ -5113,6 +5187,7 @@ "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3" }, @@ -5126,6 +5201,7 @@ "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -5140,6 +5216,7 @@ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "engines": { "node": ">=4.0" } @@ -5149,7 +5226,8 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/webpack/node_modules/schema-utils": { "version": "4.3.2", @@ -5157,6 +5235,7 @@ "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", diff --git a/package.json b/package.json index 431bab3..1682a27 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "@tsconfig/node20": "20.1.4", "@typescript-eslint/parser": "^8.18.2", "@vue/compiler-sfc": "^3.5.16", + "dompurify": "^3.3.1", "pinia": "2.3.0", "vee-validate": "^4.15.1", "vite-plugin-vuetify": "2.0.4", @@ -35,6 +36,7 @@ }, "devDependencies": { "@eslint/js": "^9.17.0", + "@types/dompurify": "^3.0.5", "@types/node": "22.10.2", "@vitejs/plugin-vue": "5.2.1", "@vue/eslint-config-prettier": "10.1.0", diff --git a/shared/lib/Mail/Collection/CollectionBaseAbstract.php b/shared/lib/Mail/Collection/CollectionBaseAbstract.php new file mode 100644 index 0000000..17959f9 --- /dev/null +++ b/shared/lib/Mail/Collection/CollectionBaseAbstract.php @@ -0,0 +1,31 @@ + + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace KTXF\Mail\Collection; + +use KTXF\Resource\Provider\Node\NodeBaseAbstract; + +/** + * Abstract Mail Collection Base Class + * + * Provides common implementation for mail collections + * + * @since 2025.05.01 + */ +abstract class CollectionBase extends NodeBaseAbstract implements CollectionBaseInterface { + + protected CollectionPropertiesBaseAbstract $properties; + + /** + * @inheritDoc + */ + public function getProperties(): CollectionPropertiesBaseInterface { + return $this->properties; + } +} diff --git a/shared/lib/Mail/Collection/CollectionBaseInterface.php b/shared/lib/Mail/Collection/CollectionBaseInterface.php new file mode 100644 index 0000000..6d83581 --- /dev/null +++ b/shared/lib/Mail/Collection/CollectionBaseInterface.php @@ -0,0 +1,30 @@ + + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace KTXF\Mail\Collection; + +use KTXF\Resource\Provider\Node\NodeBaseInterface; + +/** + * Mail Collection Base Interface + * + * Interface represents a mailbox/folder in a mail service + * + * @since 2025.05.01 + */ +interface CollectionBaseInterface extends NodeBaseInterface { + + /** + * Gets the collection properties + * + * @since 2025.05.01 + */ + public function getProperties(): CollectionPropertiesBaseInterface|CollectionPropertiesMutableInterface; + +} diff --git a/shared/lib/Mail/Collection/CollectionMutableAbstract.php b/shared/lib/Mail/Collection/CollectionMutableAbstract.php new file mode 100644 index 0000000..3dae2c8 --- /dev/null +++ b/shared/lib/Mail/Collection/CollectionMutableAbstract.php @@ -0,0 +1,49 @@ + + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace KTXF\Mail\Collection; + +use KTXF\Resource\Provider\Node\NodeMutableAbstract; +use KTXF\Resource\Provider\Node\NodePropertiesMutableInterface; + +/** + * Abstract Mail Collection Mutable Class + * + * Provides common implementation for mutable mail collections + * + * @since 2025.05.01 + */ +abstract class CollectionMutableAbstract extends NodeMutableAbstract implements CollectionMutableInterface { + + protected CollectionPropertiesMutableAbstract $properties; + + /** + * @inheritDoc + */ + public function getProperties(): CollectionPropertiesMutableInterface { + return $this->properties; + } + + /** + * @inheritDoc + */ + public function setProperties(NodePropertiesMutableInterface $value): static { + if (!$value instanceof CollectionPropertiesMutableInterface) { + throw new \InvalidArgumentException('Properties must implement CollectionPropertiesMutableInterface'); + } + + // Copy all property values + $this->properties->setLabel($value->getLabel()); + $this->properties->setRole($value->getRole()); + $this->properties->setRank($value->getRank()); + $this->properties->setSubscription($value->getSubscription()); + + return $this; + } +} diff --git a/shared/lib/Mail/Collection/CollectionMutableInterface.php b/shared/lib/Mail/Collection/CollectionMutableInterface.php new file mode 100644 index 0000000..159a688 --- /dev/null +++ b/shared/lib/Mail/Collection/CollectionMutableInterface.php @@ -0,0 +1,32 @@ + + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace KTXF\Mail\Collection; + +use KTXF\Resource\Provider\Node\NodeMutableInterface; + +/** + * Mail Collection Mutable Interface + * + * Interface for altering mailbox/folder properties in a mail service + * + * @since 2025.05.01 + * + * @method static setProperties(CollectionPropertiesMutableInterface $value) + */ +interface CollectionMutableInterface extends CollectionBaseInterface, NodeMutableInterface { + + /** + * Gets the collection properties (mutable) + * + * @since 2025.05.01 + */ + public function getProperties(): CollectionPropertiesMutableInterface; + +} diff --git a/shared/lib/Mail/Collection/CollectionPropertiesBaseAbstract.php b/shared/lib/Mail/Collection/CollectionPropertiesBaseAbstract.php new file mode 100644 index 0000000..7cf60b5 --- /dev/null +++ b/shared/lib/Mail/Collection/CollectionPropertiesBaseAbstract.php @@ -0,0 +1,68 @@ + + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace KTXF\Mail\Collection; + +use KTXF\Resource\Provider\Node\NodePropertiesBaseAbstract; + +/** + * Abstract Mail Collection Properties Base Class + * + * Provides common implementation for mail collection properties + * + * @since 2025.05.01 + */ +abstract class CollectionPropertiesBaseAbstract extends NodePropertiesBaseAbstract implements CollectionPropertiesBaseInterface { + + public const JSON_TYPE = CollectionPropertiesBaseInterface::JSON_TYPE; + + /** + * @inheritDoc + */ + public function total(): int { + return $this->data['total'] ?? 0; + } + + /** + * @inheritDoc + */ + public function unread(): int { + return $this->data['unread'] ?? 0; + } + + /** + * @inheritDoc + */ + public function getLabel(): string { + return $this->data['label'] ?? ''; + } + + /** + * @inheritDoc + */ + public function getRole(): CollectionRoles { + return isset($this->data['role']) + ? ($this->data['role'] instanceof CollectionRoles ? $this->data['role'] : CollectionRoles::from($this->data['role'])) + : CollectionRoles::Custom; + } + + /** + * @inheritDoc + */ + public function getRank(): int { + return $this->data['rank'] ?? 0; + } + + /** + * @inheritDoc + */ + public function getSubscription(): bool { + return $this->data['subscribed'] ?? false; + } +} diff --git a/shared/lib/Mail/Collection/CollectionPropertiesBaseInterface.php b/shared/lib/Mail/Collection/CollectionPropertiesBaseInterface.php new file mode 100644 index 0000000..a6197c0 --- /dev/null +++ b/shared/lib/Mail/Collection/CollectionPropertiesBaseInterface.php @@ -0,0 +1,35 @@ + + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace KTXF\Mail\Collection; + +use KTXF\Resource\Provider\Node\NodePropertiesBaseInterface; + +interface CollectionPropertiesBaseInterface extends NodePropertiesBaseInterface { + + public const JSON_TYPE = 'mail.collection'; + public const JSON_PROPERTY_TOTAL = 'total'; + public const JSON_PROPERTY_UNREAD = 'unread'; + public const JSON_PROPERTY_LABEL = 'label'; + public const JSON_PROPERTY_ROLE = 'role'; + public const JSON_PROPERTY_RANK = 'rank'; + public const JSON_PROPERTY_SUBSCRIPTION = 'subscription'; + + public function total(): int; + + public function unread(): int; + + public function getLabel(): string; + + public function getRole(): CollectionRoles; + + public function getRank(): int; + + public function getSubscription(): bool; +} diff --git a/shared/lib/Mail/Collection/CollectionPropertiesMutableAbstract.php b/shared/lib/Mail/Collection/CollectionPropertiesMutableAbstract.php new file mode 100644 index 0000000..edc820b --- /dev/null +++ b/shared/lib/Mail/Collection/CollectionPropertiesMutableAbstract.php @@ -0,0 +1,65 @@ + + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace KTXF\Mail\Collection; + +use KTXF\Resource\Provider\Node\NodePropertiesMutableAbstract; + +/** + * Abstract Mail Collection Properties Mutable Class + */ +abstract class CollectionPropertiesMutableAbstract extends CollectionPropertiesBaseAbstract implements CollectionPropertiesMutableInterface { + + public const JSON_TYPE = CollectionPropertiesBaseInterface::JSON_TYPE; + + /** + * @inheritDoc + */ + public function jsonDeserialize(array|string $data): static { + if (is_string($data)) { + $data = json_decode($data, true); + } + + $this->data = $data; + + return $this; + } + + /** + * @inheritDoc + */ + public function setLabel(string $value): static { + $this->data['label'] = $value; + return $this; + } + + /** + * @inheritDoc + */ + public function setRole(CollectionRoles $value): static { + $this->data['role'] = $value; + return $this; + } + + /** + * @inheritDoc + */ + public function setRank(int $value): static { + $this->data['rank'] = $value; + return $this; + } + + /** + * @inheritDoc + */ + public function setSubscription(bool $value): static { + $this->data['subscription'] = $value; + return $this; + } +} diff --git a/shared/lib/Mail/Collection/CollectionPropertiesMutableInterface.php b/shared/lib/Mail/Collection/CollectionPropertiesMutableInterface.php new file mode 100644 index 0000000..a16557c --- /dev/null +++ b/shared/lib/Mail/Collection/CollectionPropertiesMutableInterface.php @@ -0,0 +1,26 @@ + + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace KTXF\Mail\Collection; + +use KTXF\Resource\Provider\Node\NodePropertiesMutableInterface; + +interface CollectionPropertiesMutableInterface extends CollectionPropertiesBaseInterface, NodePropertiesMutableInterface { + + public const JSON_TYPE = CollectionPropertiesBaseInterface::JSON_TYPE; + + public function setLabel(string $value); + + public function setRole(CollectionRoles $value): static; + + public function setRank(int $value): static; + + public function setSubscription(bool $value): static; + +} diff --git a/shared/lib/Mail/Collection/ICollectionBase.php b/shared/lib/Mail/Collection/ICollectionBase.php deleted file mode 100644 index c3d3f7d..0000000 --- a/shared/lib/Mail/Collection/ICollectionBase.php +++ /dev/null @@ -1,117 +0,0 @@ - - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -namespace KTXF\Mail\Collection; - -use DateTimeImmutable; -use JsonSerializable; - -/** - * Mail Collection Base Interface - * - * Represents a mailbox/folder in a mail service. - * For future use with full mail providers (IMAP, JMAP, etc.) - * - * @since 2025.05.01 - */ -interface ICollectionBase extends JsonSerializable { - - public const JSON_TYPE = 'mail.collection'; - public const JSON_PROPERTY_TYPE = '@type'; - public const JSON_PROPERTY_PROVIDER = 'provider'; - public const JSON_PROPERTY_SERVICE = 'service'; - public const JSON_PROPERTY_IN = 'in'; - public const JSON_PROPERTY_ID = 'id'; - public const JSON_PROPERTY_LABEL = 'label'; - public const JSON_PROPERTY_ROLE = 'role'; - public const JSON_PROPERTY_TOTAL = 'total'; - public const JSON_PROPERTY_UNREAD = 'unread'; - - /** - * Gets the parent collection identifier (null for root) - * - * @since 2025.05.01 - * - * @return string|int|null - */ - public function in(): string|int|null; - - /** - * Gets the collection identifier - * - * @since 2025.05.01 - * - * @return string|int - */ - public function id(): string|int; - - /** - * Gets the collection label/name - * - * @since 2025.05.01 - * - * @return string - */ - public function getLabel(): string; - - /** - * Gets the collection role - * - * @since 2025.05.01 - * - * @return CollectionRoles - */ - public function getRole(): CollectionRoles; - - /** - * Gets the total message count - * - * @since 2025.05.01 - * - * @return int|null - */ - public function getTotal(): ?int; - - /** - * Gets the unread message count - * - * @since 2025.05.01 - * - * @return int|null - */ - public function getUnread(): ?int; - - /** - * Gets the collection signature/sync token - * - * @since 2025.05.01 - * - * @return string|null - */ - public function getSignature(): ?string; - - /** - * Gets the creation date - * - * @since 2025.05.01 - * - * @return DateTimeImmutable|null - */ - public function created(): ?DateTimeImmutable; - - /** - * Gets the modification date - * - * @since 2025.05.01 - * - * @return DateTimeImmutable|null - */ - public function modified(): ?DateTimeImmutable; - -} diff --git a/shared/lib/Mail/Collection/ICollectionMutable.php b/shared/lib/Mail/Collection/ICollectionMutable.php deleted file mode 100644 index 4ab8ae0..0000000 --- a/shared/lib/Mail/Collection/ICollectionMutable.php +++ /dev/null @@ -1,98 +0,0 @@ - - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -namespace KTXF\Mail\Collection; - -/** - * Mail Collection Mutable Interface - * - * Interface for creating and modifying mail collections with fluent setters. - * - * @since 2025.05.01 - */ -interface ICollectionMutable extends ICollectionBase { - - /** - * Sets the parent collection identifier - * - * @since 2025.05.01 - * - * @param string|int|null $in Parent collection ID or null for root - * - * @return self - */ - public function setIn(string|int|null $in): self; - - /** - * Sets the collection identifier - * - * @since 2025.05.01 - * - * @param string|int $id - * - * @return self - */ - public function setId(string|int $id): self; - - /** - * Sets the collection label/name - * - * @since 2025.05.01 - * - * @param string $label - * - * @return self - */ - public function setLabel(string $label): self; - - /** - * Sets the collection role - * - * @since 2025.05.01 - * - * @param CollectionRoles $role - * - * @return self - */ - public function setRole(CollectionRoles $role): self; - - /** - * Sets the total message count - * - * @since 2025.05.01 - * - * @param int|null $total - * - * @return self - */ - public function setTotal(?int $total): self; - - /** - * Sets the unread message count - * - * @since 2025.05.01 - * - * @param int|null $unread - * - * @return self - */ - public function setUnread(?int $unread): self; - - /** - * Sets the collection signature/sync token - * - * @since 2025.05.01 - * - * @param string|null $signature - * - * @return self - */ - public function setSignature(?string $signature): self; - -} diff --git a/shared/lib/Mail/Entity/EntityBaseAbstract.php b/shared/lib/Mail/Entity/EntityBaseAbstract.php new file mode 100644 index 0000000..a88fcb8 --- /dev/null +++ b/shared/lib/Mail/Entity/EntityBaseAbstract.php @@ -0,0 +1,32 @@ + + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace KTXF\Mail\Entity; + +use KTXF\Mail\Object\MessagePropertiesBaseInterface; +use KTXF\Resource\Provider\Node\NodeBaseAbstract; + +/** + * Abstract Mail Entity Base Class + * + * Provides common implementation for mail entities + * + * @since 2025.05.01 + */ +abstract class EntityBaseAbstract extends NodeBaseAbstract implements EntityBaseInterface { + + protected MessagePropertiesBaseInterface $properties; + + /** + * @inheritDoc + */ + public function getProperties(): MessagePropertiesBaseInterface { + return $this->properties; + } +} diff --git a/shared/lib/Mail/Entity/EntityBaseInterface.php b/shared/lib/Mail/Entity/EntityBaseInterface.php new file mode 100644 index 0000000..3a7b054 --- /dev/null +++ b/shared/lib/Mail/Entity/EntityBaseInterface.php @@ -0,0 +1,27 @@ + + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace KTXF\Mail\Entity; + +use KTXF\Mail\Object\MessagePropertiesBaseInterface; +use KTXF\Mail\Object\MessagePropertiesMutableInterface; +use KTXF\Resource\Provider\Node\NodeBaseInterface; + +interface EntityBaseInterface extends NodeBaseInterface { + + public const JSON_TYPE = 'mail.entity'; + + /** + * Gets the entity properties + * + * @since 2025.05.01 + */ + public function getProperties(): MessagePropertiesBaseInterface|MessagePropertiesMutableInterface; + +} diff --git a/shared/lib/Mail/Entity/EntityMutableAbstract.php b/shared/lib/Mail/Entity/EntityMutableAbstract.php new file mode 100644 index 0000000..1148120 --- /dev/null +++ b/shared/lib/Mail/Entity/EntityMutableAbstract.php @@ -0,0 +1,48 @@ + + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace KTXF\Mail\Entity; + +use KTXF\Mail\Object\MessagePropertiesMutableInterface; +use KTXF\Resource\Provider\Node\NodeMutableAbstract; +use KTXF\Resource\Provider\Node\NodePropertiesMutableInterface; + +/** + * Abstract Mail Entity Mutable Class + * + * Provides common implementation for mutable mail entities + * + * @since 2025.05.01 + */ +abstract class EntityMutableAbstract extends NodeMutableAbstract implements EntityMutableInterface { + + public const JSON_TYPE = EntityMutableInterface::JSON_TYPE; + + protected MessagePropertiesMutableInterface $properties; + + /** + * @inheritDoc + */ + public function getProperties(): MessagePropertiesMutableInterface { + return $this->properties; + } + + /** + * @inheritDoc + */ + public function setProperties(NodePropertiesMutableInterface $value): static { + if (!$value instanceof MessagePropertiesMutableInterface) { + throw new \InvalidArgumentException('Properties must implement MessagePropertiesMutableInterface'); + } + + $this->properties = $value; + + return $this; + } +} diff --git a/shared/lib/Mail/Entity/EntityMutableInterface.php b/shared/lib/Mail/Entity/EntityMutableInterface.php new file mode 100644 index 0000000..88bb136 --- /dev/null +++ b/shared/lib/Mail/Entity/EntityMutableInterface.php @@ -0,0 +1,29 @@ + + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace KTXF\Mail\Entity; + +use KTXF\Mail\Object\MessagePropertiesMutableInterface; +use KTXF\Resource\Provider\Node\NodeMutableInterface; + +/** + * @method static setProperties(MessagePropertiesMutableInterface $value) + */ +interface EntityMutableInterface extends EntityBaseInterface, NodeMutableInterface { + + public const JSON_TYPE = EntityBaseInterface::JSON_TYPE; + + /** + * Gets the entity properties (mutable) + * + * @since 2025.05.01 + */ + public function getProperties(): MessagePropertiesMutableInterface; + +} diff --git a/shared/lib/Mail/Entity/IMessageBase.php b/shared/lib/Mail/Entity/IMessageBase.php deleted file mode 100644 index 235d425..0000000 --- a/shared/lib/Mail/Entity/IMessageBase.php +++ /dev/null @@ -1,176 +0,0 @@ - - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -namespace KTXF\Mail\Entity; - -use DateTimeImmutable; -use JsonSerializable; - -/** - * Mail Message Base Interface - * - * Read-only interface for mail message entities. - * - * @since 2025.05.01 - */ -interface IMessageBase extends JsonSerializable { - - public const JSON_TYPE = 'mail.message'; - public const JSON_PROPERTY_TYPE = '@type'; - public const JSON_PROPERTY_ID = 'id'; - public const JSON_PROPERTY_SUBJECT = 'subject'; - public const JSON_PROPERTY_FROM = 'from'; - public const JSON_PROPERTY_REPLY_TO = 'replyTo'; - public const JSON_PROPERTY_TO = 'to'; - public const JSON_PROPERTY_CC = 'cc'; - public const JSON_PROPERTY_BCC = 'bcc'; - public const JSON_PROPERTY_DATE = 'date'; - public const JSON_PROPERTY_BODY_TEXT = 'bodyText'; - public const JSON_PROPERTY_BODY_HTML = 'bodyHtml'; - public const JSON_PROPERTY_ATTACHMENTS = 'attachments'; - public const JSON_PROPERTY_HEADERS = 'headers'; - - /** - * Gets the message identifier - * - * @since 2025.05.01 - * - * @return string|null Message ID or null for unsent messages - */ - public function getId(): ?string; - - /** - * Gets the message subject - * - * @since 2025.05.01 - * - * @return string - */ - public function getSubject(): string; - - /** - * Gets the sender address - * - * @since 2025.05.01 - * - * @return IAddress|null - */ - public function getFrom(): ?IAddress; - - /** - * Gets the reply-to address - * - * @since 2025.05.01 - * - * @return IAddress|null - */ - public function getReplyTo(): ?IAddress; - - /** - * Gets the primary recipients (To) - * - * @since 2025.05.01 - * - * @return array - */ - public function getTo(): array; - - /** - * Gets the carbon copy recipients (CC) - * - * @since 2025.05.01 - * - * @return array - */ - public function getCc(): array; - - /** - * Gets the blind carbon copy recipients (BCC) - * - * @since 2025.05.01 - * - * @return array - */ - public function getBcc(): array; - - /** - * Gets the message date - * - * @since 2025.05.01 - * - * @return DateTimeImmutable|null - */ - public function getDate(): ?DateTimeImmutable; - - /** - * Gets the plain text body - * - * @since 2025.05.01 - * - * @return string|null - */ - public function getBodyText(): ?string; - - /** - * Gets the HTML body - * - * @since 2025.05.01 - * - * @return string|null - */ - public function getBodyHtml(): ?string; - - /** - * Gets the attachments - * - * @since 2025.05.01 - * - * @return array - */ - public function getAttachments(): array; - - /** - * Gets custom headers - * - * @since 2025.05.01 - * - * @return array Header name => value - */ - public function getHeaders(): array; - - /** - * Gets a specific header value - * - * @since 2025.05.01 - * - * @param string $name Header name - * - * @return string|null Header value or null if not set - */ - public function getHeader(string $name): ?string; - - /** - * Checks if the message has any recipients - * - * @since 2025.05.01 - * - * @return bool True if To, CC, or BCC has at least one recipient - */ - public function hasRecipients(): bool; - - /** - * Checks if the message has any body content - * - * @since 2025.05.01 - * - * @return bool True if text or HTML body is set - */ - public function hasBody(): bool; - -} diff --git a/shared/lib/Mail/Entity/IMessageMutable.php b/shared/lib/Mail/Entity/IMessageMutable.php deleted file mode 100644 index 7be4b3a..0000000 --- a/shared/lib/Mail/Entity/IMessageMutable.php +++ /dev/null @@ -1,211 +0,0 @@ - - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -namespace KTXF\Mail\Entity; - -use DateTimeImmutable; - -/** - * Mail Message Mutable Interface - * - * Interface for composing and modifying mail messages with fluent setters. - * - * @since 2025.05.01 - */ -interface IMessageMutable extends IMessageBase { - - /** - * Sets the message identifier - * - * @since 2025.05.01 - * - * @param string|null $id - * - * @return self - */ - public function setId(?string $id): self; - - /** - * Sets the message subject - * - * @since 2025.05.01 - * - * @param string $subject - * - * @return self - */ - public function setSubject(string $subject): self; - - /** - * Sets the sender address - * - * @since 2025.05.01 - * - * @param IAddress|null $from - * - * @return self - */ - public function setFrom(?IAddress $from): self; - - /** - * Sets the reply-to address - * - * @since 2025.05.01 - * - * @param IAddress|null $replyTo - * - * @return self - */ - public function setReplyTo(?IAddress $replyTo): self; - - /** - * Sets the primary recipients (To) - * - * @since 2025.05.01 - * - * @param array $to - * - * @return self - */ - public function setTo(array $to): self; - - /** - * Adds a primary recipient (To) - * - * @since 2025.05.01 - * - * @param IAddress $address - * - * @return self - */ - public function addTo(IAddress $address): self; - - /** - * Sets the carbon copy recipients (CC) - * - * @since 2025.05.01 - * - * @param array $cc - * - * @return self - */ - public function setCc(array $cc): self; - - /** - * Adds a carbon copy recipient (CC) - * - * @since 2025.05.01 - * - * @param IAddress $address - * - * @return self - */ - public function addCc(IAddress $address): self; - - /** - * Sets the blind carbon copy recipients (BCC) - * - * @since 2025.05.01 - * - * @param array $bcc - * - * @return self - */ - public function setBcc(array $bcc): self; - - /** - * Adds a blind carbon copy recipient (BCC) - * - * @since 2025.05.01 - * - * @param IAddress $address - * - * @return self - */ - public function addBcc(IAddress $address): self; - - /** - * Sets the message date - * - * @since 2025.05.01 - * - * @param DateTimeImmutable|null $date - * - * @return self - */ - public function setDate(?DateTimeImmutable $date): self; - - /** - * Sets the plain text body - * - * @since 2025.05.01 - * - * @param string|null $text - * - * @return self - */ - public function setBodyText(?string $text): self; - - /** - * Sets the HTML body - * - * @since 2025.05.01 - * - * @param string|null $html - * - * @return self - */ - public function setBodyHtml(?string $html): self; - - /** - * Sets the attachments - * - * @since 2025.05.01 - * - * @param array $attachments - * - * @return self - */ - public function setAttachments(array $attachments): self; - - /** - * Adds an attachment - * - * @since 2025.05.01 - * - * @param IAttachment $attachment - * - * @return self - */ - public function addAttachment(IAttachment $attachment): self; - - /** - * Sets custom headers - * - * @since 2025.05.01 - * - * @param array $headers Header name => value - * - * @return self - */ - public function setHeaders(array $headers): self; - - /** - * Sets a specific header value - * - * @since 2025.05.01 - * - * @param string $name Header name - * @param string $value Header value - * - * @return self - */ - public function setHeader(string $name, string $value): self; - -} diff --git a/shared/lib/Mail/Entity/Message.php b/shared/lib/Mail/Entity/Message.php deleted file mode 100644 index e2136a5..0000000 --- a/shared/lib/Mail/Entity/Message.php +++ /dev/null @@ -1,383 +0,0 @@ - - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -namespace KTXF\Mail\Entity; - -use DateTimeImmutable; - -/** - * Mail Message Implementation - * - * Concrete implementation of IMessageMutable for composing mail messages. - * - * @since 2025.05.01 - */ -class Message implements IMessageMutable { - - private ?string $id = null; - private string $subject = ''; - private ?IAddress $from = null; - private ?IAddress $replyTo = null; - /** @var array */ - private array $to = []; - /** @var array */ - private array $cc = []; - /** @var array */ - private array $bcc = []; - private ?DateTimeImmutable $date = null; - private ?string $bodyText = null; - private ?string $bodyHtml = null; - /** @var array */ - private array $attachments = []; - /** @var array */ - private array $headers = []; - - /** - * Creates a message from array data - * - * @since 2025.05.01 - * - * @param array $data - * - * @return self - */ - public static function fromArray(array $data): self { - $message = new self(); - - if (isset($data[self::JSON_PROPERTY_ID])) { - $message->setId($data[self::JSON_PROPERTY_ID]); - } - if (isset($data[self::JSON_PROPERTY_SUBJECT])) { - $message->setSubject($data[self::JSON_PROPERTY_SUBJECT]); - } - if (isset($data[self::JSON_PROPERTY_FROM])) { - $message->setFrom(Address::fromArray($data[self::JSON_PROPERTY_FROM])); - } - if (isset($data[self::JSON_PROPERTY_REPLY_TO])) { - $message->setReplyTo(Address::fromArray($data[self::JSON_PROPERTY_REPLY_TO])); - } - if (isset($data[self::JSON_PROPERTY_TO])) { - $message->setTo(array_map(fn($a) => Address::fromArray($a), $data[self::JSON_PROPERTY_TO])); - } - if (isset($data[self::JSON_PROPERTY_CC])) { - $message->setCc(array_map(fn($a) => Address::fromArray($a), $data[self::JSON_PROPERTY_CC])); - } - if (isset($data[self::JSON_PROPERTY_BCC])) { - $message->setBcc(array_map(fn($a) => Address::fromArray($a), $data[self::JSON_PROPERTY_BCC])); - } - if (isset($data[self::JSON_PROPERTY_DATE])) { - $message->setDate(new DateTimeImmutable($data[self::JSON_PROPERTY_DATE])); - } - if (isset($data[self::JSON_PROPERTY_BODY_TEXT])) { - $message->setBodyText($data[self::JSON_PROPERTY_BODY_TEXT]); - } - if (isset($data[self::JSON_PROPERTY_BODY_HTML])) { - $message->setBodyHtml($data[self::JSON_PROPERTY_BODY_HTML]); - } - if (isset($data[self::JSON_PROPERTY_ATTACHMENTS])) { - $message->setAttachments(array_map(fn($a) => Attachment::fromArray($a), $data[self::JSON_PROPERTY_ATTACHMENTS])); - } - if (isset($data[self::JSON_PROPERTY_HEADERS])) { - $message->setHeaders($data[self::JSON_PROPERTY_HEADERS]); - } - - return $message; - } - - /** - * @inheritDoc - */ - public function getId(): ?string { - return $this->id; - } - - /** - * @inheritDoc - */ - public function setId(?string $id): self { - $this->id = $id; - return $this; - } - - /** - * @inheritDoc - */ - public function getSubject(): string { - return $this->subject; - } - - /** - * @inheritDoc - */ - public function setSubject(string $subject): self { - $this->subject = $subject; - return $this; - } - - /** - * @inheritDoc - */ - public function getFrom(): ?IAddress { - return $this->from; - } - - /** - * @inheritDoc - */ - public function setFrom(?IAddress $from): self { - $this->from = $from; - return $this; - } - - /** - * @inheritDoc - */ - public function getReplyTo(): ?IAddress { - return $this->replyTo; - } - - /** - * @inheritDoc - */ - public function setReplyTo(?IAddress $replyTo): self { - $this->replyTo = $replyTo; - return $this; - } - - /** - * @inheritDoc - */ - public function getTo(): array { - return $this->to; - } - - /** - * @inheritDoc - */ - public function setTo(array $to): self { - $this->to = $to; - return $this; - } - - /** - * @inheritDoc - */ - public function addTo(IAddress $address): self { - $this->to[] = $address; - return $this; - } - - /** - * @inheritDoc - */ - public function getCc(): array { - return $this->cc; - } - - /** - * @inheritDoc - */ - public function setCc(array $cc): self { - $this->cc = $cc; - return $this; - } - - /** - * @inheritDoc - */ - public function addCc(IAddress $address): self { - $this->cc[] = $address; - return $this; - } - - /** - * @inheritDoc - */ - public function getBcc(): array { - return $this->bcc; - } - - /** - * @inheritDoc - */ - public function setBcc(array $bcc): self { - $this->bcc = $bcc; - return $this; - } - - /** - * @inheritDoc - */ - public function addBcc(IAddress $address): self { - $this->bcc[] = $address; - return $this; - } - - /** - * @inheritDoc - */ - public function getDate(): ?DateTimeImmutable { - return $this->date; - } - - /** - * @inheritDoc - */ - public function setDate(?DateTimeImmutable $date): self { - $this->date = $date; - return $this; - } - - /** - * @inheritDoc - */ - public function getBodyText(): ?string { - return $this->bodyText; - } - - /** - * @inheritDoc - */ - public function setBodyText(?string $text): self { - $this->bodyText = $text; - return $this; - } - - /** - * @inheritDoc - */ - public function getBodyHtml(): ?string { - return $this->bodyHtml; - } - - /** - * @inheritDoc - */ - public function setBodyHtml(?string $html): self { - $this->bodyHtml = $html; - return $this; - } - - /** - * @inheritDoc - */ - public function getAttachments(): array { - return $this->attachments; - } - - /** - * @inheritDoc - */ - public function setAttachments(array $attachments): self { - $this->attachments = $attachments; - return $this; - } - - /** - * @inheritDoc - */ - public function addAttachment(IAttachment $attachment): self { - $this->attachments[] = $attachment; - return $this; - } - - /** - * @inheritDoc - */ - public function getHeaders(): array { - return $this->headers; - } - - /** - * @inheritDoc - */ - public function getHeader(string $name): ?string { - return $this->headers[$name] ?? null; - } - - /** - * @inheritDoc - */ - public function setHeaders(array $headers): self { - $this->headers = $headers; - return $this; - } - - /** - * @inheritDoc - */ - public function setHeader(string $name, string $value): self { - $this->headers[$name] = $value; - return $this; - } - - /** - * @inheritDoc - */ - public function hasRecipients(): bool { - return !empty($this->to) || !empty($this->cc) || !empty($this->bcc); - } - - /** - * @inheritDoc - */ - public function hasBody(): bool { - return ($this->bodyText !== null && $this->bodyText !== '') - || ($this->bodyHtml !== null && $this->bodyHtml !== ''); - } - - /** - * @inheritDoc - */ - public function jsonSerialize(): array { - $data = [ - self::JSON_PROPERTY_TYPE => self::JSON_TYPE, - ]; - - if ($this->id !== null) { - $data[self::JSON_PROPERTY_ID] = $this->id; - } - - $data[self::JSON_PROPERTY_SUBJECT] = $this->subject; - - if ($this->from !== null) { - $data[self::JSON_PROPERTY_FROM] = $this->from; - } - if ($this->replyTo !== null) { - $data[self::JSON_PROPERTY_REPLY_TO] = $this->replyTo; - } - if (!empty($this->to)) { - $data[self::JSON_PROPERTY_TO] = $this->to; - } - if (!empty($this->cc)) { - $data[self::JSON_PROPERTY_CC] = $this->cc; - } - if (!empty($this->bcc)) { - $data[self::JSON_PROPERTY_BCC] = $this->bcc; - } - if ($this->date !== null) { - $data[self::JSON_PROPERTY_DATE] = $this->date->format('c'); - } - if ($this->bodyText !== null) { - $data[self::JSON_PROPERTY_BODY_TEXT] = $this->bodyText; - } - if ($this->bodyHtml !== null) { - $data[self::JSON_PROPERTY_BODY_HTML] = $this->bodyHtml; - } - if (!empty($this->attachments)) { - $data[self::JSON_PROPERTY_ATTACHMENTS] = $this->attachments; - } - if (!empty($this->headers)) { - $data[self::JSON_PROPERTY_HEADERS] = $this->headers; - } - - return $data; - } - -} diff --git a/shared/lib/Mail/Entity/Address.php b/shared/lib/Mail/Object/Address.php similarity index 77% rename from shared/lib/Mail/Entity/Address.php rename to shared/lib/Mail/Object/Address.php index 047a4af..f98faaa 100644 --- a/shared/lib/Mail/Entity/Address.php +++ b/shared/lib/Mail/Object/Address.php @@ -7,16 +7,14 @@ declare(strict_types=1); * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace KTXF\Mail\Entity; +namespace KTXF\Mail\Object; /** - * Mail Address Implementation - * - * Concrete implementation of IAddress for email addresses. + * Address Implementation * * @since 2025.05.01 */ -class Address implements IAddress { +class Address implements AddressInterface { /** * @param string $address Email address @@ -27,6 +25,16 @@ class Address implements IAddress { private ?string $name = null, ) {} + /** + * @inheritDoc + */ + public function jsonSerialize(): array { + return array_filter([ + self::JSON_PROPERTY_ADDRESS => $this->address, + self::JSON_PROPERTY_LABEL => $this->name, + ], fn($v) => $v !== null && $v !== ''); + } + /** * Creates an Address from a formatted string * @@ -65,7 +73,7 @@ class Address implements IAddress { public static function fromArray(array $data): self { return new self( $data[self::JSON_PROPERTY_ADDRESS] ?? $data['address'] ?? '', - $data[self::JSON_PROPERTY_NAME] ?? $data['name'] ?? null, + $data[self::JSON_PROPERTY_LABEL] ?? $data['name'] ?? null, ); } @@ -77,15 +85,9 @@ class Address implements IAddress { } /** - * Sets the email address - * - * @since 2025.05.01 - * - * @param string $address - * - * @return self + * @inheritDoc */ - public function setAddress(string $address): self { + public function setAddress(string $address): static { $this->address = $address; return $this; } @@ -93,21 +95,15 @@ class Address implements IAddress { /** * @inheritDoc */ - public function getName(): ?string { + public function getLabel(): ?string { return $this->name; } /** - * Sets the display name - * - * @since 2025.05.01 - * - * @param string|null $name - * - * @return self + * @inheritDoc */ - public function setName(?string $name): self { - $this->name = $name; + public function setLabel(?string $label): static { + $this->name = $label; return $this; } @@ -121,16 +117,6 @@ class Address implements IAddress { return $this->address; } - /** - * @inheritDoc - */ - public function jsonSerialize(): array { - return array_filter([ - self::JSON_PROPERTY_ADDRESS => $this->address, - self::JSON_PROPERTY_NAME => $this->name, - ], fn($v) => $v !== null && $v !== ''); - } - /** * String representation */ diff --git a/shared/lib/Mail/Entity/IAddress.php b/shared/lib/Mail/Object/AddressInterface.php similarity index 60% rename from shared/lib/Mail/Entity/IAddress.php rename to shared/lib/Mail/Object/AddressInterface.php index 57755df..fa67592 100644 --- a/shared/lib/Mail/Entity/IAddress.php +++ b/shared/lib/Mail/Object/AddressInterface.php @@ -7,39 +7,49 @@ declare(strict_types=1); * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace KTXF\Mail\Entity; +namespace KTXF\Mail\Object; -use JsonSerializable; +use KTXF\Json\JsonSerializable; /** - * Mail Address Interface + * Address Interface * * Represents an email address with optional display name. * * @since 2025.05.01 */ -interface IAddress extends JsonSerializable { +interface AddressInterface extends JsonSerializable { public const JSON_PROPERTY_ADDRESS = 'address'; - public const JSON_PROPERTY_NAME = 'name'; + public const JSON_PROPERTY_LABEL = 'label'; /** * Gets the email address * * @since 2025.05.01 - * - * @return string Email address (e.g., "user@example.com") */ public function getAddress(): string; + /** + * Sets the email address + * + * @since 2025.05.01 + */ + public function setAddress(string $value): static; + /** * Gets the display name * * @since 2025.05.01 - * - * @return string|null Display name (e.g., "John Doe") or null */ - public function getName(): ?string; + public function getLabel(): ?string; + + /** + * Sets the display name + * + * @since 2025.05.01 + */ + public function setLabel(?string $value): static; /** * Gets the formatted address string diff --git a/shared/lib/Mail/Entity/Attachment.php b/shared/lib/Mail/Object/Attachment.php similarity index 96% rename from shared/lib/Mail/Entity/Attachment.php rename to shared/lib/Mail/Object/Attachment.php index 93caac5..58edccf 100644 --- a/shared/lib/Mail/Entity/Attachment.php +++ b/shared/lib/Mail/Object/Attachment.php @@ -7,16 +7,14 @@ declare(strict_types=1); * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace KTXF\Mail\Entity; +namespace KTXF\Mail\Object; /** - * Mail Attachment Implementation - * - * Concrete implementation of IAttachment for mail attachments. + * Attachment Implementation * * @since 2025.05.01 */ -class Attachment implements IAttachment { +class Attachment implements AttachmentInterface { /** * @param string $name File name @@ -41,6 +39,22 @@ class Attachment implements IAttachment { } } + /** + * @inheritDoc + */ + public function jsonSerialize(): array { + return array_filter([ + self::JSON_PROPERTY_ID => $this->id, + self::JSON_PROPERTY_NAME => $this->name, + self::JSON_PROPERTY_MIME_TYPE => $this->mimeType, + self::JSON_PROPERTY_SIZE => $this->size, + self::JSON_PROPERTY_CONTENT_ID => $this->contentId, + self::JSON_PROPERTY_INLINE => $this->inline ?: null, + 'contentBase64' => $this->getContentBase64(), + ], fn($v) => $v !== null); + } + + /** * Creates an attachment from a file path * @@ -177,19 +191,4 @@ class Attachment implements IAttachment { return base64_encode($this->content); } - /** - * @inheritDoc - */ - public function jsonSerialize(): array { - return array_filter([ - self::JSON_PROPERTY_ID => $this->id, - self::JSON_PROPERTY_NAME => $this->name, - self::JSON_PROPERTY_MIME_TYPE => $this->mimeType, - self::JSON_PROPERTY_SIZE => $this->size, - self::JSON_PROPERTY_CONTENT_ID => $this->contentId, - self::JSON_PROPERTY_INLINE => $this->inline ?: null, - 'contentBase64' => $this->getContentBase64(), - ], fn($v) => $v !== null); - } - } diff --git a/shared/lib/Mail/Entity/IAttachment.php b/shared/lib/Mail/Object/AttachmentInterface.php similarity index 93% rename from shared/lib/Mail/Entity/IAttachment.php rename to shared/lib/Mail/Object/AttachmentInterface.php index 92a44a1..ef1d57f 100644 --- a/shared/lib/Mail/Entity/IAttachment.php +++ b/shared/lib/Mail/Object/AttachmentInterface.php @@ -7,18 +7,18 @@ declare(strict_types=1); * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace KTXF\Mail\Entity; +namespace KTXF\Mail\Object; -use JsonSerializable; +use KTXF\Json\JsonSerializable; /** - * Mail Attachment Interface + * Attachment Interface * * Represents a file attachment on a mail message. * * @since 2025.05.01 */ -interface IAttachment extends JsonSerializable { +interface AttachmentInterface extends JsonSerializable { public const JSON_PROPERTY_ID = 'id'; public const JSON_PROPERTY_NAME = 'name'; diff --git a/shared/lib/Mail/Object/MessagePartBaseAbstract.php b/shared/lib/Mail/Object/MessagePartBaseAbstract.php new file mode 100644 index 0000000..0abef34 --- /dev/null +++ b/shared/lib/Mail/Object/MessagePartBaseAbstract.php @@ -0,0 +1,122 @@ + + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace KTXF\Mail\Object; + +/** + * Abstract Message Part Base Class + * + * Provides common implementation for message parts (read-only) + * + * @since 2025.05.01 + */ +abstract class MessagePartBaseAbstract implements MessagePartInterface { + + /** + * Internal data storage + */ + protected array $data = []; + + /** + * Sub-parts storage + * @var array + */ + protected array $parts = []; + + /** + * Constructor + * + * @param array &$data Reference to data array + */ + public function __construct(array &$data = null) { + if ($data === null) { + $data = []; + } + $this->data = &$data; + } + + /** + * @inheritDoc + */ + public function getBlobId(): ?string { + return $this->data['blobId'] ?? null; + } + + /** + * @inheritDoc + */ + public function getId(): ?string { + return $this->data['partId'] ?? null; + } + + /** + * @inheritDoc + */ + public function getType(): ?string { + return $this->data['type'] ?? null; + } + + /** + * @inheritDoc + */ + public function getDisposition(): ?string { + return $this->data['disposition'] ?? null; + } + + /** + * @inheritDoc + */ + public function getName(): ?string { + return $this->data['name'] ?? null; + } + + /** + * @inheritDoc + */ + public function getCharset(): ?string { + return $this->data['charset'] ?? null; + } + + /** + * @inheritDoc + */ + public function getLanguage(): ?string { + return $this->data['language'] ?? null; + } + + /** + * @inheritDoc + */ + public function getLocation(): ?string { + return $this->data['location'] ?? null; + } + + /** + * @inheritDoc + */ + public function getParts(): array { + return $this->parts; + } + + /** + * @inheritDoc + */ + public function jsonSerialize(): array { + $result = $this->data; + + if (!empty($this->parts)) { + $result['subParts'] = []; + foreach ($this->parts as $part) { + $result['subParts'][] = $part->jsonSerialize(); + } + } + + return $result; + } +} diff --git a/shared/lib/Mail/Object/MessagePartInterface.php b/shared/lib/Mail/Object/MessagePartInterface.php new file mode 100644 index 0000000..4a576cb --- /dev/null +++ b/shared/lib/Mail/Object/MessagePartInterface.php @@ -0,0 +1,104 @@ + + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace KTXF\Mail\Object; + +use KTXF\Json\JsonSerializable; + +/** + * Message Part Interface + * + * Represents a MIME part of a message (body, attachment, etc.) + * + * @since 2025.05.01 + */ +interface MessagePartInterface extends JsonSerializable { + + /** + * Gets the blob identifier + * + * @since 2025.05.01 + * + * @return string|null + */ + public function getBlobId(): ?string; + + /** + * Gets the part identifier + * + * @since 2025.05.01 + * + * @return string|null + */ + public function getId(): ?string; + + /** + * Gets the MIME type + * + * @since 2025.05.01 + * + * @return string|null + */ + public function getType(): ?string; + + /** + * Gets the content disposition (inline, attachment) + * + * @since 2025.05.01 + * + * @return string|null + */ + public function getDisposition(): ?string; + + /** + * Gets the part name + * + * @since 2025.05.01 + * + * @return string|null + */ + public function getName(): ?string; + + /** + * Gets the character set + * + * @since 2025.05.01 + * + * @return string|null + */ + public function getCharset(): ?string; + + /** + * Gets the language + * + * @since 2025.05.01 + * + * @return string|null + */ + public function getLanguage(): ?string; + + /** + * Gets the location + * + * @since 2025.05.01 + * + * @return string|null + */ + public function getLocation(): ?string; + + /** + * Gets the sub-parts + * + * @since 2025.05.01 + * + * @return array + */ + public function getParts(): array; + +} diff --git a/shared/lib/Mail/Object/MessagePartMutableAbstract.php b/shared/lib/Mail/Object/MessagePartMutableAbstract.php new file mode 100644 index 0000000..92f0456 --- /dev/null +++ b/shared/lib/Mail/Object/MessagePartMutableAbstract.php @@ -0,0 +1,146 @@ + + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace KTXF\Mail\Object; + +/** + * Abstract Message Part Mutable Class + * + * Provides common implementation for mutable message parts + * + * @since 2025.05.01 + */ +abstract class MessagePartMutableAbstract extends MessagePartBaseAbstract { + + /** + * Sets the blob identifier + * + * @since 2025.05.01 + * + * @param string $value + * + * @return static + */ + public function setBlobId(string $value): static { + $this->data['blobId'] = $value; + return $this; + } + + /** + * Sets the part identifier + * + * @since 2025.05.01 + * + * @param string $value + * + * @return static + */ + public function setId(string $value): static { + $this->data['partId'] = $value; + return $this; + } + + /** + * Sets the MIME type + * + * @since 2025.05.01 + * + * @param string $value + * + * @return static + */ + public function setType(string $value): static { + $this->data['type'] = $value; + return $this; + } + + /** + * Sets the content disposition + * + * @since 2025.05.01 + * + * @param string $value + * + * @return static + */ + public function setDisposition(string $value): static { + $this->data['disposition'] = $value; + return $this; + } + + /** + * Sets the part name + * + * @since 2025.05.01 + * + * @param string $value + * + * @return static + */ + public function setName(string $value): static { + $this->data['name'] = $value; + return $this; + } + + /** + * Sets the character set + * + * @since 2025.05.01 + * + * @param string $value + * + * @return static + */ + public function setCharset(string $value): static { + $this->data['charset'] = $value; + return $this; + } + + /** + * Sets the language + * + * @since 2025.05.01 + * + * @param string $value + * + * @return static + */ + public function setLanguage(string $value): static { + $this->data['language'] = $value; + return $this; + } + + /** + * Sets the location + * + * @since 2025.05.01 + * + * @param string $value + * + * @return static + */ + public function setLocation(string $value): static { + $this->data['location'] = $value; + return $this; + } + + /** + * Sets the sub-parts + * + * @since 2025.05.01 + * + * @param MessagePartInterface ...$value + * + * @return static + */ + public function setParts(MessagePartInterface ...$value): static { + $this->parts = $value; + return $this; + } +} diff --git a/shared/lib/Mail/Object/MessagePropertiesBaseAbstract.php b/shared/lib/Mail/Object/MessagePropertiesBaseAbstract.php new file mode 100644 index 0000000..597bd6e --- /dev/null +++ b/shared/lib/Mail/Object/MessagePropertiesBaseAbstract.php @@ -0,0 +1,400 @@ + + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace KTXF\Mail\Object; + +use DateTimeImmutable; +use KTXF\Resource\Provider\Node\NodePropertiesBaseAbstract; + +/** + * Abstract Message Properties Base Class + * + * Provides common implementation for message properties (read-only) + * + * @since 2025.05.01 + */ +abstract class MessagePropertiesBaseAbstract extends NodePropertiesBaseAbstract implements MessagePropertiesBaseInterface { + + public const JSON_TYPE = MessagePropertiesBaseInterface::JSON_TYPE; + + /** + * @inheritDoc + */ + public function version(): int { + return $this->data['version'] ?? 1; + } + + /** + * @inheritDoc + */ + public function getHeaders(): array { + return $this->data['headers'] ?? []; + } + + /** + * @inheritDoc + */ + public function getHeader(string $name): string|array|null { + return $this->data['headers'][$name] ?? null; + } + + /** + * @inheritDoc + */ + public function getUrid(): ?string { + return $this->data['urid'] ?? null; + } + + /** + * @inheritDoc + */ + public function getCreated(): ?DateTimeImmutable { + return $this->data['created'] ?? null; + } + + /** + * @inheritDoc + */ + public function getModified(): ?DateTimeImmutable { + return $this->data['modified'] ?? null; + } + + /** + * @inheritDoc + */ + public function getDate(): ?DateTimeImmutable { + return $this->data['date'] ?? null; + } + + /** + * @inheritDoc + */ + public function getReceived(): ?DateTimeImmutable { + return $this->data['received'] ?? null; + } + + /** + * @inheritDoc + */ + public function getSize(): ?int { + return $this->data['size'] ?? null; + } + + /** + * @inheritDoc + */ + public function getSender(): ?AddressInterface { + return $this->data['sender'] ?? null; + } + + /** + * @inheritDoc + */ + public function getFrom(): ?AddressInterface { + return $this->data['from'] ?? null; + } + + /** + * @inheritDoc + */ + public function getReplyTo(): array { + return $this->data['replyTo'] ?? []; + } + + /** + * @inheritDoc + */ + public function getTo(): array { + return $this->data['to'] ?? []; + } + + /** + * @inheritDoc + */ + public function getCc(): array { + return $this->data['cc'] ?? []; + } + + /** + * @inheritDoc + */ + public function getBcc(): array { + return $this->data['bcc'] ?? []; + } + + /** + * @inheritDoc + */ + public function getInReplyTo(): ?string { + return $this->data['inReplyTo'] ?? null; + } + + /** + * @inheritDoc + */ + public function getReferences(): array { + return $this->data['references'] ?? []; + } + + /** + * @inheritDoc + */ + public function getSubject(): string { + return $this->data['subject'] ?? ''; + } + + /** + * @inheritDoc + */ + public function getSnippet(): ?string { + return $this->data['snippet'] ?? null; + } + + /** + * @inheritDoc + */ + public function getBodyText(): ?string { + return $this->data['bodyText'] ?? null; + } + + /** + * @inheritDoc + */ + public function getBodyTextCharset(): ?string { + return $this->data['bodyTextCharset'] ?? null; + } + + /** + * @inheritDoc + */ + public function getBodyTextSize(): ?int { + return $this->data['bodyTextSize'] ?? null; + } + + /** + * @inheritDoc + */ + public function getBodyHtml(): ?string { + return $this->data['bodyHtml'] ?? null; + } + + /** + * @inheritDoc + */ + public function getBodyHtmlCharset(): ?string { + return $this->data['bodyHtmlCharset'] ?? null; + } + + /** + * @inheritDoc + */ + public function getBodyHtmlSize(): ?int { + return $this->data['bodyHtmlSize'] ?? null; + } + + /** + * @inheritDoc + */ + public function getAttachments(): array { + return $this->data['attachments'] ?? []; + } + + /** + * @inheritDoc + */ + public function getFlags(): array { + return $this->data['flags'] ?? [ + 'read' => false, + 'starred' => false, + 'important' => false, + 'answered' => false, + 'forwarded' => false, + 'draft' => false, + 'deleted' => false, + 'flagged' => false, + ]; + } + + /** + * @inheritDoc + */ + public function getFlag(string $name): bool { + return $this->data['flags'][$name] ?? false; + } + + /** + * Gets message labels + * + * @since 2025.05.01 + * + * @return array + */ + public function getLabels(): array { + return $this->data['labels'] ?? []; + } + + /** + * Gets message tags + * + * @since 2025.05.01 + * + * @return array + */ + public function getTags(): array { + return $this->data['tags'] ?? []; + } + + /** + * Gets message priority + * + * @since 2025.05.01 + * + * @return string + */ + public function getPriority(): string { + return $this->data['priority'] ?? 'normal'; + } + + /** + * Gets message sensitivity + * + * @since 2025.05.01 + * + * @return string + */ + public function getSensitivity(): string { + return $this->data['sensitivity'] ?? 'normal'; + } + + /** + * Gets encryption information + * + * @since 2025.05.01 + * + * @return array{method: string|null, signed: bool, encrypted: bool} + */ + public function getEncryption(): array { + return $this->data['encryption'] ?? [ + 'method' => null, + 'signed' => false, + 'encrypted' => false, + ]; + } + + /** + * Checks if delivery receipt is requested + * + * @since 2025.05.01 + * + * @return bool + */ + public function isDeliveryReceipt(): bool { + return $this->data['deliveryReceipt'] ?? false; + } + + /** + * Checks if read receipt is requested + * + * @since 2025.05.01 + * + * @return bool + */ + public function isReadReceipt(): bool { + return $this->data['readReceipt'] ?? false; + } + + /** + * @inheritDoc + */ + public function hasRecipients(): bool { + return !empty($this->data['to']) || !empty($this->data['cc']) || !empty($this->data['bcc']); + } + + /** + * @inheritDoc + */ + public function hasBody(): bool { + return ($this->data['bodyText'] !== null && $this->data['bodyText'] !== '') + || ($this->data['bodyHtml'] !== null && $this->data['bodyHtml'] !== ''); + } + + /** + * @inheritDoc + */ + public function getBody(): ?MessagePartInterface { + return $this->data['body'] ?? null; + } + + /** + * @inheritDoc + */ + public function jsonSerialize(): array { + $data = [ + self::JSON_PROPERTY_TYPE => self::JSON_TYPE, + self::JSON_PROPERTY_VERSION => $this->data['version'] ?? 1, + ]; + + if (!empty($this->data['headers'])) { + $data[self::JSON_PROPERTY_HEADERS] = $this->data['headers']; + } + if (isset($this->data['urid']) && $this->data['urid'] !== null) { + $data[self::JSON_PROPERTY_URID] = $this->data['urid']; + } + if (isset($this->data['date']) && $this->data['date'] !== null) { + $data[self::JSON_PROPERTY_DATE] = $this->data['date'] instanceof DateTimeImmutable + ? $this->data['date']->format('c') + : $this->data['date']; + } + if (isset($this->data['received']) && $this->data['received'] !== null) { + $data[self::JSON_PROPERTY_RECEIVED] = $this->data['received'] instanceof DateTimeImmutable + ? $this->data['received']->format('c') + : $this->data['received']; + } + if (isset($this->data['size']) && $this->data['size'] !== null) { + $data[self::JSON_PROPERTY_SIZE] = $this->data['size']; + } + if (isset($this->data['sender']) && $this->data['sender'] !== null) { + $data[self::JSON_PROPERTY_SENDER] = $this->data['sender']; + } + if (isset($this->data['from']) && $this->data['from'] !== null) { + $data[self::JSON_PROPERTY_FROM] = $this->data['from']; + } + if (!empty($this->data['replyTo'])) { + $data[self::JSON_PROPERTY_REPLY_TO] = $this->data['replyTo']; + } + if (!empty($this->data['to'])) { + $data[self::JSON_PROPERTY_TO] = $this->data['to']; + } + if (!empty($this->data['cc'])) { + $data[self::JSON_PROPERTY_CC] = $this->data['cc']; + } + if (!empty($this->data['bcc'])) { + $data[self::JSON_PROPERTY_BCC] = $this->data['bcc']; + } + if (isset($this->data['inReplyTo']) && $this->data['inReplyTo'] !== null) { + $data[self::JSON_PROPERTY_IN_REPLY_TO] = $this->data['inReplyTo']; + } + if (!empty($this->data['references'])) { + $data[self::JSON_PROPERTY_REFERENCES] = $this->data['references']; + } + + if (isset($this->data['snippet']) && $this->data['snippet'] !== null) { + $data[self::JSON_PROPERTY_SNIPPET] = $this->data['snippet']; + } + + if (!empty($this->data['attachments'])) { + $data[self::JSON_PROPERTY_ATTACHMENTS] = $this->data['attachments']; + } + + $data[self::JSON_PROPERTY_SUBJECT] = $this->data['subject'] ?? null; + $data[self::JSON_PROPERTY_BODY] = $this->data['body'] ?? null; + + return $data; + } +} diff --git a/shared/lib/Mail/Object/MessagePropertiesBaseInterface.php b/shared/lib/Mail/Object/MessagePropertiesBaseInterface.php new file mode 100644 index 0000000..6daee90 --- /dev/null +++ b/shared/lib/Mail/Object/MessagePropertiesBaseInterface.php @@ -0,0 +1,252 @@ + + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace KTXF\Mail\Object; + +use DateTimeImmutable; +use KTXF\Resource\Provider\Node\NodePropertiesBaseInterface; + +/** + * Message Properties Base Interface + * + * @since 2025.05.01 + */ +interface MessagePropertiesBaseInterface extends NodePropertiesBaseInterface { + + public const JSON_TYPE = 'mail.message'; + public const JSON_PROPERTY_HEADERS = 'headers'; + public const JSON_PROPERTY_URID = 'urid'; + public const JSON_PROPERTY_DATE = 'date'; + public const JSON_PROPERTY_RECEIVED = 'received'; + public const JSON_PROPERTY_SIZE = 'size'; + public const JSON_PROPERTY_SENDER = 'sender'; + public const JSON_PROPERTY_FROM = 'from'; + public const JSON_PROPERTY_REPLY_TO = 'replyTo'; + public const JSON_PROPERTY_TO = 'to'; + public const JSON_PROPERTY_CC = 'cc'; + public const JSON_PROPERTY_BCC = 'bcc'; + public const JSON_PROPERTY_IN_REPLY_TO = 'inReplyTo'; + public const JSON_PROPERTY_REFERENCES = 'references'; + public const JSON_PROPERTY_SUBJECT = 'subject'; + public const JSON_PROPERTY_SNIPPET = 'snippet'; + public const JSON_PROPERTY_BODY = 'body'; + public const JSON_PROPERTY_ATTACHMENTS = 'attachments'; + public const JSON_PROPERTY_TAGS = 'tags'; + + /** + * Gets custom headers + * + * @since 2025.05.01 + * + * @return array Header name => value + */ + public function getHeaders(): array; + + /** + * Gets a specific header value + * + * @since 2025.05.01 + * + * @param string $name Header name + * + * @return string|array|null Header value(s) or null if not set + */ + public function getHeader(string $name): string|array|null; + + /** + * Gets the universal resource identifier (URN) + * + * @since 2025.05.01 + * + * @return string|null + */ + public function getUrid(): ?string; + + /** + * Gets the message date + * + * @since 2025.05.01 + * + * @return DateTimeImmutable|null + */ + public function getDate(): ?DateTimeImmutable; + + /** + * Gets the received date + * + * @since 2025.05.01 + * + * @return DateTimeImmutable|null + */ + public function getReceived(): ?DateTimeImmutable; + + /** + * Gets the message size in bytes + * + * @since 2025.05.01 + * + * @return int|null + */ + public function getSize(): ?int; + + /** + * Gets the sender address (actual sender, may differ from From) + * + * @since 2025.05.01 + * + * @return AddressInterface|null + */ + public function getSender(): ?AddressInterface; + + /** + * Gets the sender address + * + * @since 2025.05.01 + * + * @return AddressInterface|null + */ + public function getFrom(): ?AddressInterface; + /** + * Gets the reply-to addresses + * + * @since 2025.05.01 + * + * @return array + */ + public function getReplyTo(): array; + + /** + * Gets the primary recipients (To) + * + * @since 2025.05.01 + * + * @return array + */ + public function getTo(): array; + + /** + * Gets the carbon copy recipients (CC) + * + * @since 2025.05.01 + * + * @return array + */ + public function getCc(): array; + + /** + * Gets the blind carbon copy recipients (BCC) + * + * @since 2025.05.01 + * + * @return array + */ + public function getBcc(): array; + + /** + * Gets the message ID this is replying to + * + * @since 2025.05.01 + * + * @return string|null + */ + public function getInReplyTo(): ?string; + + /** + * Gets the references (message IDs in thread) + * + * @since 2025.05.01 + * + * @return array + */ + public function getReferences(): array; + + /** + * Gets the message subject + * + * @since 2025.05.01 + * + * @return string + */ + public function getSubject(): string; + + /** + * Gets the message snippet/preview + * + * @since 2025.05.01 + * + * @return string|null + */ + public function getSnippet(): ?string; + + /** + * Checks if the message has any body content + * + * @since 2025.05.01 + * + * @return bool True if text or HTML body is set + */ + public function hasBody(): bool; + + /** + * Gets the message body structure + * + * @since 2025.05.01 + * + * @return MessagePartInterface|null The body structure or null if no body + */ + public function getBody(): ?MessagePartInterface; + + /** + * Gets the plain text body content + * + * @since 2025.05.01 + * + * @return string|null + */ + public function getBodyText(): ?string; + + /** + * Gets the HTML body content + * + * @since 2025.05.01 + * + * @return string|null + */ + public function getBodyHtml(): ?string; + + /** + * Gets the attachments + * + * @since 2025.05.01 + * + * @return array + */ + public function getAttachments(): array; + + /** + * Gets message flags + * + * @since 2025.05.01 + * + * @return array{read:bool,starred:bool,important:bool,answered:bool,forwarded:bool,draft:bool,deleted:bool,flagged:bool} + */ + public function getFlags(): array; + + /** + * Gets a specific flag value + * + * @since 2025.05.01 + * + * @param string $name Flag name (read, starred, important, answered, forwarded, draft, deleted, flagged) + * + * @return bool + */ + public function getFlag(string $name): bool; + +} diff --git a/shared/lib/Mail/Object/MessagePropertiesMutableAbstract.php b/shared/lib/Mail/Object/MessagePropertiesMutableAbstract.php new file mode 100644 index 0000000..5c13540 --- /dev/null +++ b/shared/lib/Mail/Object/MessagePropertiesMutableAbstract.php @@ -0,0 +1,455 @@ + + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace KTXF\Mail\Object; + +use DateTimeImmutable; + +/** + * Abstract Message Properties Mutable Class + * + * Provides common implementation for mutable message properties + * + * @since 2025.05.01 + */ +abstract class MessagePropertiesMutableAbstract extends MessagePropertiesBaseAbstract implements MessagePropertiesMutableInterface { + + public const JSON_TYPE = MessagePropertiesBaseInterface::JSON_TYPE; + + /** + * @inheritDoc + */ + public function setHeaders(array $value): static { + $this->data['headers'] = $value; + return $this; + } + + /** + * @inheritDoc + */ + public function setHeader(string $name, string|array $value): static { + $this->data['headers'][$name] = $value; + return $this; + } + + /** + * @inheritDoc + */ + public function setUrid(?string $value): static { + $this->data['urid'] = $value; + return $this; + } + + /** + * @inheritDoc + */ + public function setCreated(DateTimeImmutable $value): static { + $this->data['created'] = $value; + return $this; + } + + /** + * @inheritDoc + */ + public function setModified(DateTimeImmutable $value): static { + $this->data['modified'] = $value; + return $this; + } + + /** + * @inheritDoc + */ + public function setDate(DateTimeImmutable $value): static { + $this->data['date'] = $value; + return $this; + } + + /** + * @inheritDoc + */ + public function setReceived(?DateTimeImmutable $value): static { + $this->data['received'] = $value; + return $this; + } + + /** + * @inheritDoc + */ + public function setSize(?int $value): static { + $this->data['size'] = $value; + return $this; + } + + /** + * @inheritDoc + */ + public function setSender(?AddressInterface $value): static { + $this->data['sender'] = $value; + return $this; + } + + /** + * @inheritDoc + */ + public function setFrom(AddressInterface $value): static { + $this->data['from'] = $value; + return $this; + } + + /** + * @inheritDoc + */ + public function setReplyTo(AddressInterface ...$value): static { + $this->data['replyTo'] = $value; + return $this; + } + + /** + * @inheritDoc + */ + public function setTo(AddressInterface ...$value): static { + $this->data['to'] = $value; + return $this; + } + + /** + * @inheritDoc + */ + public function setCc(AddressInterface ...$value): static { + $this->data['cc'] = $value; + return $this; + } + + /** + * @inheritDoc + */ + public function setBcc(AddressInterface ...$value): static { + $this->data['bcc'] = $value; + return $this; + } + + /** + * @inheritDoc + */ + public function setInReplyTo(?string $value): static { + $this->data['inReplyTo'] = $value; + return $this; + } + + /** + * @inheritDoc + */ + public function setReferences(string ...$value): static { + $this->data['references'] = $value; + return $this; + } + + /** + * @inheritDoc + */ + public function setSubject(string $value): static { + $this->data['subject'] = $value; + return $this; + } + + /** + * @inheritDoc + */ + public function setSnippet(?string $value): static { + $this->data['snippet'] = $value; + return $this; + } + + /** + * @inheritDoc + */ + public function setBodyText(?string $value): static { + $this->data['bodyText'] = $value; + return $this; + } + + /** + * Sets the plain text body charset + * + * @since 2025.05.01 + * + * @param string $value + * + * @return static + */ + public function setBodyTextCharset(string $value): static { + $this->data['bodyTextCharset'] = $value; + return $this; + } + + /** + * Sets the plain text body size + * + * @since 2025.05.01 + * + * @param int $value + * + * @return static + */ + public function setBodyTextSize(int $value): static { + $this->data['bodyTextSize'] = $value; + return $this; + } + + /** + * @inheritDoc + */ + public function setBodyHtml(?string $value): static { + $this->data['bodyHtml'] = $value; + return $this; + } + + /** + * Sets the HTML body charset + * + * @since 2025.05.01 + * + * @param string $value + * + * @return static + */ + public function setBodyHtmlCharset(string $value): static { + $this->data['bodyHtmlCharset'] = $value; + return $this; + } + + /** + * Sets the HTML body size + * + * @since 2025.05.01 + * + * @param int $value + * + * @return static + */ + public function setBodyHtmlSize(int $value): static { + $this->data['bodyHtmlSize'] = $value; + return $this; + } + + /** + * @inheritDoc + */ + public function setAttachments(AttachmentInterface ...$value): static { + $this->data['attachments'] = $value; + return $this; + } + + /** + * @inheritDoc + */ + public function addAttachment(AttachmentInterface $value): static { + $this->data['attachments'][] = $value; + return $this; + } + + /** + * Sets message flags + * + * @since 2025.05.01 + * + * @param array $value + * + * @return static + */ + public function setFlags(array $value): static { + if (!isset($this->data['flags'])) { + $this->data['flags'] = [ + 'read' => false, + 'starred' => false, + 'important' => false, + 'answered' => false, + 'forwarded' => false, + 'draft' => false, + 'deleted' => false, + 'flagged' => false, + ]; + } + $this->data['flags'] = array_merge($this->data['flags'], $value); + return $this; + } + + /** + * @inheritDoc + */ + public function setFlag(string $name, bool $value): static { + if (!isset($this->data['flags'])) { + $this->data['flags'] = [ + 'read' => false, + 'starred' => false, + 'important' => false, + 'answered' => false, + 'forwarded' => false, + 'draft' => false, + 'deleted' => false, + 'flagged' => false, + ]; + } + if (array_key_exists($name, $this->data['flags'])) { + $this->data['flags'][$name] = $value; + } + return $this; + } + + /** + * Sets message labels + * + * @since 2025.05.01 + * + * @param string ...$value + * + * @return static + */ + public function setLabels(string ...$value): static { + $this->data['labels'] = $value; + return $this; + } + + /** + * Adds a message label + * + * @since 2025.05.01 + * + * @param string $value + * + * @return static + */ + public function addLabel(string $value): static { + $this->data['labels'][] = $value; + return $this; + } + + /** + * Sets message tags + * + * @since 2025.05.01 + * + * @param string ...$value + * + * @return static + */ + public function setTags(string ...$value): static { + $this->data['tags'] = $value; + return $this; + } + + /** + * Adds a message tag + * + * @since 2025.05.01 + * + * @param string $value + * + * @return static + */ + public function addTag(string $value): static { + $this->data['tags'][] = $value; + return $this; + } + + /** + * Sets message priority + * + * @since 2025.05.01 + * + * @param string $value + * + * @return static + */ + public function setPriority(string $value): static { + $this->data['priority'] = $value; + return $this; + } + + /** + * Sets message sensitivity + * + * @since 2025.05.01 + * + * @param string $value + * + * @return static + */ + public function setSensitivity(string $value): static { + $this->data['sensitivity'] = $value; + return $this; + } + + /** + * Sets encryption information + * + * @since 2025.05.01 + * + * @param array $value + * + * @return static + */ + public function setEncryption(array $value): static { + if (!isset($this->data['encryption'])) { + $this->data['encryption'] = [ + 'method' => null, + 'signed' => false, + 'encrypted' => false, + ]; + } + $this->data['encryption'] = array_merge($this->data['encryption'], $value); + return $this; + } + + /** + * Sets delivery receipt flag + * + * @since 2025.05.01 + * + * @param bool $value + * + * @return static + */ + public function setDeliveryReceipt(bool $value): static { + $this->data['deliveryReceipt'] = $value; + return $this; + } + + /** + * Sets read receipt flag + * + * @since 2025.05.01 + * + * @param bool $value + * + * @return static + */ + public function setReadReceipt(bool $value): static { + $this->data['readReceipt'] = $value; + return $this; + } + + /** + * @inheritDoc + */ + public function jsonDeserialize(array|string $data): static { + if (is_string($data)) { + $data = json_decode($data, true); + } + + // Merge deserialized data into internal storage + foreach ($data as $key => $value) { + if (!in_array($key, ['collection', 'identifier', 'signature', 'created', 'modified'])) { + $this->data[$key] = $value; + } + } + + return $this; + } +} diff --git a/shared/lib/Mail/Object/MessagePropertiesMutableInterface.php b/shared/lib/Mail/Object/MessagePropertiesMutableInterface.php new file mode 100644 index 0000000..831bb63 --- /dev/null +++ b/shared/lib/Mail/Object/MessagePropertiesMutableInterface.php @@ -0,0 +1,255 @@ + + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace KTXF\Mail\Object; + +use DateTimeImmutable; +use KTXF\Resource\Provider\Node\NodePropertiesMutableInterface; + +/** + * Message Properties Mutable Interface + * + * @since 2025.05.01 + */ +interface MessagePropertiesMutableInterface extends MessagePropertiesBaseInterface, NodePropertiesMutableInterface { + + public const JSON_TYPE = MessagePropertiesBaseInterface::JSON_TYPE; + + /** + * Sets custom headers + * + * @since 2025.05.01 + * + * @param array> $value Header name => value + * + * @return self + */ + public function setHeaders(array $value): static; + + /** + * Sets a specific header value + * + * @since 2025.05.01 + * + * @param string $name Header name + * @param string|array $value Header value(s) + * + * @return self + */ + public function setHeader(string $name, string|array $value): static; + + /** + * Sets the universal resource identifier (URN) + * + * @since 2025.05.01 + * + * @param string|null $value + * + * @return self + */ + public function setUrid(?string $value): static; + + /** + * Sets the message date + * + * @since 2025.05.01 + * + * @param DateTimeImmutable $value + * + * @return self + */ + public function setDate(DateTimeImmutable $value): static; + + /** + * Sets the received date + * + * @since 2025.05.01 + * + * @param DateTimeImmutable|null $value + * + * @return self + */ + public function setReceived(?DateTimeImmutable $value): static; + + /** + * Sets the message size in bytes + * + * @since 2025.05.01 + * + * @param int|null $value + * + * @return self + */ + public function setSize(?int $value): static; + + /** + * Sets the sender address (actual sender, may differ from From) + * + * @since 2025.05.01 + * + * @param AddressInterface|null $value + * + * @return self + */ + public function setSender(?AddressInterface $value): static; + + /** + * Sets the sender address + * + * @since 2025.05.01 + * + * @param AddressInterface $value + * + * @return self + */ + public function setFrom(AddressInterface $value): static; + + /** + * Sets the reply-to addresses + * + * @since 2025.05.01 + * + * @param AddressInterface ...$value + * + * @return self + */ + public function setReplyTo(AddressInterface ...$value): static; + + /** + * Sets the primary recipients (To) + * + * @since 2025.05.01 + * + * @param AddressInterface ...$value + * + * @return self + */ + public function setTo(AddressInterface ...$value): static; + + /** + * Sets the carbon copy recipients (CC) + * + * @since 2025.05.01 + * + * @param AddressInterface ...$value + * + * @return self + */ + public function setCc(AddressInterface ...$value): static; + + /** + * Sets the blind carbon copy recipients (BCC) + * + * @since 2025.05.01 + * + * @param AddressInterface ...$value + * + * @return self + */ + public function setBcc(AddressInterface ...$value): static; + + /** + * Sets the message ID this is replying to + * + * @since 2025.05.01 + * + * @param string|null $value + * + * @return self + */ + public function setInReplyTo(?string $value): static; + + /** + * Sets the references (message IDs in thread) + * + * @since 2025.05.01 + * + * @param string ...$value + * + * @return self + */ + public function setReferences(string ...$value): static; + + /** + * Sets the message subject + * + * @since 2025.05.01 + * + * @param string $value + * + * @return self + */ + public function setSubject(string $value): static; + + /** + * Sets the message snippet/preview + * + * @since 2025.05.01 + * + * @param string|null $value + * + * @return self + */ + public function setSnippet(?string $value): static; + + /** + * Sets the plain text body content + * + * @since 2025.05.01 + * + * @param string|null $value + * + * @return self + */ + public function setBodyText(?string $value): static; + + /** + * Sets the HTML body content + * + * @since 2025.05.01 + * + * @param string|null $value + * + * @return self + */ + public function setBodyHtml(?string $value): static; + + /** + * Sets the attachments + * + * @since 2025.05.01 + * + * @param AttachmentInterface ...$value + * + * @return self + */ + public function setAttachments(AttachmentInterface ...$value): static; + + /** + * Adds an attachment + * + * @since 2025.05.01 + * + * @param AttachmentInterface $value + * + * @return self + */ + public function addAttachment(AttachmentInterface $value): static; + /** + * Sets message tags + * + * @since 2025.05.01 + * + * @param array{read: bool, starred: bool, important: bool, answered: bool, forwarded: bool, draft: bool, deleted: bool, flagged: bool} $value + * + * @return self + */ + public function setFlag(string $label, bool $value): static; + +} diff --git a/shared/lib/Mail/Provider/ProviderBaseInterface.php b/shared/lib/Mail/Provider/ProviderBaseInterface.php index 10a3690..42be2e3 100644 --- a/shared/lib/Mail/Provider/ProviderBaseInterface.php +++ b/shared/lib/Mail/Provider/ProviderBaseInterface.php @@ -31,11 +31,11 @@ interface ProviderBaseInterface extends ResourceProviderBaseInterface{ * @since 2025.05.01 * * @param string $tenantId Tenant identifier - * @param string|null $userId User identifier for context + * @param string $userId User identifier * @param string $address Email address to find service for * - * @return IServiceBase|null Service handling the address, or null + * @return ServiceBaseInterface|null Service handling the address, or null */ - public function serviceFindByAddress(string $tenantId, ?string $userId, string $address): ?ServiceBaseInterface; + public function serviceFindByAddress(string $tenantId, string $userId, string $address): ?ServiceBaseInterface; } diff --git a/shared/lib/Mail/Provider/ProviderServiceDiscoverInterface.php b/shared/lib/Mail/Provider/ProviderServiceDiscoverInterface.php index 1f5da97..603f5ed 100644 --- a/shared/lib/Mail/Provider/ProviderServiceDiscoverInterface.php +++ b/shared/lib/Mail/Provider/ProviderServiceDiscoverInterface.php @@ -9,6 +9,8 @@ declare(strict_types=1); namespace KTXF\Mail\Provider; +use KTXF\Resource\Provider\ResourceServiceLocationInterface; + /** * Mail Provider Autodiscovery Interface * @@ -27,39 +29,24 @@ namespace KTXF\Mail\Provider; interface ProviderServiceDiscoverInterface extends ProviderBaseInterface { /** - * Discover service configuration - * * Attempts to discover service configuration using provider-specific methods. - * Each provider may use different discovery mechanisms: - * - IMAP/SMTP: config database, DNS SRV, well-known URIs, common patterns - * - JMAP: Well-known JMAP session discovery - * - Provider-specific: Known configurations for popular providers * * @since 2025.05.01 * - * @param string $identity Identity to discover configuration for - * @param array $options Provider-specific options (e.g., preferred protocols, timeout) + * @param string $tenantId Tenant identifier + * @param string $userId User identifier + * @param string $identity Identity to discover configuration for (e.g., email address) + * @param string|null $location Optional hostname to test directly (bypasses DNS lookup) + * @param string|null $secret Optional password/token to validate discovered service * - * @return array Discovery results in the format: - * [ - * 'domain' => 'example.com', - * 'provider' => 'gmail|outlook|generic|etc', // Optional detected provider - * 'results' => [ - * [ - * 'protocol' => 'imap|smtp|jmap|etc', - * 'host' => 'imap.example.com', - * 'port' => 993, - * 'security' => 'ssl|tls|starttls|none', - * 'authentication' => ['plain', 'login', 'oauth2'], // Supported auth methods - * 'confidence' => 'high|medium|low', - * 'source' => 'mozilla|srv|wellknown|common|verified', // Discovery method used - * ], - * // ... more protocol results - * ] - * ] - * - * Returns empty results array if no configuration could be discovered. + * @return ResourceServiceLocationInterface|null Discovered location or null if not found */ - public function serviceDiscover(string $identity, array $options = []): array; + public function serviceDiscover( + string $tenantId, + string $userId, + string $identity, + ?string $location = null, + ?string $secret = null + ): ResourceServiceLocationInterface|null; } diff --git a/shared/lib/Mail/Selector/ServiceSelector.php b/shared/lib/Mail/Selector/ServiceSelector.php deleted file mode 100644 index 66071c3..0000000 --- a/shared/lib/Mail/Selector/ServiceSelector.php +++ /dev/null @@ -1,183 +0,0 @@ - - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -namespace KTXF\Mail\Selector; - -use JsonSerializable; -use KTXF\Mail\Service\ServiceScope; - -/** - * Mail Service Selector - * - * Filter criteria for selecting mail services from providers. - * - * @since 2025.05.01 - */ -class ServiceSelector implements JsonSerializable { - - private ?ServiceScope $scope = null; - private ?string $owner = null; - private ?string $address = null; - private ?array $capabilities = null; - private ?bool $enabled = null; - - /** - * Filter by service scope - * - * @since 2025.05.01 - * - * @param ServiceScope|null $scope - * - * @return self - */ - public function setScope(?ServiceScope $scope): self { - $this->scope = $scope; - return $this; - } - - /** - * Gets the scope filter - * - * @since 2025.05.01 - * - * @return ServiceScope|null - */ - public function getScope(): ?ServiceScope { - return $this->scope; - } - - /** - * Filter by owner user ID - * - * @since 2025.05.01 - * - * @param string|null $owner - * - * @return self - */ - public function setOwner(?string $owner): self { - $this->owner = $owner; - return $this; - } - - /** - * Gets the owner filter - * - * @since 2025.05.01 - * - * @return string|null - */ - public function getOwner(): ?string { - return $this->owner; - } - - /** - * Filter by email address (matches primary or secondary) - * - * @since 2025.05.01 - * - * @param string|null $address - * - * @return self - */ - public function setAddress(?string $address): self { - $this->address = $address; - return $this; - } - - /** - * Gets the address filter - * - * @since 2025.05.01 - * - * @return string|null - */ - public function getAddress(): ?string { - return $this->address; - } - - /** - * Filter by required capabilities - * - * @since 2025.05.01 - * - * @param array|null $capabilities - * - * @return self - */ - public function setCapabilities(?array $capabilities): self { - $this->capabilities = $capabilities; - return $this; - } - - /** - * Gets the capabilities filter - * - * @since 2025.05.01 - * - * @return array|null - */ - public function getCapabilities(): ?array { - return $this->capabilities; - } - - /** - * Filter by enabled status - * - * @since 2025.05.01 - * - * @param bool|null $enabled - * - * @return self - */ - public function setEnabled(?bool $enabled): self { - $this->enabled = $enabled; - return $this; - } - - /** - * Gets the enabled filter - * - * @since 2025.05.01 - * - * @return bool|null - */ - public function getEnabled(): ?bool { - return $this->enabled; - } - - /** - * Checks if any filter criteria are set - * - * @since 2025.05.01 - * - * @return bool - */ - public function hasFilters(): bool { - return $this->scope !== null - || $this->owner !== null - || $this->address !== null - || $this->capabilities !== null - || $this->enabled !== null; - } - - /** - * @inheritDoc - */ - public function jsonSerialize(): array { - return array_filter([ - 'scope' => $this->scope?->value, - 'owner' => $this->owner, - 'address' => $this->address, - 'capabilities' => $this->capabilities, - 'enabled' => $this->enabled, - ], fn($v) => $v !== null); - } - -} diff --git a/shared/lib/Mail/Service/ServiceBaseInterface.php b/shared/lib/Mail/Service/ServiceBaseInterface.php index 129a3d4..4de6e5c 100644 --- a/shared/lib/Mail/Service/ServiceBaseInterface.php +++ b/shared/lib/Mail/Service/ServiceBaseInterface.php @@ -9,9 +9,9 @@ declare(strict_types=1); namespace KTXF\Mail\Service; -use KTXF\Mail\Collection\ICollectionBase; -use KTXF\Mail\Entity\IAddress; -use KTXF\Mail\Entity\IMessageBase; +use KTXF\Mail\Collection\CollectionBaseInterface; +use KTXF\Mail\Object\AddressInterface; +use KTXF\Resource\Delta\Delta; use KTXF\Resource\Filter\IFilter; use KTXF\Resource\Provider\ResourceServiceBaseInterface; use KTXF\Resource\Range\IRange; @@ -34,7 +34,13 @@ interface ServiceBaseInterface extends ResourceServiceBaseInterface { public const CAPABILITY_COLLECTION_LIST_SORT = 'CollectionListSort'; public const CAPABILITY_COLLECTION_EXTANT = 'CollectionExtant'; public const CAPABILITY_COLLECTION_FETCH = 'CollectionFetch'; - // Message capabilities + // Collection Filter + public const CAPABILITY_COLLECTION_FILTER_LABEL = 'label'; + public const CAPABILITY_COLLECTION_FILTER_ROLE = 'role'; + // Collection Sort + public const CAPABILITY_COLLECTION_SORT_LABEL = 'label'; + public const CAPABILITY_COLLECTION_SORT_RANK = 'rank'; + // Entity capabilities public const CAPABILITY_ENTITY_LIST = 'EntityList'; public const CAPABILITY_ENTITY_LIST_FILTER = 'EntityListFilter'; public const CAPABILITY_ENTITY_LIST_SORT = 'EntityListSort'; @@ -43,19 +49,24 @@ interface ServiceBaseInterface extends ResourceServiceBaseInterface { public const CAPABILITY_ENTITY_EXTANT = 'EntityExtant'; public const CAPABILITY_ENTITY_FETCH = 'EntityFetch'; // Filter capabilities - public const CAPABILITY_FILTER_ID = 'id'; - public const CAPABILITY_FILTER_SUBJECT = 'subject'; - public const CAPABILITY_FILTER_FROM = 'from'; - public const CAPABILITY_FILTER_TO = 'to'; - public const CAPABILITY_FILTER_DATE = 'date'; - public const CAPABILITY_FILTER_FLAG = 'flag'; - public const CAPABILITY_FILTER_SIZE = 'size'; - public const CAPABILITY_FILTER_BODY = 'body'; + public const CAPABILITY_ENTITY_FILTER_ALL = '*'; + public const CAPABILITY_ENTITY_FILTER_FROM = 'from'; + public const CAPABILITY_ENTITY_FILTER_TO = 'to'; + public const CAPABILITY_ENTITY_FILTER_CC = 'cc'; + public const CAPABILITY_ENTITY_FILTER_BCC = 'bcc'; + public const CAPABILITY_ENTITY_FILTER_SUBJECT = 'subject'; + public const CAPABILITY_ENTITY_FILTER_BODY = 'body'; + public const CAPABILITY_ENTITY_FILTER_DATE_BEFORE = 'before'; + public const CAPABILITY_ENTITY_FILTER_DATE_AFTER = 'after'; + public const CAPABILITY_ENTITY_FILTER_SIZE_MIN = 'min'; + public const CAPABILITY_ENTITY_FILTER_SIZE_MAX = 'max'; // Sort capabilities - public const CAPABILITY_SORT_DATE = 'date'; - public const CAPABILITY_SORT_SUBJECT = 'subject'; - public const CAPABILITY_SORT_FROM = 'from'; - public const CAPABILITY_SORT_SIZE = 'size'; + public const CAPABILITY_ENTITY_SORT_FROM = 'from'; + public const CAPABILITY_ENTITY_SORT_TO = 'to'; + public const CAPABILITY_ENTITY_SORT_SUBJECT = 'subject'; + public const CAPABILITY_ENTITY_SORT_DATE_RECEIVED = 'received'; + public const CAPABILITY_ENTITY_SORT_DATE_SENT = 'sent'; + public const CAPABILITY_ENTITY_SORT_SIZE = 'size'; public const JSON_TYPE = 'mail.service'; public const JSON_PROPERTY_PRIMARY_ADDRESS = 'primaryAddress'; @@ -66,16 +77,16 @@ interface ServiceBaseInterface extends ResourceServiceBaseInterface { * * @since 2025.05.01 * - * @return IAddress + * @return AddressInterface */ - public function getPrimaryAddress(): IAddress; + public function getPrimaryAddress(): AddressInterface; /** * Gets the secondary mailing addresses (aliases) for this service * * @since 2025.05.01 * - * @return array + * @return array */ public function getSecondaryAddresses(): array; @@ -88,7 +99,7 @@ interface ServiceBaseInterface extends ResourceServiceBaseInterface { * * @return bool True if address matches primary or any secondary address */ - public function handlesAddress(string $address): bool; + public function hasAddress(string $address): bool; /** * Lists all collections in this service @@ -98,9 +109,9 @@ interface ServiceBaseInterface extends ResourceServiceBaseInterface { * @param IFilter|null $filter Optional filter criteria * @param ISort|null $sort Optional sort order * - * @return array Collections indexed by ID + * @return array Collections indexed by ID */ - public function collectionList(?IFilter $filter = null, ?ISort $sort = null): array; + public function collectionList(string|int $location, ?IFilter $filter = null, ?ISort $sort = null): array; /** * Creates a filter builder for collections @@ -129,7 +140,7 @@ interface ServiceBaseInterface extends ResourceServiceBaseInterface { * * @return array Map of ID => exists */ - public function collectionExtant(string|int ...$identifiers): array; + public function collectionExtant(string|int $location, string|int ...$identifiers): array; /** * Fetches a single collection @@ -138,9 +149,9 @@ interface ServiceBaseInterface extends ResourceServiceBaseInterface { * * @param string|int $identifier Collection ID * - * @return ICollectionBase|null Collection or null if not found + * @return CollectionBaseInterface|null Collection or null if not found */ - public function collectionFetch(string|int $identifier): ?ICollectionBase; + public function collectionFetch(string|int $identifier): ?CollectionBaseInterface; /** * Lists messages in a collection @@ -153,9 +164,9 @@ interface ServiceBaseInterface extends ResourceServiceBaseInterface { * @param IRange|null $range Optional pagination * @param array|null $properties Optional message properties to fetch * - * @return array Messages indexed by ID + * @return array Messages indexed by ID */ - public function messageList(string|int $collection, ?IFilter $filter = null, ?ISort $sort = null, ?IRange $range = null, ?array $properties = null): array; + public function entityList(string|int $collection, ?IFilter $filter = null, ?ISort $sort = null, ?IRange $range = null, ?array $properties = null): array; /** * Creates a filter builder for messages @@ -164,7 +175,7 @@ interface ServiceBaseInterface extends ResourceServiceBaseInterface { * * @return IFilter */ - public function messageListFilter(): IFilter; + public function entityListFilter(): IFilter; /** * Creates a sort builder for messages @@ -173,7 +184,7 @@ interface ServiceBaseInterface extends ResourceServiceBaseInterface { * * @return ISort */ - public function messageListSort(): ISort; + public function entityListSort(): ISort; /** * Creates a range builder for messages @@ -184,7 +195,7 @@ interface ServiceBaseInterface extends ResourceServiceBaseInterface { * * @return IRange */ - public function messageListRange(RangeType $type): IRange; + public function entityListRange(RangeType $type): IRange; /** * Gets incremental changes since last sync @@ -197,7 +208,7 @@ interface ServiceBaseInterface extends ResourceServiceBaseInterface { * * @return array ['signature' => string, 'added' => array, 'modified' => array, 'removed' => array] */ - public function messageDelta(string|int $collection, string $signature, string $detail = 'ids'): array; + public function entityDelta(string|int $collection, string $signature, string $detail = 'ids'): Delta; /** * Checks if messages exist @@ -209,18 +220,18 @@ interface ServiceBaseInterface extends ResourceServiceBaseInterface { * * @return array Map of ID => exists */ - public function messageExtant(string|int $collection, string|int ...$identifiers): array; + public function entityExtant(string|int $collection, string|int ...$identifiers): array; /** - * Fetches one or more messages + * Fetches one or more entities * * @since 2025.05.01 * * @param string|int $collection Collection ID * @param string|int ...$identifiers Message IDs to fetch * - * @return array Messages indexed by ID + * @return array Messages indexed by ID */ - public function messageFetch(string|int $collection, string|int ...$identifiers): array; + public function entityFetch(string|int $collection, string|int ...$identifiers): array; } diff --git a/shared/lib/Mail/Service/ServiceCollectionMutableInterface.php b/shared/lib/Mail/Service/ServiceCollectionMutableInterface.php index 7736af6..99da45f 100644 --- a/shared/lib/Mail/Service/ServiceCollectionMutableInterface.php +++ b/shared/lib/Mail/Service/ServiceCollectionMutableInterface.php @@ -9,8 +9,8 @@ declare(strict_types=1); namespace KTXF\Mail\Service; -use KTXF\Mail\Collection\ICollectionBase; -use KTXF\Mail\Collection\ICollectionMutable; +use KTXF\Mail\Collection\CollectionBaseInterface; +use KTXF\Mail\Collection\CollectionMutableInterface; /** * Mail Service Collection Mutable Interface @@ -32,9 +32,9 @@ interface ServiceCollectionMutableInterface extends ServiceBaseInterface { * * @since 2025.05.01 * - * @return ICollectionMutable Fresh collection object + * @return CollectionMutableInterface Fresh collection object */ - public function collectionFresh(): ICollectionMutable; + public function collectionFresh(): CollectionMutableInterface; /** * Creates a new collection @@ -42,12 +42,12 @@ interface ServiceCollectionMutableInterface extends ServiceBaseInterface { * @since 2025.05.01 * * @param string|int|null $location Parent collection ID (null for root) - * @param ICollectionMutable $collection Collection to create + * @param CollectionMutableInterface $collection Collection to create * @param array $options Protocol-specific options * - * @return ICollectionBase Created collection with assigned ID + * @return CollectionBaseInterface Created collection with assigned ID */ - public function collectionCreate(string|int|null $location, ICollectionMutable $collection, array $options = []): ICollectionBase; + public function collectionCreate(string|int|null $location, CollectionMutableInterface $collection, array $options = []): CollectionBaseInterface; /** * Modifies an existing collection @@ -55,11 +55,11 @@ interface ServiceCollectionMutableInterface extends ServiceBaseInterface { * @since 2025.05.01 * * @param string|int $identifier Collection ID - * @param ICollectionMutable $collection Updated collection data + * @param CollectionMutableInterface $collection Updated collection data * - * @return ICollectionBase Modified collection + * @return CollectionBaseInterface Modified collection */ - public function collectionModify(string|int $identifier, ICollectionMutable $collection): ICollectionBase; + public function collectionModify(string|int $identifier, CollectionMutableInterface $collection): CollectionBaseInterface; /** * Destroys a collection @@ -67,11 +67,12 @@ interface ServiceCollectionMutableInterface extends ServiceBaseInterface { * @since 2025.05.01 * * @param string|int $identifier Collection ID - * @param bool $recursive Destroy child collections too + * @param bool $force Force destruction even if not empty + * @param bool $recursive Recursively destroy contents * * @return bool True if destroyed */ - public function collectionDestroy(string|int $identifier, bool $recursive = false): bool; + public function collectionDestroy(string|int $identifier, bool $force = false, bool $recursive = false): bool; /** * Moves a collection to a new parent @@ -81,8 +82,8 @@ interface ServiceCollectionMutableInterface extends ServiceBaseInterface { * @param string|int $identifier Collection ID * @param string|int|null $targetLocation New parent ID (null for root) * - * @return ICollectionBase Moved collection + * @return CollectionBaseInterface Moved collection */ - public function collectionMove(string|int $identifier, string|int|null $targetLocation): ICollectionBase; + public function collectionMove(string|int $identifier, string|int|null $targetLocation): CollectionBaseInterface; } diff --git a/shared/lib/Mail/Service/ServiceConfigurableInterface.php b/shared/lib/Mail/Service/ServiceConfigurableInterface.php new file mode 100644 index 0000000..1c842af --- /dev/null +++ b/shared/lib/Mail/Service/ServiceConfigurableInterface.php @@ -0,0 +1,26 @@ + + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace KTXF\Mail\Service; + +use KTXF\Resource\Provider\ResourceServiceConfigureInterface; + +/** + * Mail Service Mutable Interface + * + * Extends base service interface with setter methods for mutable properties. + * Used for service configuration and updates. + * + * @since 2025.05.01 + */ +interface ServiceConfigurableInterface extends ServiceMutableInterface, ResourceServiceConfigureInterface { + + public const JSON_TYPE = ServiceBaseInterface::JSON_TYPE; + +} diff --git a/shared/lib/Mail/Service/ServiceEntityMutableInterface.php b/shared/lib/Mail/Service/ServiceEntityMutableInterface.php new file mode 100644 index 0000000..9736cd0 --- /dev/null +++ b/shared/lib/Mail/Service/ServiceEntityMutableInterface.php @@ -0,0 +1,103 @@ + + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace KTXF\Mail\Service; + +use KTXF\Mail\Entity\EntityBaseInterface; +use KTXF\Mail\Entity\EntityMutableInterface; + +/** + * Mail Service Entity Mutable Interface + * + * Optional interface for services that support entity CRUD operations. + * Provides entity creation, modification, deletion, copying, moving, and flag management. + * + * @since 2025.05.01 + */ +interface ServiceEntityMutableInterface extends ServiceBaseInterface { + + public const CAPABILITY_ENTITY_CREATE = 'EntityCreate'; + public const CAPABILITY_ENTITY_MODIFY = 'EntityModify'; + public const CAPABILITY_ENTITY_DESTROY = 'EntityDestroy'; + public const CAPABILITY_ENTITY_COPY = 'EntityCopy'; + public const CAPABILITY_ENTITY_MOVE = 'EntityMove'; + + /** + * Creates a fresh entity instance for composition + * + * @since 2025.05.01 + * + * @return EntityMutableInterface Fresh entity object + */ + public function entityFresh(): EntityMutableInterface; + + /** + * Creates/imports an entity into a collection + * + * @since 2025.05.01 + * + * @param string|int $collection collection identifier + * @param EntityMutableInterface $entity Entity data + * @param array $options additional options + * + * @return EntityBaseInterface Created entity + */ + public function entityCreate(string|int $collection, EntityMutableInterface $entity, array $options = []): EntityBaseInterface; + + /** + * Modifies an existing entity + * + * @since 2025.05.01 + * + * @param string|int $collection Collection identifier + * @param string|int $identifier Entity identifier + * @param EntityMutableInterface $entity Entity data + * + * @return EntityBaseInterface Modified entity + */ + public function entityModify(string|int $collection, string|int $identifier, EntityMutableInterface $entity): EntityBaseInterface; + /** + * Destroys one or more entities + * + * @since 2025.05.01 + * + * @param string|int $collection Collection identifier + * @param string|int ...$identifiers Entity identifiers to destroy + * + * @return array List of destroyed entity identifiers + */ + public function entityDestroy(string|int $collection, string|int ...$identifiers): array; + + /** + * Copies entities to another collection + * + * @since 2025.05.01 + * + * @param string|int $sourceCollection Source collection identifier + * @param string|int $targetCollection Target collection identifier + * @param string|int ...$identifiers Entity identifiers to copy + * + * @return array Map of source identifier => new identifier + */ + public function entityCopy(string|int $sourceCollection, string|int $targetCollection, string|int ...$identifiers): array; + + /** + * Moves entities to another collection + * + * @since 2025.05.01 + * + * @param string|int $sourceCollection Source collection identifier + * @param string|int $targetCollection Target collection identifier + * @param string|int ...$identifiers Entity identifiers to move + * + * @return array List of moved entity identifiers + */ + public function entityMove(string|int $sourceCollection, string|int $targetCollection, string|int ...$identifiers): array; + +} diff --git a/shared/lib/Mail/Service/ServiceEntityTransmitInterface.php b/shared/lib/Mail/Service/ServiceEntityTransmitInterface.php new file mode 100644 index 0000000..cd268d1 --- /dev/null +++ b/shared/lib/Mail/Service/ServiceEntityTransmitInterface.php @@ -0,0 +1,48 @@ + + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace KTXF\Mail\Service; + +use KTXF\Mail\Entity\EntityMutableInterface; +use KTXF\Mail\Exception\SendException; + +/** + * Mail Service Transmit Interface + * + * Interface for mail services capable of transmitting outbound entities. + * + * @since 2025.05.01 + */ +interface ServiceEntityTransmitInterface extends ServiceBaseInterface { + + public const CAPABILITY_ENTITY_TRANSMIT = 'EntityTransmit'; + + /** + * Creates a fresh entity instance for composition + * + * @since 2025.05.01 + * + * @return EntityMutableInterface Fresh entity object + */ + public function entityFresh(): EntityMutableInterface; + + /** + * Transmits an outbound entity + * + * @since 2025.05.01 + * + * @param EntityMutableInterface $entity Entity to transmit + * + * @return string Entity identifier assigned by the transport + * + * @throws SendException On delivery failure + */ + public function entitySend(EntityMutableInterface $entity): string; + +} diff --git a/shared/lib/Mail/Service/ServiceMessageMutableInterface.php b/shared/lib/Mail/Service/ServiceMessageMutableInterface.php deleted file mode 100644 index ea202de..0000000 --- a/shared/lib/Mail/Service/ServiceMessageMutableInterface.php +++ /dev/null @@ -1,119 +0,0 @@ - - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -namespace KTXF\Mail\Service; - -use KTXF\Mail\Entity\IMessageBase; -use KTXF\Mail\Entity\IMessageMutable; - -/** - * Mail Service Message Mutable Interface - * - * Optional interface for services that support message CRUD operations. - * Provides message creation, modification, deletion, copying, moving, and flag management. - * - * @since 2025.05.01 - */ -interface ServiceMessageMutableInterface extends ServiceBaseInterface { - - public const CAPABILITY_ENTITY_CREATE = 'EntityCreate'; - public const CAPABILITY_ENTITY_MODIFY = 'EntityModify'; - public const CAPABILITY_ENTITY_DESTROY = 'EntityDestroy'; - public const CAPABILITY_ENTITY_COPY = 'EntityCopy'; - public const CAPABILITY_ENTITY_MOVE = 'EntityMove'; - public const CAPABILITY_ENTITY_FLAG = 'EntityFlag'; - - /** - * Creates a fresh message instance for composition - * - * @since 2025.05.01 - * - * @return IMessageMutable Fresh message object - */ - public function messageFresh(): IMessageMutable; - - /** - * Creates/imports a message into a collection - * - * @since 2025.05.01 - * - * @param string|int $collection Target collection ID - * @param IMessageMutable $message Message to create - * @param array $options Protocol-specific options (e.g., flags, keywords) - * - * @return IMessageBase Created message with assigned ID - */ - public function messageCreate(string|int $collection, IMessageMutable $message, array $options = []): IMessageBase; - - /** - * Modifies an existing message - * - * @since 2025.05.01 - * - * @param string|int $collection Collection ID - * @param string|int $identifier Message ID - * @param IMessageMutable $message Updated message data - * - * @return IMessageBase Modified message - */ - public function messageModify(string|int $collection, string|int $identifier, IMessageMutable $message): IMessageBase; - - /** - * Destroys one or more messages - * - * @since 2025.05.01 - * - * @param string|int $collection Collection ID - * @param string|int ...$identifiers Message IDs to destroy - * - * @return array Map of ID => destroyed - */ - public function messageDestroy(string|int $collection, string|int ...$identifiers): array; - - /** - * Copies messages to another collection - * - * @since 2025.05.01 - * - * @param string|int $sourceCollection Source collection ID - * @param string|int $targetCollection Target collection ID - * @param string|int ...$identifiers Message IDs to copy - * - * @return array Map of source ID => new ID - */ - public function messageCopy(string|int $sourceCollection, string|int $targetCollection, string|int ...$identifiers): array; - - /** - * Moves messages to another collection - * - * @since 2025.05.01 - * - * @param string|int $sourceCollection Source collection ID - * @param string|int $targetCollection Target collection ID - * @param string|int ...$identifiers Message IDs to move - * - * @return array Map of ID => moved - */ - public function messageMove(string|int $sourceCollection, string|int $targetCollection, string|int ...$identifiers): array; - - /** - * Sets flags on messages - * - * @since 2025.05.01 - * - * @param string|int $collection Collection ID - * @param array $flags Flags to set (e.g., ['\Seen', '\Flagged']) - * @param bool $value True to add flags, false to remove - * @param string|int ...$identifiers Message IDs - * - * @return array Map of ID => success - */ - public function messageFlag(string|int $collection, array $flags, bool $value, string|int ...$identifiers): array; - -} diff --git a/shared/lib/Mail/Service/ServiceMessageSendInterface.php b/shared/lib/Mail/Service/ServiceMessageSendInterface.php deleted file mode 100644 index 4e8f3f4..0000000 --- a/shared/lib/Mail/Service/ServiceMessageSendInterface.php +++ /dev/null @@ -1,49 +0,0 @@ - - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -namespace KTXF\Mail\Service; - -use KTXF\Mail\Entity\IMessageMutable; -use KTXF\Mail\Exception\SendException; - -/** - * Mail Service Send Interface - * - * Interface for mail services capable of sending outbound messages. - * This is the minimum capability for an outbound-only mail service. - * - * @since 2025.05.01 - */ -interface ServiceMessageSendInterface extends ServiceBaseInterface { - - public const CAPABILITY_SEND = 'Send'; - - /** - * Creates a new fresh message object - * - * @since 2025.05.01 - * - * @return IMessageMutable Fresh message object for composing - */ - public function messageFresh(): IMessageMutable; - - /** - * Sends an outbound message - * - * @since 2025.05.01 - * - * @param IMessageMutable $message Message to send - * - * @return string Message ID assigned by the transport - * - * @throws SendException On delivery failure - */ - public function messageSend(IMessageMutable $message): string; - -} diff --git a/shared/lib/Mail/Service/ServiceMutableInterface.php b/shared/lib/Mail/Service/ServiceMutableInterface.php index 8245d3e..5a539c7 100644 --- a/shared/lib/Mail/Service/ServiceMutableInterface.php +++ b/shared/lib/Mail/Service/ServiceMutableInterface.php @@ -9,7 +9,7 @@ declare(strict_types=1); namespace KTXF\Mail\Service; -use KTXF\Mail\Entity\IAddress; +use KTXF\Mail\Object\AddressInterface; /** * Mail Service Mutable Interface @@ -26,21 +26,21 @@ interface ServiceMutableInterface extends ServiceBaseInterface { * * @since 2025.05.01 * - * @param IAddress $value Primary email address + * @param AddressInterface $value Primary email address * - * @return self + * @return static */ - public function setPrimaryAddress(IAddress $value): self; + public function setPrimaryAddress(AddressInterface $value): static; /** * Sets the secondary mailing addresses (aliases) for this service * * @since 2025.05.01 * - * @param array $value Array of secondary addresses + * @param array $value Array of secondary addresses * - * @return self + * @return static */ - public function setSecondaryAddresses(array $value): self; + public function setSecondaryAddresses(array $value): static; } diff --git a/shared/lib/Resource/Delta/Delta.php b/shared/lib/Resource/Delta/Delta.php new file mode 100644 index 0000000..aadf05a --- /dev/null +++ b/shared/lib/Resource/Delta/Delta.php @@ -0,0 +1,45 @@ + + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace KTXF\Resource\Delta; + +use KTXF\Json\JsonSerializable; + +class Delta implements JsonSerializable { + + public function __construct( + public ?DeltaCollection $additions = null, + public ?DeltaCollection $modifications = null, + public ?DeltaCollection $deletions = null, + public ?string $signature = null, + ) { + if ($this->additions === null) { + $this->additions = new DeltaCollection; + } + if ($this->modifications === null) { + $this->modifications = new DeltaCollection; + } + if ($this->deletions === null) { + $this->deletions = new DeltaCollection; + } + if ($this->signature === null) { + $this->signature = ''; + } + } + + public function jsonSerialize(): array { + return [ + 'signature' => $this->signature, + 'additions' => $this->additions, + 'modifications' => $this->modifications, + 'deletions' => $this->deletions, + ]; + } + +} diff --git a/shared/lib/Resource/Delta/DeltaCollection.php b/shared/lib/Resource/Delta/DeltaCollection.php new file mode 100644 index 0000000..120fe5e --- /dev/null +++ b/shared/lib/Resource/Delta/DeltaCollection.php @@ -0,0 +1,18 @@ + + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace KTXF\Resource\Delta; + +use KTXF\Json\JsonSerializableCollection; + +class DeltaCollection extends JsonSerializableCollection { + public function __construct($data = []) { + parent::__construct($data, self::TYPE_STRING); + } +} diff --git a/shared/lib/Resource/Filter/Filter.php b/shared/lib/Resource/Filter/Filter.php index 4a4649a..f2b36c7 100644 --- a/shared/lib/Resource/Filter/Filter.php +++ b/shared/lib/Resource/Filter/Filter.php @@ -31,6 +31,7 @@ class Filter implements IFilter { 'i' => 'integer', 'b' => 'boolean', 'a' => 'array', + 'd' => 'date', default => throw new \InvalidArgumentException("Invalid type '$type' for attribute '$id'."), }; $this->attributes[$id]['length'] = (int)$length; diff --git a/shared/lib/Resource/Provider/Node/NodeBaseAbstract.php b/shared/lib/Resource/Provider/Node/NodeBaseAbstract.php new file mode 100644 index 0000000..657e84e --- /dev/null +++ b/shared/lib/Resource/Provider/Node/NodeBaseAbstract.php @@ -0,0 +1,116 @@ + + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace KTXF\Resource\Provider\Node; + +use DateTimeImmutable; + +/** + * Abstract Node Base Class + * + * Provides common implementation for all resource nodes + * + * @since 2025.05.01 + */ +abstract class NodeBaseAbstract implements NodeBaseInterface { + + /** + * Internal data storage + */ + protected array $data = []; + + public function __construct( + protected readonly string $provider, + protected readonly string|int $service, + ) { + $this->data = [ + static::JSON_PROPERTY_PROVIDER => $this->provider, + static::JSON_PROPERTY_SERVICE => $this->service, + static::JSON_PROPERTY_COLLECTION => null, + static::JSON_PROPERTY_IDENTIFIER => null, + static::JSON_PROPERTY_SIGNATURE => null, + static::JSON_PROPERTY_CREATED => null, + static::JSON_PROPERTY_MODIFIED => null, + ]; + } + + /** + * @inheritDoc + */ + public function type(): string { + return static::RESOURCE_TYPE; + } + + /** + * @inheritDoc + */ + public function provider(): string { + return $this->data[static::JSON_PROPERTY_PROVIDER]; + } + + /** + * @inheritDoc + */ + public function service(): string|int { + return $this->data[static::JSON_PROPERTY_SERVICE]; + } + + /** + * @inheritDoc + */ + public function collection(): string|int|null { + return $this->data[static::JSON_PROPERTY_COLLECTION] ?? null; + } + + /** + * @inheritDoc + */ + public function identifier(): string|int|null { + return $this->data[static::JSON_PROPERTY_IDENTIFIER] ?? null; + } + + /** + * @inheritDoc + */ + public function signature(): string|null { + return $this->data[static::JSON_PROPERTY_SIGNATURE] ?? null; + } + + /** + * @inheritDoc + */ + public function created(): DateTimeImmutable|null { + return isset($this->data[static::JSON_PROPERTY_CREATED]) + ? new DateTimeImmutable($this->data[static::JSON_PROPERTY_CREATED]) + : null; + } + + /** + * @inheritDoc + */ + public function modified(): DateTimeImmutable|null { + return isset($this->data[static::JSON_PROPERTY_MODIFIED]) + ? new DateTimeImmutable($this->data[static::JSON_PROPERTY_MODIFIED]) + : null; + } + + /** + * @inheritDoc + */ + public function jsonSerialize(): array { + $data = $this->data; + $data[static::JSON_PROPERTY_PROPERTIES] = $this->getProperties()->jsonSerialize(); + return $data; + } + + /** + * @inheritDoc + */ + abstract public function getProperties(): NodePropertiesBaseInterface; +} diff --git a/shared/lib/Resource/Provider/Node/NodeBaseInterface.php b/shared/lib/Resource/Provider/Node/NodeBaseInterface.php new file mode 100644 index 0000000..6137d60 --- /dev/null +++ b/shared/lib/Resource/Provider/Node/NodeBaseInterface.php @@ -0,0 +1,95 @@ + + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace KTXF\Resource\Provider\Node; + +use DateTimeImmutable; +use KTXF\Json\JsonSerializable; + +/** + * Resource Node Read Interface + * + * @since 2025.05.01 + */ +interface NodeBaseInterface extends JsonSerializable { + + public const RESOURCE_TYPE = 'resource.node'; + public const JSON_PROPERTY_PROVIDER = 'provider'; + public const JSON_PROPERTY_SERVICE = 'service'; + public const JSON_PROPERTY_COLLECTION = 'collection'; + public const JSON_PROPERTY_IDENTIFIER = 'identifier'; + public const JSON_PROPERTY_SIGNATURE = 'signature'; + public const JSON_PROPERTY_CREATED = 'created'; + public const JSON_PROPERTY_MODIFIED = 'modified'; + public const JSON_PROPERTY_PROPERTIES = 'properties'; + + /** + * Node type + * + * @since 2025.05.01 + */ + public function type(): string; + + /** + * Provider identifier + * + * @since 2025.05.01 + */ + public function provider(): string; + + /** + * Service identifier + * + * @since 2025.05.01 + */ + public function service(): string|int; + + /** + * Collection identifier + * + * @since 2025.05.01 + */ + public function collection(): string|int|null; + + /** + * Node identifier + * + * @since 2025.05.01 + */ + public function identifier(): string|int|null; + + /** + * Node signature/sync token + * + * @since 2025.05.01 + */ + public function signature(): string|null; + + /** + * Node creation date + * + * @since 2025.05.01 + */ + public function created(): DateTimeImmutable|null; + + /** + * Node modification date + * + * @since 2025.05.01 + */ + public function modified(): DateTimeImmutable|null; + + /** + * Get the node properties + * + * @since 2025.05.01 + */ + public function getProperties(): NodePropertiesBaseInterface|NodePropertiesMutableInterface; + +} diff --git a/shared/lib/Resource/Provider/Node/NodeMutableAbstract.php b/shared/lib/Resource/Provider/Node/NodeMutableAbstract.php new file mode 100644 index 0000000..25fdcc5 --- /dev/null +++ b/shared/lib/Resource/Provider/Node/NodeMutableAbstract.php @@ -0,0 +1,95 @@ + + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace KTXF\Resource\Provider\Node; + +/** + * Abstract Node Mutable Class + * + * Provides common implementation for mutable resource nodes + * + * @since 2025.05.01 + */ +abstract class NodeMutableAbstract extends NodeBaseAbstract implements NodeMutableInterface { + + /** + * @inheritDoc + */ + public function jsonDeserialize(array|string $data): static { + if (is_string($data)) { + $data = json_decode($data, true); + } + + $this->data = []; + + if (isset($data[static::JSON_PROPERTY_COLLECTION])) { + if (!is_string($data[static::JSON_PROPERTY_COLLECTION]) && !is_int($data[static::JSON_PROPERTY_COLLECTION])) { + throw new \InvalidArgumentException("Collection must be a string or integer"); + } + $this->data[static::JSON_PROPERTY_COLLECTION] = $data[static::JSON_PROPERTY_COLLECTION]; + } else { + $this->data[static::JSON_PROPERTY_COLLECTION] = null; + } + + if (isset($data[static::JSON_PROPERTY_IDENTIFIER])) { + if (!is_string($data[static::JSON_PROPERTY_IDENTIFIER]) && !is_int($data[static::JSON_PROPERTY_IDENTIFIER])) { + throw new \InvalidArgumentException("Identifier must be a string or integer"); + } + $this->data[static::JSON_PROPERTY_IDENTIFIER] = $data[static::JSON_PROPERTY_IDENTIFIER]; + } else { + $this->data[static::JSON_PROPERTY_IDENTIFIER] = null; + } + + if (isset($data[static::JSON_PROPERTY_SIGNATURE])) { + if (!is_string($data[static::JSON_PROPERTY_SIGNATURE]) && !is_int($data[static::JSON_PROPERTY_SIGNATURE])) { + throw new \InvalidArgumentException("Signature must be a string or integer"); + } + $this->data[static::JSON_PROPERTY_SIGNATURE] = $data[static::JSON_PROPERTY_SIGNATURE]; + } else { + $this->data[static::JSON_PROPERTY_SIGNATURE] = null; + } + + if (isset($data[static::JSON_PROPERTY_CREATED])) { + if (!is_string($data[static::JSON_PROPERTY_CREATED])) { + throw new \InvalidArgumentException("Created date must be a string in ISO 8601 format"); + } + $this->data[static::JSON_PROPERTY_CREATED] = $data[static::JSON_PROPERTY_CREATED]; + } else { + $this->data[static::JSON_PROPERTY_CREATED] = null; + } + + if (isset($data[static::JSON_PROPERTY_MODIFIED])) { + if (!is_string($data[static::JSON_PROPERTY_MODIFIED])) { + throw new \InvalidArgumentException("Modified date must be a string in ISO 8601 format"); + } + $this->data[static::JSON_PROPERTY_MODIFIED] = $data[static::JSON_PROPERTY_MODIFIED]; + } else { + $this->data[static::JSON_PROPERTY_MODIFIED] = null; + } + + if (isset($data[static::JSON_PROPERTY_PROPERTIES])) { + if (!is_array($data[static::JSON_PROPERTY_PROPERTIES])) { + throw new \InvalidArgumentException("Properties must be an array"); + } + $this->getProperties()->jsonDeserialize($data[static::JSON_PROPERTY_PROPERTIES]); + } + + return $this; + } + + /** + * @inheritDoc + */ + abstract public function getProperties(): NodePropertiesMutableInterface; + + /** + * @inheritDoc + */ + abstract public function setProperties(NodePropertiesMutableInterface $value): static; +} diff --git a/shared/lib/Resource/Provider/Node/NodeMutableInterface.php b/shared/lib/Resource/Provider/Node/NodeMutableInterface.php new file mode 100644 index 0000000..ea70304 --- /dev/null +++ b/shared/lib/Resource/Provider/Node/NodeMutableInterface.php @@ -0,0 +1,35 @@ + + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace KTXF\Resource\Provider\Node; + +use KTXF\Json\JsonDeserializable; + +/** + * Node Mutable Write Interface + * + * @since 2025.05.01 + */ +interface NodeMutableInterface extends NodeBaseInterface, JsonDeserializable { + + /** + * Get the node properties + * + * @since 2025.05.01 + */ + public function getProperties(): NodePropertiesMutableInterface; + + /** + * Sets the node properties + * + * @since 2025.05.01 + */ + public function setProperties(NodePropertiesMutableInterface $value): static; + +} diff --git a/shared/lib/Resource/Provider/Node/NodePropertiesBaseAbstract.php b/shared/lib/Resource/Provider/Node/NodePropertiesBaseAbstract.php new file mode 100644 index 0000000..c61734d --- /dev/null +++ b/shared/lib/Resource/Provider/Node/NodePropertiesBaseAbstract.php @@ -0,0 +1,57 @@ + + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace KTXF\Resource\Provider\Node; + +/** + * Abstract Node Properties Base Class + * + * Provides common implementation for node properties + * + * @since 2025.05.01 + */ +abstract class NodePropertiesBaseAbstract implements NodePropertiesBaseInterface { + + protected array $data = []; + + public function __construct(array $data) { + + if (!isset($data[static::JSON_PROPERTY_TYPE])) { + $data[static::JSON_PROPERTY_TYPE] = static::JSON_TYPE; + } + + if (!isset($data[static::JSON_PROPERTY_VERSION])) { + $data[static::JSON_PROPERTY_VERSION] = 1; + } + + $this->data = $data; + } + + /** + * @inheritDoc + */ + public function jsonSerialize(): array { + return $this->data; + } + + /** + * @inheritDoc + */ + public function type(): string { + return $this->data[static::JSON_PROPERTY_TYPE]; + } + + /** + * @inheritDoc + */ + public function version(): int { + return $this->data[static::JSON_PROPERTY_VERSION]; + } + +} diff --git a/shared/lib/Resource/Provider/Node/NodePropertiesBaseInterface.php b/shared/lib/Resource/Provider/Node/NodePropertiesBaseInterface.php new file mode 100644 index 0000000..0db937a --- /dev/null +++ b/shared/lib/Resource/Provider/Node/NodePropertiesBaseInterface.php @@ -0,0 +1,37 @@ + + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace KTXF\Resource\Provider\Node; + +use JsonSerializable; + +/** + * Resource Node Properties Read Interface + * + * @since 2025.05.01 + */ +interface NodePropertiesBaseInterface extends JsonSerializable { + + public const RESOURCE_TYPE = 'resource.data'; + + public const JSON_TYPE = 'resource.data'; + public const JSON_PROPERTY_TYPE = '@type'; + public const JSON_PROPERTY_VERSION = 'version'; + + /** + * Get resource node properties type + */ + public function type(): string; + + /** + * Get resource node properties version + */ + public function version(): int; + +} diff --git a/shared/lib/Resource/Provider/Node/NodePropertiesMutableAbstract.php b/shared/lib/Resource/Provider/Node/NodePropertiesMutableAbstract.php new file mode 100644 index 0000000..c1da41e --- /dev/null +++ b/shared/lib/Resource/Provider/Node/NodePropertiesMutableAbstract.php @@ -0,0 +1,34 @@ + + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace KTXF\Resource\Provider\Node; + +/** + * Abstract Node Properties Mutable Class + * + * Provides common implementation for mutable node properties + * + * @since 2025.05.01 + */ +abstract class NodePropertiesMutableAbstract extends NodePropertiesBaseAbstract implements NodePropertiesMutableInterface { + + /** + * @inheritDoc + */ + public function jsonDeserialize(array|string $data): static { + if (is_string($data)) { + $data = json_decode($data, true); + } + + $this->data = $data; + + return $this; + } + +} diff --git a/shared/lib/Resource/Provider/Node/NodePropertiesMutableInterface.php b/shared/lib/Resource/Provider/Node/NodePropertiesMutableInterface.php new file mode 100644 index 0000000..d38d319 --- /dev/null +++ b/shared/lib/Resource/Provider/Node/NodePropertiesMutableInterface.php @@ -0,0 +1,21 @@ + + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace KTXF\Resource\Provider\Node; + +use KTXF\Json\JsonDeserializable; + +/** + * Resource Node Properties Mutable Interface + * + * @since 2025.05.01 + */ +interface NodePropertiesMutableInterface extends NodePropertiesBaseInterface, JsonDeserializable { + +} diff --git a/shared/lib/Resource/Provider/ResourceProviderBaseInterface.php b/shared/lib/Resource/Provider/ResourceProviderBaseInterface.php index dfbd4f8..c7e0f77 100644 --- a/shared/lib/Resource/Provider/ResourceProviderBaseInterface.php +++ b/shared/lib/Resource/Provider/ResourceProviderBaseInterface.php @@ -22,7 +22,7 @@ interface ResourceProviderBaseInterface extends ProviderInterface, JsonSerializa public const JSON_TYPE = 'resource.provider'; public const JSON_PROPERTY_TYPE = '@type'; - public const JSON_PROPERTY_ID = 'id'; + public const JSON_PROPERTY_IDENTIFIER = 'identifier'; public const JSON_PROPERTY_LABEL = 'label'; public const JSON_PROPERTY_CAPABILITIES = 'capabilities'; diff --git a/shared/lib/Resource/Provider/ResourceServiceBaseInterface.php b/shared/lib/Resource/Provider/ResourceServiceBaseInterface.php index f0111a8..539d98e 100644 --- a/shared/lib/Resource/Provider/ResourceServiceBaseInterface.php +++ b/shared/lib/Resource/Provider/ResourceServiceBaseInterface.php @@ -11,10 +11,14 @@ interface ResourceServiceBaseInterface extends JsonSerializable { // JSON Constants public const JSON_TYPE = 'resource.service'; public const JSON_PROPERTY_TYPE = '@type'; - public const JSON_PROPERTY_ID = 'id'; + public const JSON_PROPERTY_PROVIDER = 'provider'; + public const JSON_PROPERTY_IDENTIFIER = 'identifier'; public const JSON_PROPERTY_LABEL = 'label'; public const JSON_PROPERTY_ENABLED = 'enabled'; public const JSON_PROPERTY_CAPABILITIES = 'capabilities'; + public const JSON_PROPERTY_LOCATION = 'location'; + public const JSON_PROPERTY_IDENTITY = 'identity'; + public const JSON_PROPERTY_AUXILIARY = 'auxiliary'; /** * Confirms if specific capability is supported @@ -41,21 +45,21 @@ interface ResourceServiceBaseInterface extends JsonSerializable { * * @since 2025.11.01 */ - public function in(): string; + public function provider(): string; /** * Unique arbitrary text string identifying this service (e.g. 1 or service1 or anything else) * * @since 2025.11.01 */ - public function id(): string|int; + public function identifier(): string|int; /** * Gets the localized human friendly name of this service (e.g. ACME Company File Service) * * @since 2025.11.01 */ - public function getLabel(): string; + public function getLabel(): string|null; /** * Gets the active status of this service @@ -82,4 +86,13 @@ interface ResourceServiceBaseInterface extends JsonSerializable { */ public function getIdentity(): ResourceServiceIdentityInterface; + /** + * Gets the auxiliary information of this service + * + * @since 2025.05.01 + * + * @return array + */ + public function getAuxiliary(): array; + } diff --git a/shared/lib/Resource/Provider/ResourceServiceConfigureInterface.php b/shared/lib/Resource/Provider/ResourceServiceConfigureInterface.php index c8a29ae..563414a 100644 --- a/shared/lib/Resource/Provider/ResourceServiceConfigureInterface.php +++ b/shared/lib/Resource/Provider/ResourceServiceConfigureInterface.php @@ -30,6 +30,15 @@ interface ResourceServiceConfigureInterface extends ResourceServiceMutateInterfa */ public function setLocation(ResourceServiceLocationInterface $value): self; + /** + * Gets a fresh instance of the location/configuration of this service + * + * @since 2025.05.01 + * + * @return ResourceServiceLocationInterface + */ + public function freshLocation(string|null $type, array $data = []): ResourceServiceLocationInterface; + /** * Sets the identity used for this service * @@ -41,4 +50,13 @@ interface ResourceServiceConfigureInterface extends ResourceServiceMutateInterfa */ public function setIdentity(ResourceServiceIdentityInterface $value): self; + /** + * Gets a fresh instance of the identity used for this service + * + * @since 2025.05.01 + * + * @return ResourceServiceIdentityInterface + */ + public function freshIdentity(string|null $type, array $data = []): ResourceServiceIdentityInterface; + } diff --git a/shared/lib/Resource/Provider/ResourceServiceIdentityCertificate.php b/shared/lib/Resource/Provider/ResourceServiceIdentityCertificate.php new file mode 100644 index 0000000..c7918fc --- /dev/null +++ b/shared/lib/Resource/Provider/ResourceServiceIdentityCertificate.php @@ -0,0 +1,82 @@ + + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace KTXF\Resource\Provider; + +/** + * Resource Service Identity Certificate + * + * Client certificate authentication credentials for resource services. + * Uses X.509 certificates for mutual TLS (mTLS) authentication. + * + * @since 2025.05.01 + */ +interface ResourceServiceIdentityCertificate extends ResourceServiceIdentityInterface { + + /** + * Gets the certificate file path or content + * + * @since 2025.05.01 + * + * @return string Path to certificate file or PEM-encoded certificate + */ + public function getCertificate(): string; + + /** + * Sets the certificate file path or content + * + * @since 2025.05.01 + * + * @param string $value Path to certificate file or PEM-encoded certificate + * + * @return void + */ + public function setCertificate(string $value): void; + + /** + * Gets the private key file path or content + * + * @since 2025.05.01 + * + * @return string Path to private key file or PEM-encoded private key + */ + public function getPrivateKey(): string; + + /** + * Sets the private key file path or content + * + * @since 2025.05.01 + * + * @param string $value Path to private key file or PEM-encoded private key + * + * @return void + */ + public function setPrivateKey(string $value): void; + + /** + * Gets the private key passphrase (if encrypted) + * + * @since 2025.05.01 + * + * @return string|null Passphrase for encrypted private key, or null if not encrypted + */ + public function getPassphrase(): ?string; + + /** + * Sets the private key passphrase (if encrypted) + * + * @since 2025.05.01 + * + * @param string|null $value Passphrase for encrypted private key + * + * @return void + */ + public function setPassphrase(?string $value): void; + +} diff --git a/shared/lib/Resource/Provider/ResourceServiceIdentityInterface.php b/shared/lib/Resource/Provider/ResourceServiceIdentityInterface.php index 63c20ad..6f8eee8 100644 --- a/shared/lib/Resource/Provider/ResourceServiceIdentityInterface.php +++ b/shared/lib/Resource/Provider/ResourceServiceIdentityInterface.php @@ -22,15 +22,16 @@ interface ResourceServiceIdentityInterface extends JsonSerializable { public const TYPE_NONE = 'NA'; public const TYPE_BASIC = 'BA'; - public const TYPE_BEARER = 'BR'; + public const TYPE_TOKEN = 'TA'; public const TYPE_OAUTH = 'OA'; + public const TYPE_CERTIFICATE = 'CC'; /** * Gets the identity/authentication type * * @since 2025.05.01 * - * @return string One of: TYPE_NONE, TYPE_BASIC, TYPE_BEARER, TYPE_OAUTH + * @return string One of: TYPE_NONE, TYPE_BASIC, TYPE_TOKEN, TYPE_OAUTH, TYPE_CERTIFICATE */ public function type(): string; diff --git a/shared/lib/Resource/Provider/ResourceServiceIdentityBearer.php b/shared/lib/Resource/Provider/ResourceServiceIdentityToken.php similarity index 63% rename from shared/lib/Resource/Provider/ResourceServiceIdentityBearer.php rename to shared/lib/Resource/Provider/ResourceServiceIdentityToken.php index 55dad81..2374fdb 100644 --- a/shared/lib/Resource/Provider/ResourceServiceIdentityBearer.php +++ b/shared/lib/Resource/Provider/ResourceServiceIdentityToken.php @@ -10,16 +10,17 @@ declare(strict_types=1); namespace KTXF\Resource\Provider; /** - * Resource Service Identity Bearer + * Resource Service Identity Token * - * Bearer token authentication credentials for resource services. + * Token authentication credentials for resource services. + * Uses a single static token/key for authentication. * * @since 2025.05.01 */ -interface ResourceServiceIdentityBearer extends ResourceServiceIdentityInterface { +interface ResourceServiceIdentityToken extends ResourceServiceIdentityInterface { /** - * Gets the bearer token + * Gets the authentication token * * @since 2025.05.01 * @@ -28,7 +29,7 @@ interface ResourceServiceIdentityBearer extends ResourceServiceIdentityInterface public function getToken(): string; /** - * Sets the bearer token + * Sets the authentication token * * @since 2025.05.01 * diff --git a/shared/lib/Resource/Provider/ResourceServiceMutateInterface.php b/shared/lib/Resource/Provider/ResourceServiceMutateInterface.php index 8d585b1..439fdd0 100644 --- a/shared/lib/Resource/Provider/ResourceServiceMutateInterface.php +++ b/shared/lib/Resource/Provider/ResourceServiceMutateInterface.php @@ -43,4 +43,15 @@ interface ResourceServiceMutateInterface extends ResourceServiceBaseInterface, J */ public function setEnabled(bool $value): self; + /** + * Sets the auxiliary information of this service + * + * @since 2025.05.01 + * + * @param array $value Arbitrary key-value pairs for additional service info + * + * @return self + */ + public function setAuxiliary(array $value): self; + } diff --git a/shared/lib/Resource/Selector/CollectionSelector.php b/shared/lib/Resource/Selector/CollectionSelector.php index d985f08..7a90fad 100644 --- a/shared/lib/Resource/Selector/CollectionSelector.php +++ b/shared/lib/Resource/Selector/CollectionSelector.php @@ -15,7 +15,7 @@ namespace KTXF\Resource\Selector; class CollectionSelector extends SelectorAbstract { protected array $keyTypes = ['string', 'integer']; - protected array $valueTypes = ['boolean', EntitySelector::class]; + protected array $valueTypes = ['boolean', EntitySelector::class, 'string']; protected string $nestedSelector = EntitySelector::class; protected string $selectorName = 'CollectionSelector';