cacheName = 'schema'; parent::__construct(); $this->cache = Cache::for($this->cacheName, null, $this->cacheTtl); // $this->registry = SchemaBuilder::getInstance(); } /** * Register REST routes */ public function registerRoutes(): void { Route::for('seo') ->post([$this, 'handleSEO']) ->auth('admin') ->args([ 'action' => 'string|required|enum:save,reset,preview', 'context'=> 'string|required' ]) ->rateLimit(30) ->register(); Route::for('seo/fields') ->get([$this, 'getFields']) ->auth('admin') ->args([ 'type'=>'string|required' ]) ->register(); } /** * Main SEO handler - routes to appropriate action */ public function handleSEO(WP_REST_Request $request): WP_REST_Response { $action = $request->get_param('action') ?? 'save'; $context = $request->get_param('context'); // Verify context is valid if (!$this->isValidContext($context)) { return $this->validationError([ 'context' => "Invalid context: {$context}" ]); } return match($action) { 'save' => $this->saveSEO($request), 'reset' => $this->resetSEO($request), 'preview' => $this->previewSchema($request), default => $this->validationError(['action' => 'Invalid action']) }; } /** * Save SEO configuration */ protected function saveSEO(WP_REST_Request $request): WP_REST_Response { $context = $request->get_param('context'); $data = $request->get_json_params(); // Remove action and context from data unset($data['action'], $data['context']); // Handle site-wide settings if (in_array($context, ['site', 'business'])) { return $this->saveSiteSettings($context, $data); } // Handle content/taxonomy/user type settings return $this->saveTypeSettings($context, $data); } /** * Save site-wide settings (WebSite or Organization) */ protected function saveSiteSettings(string $context, array $data): WP_REST_Response { $errors = []; $config = SchemaHelper::getInstance()::for($context); $result = $config->updateConfig($data); if (is_wp_error($result)) { $errors[$context] = [ 'message' => $result->get_error_message(), 'errors' => $result->get_error_data() ]; } if (!empty($errors)) { return $this->validationError($errors); } // Invalidate cache $this->cache->flush(); return$this->success(['message'=>ucfirst($context).' settings saved successfully']); } /** * Save content/taxonomy/user type settings */ protected function saveTypeSettings(string $type, array $data): WP_REST_Response { $config = SchemaHelper::getInstance(); // Separate meta and schema data if needed $meta = $data['meta'] ?? []; $schema = $data['schema'] ?? $data; // If no separation, treat all as schema $errors = []; // Save meta configuration if (!empty($meta)) { $metaResult = $config->updateMeta($type, $meta); if (!$metaResult) { $errors[] = 'meta'; } } // Save schema configuration if (!empty($schema)) { $schemaResult = $config->updateSchema($type, $schema); if (!$schemaResult) { $errors[] = 'schema'; } } if (!empty($errors)) { return $this->validationError($errors); } // Invalidate cache $this->cache->flush(); return $this->success(['status'=> 'completed', 'message' => 'Configuration saved successfully']); } /** * Reset SEO configuration to defaults */ protected function resetSEO(WP_REST_Request $request): WP_REST_Response { $context = $request->get_param('context'); $data = $request->get_json_params(); $type = $data['type']??false; $format = $data['resetMeta'] ? 'meta' : ($data['resetSchema'] ? 'schema' : ($data['resetArchive'] ? 'archive' : false)); $hasIt = $type && $format; if ($hasIt) { SchemaHelper::reset($type, $format); } // Invalidate cache $this->cache->flush(); return $this->success([ 'status' => 'completed', 'message' => 'Reset to defaults successfully', 'result' => $hasIt ? SchemaHelper::getConfig($type, $format) : [], ]); } /** * Preview schema output */ protected function previewSchema(WP_REST_Request $request): WP_REST_Response { $data = $request->get_json_params(); //Whether it is the Schema, Meta, Archive, or Reference schema $format = $data['format']??false; if (!$format) { return $this->error('Required field "Format" missing'); } //WebSite, Organization, or any registered post type/taxonomy $type = $data['type']??false; if (!$type) { return $this->error('Required field "Type" missing'); } $class = $data['class']??false; if (!$class) { return $this->error('Required "Schema Class" field missing (Ex: Thing, LocalBusiness, etc)'); } // Build schema with actual form values $schema = [ '@type' => $class, ]; $theClass = SchemaHelper::classFromConfig($schema); // Add fields with their actual values foreach ($data['fields'] ?? [] as $fieldName => $value) { if (empty($value)) { continue; } $method = 'set'.ucfirst($fieldName); if (!method_exists($theClass, $method)) { error_log('Attempted field not found in class '.$class.': '.$fieldName); continue; } $theClass->$method($value); } $schema = $theClass->outputSchema(); return $this->success([ 'schema' => $schema ]); } /** * Get fields for a schema type * Used for dynamic type switching * TODO */ public function getFields(WP_REST_Request $request): WP_REST_Response { $type = $request->get_param('type'); $class = SchemaHelper::classFromConfig(['type' => $type]); // Get Meta field definitions from registry $fields = $this->registry->getMetaConfigForType($type); return $this->success($fields); } /** * Validate context is a valid type */ protected function isValidContext(string $context): bool { // Site-wide contexts if (in_array($context, ['site', 'business'])) { return true; } // Check if it's a valid content/taxonomy/user type return (bool)Registrar::getInstance($context); } }