PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.15
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.15
2.6.26 2.6.25 2.6.24 2.6.23 2.6.22 2.6.21 2.6.20 2.6.19 2.6.18 2.6.17 2.6.16 2.6.15 2.6.14 2.6.13 2.6.12 2.6.11 2.6.10 2.6.9 2.6.8 2.6.7 2.6.6 2.6.5 2.6.4 2.6.3 2.5.23 All 138 releases
metasync / wp-mcp-server / tools / class-mcp-tool-plugin-settings.php

class-mcp-tool-plugin-settings.php in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.6.15, at wp-mcp-server/tools/class-mcp-tool-plugin-settings.php

649 lines 23.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * MCP Tool: Plugin Settings Management
4 *
5 * Provides tools for viewing and managing WordPress plugin settings.
6 * This allows AI agents to read and update all MetaSync plugin configuration.
7 *
8 * @package MetaSync
9 * @subpackage MCP_Server/Tools
10 */
11
12 if (!defined('ABSPATH')) {
13 exit;
14 }
15
16 /**
17 * Get Plugin Settings Tool
18 */
19 class MCP_Tool_Get_Plugin_Settings extends MCP_Tool_Base {
20
21 public function get_name() {
22 return 'wordpress_get_plugin_settings';
23 }
24
25 public function get_description() {
26 return 'Get all plugin settings or settings for a specific section. Returns the complete plugin configuration including features, SEO controls, API keys, and more.';
27 }
28
29 public function get_input_schema() {
30 return [
31 'type' => 'object',
32 'properties' => [
33 'section' => [
34 'type' => 'string',
35 'description' => 'Optional: specific settings section to retrieve (e.g., "general", "whitelabel", "seo"). If omitted, returns all settings.',
36 'enum' => [
37 'general',
38 'whitelabel',
39 'seo',
40 'social',
41 'advanced',
42 'features',
43 'api',
44 'all'
45 ]
46 ],
47 'keys' => [
48 'type' => 'array',
49 'description' => 'Optional: specific setting keys to retrieve. If provided, only these keys will be returned.',
50 'items' => [
51 'type' => 'string'
52 ]
53 ]
54 ],
55 'required' => []
56 ];
57 }
58
59 public function execute($params) {
60 // Validate and check permissions
61 $this->validate_params($params);
62 $this->require_capability('manage_options');
63
64 // Get all plugin options
65 $all_options = get_option(Metasync::option_name, []);
66
67 // If specific keys requested
68 if (!empty($params['keys']) && is_array($params['keys'])) {
69 $result = [];
70 foreach ($params['keys'] as $key) {
71 $key = $this->sanitize_string($key);
72 if (isset($all_options[$key])) {
73 $result[$key] = $all_options[$key];
74 }
75 }
76 return $this->success([
77 'settings' => $result,
78 'count' => count($result)
79 ]);
80 }
81
82 // If section specified (options are nested e.g. $all_options['general']['apikey'])
83 if (!empty($params['section']) && $params['section'] !== 'all') {
84 $section = $this->sanitize_string($params['section']);
85
86 if (isset($all_options[$section]) && is_array($all_options[$section])) {
87 $result = $all_options[$section];
88 } else {
89 // Fallback: map section to flat keys for legacy structure
90 $section_keys = $this->get_section_keys($section);
91 $result = [];
92 foreach ($section_keys as $key) {
93 if (isset($all_options[$key])) {
94 $result[$key] = $all_options[$key];
95 }
96 }
97 }
98
99 return $this->success([
100 'section' => $section,
101 'settings' => $result,
102 'count' => is_array($result) ? count($result) : 0
103 ]);
104 }
105
106 // Return all settings
107 return $this->success([
108 'settings' => $all_options,
109 'count' => count($all_options),
110 'available_sections' => [
111 'general' => 'API keys, integration settings',
112 'whitelabel' => 'Branding and white-label configuration',
113 'seo' => 'SEO controls and indexation settings',
114 'social' => 'Social media and OpenGraph settings',
115 'advanced' => 'Advanced plugin features',
116 'features' => 'Feature toggles and visibility',
117 'api' => 'API configuration and tokens'
118 ]
119 ]);
120 }
121
122 /**
123 * Get setting keys for a specific section
124 */
125 private function get_section_keys($section) {
126 $section_map = [
127 'general' => [
128 'searchatlas_api_key',
129 'apikey',
130 'permalink_structure',
131 'hide_dashboard_framework',
132 'show_admin_bar_status',
133 'enable_auto_updates',
134 'enable_schema_markup',
135 'default_schema_type'
136 ],
137 'whitelabel' => [
138 'white_label_plugin_name',
139 'whitelabel_otto_name',
140 'whitelabel_logo_url',
141 'whitelabel_domain_url',
142 'white_label_plugin_description',
143 'white_label_plugin_author',
144 'white_label_plugin_author_uri',
145 'white_label_plugin_uri',
146 'white_label_plugin_menu_slug',
147 'white_label_plugin_menu_icon',
148 'whitelabel_settings_password'
149 ],
150 'seo' => [
151 'index_date_archives',
152 'index_tag_archives',
153 'index_author_archives',
154 'index_format_archives',
155 'index_category_archives',
156 'override_robots_tags',
157 'enable_googleinstantindex',
158 'google_index_api_config'
159 ],
160 'social' => [
161 'otto_pixel_uuid',
162 'otto_disable_on_loggedin',
163 'otto_disable_preview_button',
164 'periodic_clear_ottopage_cache',
165 'periodic_clear_ottopost_cache',
166 'periodic_clear_otto_cache'
167 ],
168 'advanced' => [
169 'disable_common_robots_metabox',
170 'disable_advance_robots_metabox',
171 'disable_redirection_metabox',
172 'disable_canonical_metabox',
173 'disable_social_opengraph_metabox',
174 'disable_schema_markup_metabox',
175 'disable_seo_metabox'
176 ],
177 'features' => [
178 'enabled_elementor_plugin_css',
179 'enabled_elementor_plugin_css_color',
180 'import_external_data',
181 'content_genius_sync_roles'
182 ],
183 'api' => [
184 'searchatlas_api_key',
185 'apikey',
186 'google_index_api_config'
187 ]
188 ];
189
190 return $section_map[$section] ?? [];
191 }
192 }
193
194 /**
195 * Update Plugin Settings Tool
196 */
197 class MCP_Tool_Update_Plugin_Settings extends MCP_Tool_Base {
198
199 public function get_name() {
200 return 'wordpress_update_plugin_settings';
201 }
202
203 public function get_description() {
204 return 'Update one or more plugin settings. Allows modifying plugin configuration including features, SEO controls, API keys, whitelabel settings, and more.';
205 }
206
207 public function get_input_schema() {
208 return [
209 'type' => 'object',
210 'properties' => [
211 'settings' => [
212 'type' => 'object',
213 'description' => 'Key-value pairs of settings to update. Each key is a setting name and value is the new value.',
214 'additionalProperties' => true
215 ],
216 'merge' => [
217 'type' => 'boolean',
218 'description' => 'If true (default), merges with existing settings. If false, replaces all settings with provided values.',
219 'default' => true
220 ]
221 ],
222 'required' => ['settings']
223 ];
224 }
225
226 public function execute($params) {
227 // Validate and check permissions
228 $this->validate_params($params);
229 $this->require_capability('manage_options');
230
231 if (empty($params['settings']) || !is_array($params['settings'])) {
232 throw new Exception('Settings must be a non-empty object/array');
233 }
234
235 $merge = isset($params['merge']) ? (bool)$params['merge'] : true;
236
237 // Resolve any aliased or misnamed keys to their canonical nested paths
238 $settings = $this->resolve_setting_aliases($params['settings']);
239
240 // Get current options
241 $current_options = get_option(Metasync::option_name, []);
242
243 if ($merge) {
244 // Deep merge to preserve nested sub-arrays (e.g. general, seo_controls)
245 $new_options = $this->array_merge_deep($current_options, $settings);
246 } else {
247 // Replace all settings
248 $new_options = $settings;
249 }
250
251 // Sanitize sensitive fields
252 $new_options = $this->sanitize_settings($new_options);
253
254 // Update the option
255 $updated = update_option(Metasync::option_name, $new_options);
256
257 if ($updated === false && $current_options !== $new_options) {
258 throw new Exception('Failed to update plugin settings');
259 }
260
261 // Clear any relevant caches
262 wp_cache_delete(Metasync::option_name, 'options');
263
264 return $this->success([
265 'message' => 'Plugin settings updated successfully',
266 'updated_keys' => array_keys($params['settings']),
267 'merge_mode' => $merge,
268 'total_settings' => count($new_options)
269 ]);
270 }
271
272 /**
273 * Resolve aliased or flat keys to their canonical nested location.
274 *
275 * When an MCP consumer sends a flat key like "enable_schema_markup", this
276 * method routes it into the correct nested sub-array ("general") so it
277 * updates the same option the admin UI uses.
278 */
279 private function resolve_setting_aliases($settings) {
280 // Map of flat keys → [section, canonical_key]
281 $nested_key_map = [
282 'enable_schema_markup' => ['general', 'enable_schema_markup'],
283 'enable_schema' => ['general', 'enable_schema_markup'],
284 'default_schema_type' => ['general', 'default_schema_type'],
285 ];
286
287 $resolved = [];
288 foreach ($settings as $key => $value) {
289 if (isset($nested_key_map[$key])) {
290 list($section, $canonical) = $nested_key_map[$key];
291 if (!isset($resolved[$section])) {
292 $resolved[$section] = [];
293 }
294 $resolved[$section][$canonical] = $value;
295 } else {
296 $resolved[$key] = $value;
297 }
298 }
299
300 return $resolved;
301 }
302
303 /**
304 * Recursively merge two arrays, preserving nested sub-arrays.
305 */
306 private function array_merge_deep($base, $override) {
307 foreach ($override as $key => $value) {
308 if (is_array($value) && isset($base[$key]) && is_array($base[$key])) {
309 $base[$key] = $this->array_merge_deep($base[$key], $value);
310 } else {
311 $base[$key] = $value;
312 }
313 }
314 return $base;
315 }
316
317 /**
318 * Sanitize settings before saving
319 */
320 private function sanitize_settings($settings) {
321 $sanitized = [];
322
323 foreach ($settings as $key => $value) {
324 $key = sanitize_key($key);
325
326 // Handle different types of values
327 if (is_array($value)) {
328 $sanitized[$key] = $this->sanitize_array($value);
329 } elseif (is_bool($value)) {
330 $sanitized[$key] = (bool)$value;
331 } elseif (is_numeric($value)) {
332 $sanitized[$key] = $value;
333 } elseif ($this->is_sensitive_field($key)) {
334 // Don't sanitize sensitive fields too aggressively
335 $sanitized[$key] = $value;
336 } elseif ($this->is_url_field($key)) {
337 $sanitized[$key] = esc_url_raw($value);
338 } else {
339 $sanitized[$key] = sanitize_text_field($value);
340 }
341 }
342
343 return $sanitized;
344 }
345
346 /**
347 * Sanitize array values recursively
348 */
349 private function sanitize_array($array) {
350 $sanitized = [];
351 foreach ($array as $key => $value) {
352 $key = sanitize_key($key);
353 if (is_array($value)) {
354 $sanitized[$key] = $this->sanitize_array($value);
355 } elseif (is_bool($value)) {
356 $sanitized[$key] = $value;
357 } elseif (is_numeric($value)) {
358 $sanitized[$key] = $value;
359 } else {
360 $sanitized[$key] = sanitize_text_field($value);
361 }
362 }
363 return $sanitized;
364 }
365
366 /**
367 * Check if field is sensitive (API keys, tokens, passwords)
368 */
369 private function is_sensitive_field($key) {
370 $sensitive_patterns = ['api_key', 'apikey', 'token', 'password', 'secret'];
371 foreach ($sensitive_patterns as $pattern) {
372 if (stripos($key, $pattern) !== false) {
373 return true;
374 }
375 }
376 return false;
377 }
378
379 /**
380 * Check if field should be a URL
381 */
382 private function is_url_field($key) {
383 $url_patterns = ['url', 'uri', 'domain', 'logo', 'link'];
384 foreach ($url_patterns as $pattern) {
385 if (stripos($key, $pattern) !== false) {
386 return true;
387 }
388 }
389 return false;
390 }
391 }
392
393 /**
394 * List Available Settings Tool
395 */
396 class MCP_Tool_List_Plugin_Settings_Schema extends MCP_Tool_Base {
397
398 public function get_name() {
399 return 'wordpress_list_plugin_settings_schema';
400 }
401
402 public function get_description() {
403 return 'Get a schema of all available plugin settings with descriptions and expected types. Useful for understanding what settings can be configured.';
404 }
405
406 public function get_input_schema() {
407 return [
408 'type' => 'object',
409 'properties' => (object)[],
410 'required' => []
411 ];
412 }
413
414 public function execute($params) {
415 $this->validate_params($params);
416 $this->require_capability('manage_options');
417
418 $schema = [
419 'general' => [
420 'description' => 'General plugin configuration',
421 'settings' => [
422 'searchatlas_api_key' => [
423 'type' => 'string',
424 'description' => 'Search Atlas API key for integration',
425 'sensitive' => true
426 ],
427 'apikey' => [
428 'type' => 'string',
429 'description' => 'Plugin authentication token',
430 'sensitive' => true
431 ],
432 'hide_dashboard_framework' => [
433 'type' => 'boolean',
434 'description' => 'Hide the dashboard framework'
435 ],
436 'show_admin_bar_status' => [
437 'type' => 'boolean',
438 'description' => 'Show plugin status in admin bar'
439 ],
440 'enable_auto_updates' => [
441 'type' => 'boolean',
442 'description' => 'Enable automatic plugin updates'
443 ],
444 'enable_schema_markup' => [
445 'type' => 'boolean',
446 'description' => 'Enable automatic schema markup generation. Canonical key — do NOT use "enable_schema".'
447 ],
448 'default_schema_type' => [
449 'type' => 'string',
450 'description' => 'Default schema type for new posts (e.g., "article", "WebSite")'
451 ]
452 ]
453 ],
454 'whitelabel' => [
455 'description' => 'White-label branding configuration',
456 'settings' => [
457 'white_label_plugin_name' => [
458 'type' => 'string',
459 'description' => 'Custom plugin name'
460 ],
461 'whitelabel_otto_name' => [
462 'type' => 'string',
463 'description' => 'Custom Otto feature name'
464 ],
465 'whitelabel_logo_url' => [
466 'type' => 'string',
467 'description' => 'URL to custom logo image'
468 ],
469 'whitelabel_domain_url' => [
470 'type' => 'string',
471 'description' => 'Custom dashboard domain URL'
472 ],
473 'white_label_plugin_description' => [
474 'type' => 'string',
475 'description' => 'Custom plugin description'
476 ],
477 'white_label_plugin_author' => [
478 'type' => 'string',
479 'description' => 'Custom author name'
480 ],
481 'white_label_plugin_author_uri' => [
482 'type' => 'string',
483 'description' => 'Custom author URI'
484 ],
485 'white_label_plugin_uri' => [
486 'type' => 'string',
487 'description' => 'Custom plugin URI'
488 ]
489 ]
490 ],
491 'seo' => [
492 'description' => 'SEO controls and indexation settings',
493 'settings' => [
494 'index_date_archives' => [
495 'type' => 'boolean',
496 'description' => 'Disallow date archives from indexation'
497 ],
498 'index_tag_archives' => [
499 'type' => 'boolean',
500 'description' => 'Disallow tag archives from indexation'
501 ],
502 'index_author_archives' => [
503 'type' => 'boolean',
504 'description' => 'Disallow author archives from indexation'
505 ],
506 'index_format_archives' => [
507 'type' => 'boolean',
508 'description' => 'Disallow format archives from indexation'
509 ],
510 'index_category_archives' => [
511 'type' => 'boolean',
512 'description' => 'Disallow category archives from indexation'
513 ],
514 'override_robots_tags' => [
515 'type' => 'boolean',
516 'description' => 'Override robots tags from other plugins'
517 ],
518 'enable_googleinstantindex' => [
519 'type' => 'boolean',
520 'description' => 'Enable Google Instant Indexing'
521 ]
522 ]
523 ],
524 'social' => [
525 'description' => 'Social media and Otto settings',
526 'settings' => [
527 'otto_pixel_uuid' => [
528 'type' => 'string',
529 'description' => 'Otto Pixel UUID for tracking'
530 ],
531 'otto_disable_on_loggedin' => [
532 'type' => 'boolean',
533 'description' => 'Disable Otto for logged-in users'
534 ],
535 'otto_disable_preview_button' => [
536 'type' => 'boolean',
537 'description' => 'Disable Otto frontend toolbar'
538 ]
539 ]
540 ],
541 'advanced' => [
542 'description' => 'Advanced meta box visibility controls',
543 'settings' => [
544 'disable_common_robots_metabox' => [
545 'type' => 'boolean',
546 'description' => 'Disable common robots meta box in post editor'
547 ],
548 'disable_advance_robots_metabox' => [
549 'type' => 'boolean',
550 'description' => 'Disable advanced robots meta box in post editor'
551 ],
552 'disable_redirection_metabox' => [
553 'type' => 'boolean',
554 'description' => 'Disable redirection meta box in post editor'
555 ],
556 'disable_canonical_metabox' => [
557 'type' => 'boolean',
558 'description' => 'Disable canonical meta box in post editor'
559 ],
560 'disable_social_opengraph_metabox' => [
561 'type' => 'boolean',
562 'description' => 'Disable social/OpenGraph meta box in post editor'
563 ],
564 'disable_schema_markup_metabox' => [
565 'type' => 'boolean',
566 'description' => 'Disable schema markup meta box in post editor'
567 ],
568 'disable_seo_metabox' => [
569 'type' => 'boolean',
570 'description' => 'Disable SEO Title & Meta Description meta box in Classic editor'
571 ]
572 ]
573 ],
574 'mcp' => [
575 'description' => 'MCP Server configuration',
576 'settings' => [
577 'metasync_mcp_enabled' => [
578 'type' => 'boolean',
579 'description' => 'Enable/disable the MCP server',
580 'stored_separately' => true,
581 'option_name' => 'metasync_mcp_enabled'
582 ],
583 'metasync_mcp_api_key' => [
584 'type' => 'string',
585 'description' => 'MCP server API key for authentication',
586 'sensitive' => true,
587 'stored_separately' => true,
588 'option_name' => 'metasync_mcp_api_key'
589 ]
590 ]
591 ]
592 ];
593
594 return $this->success([
595 'schema' => $schema,
596 'sections' => array_keys($schema),
597 'total_sections' => count($schema),
598 'note' => 'Some settings like MCP configuration are stored as separate options, not in the main metasync_options array'
599 ]);
600 }
601 }
602
603 /**
604 * Get MCP Server Settings Tool
605 */
606 class MCP_Tool_Get_MCP_Settings extends MCP_Tool_Base {
607
608 public function get_name() {
609 return 'wordpress_get_mcp_settings';
610 }
611
612 public function get_description() {
613 return 'Get MCP server-specific settings including authentication information and endpoint URLs.';
614 }
615
616 public function get_input_schema() {
617 return [
618 'type' => 'object',
619 'properties' => (object)[],
620 'required' => []
621 ];
622 }
623
624 public function execute($params) {
625 $this->validate_params($params);
626 $this->require_capability('manage_options');
627
628 $options = get_option('metasync_options', []);
629 $plugin_auth_token = isset($options['general']['apikey']) ? $options['general']['apikey'] : '';
630
631 return $this->success([
632 'mcp_enabled' => true, // MCP is always enabled
633 'authentication' => [
634 'type' => 'plugin_auth_token',
635 'header' => 'X-API-Key',
636 'token_set' => !empty($plugin_auth_token),
637 'token_length' => !empty($plugin_auth_token) ? strlen($plugin_auth_token) : 0,
638 'token_preview' => !empty($plugin_auth_token) ? substr($plugin_auth_token, 0, 8) . '...' : ''
639 ],
640 'endpoints' => [
641 'rest_endpoint' => rest_url('metasync/v1/mcp'),
642 'health_endpoint' => rest_url('metasync/v1/mcp/health')
643 ],
644 'version' => METASYNC_VERSION
645 ]);
646 }
647 }
648
649