PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.7.2
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.7.2
2.7.1 2.7.2 2.7.0 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 All 141 releases
metasync / wp-mcp-server / tools / class-mcp-tool-schema-markup.php

class-mcp-tool-schema-markup.php in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.7.2, at wp-mcp-server/tools/class-mcp-tool-schema-markup.php

632 lines 21.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * MCP Tools for Schema Markup Operations
4 *
5 * Provides MCP tools for managing schema markup on posts and pages.
6 *
7 * @package MetaSync
8 * @subpackage MCP_Server/Tools
9 */
10
11 if (!defined('ABSPATH')) {
12 exit;
13 }
14
15 require_once plugin_dir_path(dirname(__FILE__)) . 'class-mcp-tool-base.php';
16
17 /**
18 * Get Schema Markup Tool
19 *
20 * Retrieves schema markup configuration for a post
21 */
22 class MCP_Tool_Get_Schema_Markup extends MCP_Tool_Base {
23
24 public function get_name() {
25 return 'wordpress_get_schema_markup';
26 }
27
28 public function get_description() {
29 return 'Get schema markup configuration for a post or page';
30 }
31
32 public function get_input_schema() {
33 return [
34 'type' => 'object',
35 'properties' => [
36 'post_id' => [
37 'type' => 'integer',
38 'description' => 'Post ID',
39 ],
40 ],
41 'required' => ['post_id'],
42 ];
43 }
44
45 public function execute($params) {
46 $this->validate_params($params);
47 $this->require_capability('edit_posts');
48
49 $post_id = intval($params['post_id']);
50
51 // Validate post exists
52 $post = $this->verify_post_exists($post_id);
53
54 // Get schema data
55 $schema_data = get_post_meta($post_id, 'metasync_schema_markup', true);
56 $validation_errors = get_post_meta($post_id, '_metasync_schema_validation_errors', true);
57
58 $result = [
59 'post_id' => $post_id,
60 'post_title' => $post->post_title,
61 'post_type' => $post->post_type,
62 'schema_enabled' => false,
63 'schema_types' => [],
64 'has_validation_errors' => false,
65 'validation_errors' => [],
66 ];
67
68 if (!empty($schema_data)) {
69 $result['schema_enabled'] = isset($schema_data['enabled']) ? (bool)$schema_data['enabled'] : false;
70 $result['schema_types'] = isset($schema_data['types']) ? $schema_data['types'] : [];
71 }
72
73 if (!empty($validation_errors) && is_array($validation_errors)) {
74 $result['has_validation_errors'] = true;
75 $result['validation_errors'] = $validation_errors;
76 }
77
78 return $this->success($result);
79 }
80 }
81
82 /**
83 * Update Schema Markup Tool
84 *
85 * Updates schema markup configuration for a post
86 */
87 class MCP_Tool_Update_Schema_Markup extends MCP_Tool_Base {
88
89 public function get_name() {
90 return 'wordpress_update_schema_markup';
91 }
92
93 public function get_description() {
94 return 'Update schema markup configuration for a post or page';
95 }
96
97 public function get_input_schema() {
98 return [
99 'type' => 'object',
100 'properties' => [
101 'post_id' => [
102 'type' => 'integer',
103 'description' => 'Post ID',
104 ],
105 'enabled' => [
106 'type' => 'boolean',
107 'description' => 'Enable or disable schema markup for this post',
108 ],
109 'types' => [
110 'type' => 'array',
111 'description' => 'Array of schema types with their configuration',
112 'items' => [
113 'type' => 'object',
114 'properties' => [
115 'type' => [
116 'type' => 'string',
117 'enum' => ['article', 'FAQPage', 'product', 'recipe', 'Event', 'JobPosting', 'Review', 'Course', 'Organization', 'Person', 'WebSite', 'NewsArticle', 'LocalBusiness', 'HowTo', 'VideoObject'],
118 ],
119 'fields' => [
120 'type' => 'object',
121 'description' => 'Schema-specific fields',
122 ],
123 ],
124 ],
125 ],
126 ],
127 'required' => ['post_id', 'enabled'],
128 ];
129 }
130
131 public function execute($params) {
132 $this->validate_params($params);
133 $this->require_capability('edit_posts');
134
135 $post_id = intval($params['post_id']);
136
137 // Validate post exists and user can edit it
138 $post = $this->verify_post_exists($post_id);
139
140 $this->check_post_permission($post_id);
141
142 // Preserve OTTO-owned data while replacing the user-managed fields.
143 $schema_data = get_post_meta($post_id, 'metasync_schema_markup', true);
144 if (!is_array($schema_data)) {
145 $schema_data = [];
146 }
147 $schema_data['enabled'] = (bool)$params['enabled'];
148 $schema_data['types'] = [];
149
150 // Process schema types
151 if (isset($params['types']) && is_array($params['types'])) {
152 foreach ($params['types'] as $type_data) {
153 if (!empty($type_data['type'])) {
154 $schema_data['types'][] = [
155 'type' => sanitize_text_field($type_data['type']),
156 'fields' => isset($type_data['fields']) ? $type_data['fields'] : [],
157 ];
158 }
159 }
160 }
161
162 // Save schema data
163 update_post_meta($post_id, 'metasync_schema_markup', wp_slash($schema_data));
164
165 // Clear validation errors
166 delete_post_meta($post_id, '_metasync_schema_validation_errors');
167
168 return $this->success([
169 'post_id' => $post_id,
170 'schema_enabled' => $schema_data['enabled'],
171 'schema_types_count' => count($schema_data['types']),
172 'message' => 'Schema markup updated successfully',
173 ]);
174 }
175 }
176
177 /**
178 * Add Schema Type Tool
179 *
180 * Adds a new schema type to a post
181 */
182 class MCP_Tool_Add_Schema_Type extends MCP_Tool_Base {
183
184 public function get_name() {
185 return 'wordpress_add_schema_type';
186 }
187
188 public function get_description() {
189 return 'Add a new schema type (article, FAQ, product, recipe, Event, JobPosting, Review, Course, Organization, Person, WebSite, NewsArticle, LocalBusiness, HowTo, VideoObject) to a post';
190 }
191
192 public function get_input_schema() {
193 return [
194 'type' => 'object',
195 'properties' => [
196 'post_id' => [
197 'type' => 'integer',
198 'description' => 'Post ID',
199 ],
200 'schema_type' => [
201 'type' => 'string',
202 'enum' => ['article', 'FAQPage', 'product', 'recipe', 'Event', 'JobPosting', 'Review', 'Course', 'Organization', 'Person', 'WebSite', 'NewsArticle', 'LocalBusiness', 'HowTo', 'VideoObject'],
203 'description' => 'Schema type to add',
204 ],
205 'fields' => [
206 'type' => 'object',
207 'description' => 'Schema-specific fields configuration',
208 ],
209 ],
210 'required' => ['post_id', 'schema_type'],
211 ];
212 }
213
214 public function execute($params) {
215 $this->validate_params($params);
216 $this->require_capability('edit_posts');
217
218 $post_id = intval($params['post_id']);
219 $schema_type = sanitize_text_field($params['schema_type']);
220 $fields = isset($params['fields']) ? $params['fields'] : [];
221
222 // Validate post exists and user can edit it
223 $post = $this->verify_post_exists($post_id);
224
225 $this->check_post_permission($post_id);
226
227 // Get existing schema data
228 $schema_data = get_post_meta($post_id, 'metasync_schema_markup', true);
229 if (empty($schema_data)) {
230 $schema_data = ['enabled' => true, 'types' => []];
231 }
232
233 // Check if schema type already exists
234 foreach ($schema_data['types'] as $existing_type) {
235 if ($existing_type['type'] === $schema_type) {
236 throw new Exception(sprintf("Schema type '%s' already exists for this post", esc_html($schema_type)));
237 }
238 }
239
240 // Add new schema type
241 $schema_data['types'][] = [
242 'type' => $schema_type,
243 'fields' => $fields,
244 ];
245
246 // Save updated schema data
247 update_post_meta($post_id, 'metasync_schema_markup', wp_slash($schema_data));
248
249 return $this->success([
250 'post_id' => $post_id,
251 'schema_type' => $schema_type,
252 'schema_types_count' => count($schema_data['types']),
253 'message' => "Schema type '{$schema_type}' added successfully",
254 ]);
255 }
256 }
257
258 /**
259 * Remove Schema Type Tool
260 *
261 * Removes a schema type from a post
262 */
263 class MCP_Tool_Remove_Schema_Type extends MCP_Tool_Base {
264
265 public function get_name() {
266 return 'wordpress_remove_schema_type';
267 }
268
269 public function get_description() {
270 return 'Remove a schema type from a post';
271 }
272
273 public function get_input_schema() {
274 return [
275 'type' => 'object',
276 'properties' => [
277 'post_id' => [
278 'type' => 'integer',
279 'description' => 'Post ID',
280 ],
281 'schema_type' => [
282 'type' => 'string',
283 'enum' => ['article', 'FAQPage', 'product', 'recipe', 'Event', 'JobPosting', 'Review', 'Course', 'Organization', 'Person', 'WebSite', 'NewsArticle', 'LocalBusiness', 'HowTo', 'VideoObject'],
284 'description' => 'Schema type to remove',
285 ],
286 ],
287 'required' => ['post_id', 'schema_type'],
288 ];
289 }
290
291 public function execute($params) {
292 $this->validate_params($params);
293 $this->require_capability('edit_posts');
294
295 $post_id = intval($params['post_id']);
296 $schema_type = sanitize_text_field($params['schema_type']);
297
298 // Validate post exists and user can edit it
299 $post = $this->verify_post_exists($post_id);
300
301 $this->check_post_permission($post_id);
302
303 // Get existing schema data
304 $schema_data = get_post_meta($post_id, 'metasync_schema_markup', true);
305 if (empty($schema_data) || empty($schema_data['types'])) {
306 throw new Exception('No schema types found for this post');
307 }
308
309 // Remove the specified schema type
310 $found = false;
311 $new_types = [];
312 foreach ($schema_data['types'] as $existing_type) {
313 if ($existing_type['type'] === $schema_type) {
314 $found = true;
315 continue; // Skip this type (remove it)
316 }
317 $new_types[] = $existing_type;
318 }
319
320 if (!$found) {
321 throw new Exception(sprintf("Schema type '%s' not found for this post", esc_html($schema_type)));
322 }
323
324 $schema_data['types'] = $new_types;
325
326 // Save updated schema data
327 update_post_meta($post_id, 'metasync_schema_markup', wp_slash($schema_data));
328
329 // Clear validation errors
330 delete_post_meta($post_id, '_metasync_schema_validation_errors');
331
332 return $this->success([
333 'post_id' => $post_id,
334 'schema_type' => $schema_type,
335 'schema_types_count' => count($schema_data['types']),
336 'message' => "Schema type '{$schema_type}' removed successfully",
337 ]);
338 }
339 }
340
341 /**
342 * Validate Schema Tool
343 *
344 * Validates schema markup for a post
345 */
346 class MCP_Tool_Validate_Schema extends MCP_Tool_Base {
347
348 public function get_name() {
349 return 'wordpress_validate_schema';
350 }
351
352 public function get_description() {
353 return 'Validate schema markup configuration for a post';
354 }
355
356 public function get_input_schema() {
357 return [
358 'type' => 'object',
359 'properties' => [
360 'post_id' => [
361 'type' => 'integer',
362 'description' => 'Post ID',
363 ],
364 ],
365 'required' => ['post_id'],
366 ];
367 }
368
369 public function execute($params) {
370 $this->validate_params($params);
371 $this->require_capability('edit_posts');
372
373 $post_id = intval($params['post_id']);
374
375 // Validate post exists
376 $post = $this->verify_post_exists($post_id);
377
378 // Get schema data
379 $schema_data = get_post_meta($post_id, 'metasync_schema_markup', true);
380
381 if (empty($schema_data) || !isset($schema_data['enabled']) || !$schema_data['enabled']) {
382 return $this->success([
383 'post_id' => $post_id,
384 'valid' => true,
385 'message' => 'Schema markup is not enabled for this post',
386 ]);
387 }
388
389 if (empty($schema_data['types'])) {
390 return $this->success([
391 'post_id' => $post_id,
392 'valid' => false,
393 'errors' => [['message' => 'No schema types configured']],
394 ]);
395 }
396
397 // Get the schema markup class for validation
398 require_once plugin_dir_path(dirname(dirname(__FILE__))) . 'schema-markup/class-metasync-schema-markup.php';
399 $schema_markup = new Metasync_Schema_Markup('metasync', METASYNC_VERSION);
400
401 // Use reflection to access private validation method
402 $reflection = new ReflectionClass($schema_markup);
403 $validate_method = $reflection->getMethod('validate_schema_requirements');
404 $validate_method->setAccessible(true);
405
406 // Validate all schema types
407 $all_errors = [];
408 foreach ($schema_data['types'] as $schema_type_data) {
409 $errors = $validate_method->invoke(
410 $schema_markup,
411 $post_id,
412 $schema_type_data['type'],
413 $schema_type_data['fields']
414 );
415
416 if (!empty($errors)) {
417 $all_errors = array_merge($all_errors, $errors);
418 }
419 }
420
421 $is_valid = empty($all_errors);
422
423 // Update validation errors in post meta
424 if ($is_valid) {
425 delete_post_meta($post_id, '_metasync_schema_validation_errors');
426 } else {
427 update_post_meta($post_id, '_metasync_schema_validation_errors', $all_errors);
428 }
429
430 return $this->success([
431 'post_id' => $post_id,
432 'valid' => $is_valid,
433 'errors' => $all_errors,
434 'message' => $is_valid ? 'Schema markup is valid' : 'Schema markup has validation errors',
435 ]);
436 }
437 }
438
439 /**
440 * Get Schema Content Tool
441 *
442 * Returns the full content fields for a specific schema type on a post
443 */
444 class MCP_Tool_Get_Schema_Content extends MCP_Tool_Base {
445
446 public function get_name() {
447 return 'wordpress_get_schema_content';
448 }
449
450 public function get_description() {
451 return 'Get the full content fields for a specific schema type on a post';
452 }
453
454 public function get_input_schema() {
455 return [
456 'type' => 'object',
457 'properties' => [
458 'post_id' => [
459 'type' => 'integer',
460 'description' => 'Post ID',
461 ],
462 'schema_type' => [
463 'type' => 'string',
464 'enum' => ['article', 'FAQPage', 'product', 'recipe', 'Event', 'JobPosting', 'Review', 'Course', 'Organization', 'Person', 'WebSite', 'NewsArticle', 'LocalBusiness', 'HowTo', 'VideoObject'],
465 'description' => 'Schema type to retrieve content for',
466 ],
467 ],
468 'required' => ['post_id', 'schema_type'],
469 ];
470 }
471
472 public function execute($params) {
473 $this->validate_params($params);
474 $this->require_capability('edit_posts');
475
476 $post_id = intval($params['post_id']);
477 $schema_type = sanitize_text_field($params['schema_type']);
478
479 // Validate post exists
480 $post = $this->verify_post_exists($post_id);
481
482 // Get schema data
483 $schema_data = get_post_meta($post_id, 'metasync_schema_markup', true);
484
485 if (empty($schema_data) || empty($schema_data['types'])) {
486 return $this->success([
487 'post_id' => $post_id,
488 'schema_type' => $schema_type,
489 'fields' => null,
490 'message' => 'No schema types configured for this post',
491 'validation_warnings' => [],
492 ]);
493 }
494
495 // Find the requested schema type
496 $found_fields = null;
497 foreach ($schema_data['types'] as $type_data) {
498 if ($type_data['type'] === $schema_type) {
499 $found_fields = isset($type_data['fields']) ? $type_data['fields'] : [];
500 break;
501 }
502 }
503
504 if ($found_fields === null) {
505 return $this->success([
506 'post_id' => $post_id,
507 'schema_type' => $schema_type,
508 'fields' => null,
509 'message' => sprintf("Schema type '%s' not found for this post", esc_html($schema_type)),
510 'validation_warnings' => [],
511 ]);
512 }
513
514 // Run validation
515 require_once plugin_dir_path(dirname(dirname(__FILE__))) . 'schema-markup/class-metasync-schema-markup.php';
516 $schema_markup = Metasync_Schema_Markup::get_instance();
517 $validation_warnings = $schema_markup->validate_schema_requirements($post_id, $schema_type, $found_fields);
518
519 return $this->success([
520 'post_id' => $post_id,
521 'schema_type' => $schema_type,
522 'fields' => $found_fields,
523 'validation_warnings' => $validation_warnings,
524 ]);
525 }
526 }
527
528 /**
529 * Set Schema Content Tool
530 *
531 * Sets the content fields for a specific schema type on a post
532 */
533 class MCP_Tool_Set_Schema_Content extends MCP_Tool_Base {
534
535 public function get_name() {
536 return 'wordpress_set_schema_content';
537 }
538
539 public function get_description() {
540 return 'Set the content fields for a specific schema type on a post. Merges into existing schema markup data. For HowTo, steps must use the "instructions" key (not "text" or "instruction").';
541 }
542
543 public function get_input_schema() {
544 return [
545 'type' => 'object',
546 'properties' => [
547 'post_id' => [
548 'type' => 'integer',
549 'description' => 'Post ID',
550 ],
551 'schema_type' => [
552 'type' => 'string',
553 'enum' => ['article', 'FAQPage', 'product', 'recipe', 'Event', 'JobPosting', 'Review', 'Course', 'Organization', 'Person', 'WebSite', 'NewsArticle', 'LocalBusiness', 'HowTo', 'VideoObject'],
554 'description' => 'Schema type to set content for',
555 ],
556 'fields' => [
557 'type' => 'object',
558 'description' => 'Content fields for the schema type. FAQPage: faq_items array of {question, answer}. HowTo: steps array of {instructions, image?} (use "instructions" key, not "text"), plus total_time (minutes). product: price, currency, availability, condition, sku, brand. recipe: ingredients (string array), instructions (string array), prep_time, cook_time, total_time, calories, yield.',
559 ],
560 ],
561 'required' => ['post_id', 'schema_type', 'fields'],
562 ];
563 }
564
565 public function execute($params) {
566 $this->validate_params($params);
567 $this->require_capability('edit_posts');
568
569 $post_id = intval($params['post_id']);
570 $schema_type = sanitize_text_field($params['schema_type']);
571
572 // Validate post exists and user can edit it
573 $this->verify_post_exists($post_id);
574 $this->check_post_permission($post_id);
575
576 // Sanitize fields before any DB write (same as Classic Editor and REST paths)
577 require_once plugin_dir_path(dirname(dirname(__FILE__))) . 'schema-markup/class-metasync-schema-markup.php';
578 $schema_markup = Metasync_Schema_Markup::get_instance();
579 $content = $schema_markup->sanitize_schema_fields(
580 isset($params['fields']) && is_array($params['fields']) ? $params['fields'] : [],
581 $schema_type
582 );
583
584 // Get existing schema data
585 $schema_data = get_post_meta($post_id, 'metasync_schema_markup', true);
586 if (empty($schema_data)) {
587 $schema_data = ['enabled' => true, 'types' => []];
588 }
589
590 // Find and update or add the schema type
591 $found = false;
592 foreach ($schema_data['types'] as &$type_data) {
593 if ($type_data['type'] === $schema_type) {
594 $type_data['fields'] = $content;
595 $found = true;
596 break;
597 }
598 }
599 unset($type_data);
600
601 if (!$found) {
602 $schema_data['types'][] = [
603 'type' => $schema_type,
604 'fields' => $content,
605 ];
606 }
607
608 // Save updated schema data
609 update_post_meta($post_id, 'metasync_schema_markup', wp_slash($schema_data));
610
611 // Validate
612 $validation_warnings = $schema_markup->validate_schema_requirements($post_id, $schema_type, $content);
613
614 // Update validation errors
615 if (!empty($validation_warnings)) {
616 update_post_meta($post_id, '_metasync_schema_validation_errors', $validation_warnings);
617 } else {
618 delete_post_meta($post_id, '_metasync_schema_validation_errors');
619 }
620
621 // Cross-plugin sync
622 $schema_markup->sync_schema_to_seo_plugins($post_id, $schema_type, $content);
623
624 return $this->success([
625 'post_id' => $post_id,
626 'schema_type' => $schema_type,
627 'message' => "Schema content for '{$schema_type}' updated successfully",
628 'validation_warnings' => $validation_warnings,
629 ]);
630 }
631 }
632