namespace, '/' . $this->rest_base . '/(?P[a-zA-Z0-9_-]+)/settings', [ [ 'methods' => WP_REST_Server::READABLE, 'callback' => [$this, 'get_settings'], 'permission_callback' => '__return_true', // Public: settings are non-sensitive, needed by customer pages 'args' => [ 'module_id' => [ 'required' => true, 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', ], ], ], ]); // POST /woonoow/v1/modules/{module_id}/settings register_rest_route($this->namespace, '/' . $this->rest_base . '/(?P[a-zA-Z0-9_-]+)/settings', [ [ 'methods' => WP_REST_Server::CREATABLE, 'callback' => [$this, 'update_settings'], 'permission_callback' => [$this, 'check_permission'], 'args' => [ 'module_id' => [ 'required' => true, 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', ], ], ], ]); // GET /woonoow/v1/modules/{module_id}/schema register_rest_route($this->namespace, '/' . $this->rest_base . '/(?P[a-zA-Z0-9_-]+)/schema', [ [ 'methods' => WP_REST_Server::READABLE, 'callback' => [$this, 'get_schema'], 'permission_callback' => [$this, 'check_permission'], 'args' => [ 'module_id' => [ 'required' => true, 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', ], ], ], ]); } /** * Check permission * * @return bool */ public function check_permission() { return current_user_can('manage_options'); } /** * Get module settings * * @param WP_REST_Request $request * @return WP_REST_Response|WP_Error */ public function get_settings($request) { $module_id = $request['module_id']; // Verify module exists $modules = ModuleRegistry::get_all_modules(); if (!isset($modules[$module_id])) { return new WP_Error( 'invalid_module', __('Invalid module ID', 'woonoow'), ['status' => 404] ); } // Get settings from database $settings = get_option("woonoow_module_{$module_id}_settings", []); // Apply defaults from schema if available $schema = apply_filters('woonoow/module_settings_schema', []); if (isset($schema[$module_id])) { $defaults = $this->get_schema_defaults($schema[$module_id]); $settings = wp_parse_args($settings, $defaults); } return new WP_REST_Response($settings, 200); } /** * Update module settings * * @param WP_REST_Request $request * @return WP_REST_Response|WP_Error */ public function update_settings($request) { $module_id = $request['module_id']; $new_settings = $request->get_json_params(); // Verify module exists $modules = ModuleRegistry::get_all_modules(); if (!isset($modules[$module_id])) { return new WP_Error( 'invalid_module', __('Invalid module ID', 'woonoow'), ['status' => 404] ); } // Validate against schema if available $schema = apply_filters('woonoow/module_settings_schema', []); if (isset($schema[$module_id])) { $validated = $this->validate_settings($new_settings, $schema[$module_id]); if (is_wp_error($validated)) { return $validated; } $new_settings = $validated; } // Save settings update_option("woonoow_module_{$module_id}_settings", $new_settings); // Allow addons to react to settings changes do_action("woonoow/module_settings_updated/{$module_id}", $new_settings); do_action('woonoow/module_settings_updated', $module_id, $new_settings); return rest_ensure_response([ 'success' => true, 'message' => __('Settings saved successfully', 'woonoow'), 'settings' => $new_settings, ], 200); } /** * Get settings schema for a module * * @param WP_REST_Request $request * @return WP_REST_Response|WP_Error */ public function get_schema($request) { $module_id = $request['module_id']; // Verify module exists $modules = ModuleRegistry::get_all_modules(); if (!isset($modules[$module_id])) { return new WP_Error( 'invalid_module', __('Invalid module ID', 'woonoow'), ['status' => 404] ); } // Get schema from filter $all_schemas = apply_filters('woonoow/module_settings_schema', []); $schema = $all_schemas[$module_id] ?? null; if (!$schema) { return new WP_REST_Response([ 'schema' => null, 'message' => __('No schema available for this module', 'woonoow'), ], 200); } return new WP_REST_Response([ 'schema' => $schema, ], 200); } /** * Get default values from schema * * @param array $schema * @return array */ private function get_schema_defaults($schema) { $defaults = []; foreach ($schema as $key => $field) { if (isset($field['default'])) { $defaults[$key] = $field['default']; } } return $defaults; } /** * Validate settings against schema * * @param array $settings * @param array $schema * @return array|WP_Error */ private function validate_settings($settings, $schema) { $validated = []; $errors = []; foreach ($schema as $key => $field) { $value = $settings[$key] ?? null; // Check required fields if (!empty($field['required']) && ($value === null || $value === '')) { $errors[$key] = sprintf( __('%s is required', 'woonoow'), $field['label'] ?? $key ); continue; } // Skip validation if value is null and not required if ($value === null) { continue; } // Type validation $type = $field['type'] ?? 'text'; switch ($type) { case 'text': case 'textarea': case 'email': case 'url': $validated[$key] = sanitize_text_field($value); break; case 'number': $validated[$key] = floatval($value); break; case 'toggle': case 'checkbox': $validated[$key] = (bool) $value; break; case 'select': // Validate against allowed options if (isset($field['options']) && !isset($field['options'][$value])) { $errors[$key] = sprintf( __('Invalid value for %s', 'woonoow'), $field['label'] ?? $key ); } else { $validated[$key] = sanitize_text_field($value); } break; default: $validated[$key] = $value; } } if (!empty($errors)) { return new WP_Error( 'validation_failed', __('Settings validation failed', 'woonoow'), ['status' => 400, 'errors' => $errors] ); } return $validated; } }