From 7ebdba2e33320693f8a6e7c243d329cb9b028371 Mon Sep 17 00:00:00 2001 From: Gregor Harlan Date: Sun, 12 Apr 2026 17:24:17 +0200 Subject: [PATCH] feat: auto-discover API functions via attributes and ClassDiscovery Replace the hardcoded API function registry with automatic class discovery using PHP attributes. Each API function class is now annotated with #[AsApiFunction('name')] and discovered at runtime via the new ClassDiscovery service, which scans Composer's classmap and PSR-4 directories filtered to core, active addons, and project-level code. The cache uses xxh128 hashing for addon-based invalidation, with an additional file-listing hash check in debug mode so new classes are found immediately without manual cache clearing. ApiFunction::register() remains available as an alternative to attributes. Co-Authored-By: Claude Opus 4.6 (1M context) --- .tools/psalm/baseline.xml | 6 + addons/debug/boot.php | 2 - addons/debug/lib/api_debug.php | 2 + src/Addon/ApiFunction/AddonOperation.php | 2 + src/ApiFunction/ApiFunction.php | 60 ++-- src/ApiFunction/AsApiFunction.php | 13 + src/ClassDiscovery.php | 337 ++++++++++++++++++ src/Content/ApiFunction/ArticleAdd.php | 2 + src/Content/ApiFunction/ArticleCopy.php | 2 + src/Content/ApiFunction/ArticleDelete.php | 2 + src/Content/ApiFunction/ArticleEdit.php | 2 + src/Content/ApiFunction/ArticleMove.php | 2 + src/Content/ApiFunction/ArticleSliceMove.php | 2 + .../ApiFunction/ArticleSliceStatusChange.php | 2 + .../ApiFunction/ArticleStatusChange.php | 2 + src/Content/ApiFunction/ArticleToCategory.php | 2 + .../ApiFunction/ArticleToStartArticle.php | 2 + src/Content/ApiFunction/CategoryAdd.php | 2 + src/Content/ApiFunction/CategoryDelete.php | 2 + src/Content/ApiFunction/CategoryEdit.php | 2 + src/Content/ApiFunction/CategoryMove.php | 2 + .../ApiFunction/CategoryStatusChange.php | 2 + src/Content/ApiFunction/CategoryToArticle.php | 2 + src/Content/ApiFunction/ContentCopy.php | 2 + .../ApiFunction/DefaultFieldsCreate.php | 2 + src/Security/ApiFunction/UserHasSession.php | 2 + src/Security/ApiFunction/UserImpersonate.php | 2 + .../ApiFunction/UserRemoveAuthMethod.php | 2 + .../ApiFunction/UserRemoveSession.php | 2 + .../ApiFunction/UserSessionStatus.php | 2 + 30 files changed, 432 insertions(+), 36 deletions(-) create mode 100644 src/ApiFunction/AsApiFunction.php create mode 100644 src/ClassDiscovery.php diff --git a/.tools/psalm/baseline.xml b/.tools/psalm/baseline.xml index 3f1b5b74b2..6bbb488fd0 100644 --- a/.tools/psalm/baseline.xml +++ b/.tools/psalm/baseline.xml @@ -1819,6 +1819,12 @@ + + + + + getValue('value')]]> diff --git a/addons/debug/boot.php b/addons/debug/boot.php index d58280bcea..3f7c0db0d4 100644 --- a/addons/debug/boot.php +++ b/addons/debug/boot.php @@ -19,8 +19,6 @@ use function Redaxo\Core\View\escape; -ApiFunction::register('debug', rex_api_debug::class); - if (!rex_debug_clockwork::isRexDebugEnabled() || 'debug' === Request::get(ApiFunction::REQ_CALL_PARAM)) { return; } diff --git a/addons/debug/lib/api_debug.php b/addons/debug/lib/api_debug.php index 82b4c2f2aa..8aeb96e80e 100644 --- a/addons/debug/lib/api_debug.php +++ b/addons/debug/lib/api_debug.php @@ -1,6 +1,7 @@ > + * @var array>|null */ - private static $functions = [ - 'addon_operation' => AddonOperation::class, - 'article_add' => ContentApiFunction\ArticleAdd::class, - 'article_copy' => ContentApiFunction\ArticleCopy::class, - 'article_delete' => ContentApiFunction\ArticleDelete::class, - 'article_edit' => ContentApiFunction\ArticleEdit::class, - 'article_move' => ContentApiFunction\ArticleMove::class, - 'article_slice_move' => ContentApiFunction\ArticleSliceMove::class, - 'article_slice_status_change' => ContentApiFunction\ArticleSliceStatusChange::class, - 'article_status_change' => ContentApiFunction\ArticleStatusChange::class, - 'article_to_category' => ContentApiFunction\ArticleToCategory::class, - 'article_to_startarticle' => ContentApiFunction\ArticleToStartArticle::class, - 'category_add' => ContentApiFunction\CategoryAdd::class, - 'category_delete' => ContentApiFunction\CategoryDelete::class, - 'category_edit' => ContentApiFunction\CategoryEdit::class, - 'category_move' => ContentApiFunction\CategoryMove::class, - 'category_status_change' => ContentApiFunction\CategoryStatusChange::class, - 'category_to_article' => ContentApiFunction\CategoryToArticle::class, - 'content_copy' => ContentApiFunction\ContentCopy::class, - 'metainfo_default_fields_create' => DefaultFieldsCreate::class, - 'user_has_session' => SecurityApiFunction\UserHasSession::class, - 'user_impersonate' => SecurityApiFunction\UserImpersonate::class, - 'user_remove_auth_method' => SecurityApiFunction\UserRemoveAuthMethod::class, - 'user_remove_session' => SecurityApiFunction\UserRemoveSession::class, - 'user_session_status' => SecurityApiFunction\UserSessionStatus::class, - ]; + private static ?array $functions = null; /** * The api function which is bound to the current request. @@ -127,7 +99,11 @@ public static function factory(): ?self $api = Request::request(self::REQ_CALL_PARAM, 'string'); if ($api) { - $apiClass = self::$functions[$api]; + $apiClass = self::loadFunctions()[$api] ?? null; + + if (null === $apiClass) { + throw new NotFoundHttpException('API function "' . $api . '" is not registered.'); + } if (class_exists($apiClass)) { $apiImpl = new $apiClass(); @@ -146,6 +122,7 @@ public static function factory(): ?self /** @param class-string $class */ public static function register(string $name, string $class): void { + self::loadFunctions(); self::$functions[$name] = $class; } @@ -312,9 +289,24 @@ protected function requiresCsrfProtection() return false; } + /** @return array> */ + private static function loadFunctions(): array + { + if (null !== self::$functions) { + return self::$functions; + } + + $functions = []; + foreach (ClassDiscovery::getInstance()->discoverByAttribute(AsApiFunction::class, self::class) as $class => $attribute) { + $functions[$attribute->name] = $class; + } + + return self::$functions = $functions; + } + private static function getName(string $class): string { - $name = array_search($class, self::$functions, true); + $name = array_search($class, self::loadFunctions(), true); if (false !== $name) { return $name; } diff --git a/src/ApiFunction/AsApiFunction.php b/src/ApiFunction/AsApiFunction.php new file mode 100644 index 0000000000..01a6ce349a --- /dev/null +++ b/src/ApiFunction/AsApiFunction.php @@ -0,0 +1,13 @@ +|null */ + private ?array $relevantPaths = null; + + /** @var array|object>>|null */ + private ?array $cacheData = null; + + private function __construct( + private readonly ClassLoader $classLoader, + ) {} + + public static function getInstance(): self + { + return self::$instance ??= new self(self::findClassLoader()); + } + + /** + * Discovers all non-abstract classes that carry the given attribute. + * + * @template TAttribute of object + * @template TParent of object + * @param class-string $attributeClass + * @param class-string|null $parentClass Only include classes extending/implementing this type + * @return array, TAttribute> Map of class name to attribute instance + */ + public function discoverByAttribute(string $attributeClass, ?string $parentClass = null): array + { + $cacheKey = $attributeClass . ($parentClass ? '|' . $parentClass : ''); + $cacheData = $this->loadCacheData(); + + if (isset($cacheData[$cacheKey])) { + // Reconstruct attribute instances from cached arrays (JSON roundtrip converts objects to arrays) + $result = []; + foreach ($cacheData[$cacheKey] as $class => $entry) { + /** + * @var TAttribute $attribute + * @psalm-suppress MixedMethodCall + */ + $attribute = is_array($entry) ? new $attributeClass(...$entry) : $entry; + $result[$class] = $attribute; + } + /** @var array, TAttribute> */ + return $result; + } + + $result = []; + + foreach ($this->getRelevantClasses() as $class) { + if (!class_exists($class)) { + continue; + } + + $reflection = new ReflectionClass($class); + + if ($reflection->isAbstract()) { + continue; + } + + $attributes = $reflection->getAttributes($attributeClass); + + if ([] === $attributes) { + continue; + } + + if (null !== $parentClass && !$reflection->isSubclassOf($parentClass)) { + continue; + } + + /** @var class-string $class */ + $result[$class] = $attributes[0]->newInstance(); + } + + $this->saveCacheData($cacheKey, $result); + + return $result; + } + + public static function clearCache(): void + { + File::delete(self::getCacheFile()); + } + + /** @return list */ + private function getRelevantClasses(): array + { + $relevantPaths = $this->getRelevantPaths(); + $classes = []; + + // 1. Classes from Composer's classmap (available for all autoload types when optimized) + foreach ($this->classLoader->getClassMap() as $class => $file) { + $realFile = (string) realpath($file); + if ($this->isRelevantPath($realFile, $relevantPaths)) { + $classes[] = $class; + } + } + + // 2. For PSR-4 prefixes, scan directories not yet covered by the classmap. + // With an optimized/authoritative classmap, all classes are already in the classmap above. + if ($this->classLoader->isClassMapAuthoritative()) { + return $classes; + } + + foreach ($this->classLoader->getPrefixesPsr4() as $namespace => $dirs) { + foreach ($dirs as $dir) { + $realDir = (string) realpath($dir); + if (!is_dir($realDir) || !$this->isRelevantPath($realDir . DIRECTORY_SEPARATOR, $relevantPaths)) { + continue; + } + + foreach ($this->scanDirectory($realDir, $namespace) as $class) { + if (!array_key_exists($class, $this->classLoader->getClassMap())) { + $classes[] = $class; + } + } + } + } + + return $classes; + } + + /** @param list $relevantPaths */ + private function isRelevantPath(string $path, array $relevantPaths): bool + { + foreach ($relevantPaths as $relevantPath) { + if (str_starts_with($path, $relevantPath)) { + return true; + } + } + + return false; + } + + /** @return list */ + private function getRelevantPaths(): array + { + if (null !== $this->relevantPaths) { + return $this->relevantPaths; + } + + $paths = []; + + // PSR-4 directories of the root composer package (project-level code) + $rootPath = realpath(InstalledVersions::getRootPackage()['install_path']); + if (false !== $rootPath) { + $vendorPrefix = $rootPath . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR; + foreach ($this->classLoader->getPrefixesPsr4() as $dirs) { + foreach ($dirs as $dir) { + $realDir = (string) realpath($dir); + if ('' !== $realDir && !str_starts_with($realDir, $vendorPrefix)) { + $paths[] = $realDir . DIRECTORY_SEPARATOR; + } + } + } + } + + // Core source directory (dirname of this file's parent = src/) + $paths[] = __DIR__ . DIRECTORY_SEPARATOR; + + // Active addon paths + foreach (Addon::getAvailableAddons() as $addon) { + $paths[] = $addon->path . DIRECTORY_SEPARATOR; + } + + return $this->relevantPaths = $paths; + } + + /** @return list */ + private function scanDirectory(string $dir, string $namespace): array + { + $classes = []; + + /** @var RecursiveDirectoryIterator $file */ + foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS)) as $file) { + if (!$file->isFile() || 'php' !== $file->getExtension()) { + continue; + } + + if (!$this->fileDefinesClass($file->getPathname())) { + continue; + } + + $relativePath = substr($file->getPathname(), strlen($dir) + 1); + $class = $namespace . str_replace(['/', '.php'], ['\\', ''], $relativePath); + + $classes[] = $class; + } + + return $classes; + } + + /** Checks whether a PHP file defines a class, interface, enum, or trait. */ + private function fileDefinesClass(string $filePath): bool + { + $content = file_get_contents($filePath); + + if (false === $content) { + return false; + } + + $tokens = token_get_all($content, TOKEN_PARSE); + + foreach ($tokens as $token) { + if (!is_array($token)) { + continue; + } + + if (T_CLASS === $token[0] || T_INTERFACE === $token[0] || T_TRAIT === $token[0] || T_ENUM === $token[0]) { + return true; + } + } + + return false; + } + + /** @return array|object>> */ + private function loadCacheData(): array + { + if (null !== $this->cacheData) { + return $this->cacheData; + } + + /** @var array{addons_hash?: string, files_hash?: string, data?: array>>} $cache */ + $cache = File::getCache(self::getCacheFile()); + + if (!isset($cache['addons_hash']) || $cache['addons_hash'] !== $this->getAddonHash()) { + return $this->cacheData = []; + } + + // In debug mode, check if PHP files were added/removed since cache was built + if (Core::isDebugMode()) { + if (!isset($cache['files_hash']) || $cache['files_hash'] !== $this->getPhpFilesHash()) { + return $this->cacheData = []; + } + } + + return $this->cacheData = $cache['data'] ?? []; + } + + /** @param array|object> $data */ + private function saveCacheData(string $cacheKey, array $data): void + { + $this->cacheData = $this->loadCacheData(); + $this->cacheData[$cacheKey] = $data; + + File::putCache(self::getCacheFile(), [ + 'addons_hash' => $this->getAddonHash(), + 'files_hash' => $this->getPhpFilesHash(), + 'data' => $this->cacheData, + ]); + } + + private function getAddonHash(): string + { + $parts = []; + foreach (Addon::getAvailableAddons() as $addon) { + $parts[] = $addon->name . ':' . $addon->getVersion(); + } + + return hash('xxh128', implode(',', $parts)); + } + + /** + * Builds a hash of all PHP file paths in the relevant directories. + * This is cheap (just directory listing, no file reading) and detects + * added/removed files so the cache is invalidated automatically in debug mode. + */ + private function getPhpFilesHash(): string + { + $files = []; + + foreach ($this->getRelevantPaths() as $path) { + if (!is_dir($path)) { + continue; + } + + /** @var RecursiveDirectoryIterator $file */ + foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS)) as $file) { + if ($file->isFile() && 'php' === $file->getExtension()) { + $files[] = $file->getPathname(); + } + } + } + + sort($files, SORT_STRING); + + return hash('xxh128', implode("\n", $files)); + } + + private static function getCacheFile(): string + { + return Path::coreCache('class_discovery.cache'); + } + + private static function findClassLoader(): ClassLoader + { + return array_first(ClassLoader::getRegisteredLoaders()) + ?? throw new RuntimeException('Composer ClassLoader not found.'); + } +} diff --git a/src/Content/ApiFunction/ArticleAdd.php b/src/Content/ApiFunction/ArticleAdd.php index 275872a7a6..70e82306f3 100644 --- a/src/Content/ApiFunction/ArticleAdd.php +++ b/src/Content/ApiFunction/ArticleAdd.php @@ -3,6 +3,7 @@ namespace Redaxo\Core\Content\ApiFunction; use Redaxo\Core\ApiFunction\ApiFunction; +use Redaxo\Core\ApiFunction\AsApiFunction; use Redaxo\Core\ApiFunction\Exception\ApiFunctionException; use Redaxo\Core\ApiFunction\Result; use Redaxo\Core\Content\ArticleHandler; @@ -12,6 +13,7 @@ /** * @internal */ +#[AsApiFunction('article_add')] class ArticleAdd extends ApiFunction { public function execute() diff --git a/src/Content/ApiFunction/ArticleCopy.php b/src/Content/ApiFunction/ArticleCopy.php index a3f04c6f65..53ef185ada 100644 --- a/src/Content/ApiFunction/ArticleCopy.php +++ b/src/Content/ApiFunction/ArticleCopy.php @@ -3,6 +3,7 @@ namespace Redaxo\Core\Content\ApiFunction; use Redaxo\Core\ApiFunction\ApiFunction; +use Redaxo\Core\ApiFunction\AsApiFunction; use Redaxo\Core\ApiFunction\Exception\ApiFunctionException; use Redaxo\Core\ApiFunction\Result; use Redaxo\Core\Backend\Controller; @@ -16,6 +17,7 @@ /** * @internal */ +#[AsApiFunction('article_copy')] class ArticleCopy extends ApiFunction { public function execute() diff --git a/src/Content/ApiFunction/ArticleDelete.php b/src/Content/ApiFunction/ArticleDelete.php index 15c703ef71..f6f0bc1b10 100644 --- a/src/Content/ApiFunction/ArticleDelete.php +++ b/src/Content/ApiFunction/ArticleDelete.php @@ -3,6 +3,7 @@ namespace Redaxo\Core\Content\ApiFunction; use Redaxo\Core\ApiFunction\ApiFunction; +use Redaxo\Core\ApiFunction\AsApiFunction; use Redaxo\Core\ApiFunction\Exception\ApiFunctionException; use Redaxo\Core\ApiFunction\Result; use Redaxo\Core\Content\ArticleHandler; @@ -12,6 +13,7 @@ /** * @internal */ +#[AsApiFunction('article_delete')] class ArticleDelete extends ApiFunction { public function execute() diff --git a/src/Content/ApiFunction/ArticleEdit.php b/src/Content/ApiFunction/ArticleEdit.php index b25e2493ff..898bef5033 100644 --- a/src/Content/ApiFunction/ArticleEdit.php +++ b/src/Content/ApiFunction/ArticleEdit.php @@ -3,6 +3,7 @@ namespace Redaxo\Core\Content\ApiFunction; use Redaxo\Core\ApiFunction\ApiFunction; +use Redaxo\Core\ApiFunction\AsApiFunction; use Redaxo\Core\ApiFunction\Exception\ApiFunctionException; use Redaxo\Core\ApiFunction\Result; use Redaxo\Core\Content\ArticleHandler; @@ -12,6 +13,7 @@ /** * @internal */ +#[AsApiFunction('article_edit')] class ArticleEdit extends ApiFunction { public function execute() diff --git a/src/Content/ApiFunction/ArticleMove.php b/src/Content/ApiFunction/ArticleMove.php index 00b688ac93..e5eea34d94 100644 --- a/src/Content/ApiFunction/ArticleMove.php +++ b/src/Content/ApiFunction/ArticleMove.php @@ -3,6 +3,7 @@ namespace Redaxo\Core\Content\ApiFunction; use Redaxo\Core\ApiFunction\ApiFunction; +use Redaxo\Core\ApiFunction\AsApiFunction; use Redaxo\Core\ApiFunction\Exception\ApiFunctionException; use Redaxo\Core\ApiFunction\Result; use Redaxo\Core\Content\Article; @@ -14,6 +15,7 @@ /** * @internal */ +#[AsApiFunction('article_move')] class ArticleMove extends ApiFunction { /** diff --git a/src/Content/ApiFunction/ArticleSliceMove.php b/src/Content/ApiFunction/ArticleSliceMove.php index af425db38b..651260b2c4 100644 --- a/src/Content/ApiFunction/ArticleSliceMove.php +++ b/src/Content/ApiFunction/ArticleSliceMove.php @@ -3,6 +3,7 @@ namespace Redaxo\Core\Content\ApiFunction; use Redaxo\Core\ApiFunction\ApiFunction; +use Redaxo\Core\ApiFunction\AsApiFunction; use Redaxo\Core\ApiFunction\Exception\ApiFunctionException; use Redaxo\Core\ApiFunction\Result; use Redaxo\Core\Content\Article; @@ -15,6 +16,7 @@ /** * @internal */ +#[AsApiFunction('article_slice_move')] class ArticleSliceMove extends ApiFunction { public function execute() diff --git a/src/Content/ApiFunction/ArticleSliceStatusChange.php b/src/Content/ApiFunction/ArticleSliceStatusChange.php index 7b83777f4a..89f9cec49c 100644 --- a/src/Content/ApiFunction/ArticleSliceStatusChange.php +++ b/src/Content/ApiFunction/ArticleSliceStatusChange.php @@ -3,6 +3,7 @@ namespace Redaxo\Core\Content\ApiFunction; use Redaxo\Core\ApiFunction\ApiFunction; +use Redaxo\Core\ApiFunction\AsApiFunction; use Redaxo\Core\ApiFunction\Exception\ApiFunctionException; use Redaxo\Core\ApiFunction\Result; use Redaxo\Core\Content\Article; @@ -14,6 +15,7 @@ /** * @internal */ +#[AsApiFunction('article_slice_status_change')] class ArticleSliceStatusChange extends ApiFunction { public function execute() diff --git a/src/Content/ApiFunction/ArticleStatusChange.php b/src/Content/ApiFunction/ArticleStatusChange.php index b7da5c0b4e..3f2b8820a3 100644 --- a/src/Content/ApiFunction/ArticleStatusChange.php +++ b/src/Content/ApiFunction/ArticleStatusChange.php @@ -3,6 +3,7 @@ namespace Redaxo\Core\Content\ApiFunction; use Redaxo\Core\ApiFunction\ApiFunction; +use Redaxo\Core\ApiFunction\AsApiFunction; use Redaxo\Core\ApiFunction\Exception\ApiFunctionException; use Redaxo\Core\ApiFunction\Result; use Redaxo\Core\Content\ArticleHandler; @@ -13,6 +14,7 @@ /** * @internal */ +#[AsApiFunction('article_status_change')] class ArticleStatusChange extends ApiFunction { public function execute() diff --git a/src/Content/ApiFunction/ArticleToCategory.php b/src/Content/ApiFunction/ArticleToCategory.php index a3a7e4d79c..e7bdda30a8 100644 --- a/src/Content/ApiFunction/ArticleToCategory.php +++ b/src/Content/ApiFunction/ArticleToCategory.php @@ -3,6 +3,7 @@ namespace Redaxo\Core\Content\ApiFunction; use Redaxo\Core\ApiFunction\ApiFunction; +use Redaxo\Core\ApiFunction\AsApiFunction; use Redaxo\Core\ApiFunction\Exception\ApiFunctionException; use Redaxo\Core\ApiFunction\Result; use Redaxo\Core\Content\Article; @@ -14,6 +15,7 @@ /** * @internal */ +#[AsApiFunction('article_to_category')] class ArticleToCategory extends ApiFunction { public function execute() diff --git a/src/Content/ApiFunction/ArticleToStartArticle.php b/src/Content/ApiFunction/ArticleToStartArticle.php index ec70a5b560..7ab6f54246 100644 --- a/src/Content/ApiFunction/ArticleToStartArticle.php +++ b/src/Content/ApiFunction/ArticleToStartArticle.php @@ -3,6 +3,7 @@ namespace Redaxo\Core\Content\ApiFunction; use Redaxo\Core\ApiFunction\ApiFunction; +use Redaxo\Core\ApiFunction\AsApiFunction; use Redaxo\Core\ApiFunction\Exception\ApiFunctionException; use Redaxo\Core\ApiFunction\Result; use Redaxo\Core\Content\Article; @@ -14,6 +15,7 @@ /** * @internal */ +#[AsApiFunction('article_to_startarticle')] class ArticleToStartArticle extends ApiFunction { public function execute() diff --git a/src/Content/ApiFunction/CategoryAdd.php b/src/Content/ApiFunction/CategoryAdd.php index 7506a49d84..3e5d99da98 100644 --- a/src/Content/ApiFunction/CategoryAdd.php +++ b/src/Content/ApiFunction/CategoryAdd.php @@ -3,6 +3,7 @@ namespace Redaxo\Core\Content\ApiFunction; use Redaxo\Core\ApiFunction\ApiFunction; +use Redaxo\Core\ApiFunction\AsApiFunction; use Redaxo\Core\ApiFunction\Exception\ApiFunctionException; use Redaxo\Core\ApiFunction\Result; use Redaxo\Core\Content\CategoryHandler; @@ -12,6 +13,7 @@ /** * @internal */ +#[AsApiFunction('category_add')] class CategoryAdd extends ApiFunction { public function execute() diff --git a/src/Content/ApiFunction/CategoryDelete.php b/src/Content/ApiFunction/CategoryDelete.php index e2801117b1..4b14a17fde 100644 --- a/src/Content/ApiFunction/CategoryDelete.php +++ b/src/Content/ApiFunction/CategoryDelete.php @@ -3,6 +3,7 @@ namespace Redaxo\Core\Content\ApiFunction; use Redaxo\Core\ApiFunction\ApiFunction; +use Redaxo\Core\ApiFunction\AsApiFunction; use Redaxo\Core\ApiFunction\Exception\ApiFunctionException; use Redaxo\Core\ApiFunction\Result; use Redaxo\Core\Content\CategoryHandler; @@ -12,6 +13,7 @@ /** * @internal */ +#[AsApiFunction('category_delete')] class CategoryDelete extends ApiFunction { public function execute() diff --git a/src/Content/ApiFunction/CategoryEdit.php b/src/Content/ApiFunction/CategoryEdit.php index 17714489e1..7946f9ca20 100644 --- a/src/Content/ApiFunction/CategoryEdit.php +++ b/src/Content/ApiFunction/CategoryEdit.php @@ -3,6 +3,7 @@ namespace Redaxo\Core\Content\ApiFunction; use Redaxo\Core\ApiFunction\ApiFunction; +use Redaxo\Core\ApiFunction\AsApiFunction; use Redaxo\Core\ApiFunction\Exception\ApiFunctionException; use Redaxo\Core\ApiFunction\Result; use Redaxo\Core\Content\CategoryHandler; @@ -12,6 +13,7 @@ /** * @internal */ +#[AsApiFunction('category_edit')] class CategoryEdit extends ApiFunction { public function execute() diff --git a/src/Content/ApiFunction/CategoryMove.php b/src/Content/ApiFunction/CategoryMove.php index 5436543251..af64c192a6 100644 --- a/src/Content/ApiFunction/CategoryMove.php +++ b/src/Content/ApiFunction/CategoryMove.php @@ -3,6 +3,7 @@ namespace Redaxo\Core\Content\ApiFunction; use Redaxo\Core\ApiFunction\ApiFunction; +use Redaxo\Core\ApiFunction\AsApiFunction; use Redaxo\Core\ApiFunction\Exception\ApiFunctionException; use Redaxo\Core\ApiFunction\Result; use Redaxo\Core\Content\Article; @@ -14,6 +15,7 @@ /** * @internal */ +#[AsApiFunction('category_move')] class CategoryMove extends ApiFunction { public function execute() diff --git a/src/Content/ApiFunction/CategoryStatusChange.php b/src/Content/ApiFunction/CategoryStatusChange.php index 60956e7dac..bf312c1660 100644 --- a/src/Content/ApiFunction/CategoryStatusChange.php +++ b/src/Content/ApiFunction/CategoryStatusChange.php @@ -3,6 +3,7 @@ namespace Redaxo\Core\Content\ApiFunction; use Redaxo\Core\ApiFunction\ApiFunction; +use Redaxo\Core\ApiFunction\AsApiFunction; use Redaxo\Core\ApiFunction\Exception\ApiFunctionException; use Redaxo\Core\ApiFunction\Result; use Redaxo\Core\Content\CategoryHandler; @@ -13,6 +14,7 @@ /** * @internal */ +#[AsApiFunction('category_status_change')] class CategoryStatusChange extends ApiFunction { public function execute() diff --git a/src/Content/ApiFunction/CategoryToArticle.php b/src/Content/ApiFunction/CategoryToArticle.php index 7333e88f7b..c4a402d5bb 100644 --- a/src/Content/ApiFunction/CategoryToArticle.php +++ b/src/Content/ApiFunction/CategoryToArticle.php @@ -3,6 +3,7 @@ namespace Redaxo\Core\Content\ApiFunction; use Redaxo\Core\ApiFunction\ApiFunction; +use Redaxo\Core\ApiFunction\AsApiFunction; use Redaxo\Core\ApiFunction\Exception\ApiFunctionException; use Redaxo\Core\ApiFunction\Result; use Redaxo\Core\Content\Article; @@ -14,6 +15,7 @@ /** * @internal */ +#[AsApiFunction('category_to_article')] class CategoryToArticle extends ApiFunction { public function execute() diff --git a/src/Content/ApiFunction/ContentCopy.php b/src/Content/ApiFunction/ContentCopy.php index c3768bef34..5a740b6371 100644 --- a/src/Content/ApiFunction/ContentCopy.php +++ b/src/Content/ApiFunction/ContentCopy.php @@ -3,6 +3,7 @@ namespace Redaxo\Core\Content\ApiFunction; use Redaxo\Core\ApiFunction\ApiFunction; +use Redaxo\Core\ApiFunction\AsApiFunction; use Redaxo\Core\ApiFunction\Exception\ApiFunctionException; use Redaxo\Core\ApiFunction\Result; use Redaxo\Core\Content\ContentHandler; @@ -13,6 +14,7 @@ /** * @internal */ +#[AsApiFunction('content_copy')] class ContentCopy extends ApiFunction { /** diff --git a/src/MetaInfo/ApiFunction/DefaultFieldsCreate.php b/src/MetaInfo/ApiFunction/DefaultFieldsCreate.php index 7bd5240b6d..6aab4c24f2 100644 --- a/src/MetaInfo/ApiFunction/DefaultFieldsCreate.php +++ b/src/MetaInfo/ApiFunction/DefaultFieldsCreate.php @@ -3,6 +3,7 @@ namespace Redaxo\Core\MetaInfo\ApiFunction; use Redaxo\Core\ApiFunction\ApiFunction; +use Redaxo\Core\ApiFunction\AsApiFunction; use Redaxo\Core\ApiFunction\Exception\ApiFunctionException; use Redaxo\Core\ApiFunction\Result; use Redaxo\Core\Core; @@ -18,6 +19,7 @@ /** * @internal */ +#[AsApiFunction('metainfo_default_fields_create')] class DefaultFieldsCreate extends ApiFunction { public function execute() diff --git a/src/Security/ApiFunction/UserHasSession.php b/src/Security/ApiFunction/UserHasSession.php index 326eba1b86..e6ac452394 100644 --- a/src/Security/ApiFunction/UserHasSession.php +++ b/src/Security/ApiFunction/UserHasSession.php @@ -3,6 +3,7 @@ namespace Redaxo\Core\Security\ApiFunction; use Redaxo\Core\ApiFunction\ApiFunction; +use Redaxo\Core\ApiFunction\AsApiFunction; use Redaxo\Core\ApiFunction\Exception\ApiFunctionException; use Redaxo\Core\Core; use Redaxo\Core\Http\Request; @@ -11,6 +12,7 @@ /** * @internal */ +#[AsApiFunction('user_has_session')] class UserHasSession extends ApiFunction { /** @return never */ diff --git a/src/Security/ApiFunction/UserImpersonate.php b/src/Security/ApiFunction/UserImpersonate.php index 49b151cb87..f6faf455af 100644 --- a/src/Security/ApiFunction/UserImpersonate.php +++ b/src/Security/ApiFunction/UserImpersonate.php @@ -3,6 +3,7 @@ namespace Redaxo\Core\Security\ApiFunction; use Redaxo\Core\ApiFunction\ApiFunction; +use Redaxo\Core\ApiFunction\AsApiFunction; use Redaxo\Core\ApiFunction\Exception\ApiFunctionException; use Redaxo\Core\Core; use Redaxo\Core\Filesystem\Url; @@ -15,6 +16,7 @@ /** * @internal */ +#[AsApiFunction('user_impersonate')] class UserImpersonate extends ApiFunction { /** @return never */ diff --git a/src/Security/ApiFunction/UserRemoveAuthMethod.php b/src/Security/ApiFunction/UserRemoveAuthMethod.php index 932ceaaacc..6d6821e416 100644 --- a/src/Security/ApiFunction/UserRemoveAuthMethod.php +++ b/src/Security/ApiFunction/UserRemoveAuthMethod.php @@ -3,6 +3,7 @@ namespace Redaxo\Core\Security\ApiFunction; use Redaxo\Core\ApiFunction\ApiFunction; +use Redaxo\Core\ApiFunction\AsApiFunction; use Redaxo\Core\ApiFunction\Exception\ApiFunctionException; use Redaxo\Core\ApiFunction\Result; use Redaxo\Core\Core; @@ -14,6 +15,7 @@ /** * @internal */ +#[AsApiFunction('user_remove_auth_method')] class UserRemoveAuthMethod extends ApiFunction { public function execute() diff --git a/src/Security/ApiFunction/UserRemoveSession.php b/src/Security/ApiFunction/UserRemoveSession.php index c11b67153a..bec9beec10 100644 --- a/src/Security/ApiFunction/UserRemoveSession.php +++ b/src/Security/ApiFunction/UserRemoveSession.php @@ -3,6 +3,7 @@ namespace Redaxo\Core\Security\ApiFunction; use Redaxo\Core\ApiFunction\ApiFunction; +use Redaxo\Core\ApiFunction\AsApiFunction; use Redaxo\Core\ApiFunction\Exception\ApiFunctionException; use Redaxo\Core\ApiFunction\Result; use Redaxo\Core\Core; @@ -14,6 +15,7 @@ /** * @internal */ +#[AsApiFunction('user_remove_session')] class UserRemoveSession extends ApiFunction { public function execute() diff --git a/src/Security/ApiFunction/UserSessionStatus.php b/src/Security/ApiFunction/UserSessionStatus.php index 6b219a97c8..b4089b5749 100644 --- a/src/Security/ApiFunction/UserSessionStatus.php +++ b/src/Security/ApiFunction/UserSessionStatus.php @@ -3,6 +3,7 @@ namespace Redaxo\Core\Security\ApiFunction; use Redaxo\Core\ApiFunction\ApiFunction; +use Redaxo\Core\ApiFunction\AsApiFunction; use Redaxo\Core\Core; use Redaxo\Core\Http\Response; use Redaxo\Core\Security\BackendLogin; @@ -10,6 +11,7 @@ /** * @internal */ +#[AsApiFunction('user_session_status')] class UserSessionStatus extends ApiFunction { /** @return never */