actions[] = compact('hookName', 'component', 'callback', 'priority', 'acceptedArgs'); } public function addFilter(string $hookName, object $component, string $callback, int $priority = 10, int $acceptedArgs = 1): void { $this->filters[] = compact('hookName', 'component', 'callback', 'priority', 'acceptedArgs'); } public function addAjax(string $action, object $component, string $callback, bool $authenticated = true): void { $this->ajax[] = compact('action', 'component', 'callback', 'authenticated'); } public function addCron(string $hookName, object $component, string $callback, int $priority = 10): void { $this->cron[] = compact('hookName', 'component', 'callback', 'priority'); } public function addShortcode(string $tag, object $component, string $callback): void { $this->shortcodes[] = compact('tag', 'component', 'callback'); } public function addRest(string $namespace, string $route, array $args): void { // WordPress accepts one endpoint or a list of endpoints with route options. $endpoints = isset($args['callback']) ? [$args] : array_filter($args, 'is_numeric', ARRAY_FILTER_USE_KEY); if ($endpoints === []) { throw new \InvalidArgumentException('REST routes require at least one endpoint with an explicit permission_callback.'); } // Validate the entire route before queueing any endpoint. Never infer public access. foreach ($endpoints as $endpoint) { if (! is_array($endpoint) || ! isset($endpoint['permission_callback']) || ! is_callable($endpoint['permission_callback']) ) { throw new \InvalidArgumentException('Every REST endpoint requires an explicit callable permission_callback.'); } } $this->rest[] = [ 'namespace' => $namespace, 'route' => $route, 'args' => $args, ]; } public function run(): void { foreach ($this->actions as $action) { add_action($action['hookName'], [$action['component'], $action['callback']], $action['priority'], $action['acceptedArgs']); } foreach ($this->filters as $filter) { add_filter($filter['hookName'], [$filter['component'], $filter['callback']], $filter['priority'], $filter['acceptedArgs']); } foreach ($this->ajax as $ajax) { $action = $ajax['authenticated'] ? 'wp_ajax_' . $ajax['action'] : 'wp_ajax_nopriv_' . $ajax['action']; add_action($action, [$ajax['component'], $ajax['callback']]); } foreach ($this->cron as $cron) { add_action($cron['hookName'], [$cron['component'], $cron['callback']], $cron['priority']); } foreach ($this->shortcodes as $shortcode) { add_shortcode($shortcode['tag'], [$shortcode['component'], $shortcode['callback']]); } foreach ($this->rest as $rest) { add_action('rest_api_init', function () use ($rest) { register_rest_route($rest['namespace'], $rest['route'], $rest['args']); }); } } }