'%title% %sep% %sitename%', 'description' => '%excerpt%', 'schema_type' => 'WebPage', 'article_type' => '', 'media_type' => '' ]; /** * Register API routes * * @since 1.0.0 */ public function register_routes(): void { // Get/Save global SEO settings register_rest_route( $this->namespace, '/' . $this->rest_base . '/settings', [ [ 'methods' => 'GET', 'callback' => [$this, 'get_settings'], 'permission_callback' => [$this, 'check_read_permissions'], 'args' => $this->get_settings_query_args() ], [ 'methods' => 'POST', 'callback' => [$this, 'save_settings'], 'permission_callback' => [$this, 'check_manage_permissions'], 'args' => $this->get_save_settings_args() ] ] ); // Get all global SEO settings (all post types) register_rest_route( $this->namespace, '/' . $this->rest_base . '/settings/all', [ [ 'methods' => 'GET', 'callback' => [$this, 'get_all_settings'], 'permission_callback' => [$this, 'check_read_permissions'] ] ] ); // Reset settings for a specific post type register_rest_route( $this->namespace, '/' . $this->rest_base . '/settings/reset', [ [ 'methods' => 'POST', 'callback' => [$this, 'reset_settings'], 'permission_callback' => [$this, 'check_manage_permissions'], 'args' => [ 'post_type' => [ 'required' => true, 'type' => 'string', 'description' => 'Post type to reset settings for', 'sanitize_callback' => 'sanitize_key' ] ] ] ] ); } /** * Get global SEO settings for a specific post type * * @since 1.0.0 * * @param WP_REST_Request $request Request object * @return WP_REST_Response|WP_Error Response object or error */ public function get_settings(WP_REST_Request $request) { $post_type = $request->get_param('post_type'); // Validate post type $validation = $this->validate_post_type($post_type); if (is_wp_error($validation)) { return $validation; } // Get all settings $all_settings = get_option(self::OPTION_NAME, []); // Get settings for specific post type or return defaults $settings = $all_settings[$post_type] ?? $this->get_default_settings($post_type); return new WP_REST_Response([ 'success' => true, 'data' => $settings, 'post_type' => $post_type, 'message' => sprintf('Settings retrieved successfully for post type: %s', $post_type) ], 200); } /** * Save global SEO settings for a specific post type * * @since 1.0.0 * * @param WP_REST_Request $request Request object * @return WP_REST_Response|WP_Error Response object or error */ public function save_settings(WP_REST_Request $request) { $post_type = $request->get_param('post_type'); $settings = $request->get_param('settings'); // Validate post type $validation = $this->validate_post_type($post_type); if (is_wp_error($validation)) { return $validation; } // Validate settings structure if (empty($settings) || !is_array($settings)) { return new WP_Error( 'invalid_settings', 'Settings must be provided as an array', ['status' => 400] ); } // Sanitize settings $sanitized_settings = $this->sanitize_settings($settings); // Get all existing settings $all_settings = get_option(self::OPTION_NAME, []); // Update settings for this post type $all_settings[$post_type] = $sanitized_settings; // Save to database $updated = update_option(self::OPTION_NAME, $all_settings); if ($updated || $all_settings[$post_type] === $sanitized_settings) { return new WP_REST_Response([ 'success' => true, 'data' => $sanitized_settings, 'post_type' => $post_type, 'message' => sprintf('Settings saved successfully for post type: %s', $post_type) ], 200); } return new WP_Error( 'save_failed', 'Failed to save settings', ['status' => 500] ); } /** * Get all global SEO settings for all post types * * @since 1.0.0 * * @param WP_REST_Request $request Request object * @return WP_REST_Response Response object */ public function get_all_settings(WP_REST_Request $request): WP_REST_Response { $all_settings = get_option(self::OPTION_NAME, []); return new WP_REST_Response([ 'success' => true, 'data' => $all_settings, 'count' => count($all_settings), 'message' => 'All global SEO settings retrieved successfully' ], 200); } /** * Reset settings for a specific post type to defaults * * @since 1.0.0 * * @param WP_REST_Request $request Request object * @return WP_REST_Response|WP_Error Response object or error */ public function reset_settings(WP_REST_Request $request) { $post_type = $request->get_param('post_type'); // Validate post type $validation = $this->validate_post_type($post_type); if (is_wp_error($validation)) { return $validation; } // Get all settings $all_settings = get_option(self::OPTION_NAME, []); // Remove settings for this post type (will fall back to defaults) unset($all_settings[$post_type]); // Save updated settings update_option(self::OPTION_NAME, $all_settings); // Get default settings $default_settings = $this->get_default_settings($post_type); return new WP_REST_Response([ 'success' => true, 'data' => $default_settings, 'post_type' => $post_type, 'message' => sprintf('Settings reset to defaults for post type: %s', $post_type) ], 200); } /** * Validate post type * * @since 1.0.0 * * @param string $post_type Post type to validate * @return true|WP_Error True if valid, WP_Error otherwise */ private function validate_post_type(string $post_type) { if (empty($post_type)) { return new WP_Error( 'missing_post_type', 'Post type parameter is required', ['status' => 400] ); } // Check if post type exists if (!post_type_exists($post_type)) { return new WP_Error( 'invalid_post_type', sprintf('Post type "%s" does not exist', $post_type), ['status' => 400] ); } // Check if post type is public $post_type_object = get_post_type_object($post_type); if (!$post_type_object || !$post_type_object->public) { return new WP_Error( 'non_public_post_type', sprintf('Post type "%s" is not public', $post_type), ['status' => 400] ); } return true; } /** * Get default settings for a post type * * @since 1.0.0 * * @param string $post_type Post type * @return array Default settings */ private function get_default_settings(string $post_type): array { $defaults = self::DEFAULT_SETTINGS; // Customize defaults based on post type switch ($post_type) { case 'post': $defaults['schema_type'] = 'Article'; $defaults['article_type'] = 'BlogPosting'; $defaults['media_type'] = ''; break; case 'page': $defaults['schema_type'] = 'WebPage'; $defaults['article_type'] = ''; $defaults['media_type'] = ''; break; case 'attachment': $defaults['schema_type'] = 'Media'; $defaults['article_type'] = ''; $defaults['media_type'] = 'ImageObject'; $defaults['title'] = '%title% %sep% %sitename%'; $defaults['description'] = '%caption%'; break; case 'product': $defaults['schema_type'] = 'Product'; $defaults['article_type'] = ''; $defaults['media_type'] = ''; break; default: // For custom post types, use generic defaults $defaults['schema_type'] = 'WebPage'; $defaults['article_type'] = ''; $defaults['media_type'] = ''; break; } return $defaults; } /** * Sanitize settings array * * @since 1.0.0 * * @param array $settings Settings to sanitize * @return array Sanitized settings */ private function sanitize_settings(array $settings): array { $sanitized = []; // Sanitize title if (isset($settings['title'])) { $sanitized['title'] = sanitize_text_field($settings['title']); } // Sanitize description if (isset($settings['description'])) { $sanitized['description'] = sanitize_text_field($settings['description']); } // Sanitize schema_type if (isset($settings['schema_type'])) { $sanitized['schema_type'] = sanitize_text_field($settings['schema_type']); } // Sanitize article_type if (isset($settings['article_type'])) { $sanitized['article_type'] = sanitize_text_field($settings['article_type']); } // Sanitize media_type if (isset($settings['media_type'])) { $sanitized['media_type'] = sanitize_text_field($settings['media_type']); } return $sanitized; } /** * Get query arguments for GET settings endpoint * * @since 1.0.0 * * @return array Arguments array */ private function get_settings_query_args(): array { return [ 'post_type' => [ 'required' => true, 'type' => 'string', 'description' => 'Post type to retrieve settings for', 'sanitize_callback' => 'sanitize_key' ] ]; } /** * Get arguments for POST save settings endpoint * * @since 1.0.0 * * @return array Arguments array */ private function get_save_settings_args(): array { return [ 'post_type' => [ 'required' => true, 'type' => 'string', 'description' => 'Post type to save settings for', 'sanitize_callback' => 'sanitize_key' ], 'settings' => [ 'required' => true, 'type' => 'object', 'description' => 'Settings object containing title, description, schema_type, article_type, and media_type', 'properties' => [ 'title' => [ 'type' => 'string', 'description' => 'Title format with variables like %title%, %sitename%, %sep%' ], 'description' => [ 'type' => 'string', 'description' => 'Description format with variables like %excerpt%' ], 'schema_type' => [ 'type' => 'string', 'description' => 'Schema.org type (e.g., Article, WebPage, Media, Product)' ], 'article_type' => [ 'type' => 'string', 'description' => 'Article type (e.g., BlogPosting, NewsArticle) - used when schema_type is Article' ], 'media_type' => [ 'type' => 'string', 'description' => 'Media type (e.g., ImageObject, VideoObject) - used when schema_type is Media' ] ] ] ]; } /** * Check read permissions * * @since 1.0.0 * * @return bool True if user has read permissions */ public function check_read_permissions(): bool { return current_user_can('edit_posts'); } /** * Check manage permissions * * @since 1.0.0 * * @return bool True if user has manage permissions */ public function check_manage_permissions(): bool { return current_user_can('manage_options'); } }